Skip to content

Commit

Permalink
feat(frontend): search by phone
Browse files Browse the repository at this point in the history
  • Loading branch information
pYassine committed Jan 9, 2025
1 parent 7ff9b54 commit 5efdbab
Show file tree
Hide file tree
Showing 20 changed files with 409 additions and 93 deletions.
46 changes: 21 additions & 25 deletions packages/backend/src/usagers/controllers/usagers.controller.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {
BadRequestException,
Body,
Controller,
Delete,
Expand Down Expand Up @@ -60,13 +59,12 @@ import {
CerfaDocType,
UsagerDecision,
getUsagerDeadlines,
normalizeString,
CriteriaSearchField,
} from "@domifa/common";
import { UsagerHistoryStateService } from "../services/usagerHistoryState.service";
import { domifaConfig } from "../../config";
import { FileManagerService } from "../../util/file-manager/file-manager.service";
import { Not } from "typeorm";
import { isValid, parse } from "date-fns";

@Controller("usagers")
@ApiTags("usagers")
Expand Down Expand Up @@ -164,28 +162,22 @@ export class UsagersController {
structureId: user.structureId,
});

const searchString = normalizeString(search?.searchString).trim();

if (searchString && search.searchStringField === "DEFAULT") {
query.andWhere("nom_prenom_surnom_ref ILIKE :str", {
str: `%${searchString}%`,
});
} else if (searchString && search.searchStringField === "DATE_NAISSANCE") {
const parsedDate = parse(
searchString.replace(/\D/g, ""),
"ddMMyyyy",
new Date()
);

if (!isValid(parsedDate)) {
throw new BadRequestException(
'Format de date invalide. Utilisez le format "dd MM yyyy"'
);
if (search.searchString) {
if (search.searchStringField === CriteriaSearchField.DEFAULT) {
query.andWhere("nom_prenom_surnom_ref ILIKE :str", {
str: `%${search.searchString}%`,
});
} else if (search.searchStringField === CriteriaSearchField.BIRTH_DATE) {
query.andWhere(`DATE("dateNaissance") = DATE(:date)`, {
date: search.searchString,
});
} else if (
search.searchStringField === CriteriaSearchField.PHONE_NUMBER
) {
query.andWhere(`telephone->>'numero' ILIKE :phone`, {
phone: `%${search.searchString}%`,
});
}

query.andWhere(`DATE("dateNaissance") = DATE(:date)`, {
date: parsedDate,
});
}

if (search?.lastInteractionDate) {
Expand Down Expand Up @@ -225,7 +217,11 @@ export class UsagersController {
}
}

if (!searchString && !search?.echeance && !search?.lastInteractionDate) {
if (
!search.searchString &&
!search?.echeance &&
!search?.lastInteractionDate
) {
query.take(100);
}

Expand Down
35 changes: 32 additions & 3 deletions packages/backend/src/usagers/dto/search-usager.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,14 @@ import {
StripTagsTransform,
} from "../../_common/decorators";
import {
CriteriaSearchField,
normalizeString,
UsagersFilterCriteriaDernierPassage,
UsagersFilterCriteriaEcheance,
} from "@domifa/common";
import { Transform } from "class-transformer";
import { isValid, parse } from "date-fns";
import { BadRequestException } from "@nestjs/common";

export class SearchUsagerDto {
@ApiProperty({
Expand All @@ -19,11 +24,35 @@ export class SearchUsagerDto {
@MinLength(1)
@StripTagsTransform()
@LowerCaseTransform()
@Transform(({ value, obj }) => {
if (!value) {
return null;
}

switch (obj.searchStringField) {
case CriteriaSearchField.PHONE_NUMBER:
return value.replace(/\D/g, "");

case CriteriaSearchField.BIRTH_DATE:
const cleanDate = value.replace(/\D/g, "");
const parsedDate = parse(cleanDate, "ddMMyyyy", new Date());

if (!isValid(parsedDate)) {
throw new BadRequestException(
'Format de date invalide. Utilisez le format "dd/MM/yyyy"'
);
}
return cleanDate;

case CriteriaSearchField.DEFAULT:
default:
return normalizeString(value).trim();
}
})
public searchString!: string;

@IsOptional()
@IsIn(["DEFAULT", "DATE_NAISSANCE"])
public readonly searchStringField: "DEFAULT" | "DATE_NAISSANCE";
@IsIn(Object.values(CriteriaSearchField))
public readonly searchStringField: CriteriaSearchField;

@IsIn([
"EXCEEDED",
Expand Down
5 changes: 5 additions & 0 deletions packages/common/src/search/types/CriteriaSearchField.enum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export enum CriteriaSearchField {
DEFAULT = "DEFAULT", // Nom, prénom des domiciliés & ayant-droit
BIRTH_DATE = "BIRTH_DATE", // Date de naissance des domiciliés & ayant-droit
PHONE_NUMBER = "PHONE_NUMBER", // Numéro des domiciliés
}
1 change: 1 addition & 0 deletions packages/common/src/search/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// @index('./*', f => `export * from '${f.path}'`)
export * from "./CriteriaSearchField.enum";
export * from "./SortValues.type";
export * from "./Timings.type";
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,6 @@ export type UsagerLight = AppEntity &
| "pinnedNote"
| "datePremiereDom"
| "nbNotes"
>;
> & {
phoneNumber?: string;
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { NgbModule } from "@ng-bootstrap/ng-bootstrap";
import { CUSTOM_ELEMENTS_SCHEMA } from "@angular/core";

import { ManageUsagersModule } from "../../manage-usagers.module";
import { CriteriaSearchField } from "@domifa/common";

describe("ManageFiltersComponent", () => {
let component: ManageFiltersComponent;
Expand All @@ -24,7 +25,7 @@ describe("ManageFiltersComponent", () => {
interactionType: null,
entretien: null,
searchString: null,
searchStringField: "DEFAULT",
searchStringField: CriteriaSearchField.DEFAULT,
page: 0,
lastInteractionDate: null,
sortKey: "NOM",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,12 @@ <h1 class="col-md-2 col-6 title text-start order-1 order-md-1">
(click)="
updateFilters({
element: 'searchStringField',
value: 'DATE_NAISSANCE'
value: CriteriaSearchField.BIRTH_DATE
})
"
role="option"
[attr.aria-selected]="
filters.searchStringField === 'DATE_NAISSANCE'
filters.searchStringField === CriteriaSearchField.BIRTH_DATE
"
ngbDropdownItem
>
Expand All @@ -74,15 +74,34 @@ <h1 class="col-md-2 col-6 title text-start order-1 order-md-1">
(click)="
updateFilters({
element: 'searchStringField',
value: 'DEFAULT'
value: CriteriaSearchField.DEFAULT
})
"
role="option"
[attr.aria-selected]="filters.searchStringField === 'DEFAULT'"
[attr.aria-selected]="
filters.searchStringField === CriteriaSearchField.DEFAULT
"
ngbDropdownItem
>
{{ SEARCH_STRING_FIELD_LABELS.DEFAULT.label }}
</button>
<button
type="button"
(click)="
updateFilters({
element: 'searchStringField',
value: CriteriaSearchField.PHONE_NUMBER
})
"
role="option"
[attr.aria-selected]="
filters.searchStringField ===
CriteriaSearchField.PHONE_NUMBER
"
ngbDropdownItem
>
{{ SEARCH_STRING_FIELD_LABELS.PHONE_NUMBER.label }}
</button>
</div>
</div>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { _usagerReducer, MATOMO_INJECTORS } from "../../../../shared";

import { ManageUsagersPageComponent } from "./manage-usagers-page.component";
import { StoreModule } from "@ngrx/store";
import { CriteriaSearchField } from "@domifa/common";

describe("ManageUsagersPageComponent", () => {
let component: ManageUsagersPageComponent;
Expand Down Expand Up @@ -49,7 +50,7 @@ describe("ManageUsagersPageComponent", () => {
interactionType: null,
entretien: null,
searchString: null,
searchStringField: "DEFAULT",
searchStringField: CriteriaSearchField.DEFAULT,
page: 1,
lastInteractionDate: null,
sortKey: "NOM",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { CriteriaSearchField } from "../usager-filter/UsagersFilterCriteria";

import {
AfterViewInit,
Component,
Expand Down Expand Up @@ -32,7 +30,12 @@ import {
tap,
withLatestFrom,
} from "rxjs/operators";
import { fadeInOut } from "../../../../shared";
import {
fadeInOut,
selectAllUsagers,
selectUsagerStateData,
UsagerState,
} from "../../../../shared";

import { UsagerFormModel } from "../../../usager-shared/interfaces";

Expand All @@ -44,15 +47,9 @@ import {
} from "../usager-filter";
import { Store } from "@ngrx/store";
import { ManageUsagersService } from "../../services/manage-usagers.service";
import { SortValues, UserStructure } from "@domifa/common";
import { CriteriaSearchField, SortValues, UserStructure } from "@domifa/common";
import { MatomoTracker } from "ngx-matomo-client";
import { AuthService, CustomToastService } from "../../../shared/services";
import {} from "../../../../shared/store/usager-actions.service";
import {
selectAllUsagers,
selectUsagerStateData,
UsagerState,
} from "../../../../shared/store/usager-actions-reducer.service";
import { UsagerLight } from "../../../../../_common/model";
import {
calculateUsagersCountByStatus,
Expand All @@ -74,6 +71,7 @@ export class ManageUsagersPageComponent
public searching: boolean;

public usagersRadiesLoadedCount = 0;
public readonly CriteriaSearchField = CriteriaSearchField;

public chargerTousRadies$ = new BehaviorSubject(false);
public searchTrigger$ = new Subject<void>();
Expand Down Expand Up @@ -107,10 +105,14 @@ export class ManageUsagersPageComponent
label: "ID, nom, prénom, ayant-droit ou mandataire",
placeholder: "Recherche par ID, nom, prénom, ayant-droit ou mandataire",
},
DATE_NAISSANCE: {
BIRTH_DATE: {
label: "Date de naissance (format attendu: JJ/MM/AAAA)",
placeholder: "Recherche par date de naissance JJ/MM/AAAA",
},
PHONE_NUMBER: {
label: "Numéro de téléphone",
placeholder: "Recherche par numéro de téléphone",
},
};

public filters: UsagersFilterCriteria;
Expand Down Expand Up @@ -348,7 +350,7 @@ export class ManageUsagersPageComponent
(this.filters.statut === "TOUS" || this.filters.statut === "RADIE")
) {
if (
this.filters.searchStringField === "DATE_NAISSANCE" &&
this.filters.searchStringField === "BIRTH_DATE" &&
!this.validateDateSearchInput(filters?.searchString)
) {
return of(filters.searchString);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@
(click)="goToProfil(usager)"
>
<span *ngIf="usager.telephone?.numero">
{{ usager.telephone | formatInternationalPhoneNumber }}</span
{{ usager?.phoneNumber || "Non renseigné" }}</span
>
<span class="visually-hidden" *ngIf="!usager.telephone?.numero"
>Téléphone non renseigné</span
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
CriteriaSearchField,
Search,
UsagersFilterCriteriaDernierPassage,
UsagersFilterCriteriaEcheance,
Expand All @@ -20,7 +21,6 @@ export type UsagersFilterCriteriaSortKey =

export type UsagersFilterCriteriaEntretien = "COMING" | "OVERDUE";

export type CriteriaSearchField = "DEFAULT" | "DATE_NAISSANCE";
export class UsagersFilterCriteria extends Search {
// text search filter
// DEFAULT = Nom, prénom du domicilié, nom, prénom d'un des ayant-droits
Expand All @@ -41,7 +41,8 @@ export class UsagersFilterCriteria extends Search {
this.entretien = search?.entretien || null;
this.echeance = search?.echeance || null;
this.searchString = search?.searchString || null;
this.searchStringField = search?.searchStringField || "DEFAULT";
this.searchStringField =
search?.searchStringField || CriteriaSearchField.DEFAULT;
this.statut = search?.statut || "VALIDE";
this.page = search?.page || 1;
this.sortKey = search?.sortKey || "NOM";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { UsagersFilterCriteria } from "../UsagersFilterCriteria";
import { format, isValid, parseISO } from "date-fns";
import { UsagerLight } from "../../../../../../_common/model";
import { UsagerProcuration } from "../../../../usager-shared/interfaces/UsagerProcuration.interface";
import { UsagerAyantDroit } from "@domifa/common";
import { CriteriaSearchField, UsagerAyantDroit } from "@domifa/common";

const validateBirthDate = (date?: Date | string): string | undefined => {
if (!date) return undefined;
Expand All @@ -17,7 +17,14 @@ export const getAttributes = (
}: Pick<UsagersFilterCriteria, "searchString" | "searchStringField">
) => {
let attributes = [];
if (searchStringField === "DATE_NAISSANCE") {

if (searchStringField === CriteriaSearchField.PHONE_NUMBER) {
return usager?.phoneNumber
? [usager.phoneNumber.replace(/[^0-9+]/g, "")]
: [];
}

if (searchStringField === CriteriaSearchField.BIRTH_DATE) {
const attributes: string[] = [];

if (usager.dateNaissance) {
Expand Down
Loading

0 comments on commit 5efdbab

Please sign in to comment.