From 2b97e68c3ed5732f2749a69db1e56b67b0b61b70 Mon Sep 17 00:00:00 2001 From: Guilherme Minozzi Date: Wed, 22 Apr 2026 12:57:18 -0300 Subject: [PATCH 1/7] feat: add domain layer --- .../input-use-active-ingredients-model.ts | 17 +++++++++++++++++ ...eate-input-use-active-ingredient-use-case.ts | 9 +++++++++ ...lete-input-use-active-ingredient-use-case.ts | 8 ++++++++ .../get-input-use-active-ingredient-use-case.ts | 9 +++++++++ ...get-input-use-active-ingredients-use-case.ts | 11 +++++++++++ .../index.ts | 5 +++++ ...date-input-use-active-ingredient-use-case.ts | 9 +++++++++ 7 files changed, 68 insertions(+) create mode 100644 src/app/modules/input-uses/domain/models/input-use-active-ingredients-model.ts create mode 100644 src/app/modules/input-uses/domain/use-cases/input-use-active-ingredients-use-cases/create-input-use-active-ingredient-use-case.ts create mode 100644 src/app/modules/input-uses/domain/use-cases/input-use-active-ingredients-use-cases/delete-input-use-active-ingredient-use-case.ts create mode 100644 src/app/modules/input-uses/domain/use-cases/input-use-active-ingredients-use-cases/get-input-use-active-ingredient-use-case.ts create mode 100644 src/app/modules/input-uses/domain/use-cases/input-use-active-ingredients-use-cases/get-input-use-active-ingredients-use-case.ts create mode 100644 src/app/modules/input-uses/domain/use-cases/input-use-active-ingredients-use-cases/index.ts create mode 100644 src/app/modules/input-uses/domain/use-cases/input-use-active-ingredients-use-cases/update-input-use-active-ingredient-use-case.ts diff --git a/src/app/modules/input-uses/domain/models/input-use-active-ingredients-model.ts b/src/app/modules/input-uses/domain/models/input-use-active-ingredients-model.ts new file mode 100644 index 00000000..58545ec8 --- /dev/null +++ b/src/app/modules/input-uses/domain/models/input-use-active-ingredients-model.ts @@ -0,0 +1,17 @@ +import type { WithId } from '@/core/domain/types' + +export type InputUseActiveIngredientDetailsModel = { + name: string +} + +export type InputUseActiveIngredientDetailsApiResponse = { + name: string +} + +export type InputUseActiveIngredientModel = WithId<{ + name: string +}> + +export type InputUseActiveIngredientApiResponse = WithId<{ + name: string +}> diff --git a/src/app/modules/input-uses/domain/use-cases/input-use-active-ingredients-use-cases/create-input-use-active-ingredient-use-case.ts b/src/app/modules/input-uses/domain/use-cases/input-use-active-ingredients-use-cases/create-input-use-active-ingredient-use-case.ts new file mode 100644 index 00000000..e83cbe67 --- /dev/null +++ b/src/app/modules/input-uses/domain/use-cases/input-use-active-ingredients-use-cases/create-input-use-active-ingredient-use-case.ts @@ -0,0 +1,9 @@ +import type { InputUseActiveIngredientDetailsModel } from '../../models/input-use-active-ingredients-model' +import type { RequestInterface } from '@/core/domain/types' + +export type CreateInputUseActiveIngredientUseCase = RequestInterface< + { + inputUseActiveIngredient: InputUseActiveIngredientDetailsModel + }, + void +> diff --git a/src/app/modules/input-uses/domain/use-cases/input-use-active-ingredients-use-cases/delete-input-use-active-ingredient-use-case.ts b/src/app/modules/input-uses/domain/use-cases/input-use-active-ingredients-use-cases/delete-input-use-active-ingredient-use-case.ts new file mode 100644 index 00000000..7f36adbc --- /dev/null +++ b/src/app/modules/input-uses/domain/use-cases/input-use-active-ingredients-use-cases/delete-input-use-active-ingredient-use-case.ts @@ -0,0 +1,8 @@ +import type { RequestInterface } from '@/core/domain/types' + +export type DeleteInputUseActiveIngredientUseCase = RequestInterface< + { + id: number + }, + void +> diff --git a/src/app/modules/input-uses/domain/use-cases/input-use-active-ingredients-use-cases/get-input-use-active-ingredient-use-case.ts b/src/app/modules/input-uses/domain/use-cases/input-use-active-ingredients-use-cases/get-input-use-active-ingredient-use-case.ts new file mode 100644 index 00000000..ce686687 --- /dev/null +++ b/src/app/modules/input-uses/domain/use-cases/input-use-active-ingredients-use-cases/get-input-use-active-ingredient-use-case.ts @@ -0,0 +1,9 @@ +import type { InputUseActiveIngredientDetailsModel } from '../../models/input-use-active-ingredients-model' +import type { RequestInterface } from '@/core/domain/types' + +export type GetInputUseActiveIngredientUseCase = RequestInterface< + { + id: number + }, + InputUseActiveIngredientDetailsModel +> diff --git a/src/app/modules/input-uses/domain/use-cases/input-use-active-ingredients-use-cases/get-input-use-active-ingredients-use-case.ts b/src/app/modules/input-uses/domain/use-cases/input-use-active-ingredients-use-cases/get-input-use-active-ingredients-use-case.ts new file mode 100644 index 00000000..935e025a --- /dev/null +++ b/src/app/modules/input-uses/domain/use-cases/input-use-active-ingredients-use-cases/get-input-use-active-ingredients-use-case.ts @@ -0,0 +1,11 @@ +import type { InputUseActiveIngredientModel } from '../../models/input-use-active-ingredients-model' +import type { + RequestInterface, + ListParams, + ListResponse, +} from '@/core/domain/types' + +export type GetInputUseActiveIngredientsUseCase = RequestInterface< + ListParams, + ListResponse +> diff --git a/src/app/modules/input-uses/domain/use-cases/input-use-active-ingredients-use-cases/index.ts b/src/app/modules/input-uses/domain/use-cases/input-use-active-ingredients-use-cases/index.ts new file mode 100644 index 00000000..05a93212 --- /dev/null +++ b/src/app/modules/input-uses/domain/use-cases/input-use-active-ingredients-use-cases/index.ts @@ -0,0 +1,5 @@ +export * from './create-input-use-active-ingredient-use-case' +export * from './delete-input-use-active-ingredient-use-case' +export * from './get-input-use-active-ingredient-use-case' +export * from './get-input-use-active-ingredients-use-case' +export * from './update-input-use-active-ingredient-use-case' diff --git a/src/app/modules/input-uses/domain/use-cases/input-use-active-ingredients-use-cases/update-input-use-active-ingredient-use-case.ts b/src/app/modules/input-uses/domain/use-cases/input-use-active-ingredients-use-cases/update-input-use-active-ingredient-use-case.ts new file mode 100644 index 00000000..4ada8589 --- /dev/null +++ b/src/app/modules/input-uses/domain/use-cases/input-use-active-ingredients-use-cases/update-input-use-active-ingredient-use-case.ts @@ -0,0 +1,9 @@ +import type { InputUseActiveIngredientDetailsModel } from '../../models/input-use-active-ingredients-model' +import type { RequestInterface, WithId } from '@/core/domain/types' + +export type UpdateInputUseActiveIngredientUseCase = RequestInterface< + { + inputUseActiveIngredient: WithId + }, + void +> From a31f8646e134e078705bc4840577d95427a3c1ae Mon Sep 17 00:00:00 2001 From: Guilherme Minozzi Date: Wed, 22 Apr 2026 13:00:31 -0300 Subject: [PATCH 2/7] feat: add data layer --- .../index.ts | 5 ++ ...te-input-use-active-ingredient-use-case.ts | 39 ++++++++++ ...te-input-use-active-ingredient-use-case.ts | 40 ++++++++++ ...et-input-use-active-ingredient-use-case.ts | 50 +++++++++++++ ...t-input-use-active-ingredients-use-case.ts | 73 +++++++++++++++++++ ...te-input-use-active-ingredient-use-case.ts | 39 ++++++++++ 6 files changed, 246 insertions(+) create mode 100644 src/app/modules/input-uses/data/use-cases/input-use-active-ingredients-use-cases/index.ts create mode 100644 src/app/modules/input-uses/data/use-cases/input-use-active-ingredients-use-cases/remote-create-input-use-active-ingredient-use-case.ts create mode 100644 src/app/modules/input-uses/data/use-cases/input-use-active-ingredients-use-cases/remote-delete-input-use-active-ingredient-use-case.ts create mode 100644 src/app/modules/input-uses/data/use-cases/input-use-active-ingredients-use-cases/remote-get-input-use-active-ingredient-use-case.ts create mode 100644 src/app/modules/input-uses/data/use-cases/input-use-active-ingredients-use-cases/remote-get-input-use-active-ingredients-use-case.ts create mode 100644 src/app/modules/input-uses/data/use-cases/input-use-active-ingredients-use-cases/remote-update-input-use-active-ingredient-use-case.ts diff --git a/src/app/modules/input-uses/data/use-cases/input-use-active-ingredients-use-cases/index.ts b/src/app/modules/input-uses/data/use-cases/input-use-active-ingredients-use-cases/index.ts new file mode 100644 index 00000000..ffb89cad --- /dev/null +++ b/src/app/modules/input-uses/data/use-cases/input-use-active-ingredients-use-cases/index.ts @@ -0,0 +1,5 @@ +export * from './remote-create-input-use-active-ingredient-use-case' +export * from './remote-delete-input-use-active-ingredient-use-case' +export * from './remote-get-input-use-active-ingredient-use-case' +export * from './remote-get-input-use-active-ingredients-use-case' +export * from './remote-update-input-use-active-ingredient-use-case' diff --git a/src/app/modules/input-uses/data/use-cases/input-use-active-ingredients-use-cases/remote-create-input-use-active-ingredient-use-case.ts b/src/app/modules/input-uses/data/use-cases/input-use-active-ingredients-use-cases/remote-create-input-use-active-ingredient-use-case.ts new file mode 100644 index 00000000..c41d2c0d --- /dev/null +++ b/src/app/modules/input-uses/data/use-cases/input-use-active-ingredients-use-cases/remote-create-input-use-active-ingredient-use-case.ts @@ -0,0 +1,39 @@ +import { type HttpClient, HttpStatusCode } from '@/core/data/protocols/http' +import { + BadRequestError, + ForbiddenError, + UnexpectedError, +} from '@/core/domain/errors' + +import type { CreateInputUseActiveIngredientUseCase } from '../../../domain/use-cases/input-use-active-ingredients-use-cases' + +export class RemoteCreateInputUseActiveIngredientUseCase + implements CreateInputUseActiveIngredientUseCase +{ + constructor( + private readonly url: string, + private readonly httpClient: HttpClient + ) {} + + execute: CreateInputUseActiveIngredientUseCase['execute'] = async ({ + inputUseActiveIngredient, + }) => { + const { statusCode } = await this.httpClient.request({ + url: this.url, + method: 'post', + body: inputUseActiveIngredient, + }) + + if (statusCode === HttpStatusCode.created) return + + if (statusCode === HttpStatusCode.badRequest) throw new BadRequestError() + + if (statusCode === HttpStatusCode.forbidden) { + throw new ForbiddenError( + 'Você não tem permissão para criar um princípio ativo.' + ) + } + + throw new UnexpectedError() + } +} diff --git a/src/app/modules/input-uses/data/use-cases/input-use-active-ingredients-use-cases/remote-delete-input-use-active-ingredient-use-case.ts b/src/app/modules/input-uses/data/use-cases/input-use-active-ingredients-use-cases/remote-delete-input-use-active-ingredient-use-case.ts new file mode 100644 index 00000000..e5899e2b --- /dev/null +++ b/src/app/modules/input-uses/data/use-cases/input-use-active-ingredients-use-cases/remote-delete-input-use-active-ingredient-use-case.ts @@ -0,0 +1,40 @@ +import { type HttpClient, HttpStatusCode } from '@/core/data/protocols/http' +import { + UnexpectedError, + NotFoundError, + ForbiddenError, +} from '@/core/domain/errors' + +import type { DeleteInputUseActiveIngredientUseCase } from '../../../domain/use-cases/input-use-active-ingredients-use-cases' + +export class RemoteDeleteInputUseActiveIngredientUseCase + implements DeleteInputUseActiveIngredientUseCase +{ + constructor( + private readonly url: string, + private readonly httpClient: HttpClient + ) {} + + execute: DeleteInputUseActiveIngredientUseCase['execute'] = async ({ + id, + }) => { + const { statusCode } = await this.httpClient.request({ + url: `${this.url}/${id}`, + method: 'delete', + }) + + if (statusCode === HttpStatusCode.noContent) return + + if (statusCode === HttpStatusCode.notFound) { + throw new NotFoundError('Princípio Ativo') + } + + if (statusCode === HttpStatusCode.forbidden) { + throw new ForbiddenError( + 'Você não tem permissão para excluir um princípio ativo.' + ) + } + + throw new UnexpectedError() + } +} diff --git a/src/app/modules/input-uses/data/use-cases/input-use-active-ingredients-use-cases/remote-get-input-use-active-ingredient-use-case.ts b/src/app/modules/input-uses/data/use-cases/input-use-active-ingredients-use-cases/remote-get-input-use-active-ingredient-use-case.ts new file mode 100644 index 00000000..45aee986 --- /dev/null +++ b/src/app/modules/input-uses/data/use-cases/input-use-active-ingredients-use-cases/remote-get-input-use-active-ingredient-use-case.ts @@ -0,0 +1,50 @@ +import { type HttpClient, HttpStatusCode } from '@/core/data/protocols/http' +import { + BadRequestError, + ForbiddenError, + NotFoundError, + UnexpectedError, +} from '@/core/domain/errors' + +import type { + InputUseActiveIngredientDetailsApiResponse, + InputUseActiveIngredientDetailsModel, +} from '../../../domain/models/input-use-active-ingredients-model' +import type { GetInputUseActiveIngredientUseCase } from '../../../domain/use-cases/input-use-active-ingredients-use-cases' + +export class RemoteGetInputUseActiveIngredientUseCase + implements GetInputUseActiveIngredientUseCase +{ + constructor( + private readonly url: string, + private readonly httpClient: HttpClient< + InputUseActiveIngredientDetailsModel, + InputUseActiveIngredientDetailsApiResponse + > + ) {} + + execute: GetInputUseActiveIngredientUseCase['execute'] = async ({ id }) => { + const { statusCode, body } = await this.httpClient.request({ + url: `${this.url}/${id}`, + method: 'get', + }) + + if (statusCode === HttpStatusCode.ok && !!body) + return { + name: body.name, + } + + if (statusCode === HttpStatusCode.badRequest) throw new BadRequestError() + + if (statusCode === HttpStatusCode.notFound) + throw new NotFoundError('Princípio Ativo') + + if (statusCode === HttpStatusCode.forbidden) { + throw new ForbiddenError( + 'Você não tem permissão para acessar os dados deste princípio ativo.' + ) + } + + throw new UnexpectedError() + } +} diff --git a/src/app/modules/input-uses/data/use-cases/input-use-active-ingredients-use-cases/remote-get-input-use-active-ingredients-use-case.ts b/src/app/modules/input-uses/data/use-cases/input-use-active-ingredients-use-cases/remote-get-input-use-active-ingredients-use-case.ts new file mode 100644 index 00000000..a42d0eba --- /dev/null +++ b/src/app/modules/input-uses/data/use-cases/input-use-active-ingredients-use-cases/remote-get-input-use-active-ingredients-use-case.ts @@ -0,0 +1,73 @@ +import { type HttpClient, HttpStatusCode } from '@/core/data/protocols/http' +import { + UnexpectedError, + NotFoundError, + ForbiddenError, +} from '@/core/domain/errors' + +import type { + InputUseActiveIngredientApiResponse, + InputUseActiveIngredientModel, +} from '../../../domain/models/input-use-active-ingredients-model' +import type { GetInputUseActiveIngredientsUseCase } from '../../../domain/use-cases/input-use-active-ingredients-use-cases' +import type { ListApiResponse, MapApiProperties } from '@/core/domain/types' + +export class RemoteGetInputUseActiveIngredientsUseCase + implements GetInputUseActiveIngredientsUseCase +{ + constructor( + private readonly url: string, + private readonly httpClient: HttpClient< + InputUseActiveIngredientModel, + InputUseActiveIngredientApiResponse, + ListApiResponse + > + ) {} + + execute: GetInputUseActiveIngredientsUseCase['execute'] = async ({ + filters, + pagination, + sort, + }) => { + const mapApiProperties: MapApiProperties< + InputUseActiveIngredientModel, + InputUseActiveIngredientApiResponse + > = { + id: 'id', + name: 'name', + } + + const { statusCode, body } = await this.httpClient.request({ + url: `${this.url}/search`, + method: 'post', + filters, + pagination, + sort, + mapApiProperties, + }) + + if (statusCode === HttpStatusCode.ok && !!body) { + return { + resources: body.content.map((item) => { + return { + id: item.id, + name: item.name, + } + }), + totalPages: Math.ceil(body.numberOfElements / body.pageable.pageSize), + } + } + + if (statusCode === HttpStatusCode.notFound) { + throw new NotFoundError('Princípio Ativo') + } + + if (statusCode === HttpStatusCode.forbidden) { + throw new ForbiddenError( + 'Você não tem permissão para buscar os princípios ativos.' + ) + } + + throw new UnexpectedError() + } +} diff --git a/src/app/modules/input-uses/data/use-cases/input-use-active-ingredients-use-cases/remote-update-input-use-active-ingredient-use-case.ts b/src/app/modules/input-uses/data/use-cases/input-use-active-ingredients-use-cases/remote-update-input-use-active-ingredient-use-case.ts new file mode 100644 index 00000000..62dbd317 --- /dev/null +++ b/src/app/modules/input-uses/data/use-cases/input-use-active-ingredients-use-cases/remote-update-input-use-active-ingredient-use-case.ts @@ -0,0 +1,39 @@ +import { type HttpClient, HttpStatusCode } from '@/core/data/protocols/http' +import { + BadRequestError, + ForbiddenError, + UnexpectedError, +} from '@/core/domain/errors' + +import type { UpdateInputUseActiveIngredientUseCase } from '../../../domain/use-cases/input-use-active-ingredients-use-cases' + +export class RemoteUpdateInputUseActiveIngredientUseCase + implements UpdateInputUseActiveIngredientUseCase +{ + constructor( + private readonly url: string, + private readonly httpClient: HttpClient + ) {} + + execute: UpdateInputUseActiveIngredientUseCase['execute'] = async ({ + inputUseActiveIngredient: { id, ...inputUseActiveIngredient }, + }) => { + const { statusCode } = await this.httpClient.request({ + url: `${this.url}/${id}`, + method: 'patch', + body: inputUseActiveIngredient, + }) + + if (statusCode === HttpStatusCode.noContent) return + + if (statusCode === HttpStatusCode.badRequest) throw new BadRequestError() + + if (statusCode === HttpStatusCode.forbidden) { + throw new ForbiddenError( + 'Você não tem permissão para editar um princípio ativo.' + ) + } + + throw new UnexpectedError() + } +} From f9ef2e666718c31b51ea228d4c4205048d891ab6 Mon Sep 17 00:00:00 2001 From: Guilherme Minozzi Date: Wed, 22 Apr 2026 13:03:39 -0300 Subject: [PATCH 3/7] feat: add main with factories layer --- .../input-use-active-ingredients-use-cases/index.ts | 5 +++++ ...e-input-use-active-ingredient-use-case-factory.ts | 12 ++++++++++++ ...e-input-use-active-ingredient-use-case-factory.ts | 12 ++++++++++++ ...t-input-use-active-ingredient-use-case-factory.ts | 12 ++++++++++++ ...-input-use-active-ingredients-use-case-factory.ts | 12 ++++++++++++ ...e-input-use-active-ingredient-use-case-factory.ts | 12 ++++++++++++ 6 files changed, 65 insertions(+) create mode 100644 src/app/modules/input-uses/main/factories/use-cases/input-use-active-ingredients-use-cases/index.ts create mode 100644 src/app/modules/input-uses/main/factories/use-cases/input-use-active-ingredients-use-cases/remote-create-input-use-active-ingredient-use-case-factory.ts create mode 100644 src/app/modules/input-uses/main/factories/use-cases/input-use-active-ingredients-use-cases/remote-delete-input-use-active-ingredient-use-case-factory.ts create mode 100644 src/app/modules/input-uses/main/factories/use-cases/input-use-active-ingredients-use-cases/remote-get-input-use-active-ingredient-use-case-factory.ts create mode 100644 src/app/modules/input-uses/main/factories/use-cases/input-use-active-ingredients-use-cases/remote-get-input-use-active-ingredients-use-case-factory.ts create mode 100644 src/app/modules/input-uses/main/factories/use-cases/input-use-active-ingredients-use-cases/remote-update-input-use-active-ingredient-use-case-factory.ts diff --git a/src/app/modules/input-uses/main/factories/use-cases/input-use-active-ingredients-use-cases/index.ts b/src/app/modules/input-uses/main/factories/use-cases/input-use-active-ingredients-use-cases/index.ts new file mode 100644 index 00000000..3a73b604 --- /dev/null +++ b/src/app/modules/input-uses/main/factories/use-cases/input-use-active-ingredients-use-cases/index.ts @@ -0,0 +1,5 @@ +export * from './remote-create-input-use-active-ingredient-use-case-factory' +export * from './remote-delete-input-use-active-ingredient-use-case-factory' +export * from './remote-get-input-use-active-ingredient-use-case-factory' +export * from './remote-get-input-use-active-ingredients-use-case-factory' +export * from './remote-update-input-use-active-ingredient-use-case-factory' diff --git a/src/app/modules/input-uses/main/factories/use-cases/input-use-active-ingredients-use-cases/remote-create-input-use-active-ingredient-use-case-factory.ts b/src/app/modules/input-uses/main/factories/use-cases/input-use-active-ingredients-use-cases/remote-create-input-use-active-ingredient-use-case-factory.ts new file mode 100644 index 00000000..c7189511 --- /dev/null +++ b/src/app/modules/input-uses/main/factories/use-cases/input-use-active-ingredients-use-cases/remote-create-input-use-active-ingredient-use-case-factory.ts @@ -0,0 +1,12 @@ +import { makeApiHttpClient } from '@/core/main/factories/http' + +import { RemoteCreateInputUseActiveIngredientUseCase } from '../../../../data/use-cases/input-use-active-ingredients-use-cases' + +import type { CreateInputUseActiveIngredientUseCase } from '../../../../domain/use-cases/input-use-active-ingredients-use-cases' + +export function makeRemoteCreateInputUseActiveIngredientUseCase(): CreateInputUseActiveIngredientUseCase { + return new RemoteCreateInputUseActiveIngredientUseCase( + '/input-uses/active-ingredients', + makeApiHttpClient() + ) +} diff --git a/src/app/modules/input-uses/main/factories/use-cases/input-use-active-ingredients-use-cases/remote-delete-input-use-active-ingredient-use-case-factory.ts b/src/app/modules/input-uses/main/factories/use-cases/input-use-active-ingredients-use-cases/remote-delete-input-use-active-ingredient-use-case-factory.ts new file mode 100644 index 00000000..f8e8e1a2 --- /dev/null +++ b/src/app/modules/input-uses/main/factories/use-cases/input-use-active-ingredients-use-cases/remote-delete-input-use-active-ingredient-use-case-factory.ts @@ -0,0 +1,12 @@ +import { makeApiHttpClient } from '@/core/main/factories/http' + +import { RemoteDeleteInputUseActiveIngredientUseCase } from '../../../../data/use-cases/input-use-active-ingredients-use-cases' + +import type { DeleteInputUseActiveIngredientUseCase } from '../../../../domain/use-cases/input-use-active-ingredients-use-cases' + +export function makeRemoteDeleteInputUseActiveIngredientUseCase(): DeleteInputUseActiveIngredientUseCase { + return new RemoteDeleteInputUseActiveIngredientUseCase( + '/input-uses/active-ingredients', + makeApiHttpClient() + ) +} diff --git a/src/app/modules/input-uses/main/factories/use-cases/input-use-active-ingredients-use-cases/remote-get-input-use-active-ingredient-use-case-factory.ts b/src/app/modules/input-uses/main/factories/use-cases/input-use-active-ingredients-use-cases/remote-get-input-use-active-ingredient-use-case-factory.ts new file mode 100644 index 00000000..64d226a3 --- /dev/null +++ b/src/app/modules/input-uses/main/factories/use-cases/input-use-active-ingredients-use-cases/remote-get-input-use-active-ingredient-use-case-factory.ts @@ -0,0 +1,12 @@ +import { makeApiHttpClient } from '@/core/main/factories/http' + +import { RemoteGetInputUseActiveIngredientUseCase } from '../../../../data/use-cases/input-use-active-ingredients-use-cases' + +import type { GetInputUseActiveIngredientUseCase } from '../../../../domain/use-cases/input-use-active-ingredients-use-cases' + +export function makeRemoteGetInputUseActiveIngredientUseCase(): GetInputUseActiveIngredientUseCase { + return new RemoteGetInputUseActiveIngredientUseCase( + '/input-uses/active-ingredients', + makeApiHttpClient() + ) +} diff --git a/src/app/modules/input-uses/main/factories/use-cases/input-use-active-ingredients-use-cases/remote-get-input-use-active-ingredients-use-case-factory.ts b/src/app/modules/input-uses/main/factories/use-cases/input-use-active-ingredients-use-cases/remote-get-input-use-active-ingredients-use-case-factory.ts new file mode 100644 index 00000000..18352a3b --- /dev/null +++ b/src/app/modules/input-uses/main/factories/use-cases/input-use-active-ingredients-use-cases/remote-get-input-use-active-ingredients-use-case-factory.ts @@ -0,0 +1,12 @@ +import { makeApiHttpClient } from '@/core/main/factories/http' + +import { RemoteGetInputUseActiveIngredientsUseCase } from '../../../../data/use-cases/input-use-active-ingredients-use-cases' + +import type { GetInputUseActiveIngredientsUseCase } from '../../../../domain/use-cases/input-use-active-ingredients-use-cases' + +export function makeRemoteGetInputUseActiveIngredientsUseCase(): GetInputUseActiveIngredientsUseCase { + return new RemoteGetInputUseActiveIngredientsUseCase( + '/input-uses/active-ingredients', + makeApiHttpClient() + ) +} diff --git a/src/app/modules/input-uses/main/factories/use-cases/input-use-active-ingredients-use-cases/remote-update-input-use-active-ingredient-use-case-factory.ts b/src/app/modules/input-uses/main/factories/use-cases/input-use-active-ingredients-use-cases/remote-update-input-use-active-ingredient-use-case-factory.ts new file mode 100644 index 00000000..71306fc8 --- /dev/null +++ b/src/app/modules/input-uses/main/factories/use-cases/input-use-active-ingredients-use-cases/remote-update-input-use-active-ingredient-use-case-factory.ts @@ -0,0 +1,12 @@ +import { makeApiHttpClient } from '@/core/main/factories/http' + +import { RemoteUpdateInputUseActiveIngredientUseCase } from '../../../../data/use-cases/input-use-active-ingredients-use-cases' + +import type { UpdateInputUseActiveIngredientUseCase } from '../../../../domain/use-cases/input-use-active-ingredients-use-cases' + +export function makeRemoteUpdateInputUseActiveIngredientUseCase(): UpdateInputUseActiveIngredientUseCase { + return new RemoteUpdateInputUseActiveIngredientUseCase( + '/input-uses/active-ingredients', + makeApiHttpClient() + ) +} From df2fe65cd8c4251f4dd7bbfcdf3bf887d009eb59 Mon Sep 17 00:00:00 2001 From: Guilherme Minozzi Date: Wed, 22 Apr 2026 13:06:02 -0300 Subject: [PATCH 4/7] feat: add mocks layer --- ...ate-input-use-active-ingredient-handler.ts | 19 +++++ ...ete-input-use-active-ingredient-handler.ts | 17 +++++ ...get-input-use-active-ingredient-handler.ts | 33 +++++++++ ...et-input-use-active-ingredients-handler.ts | 72 +++++++++++++++++++ .../index.ts | 5 ++ ...ate-input-use-active-ingredient-handler.ts | 19 +++++ 6 files changed, 165 insertions(+) create mode 100644 src/app/modules/input-uses/mocks/handlers/input-use-active-ingredients-handlers/create-input-use-active-ingredient-handler.ts create mode 100644 src/app/modules/input-uses/mocks/handlers/input-use-active-ingredients-handlers/delete-input-use-active-ingredient-handler.ts create mode 100644 src/app/modules/input-uses/mocks/handlers/input-use-active-ingredients-handlers/get-input-use-active-ingredient-handler.ts create mode 100644 src/app/modules/input-uses/mocks/handlers/input-use-active-ingredients-handlers/get-input-use-active-ingredients-handler.ts create mode 100644 src/app/modules/input-uses/mocks/handlers/input-use-active-ingredients-handlers/index.ts create mode 100644 src/app/modules/input-uses/mocks/handlers/input-use-active-ingredients-handlers/update-input-use-active-ingredient-handler.ts diff --git a/src/app/modules/input-uses/mocks/handlers/input-use-active-ingredients-handlers/create-input-use-active-ingredient-handler.ts b/src/app/modules/input-uses/mocks/handlers/input-use-active-ingredients-handlers/create-input-use-active-ingredient-handler.ts new file mode 100644 index 00000000..0298db07 --- /dev/null +++ b/src/app/modules/input-uses/mocks/handlers/input-use-active-ingredients-handlers/create-input-use-active-ingredient-handler.ts @@ -0,0 +1,19 @@ +import { HttpResponse, type PathParams } from 'msw' + +import { HttpStatusCode } from '@/core/data/protocols/http' +import { httpWithMiddleware } from '@/core/mocks/lib' +import { withAuth, withDelay } from '@/core/mocks/middleware' + +import type { InputUseActiveIngredientDetailsModel } from '@/app/modules/input-uses/domain/models/input-use-active-ingredients-model' + +export const createInputUseActiveIngredientHandler = httpWithMiddleware< + PathParams, + InputUseActiveIngredientDetailsModel, + never +>({ + routePath: '/api/input-uses/active-ingredients', + method: 'post', + middlewares: [withDelay(), withAuth], + resolver: async () => + HttpResponse.json({}, { status: HttpStatusCode.created }), +}) diff --git a/src/app/modules/input-uses/mocks/handlers/input-use-active-ingredients-handlers/delete-input-use-active-ingredient-handler.ts b/src/app/modules/input-uses/mocks/handlers/input-use-active-ingredients-handlers/delete-input-use-active-ingredient-handler.ts new file mode 100644 index 00000000..8f29355c --- /dev/null +++ b/src/app/modules/input-uses/mocks/handlers/input-use-active-ingredients-handlers/delete-input-use-active-ingredient-handler.ts @@ -0,0 +1,17 @@ +import { HttpResponse, type PathParams } from 'msw' + +import { HttpStatusCode } from '@/core/data/protocols/http' +import { httpWithMiddleware } from '@/core/mocks/lib' +import { withAuth, withDelay } from '@/core/mocks/middleware' + +export const deleteInputUseActiveIngredientHandler = httpWithMiddleware< + PathParams<'id'>, + never, + never +>({ + routePath: '/api/input-uses/active-ingredients/:id', + method: 'delete', + middlewares: [withDelay(), withAuth], + resolver: async () => + HttpResponse.json(undefined, { status: HttpStatusCode.noContent }), +}) diff --git a/src/app/modules/input-uses/mocks/handlers/input-use-active-ingredients-handlers/get-input-use-active-ingredient-handler.ts b/src/app/modules/input-uses/mocks/handlers/input-use-active-ingredients-handlers/get-input-use-active-ingredient-handler.ts new file mode 100644 index 00000000..aee2fc22 --- /dev/null +++ b/src/app/modules/input-uses/mocks/handlers/input-use-active-ingredients-handlers/get-input-use-active-ingredient-handler.ts @@ -0,0 +1,33 @@ +import { HttpResponse, type PathParams } from 'msw' + +import { HttpStatusCode } from '@/core/data/protocols/http' +import { httpWithMiddleware } from '@/core/mocks/lib' +import { withAuth, withDelay } from '@/core/mocks/middleware' + +import allActiveIngredientsData from '@database/allActiveIngredientsData.json' + +import type { InputUseActiveIngredientApiResponse } from '@/app/modules/input-uses/domain/models/input-use-active-ingredients-model' + +export const getInputUseActiveIngredientHandler = httpWithMiddleware< + PathParams<'id'>, + never, + InputUseActiveIngredientApiResponse +>({ + routePath: '/api/input-uses/active-ingredients/:id', + method: 'get', + middlewares: [withDelay(), withAuth], + resolver: async ({ params }) => { + const id = Number(params.id) + const activeIngredient = allActiveIngredientsData.find( + (item) => item.id === id + ) + + if (!activeIngredient) { + return HttpResponse.json({} as InputUseActiveIngredientApiResponse, { + status: HttpStatusCode.notFound, + }) + } + + return HttpResponse.json(activeIngredient, { status: HttpStatusCode.ok }) + }, +}) diff --git a/src/app/modules/input-uses/mocks/handlers/input-use-active-ingredients-handlers/get-input-use-active-ingredients-handler.ts b/src/app/modules/input-uses/mocks/handlers/input-use-active-ingredients-handlers/get-input-use-active-ingredients-handler.ts new file mode 100644 index 00000000..f34f3116 --- /dev/null +++ b/src/app/modules/input-uses/mocks/handlers/input-use-active-ingredients-handlers/get-input-use-active-ingredients-handler.ts @@ -0,0 +1,72 @@ +import { HttpResponse, type PathParams } from 'msw' + +import { HttpStatusCode } from '@/core/data/protocols/http' +import { httpWithMiddleware } from '@/core/mocks/lib' +import { withAuth, withDelay } from '@/core/mocks/middleware' +import { filterData, paginateData, sortData } from '@/core/mocks/utils' + +import allActiveIngredientsData from '@database/allActiveIngredientsData.json' + +import type { InputUseActiveIngredientApiResponse } from '@/app/modules/input-uses/domain/models/input-use-active-ingredients-model' +import type { MockParams } from '@/core/mocks/types/mock-params-type' +import type { MockResponse } from '@/core/mocks/types/mock-response-type' + +export const getInputUseActiveIngredientsHandler = httpWithMiddleware< + PathParams, + MockParams, + MockResponse +>({ + routePath: '/api/input-uses/active-ingredients/search', + method: 'post', + middlewares: [withDelay(), withAuth], + resolver: async ({ request }) => { + const { filters, page, rows, sort } = await request.json() + + if (!allActiveIngredientsData.length) { + return HttpResponse.json( + { + content: [], + numberOfElements: 0, + pageable: { + pageSize: 0, + }, + }, + { + status: HttpStatusCode.notFound, + } + ) + } + + let activeIngredients = allActiveIngredientsData + + if (filters) { + activeIngredients = filterData( + filters, + activeIngredients + ) + } + if (sort) { + activeIngredients = sortData( + sort, + activeIngredients + ) + } + + const numberOfElements = activeIngredients.length + activeIngredients = paginateData( + { page, perPage: rows }, + activeIngredients + ) + + return HttpResponse.json( + { + content: activeIngredients, + numberOfElements, + pageable: { + pageSize: rows, + }, + }, + { status: HttpStatusCode.ok } + ) + }, +}) diff --git a/src/app/modules/input-uses/mocks/handlers/input-use-active-ingredients-handlers/index.ts b/src/app/modules/input-uses/mocks/handlers/input-use-active-ingredients-handlers/index.ts new file mode 100644 index 00000000..35b0a75e --- /dev/null +++ b/src/app/modules/input-uses/mocks/handlers/input-use-active-ingredients-handlers/index.ts @@ -0,0 +1,5 @@ +export * from './create-input-use-active-ingredient-handler' +export * from './delete-input-use-active-ingredient-handler' +export * from './get-input-use-active-ingredient-handler' +export * from './get-input-use-active-ingredients-handler' +export * from './update-input-use-active-ingredient-handler' diff --git a/src/app/modules/input-uses/mocks/handlers/input-use-active-ingredients-handlers/update-input-use-active-ingredient-handler.ts b/src/app/modules/input-uses/mocks/handlers/input-use-active-ingredients-handlers/update-input-use-active-ingredient-handler.ts new file mode 100644 index 00000000..64992419 --- /dev/null +++ b/src/app/modules/input-uses/mocks/handlers/input-use-active-ingredients-handlers/update-input-use-active-ingredient-handler.ts @@ -0,0 +1,19 @@ +import { HttpResponse, type PathParams } from 'msw' + +import { HttpStatusCode } from '@/core/data/protocols/http' +import { httpWithMiddleware } from '@/core/mocks/lib' +import { withAuth, withDelay } from '@/core/mocks/middleware' + +import type { InputUseActiveIngredientApiResponse } from '@/app/modules/input-uses/domain/models/input-use-active-ingredients-model' + +export const updateInputUseActiveIngredientHandler = httpWithMiddleware< + PathParams<'id'>, + Omit, + never +>({ + routePath: '/api/input-uses/active-ingredients/:id', + method: 'patch', + middlewares: [withDelay(), withAuth], + resolver: async () => + HttpResponse.json(undefined, { status: HttpStatusCode.noContent }), +}) From 2f59d67e39bf8d1a91bdcaf674cb843d7e2c06d4 Mon Sep 17 00:00:00 2001 From: Guilherme Minozzi Date: Wed, 22 Apr 2026 15:29:50 -0300 Subject: [PATCH 5/7] feat: add presentation layer --- .../index.ts | 1 + ...-use-active-ingredient-data-table.hook.tsx | 94 +++++++++++ ...input-use-active-ingredient-data-table.tsx | 37 +++++ .../index.ts | 1 + ...ut-use-active-ingredient-delete-dialog.tsx | 83 ++++++++++ .../input-use-active-ingredient-context.tsx | 149 ++++++++++++++++++ ...reate-input-use-active-ingredient-form.tsx | 116 ++++++++++++++ .../edit-input-use-active-ingredient-form.tsx | 141 +++++++++++++++++ .../input-use-active-ingredient-form/index.ts | 1 + ...nput-use-active-ingredient-form-inputs.tsx | 28 ++++ .../input-use-active-ingredient-form.tsx | 18 +++ ...use-active-ingredient-initial-form-data.ts | 6 + .../input-use-product-form-inputs.tsx | 20 +-- ...nput-use-active-ingredient-context.hook.ts | 15 ++ ...input-use-active-ingredients-query.hook.ts | 49 ++++++ .../input-use-active-ingredient-query.hook.ts | 37 +++++ ...input-use-active-ingredients-query.hook.ts | 56 +++++++ .../input-use-active-ingredients-screen.tsx | 67 ++++++++ .../input-use-active-ingredient-types.ts | 6 + ...input-use-active-ingredient-form-schema.ts | 12 ++ 20 files changed, 928 insertions(+), 9 deletions(-) create mode 100644 src/app/modules/input-uses/presentation/components/input-use-active-ingredient-data-table/index.ts create mode 100644 src/app/modules/input-uses/presentation/components/input-use-active-ingredient-data-table/input-use-active-ingredient-data-table.hook.tsx create mode 100644 src/app/modules/input-uses/presentation/components/input-use-active-ingredient-data-table/input-use-active-ingredient-data-table.tsx create mode 100644 src/app/modules/input-uses/presentation/components/input-use-active-ingredient-delete-dialog/index.ts create mode 100644 src/app/modules/input-uses/presentation/components/input-use-active-ingredient-delete-dialog/input-use-active-ingredient-delete-dialog.tsx create mode 100644 src/app/modules/input-uses/presentation/contexts/input-use-active-ingredient-context.tsx create mode 100644 src/app/modules/input-uses/presentation/forms/input-use-active-ingredient-form/create-input-use-active-ingredient-form.tsx create mode 100644 src/app/modules/input-uses/presentation/forms/input-use-active-ingredient-form/edit-input-use-active-ingredient-form.tsx create mode 100644 src/app/modules/input-uses/presentation/forms/input-use-active-ingredient-form/index.ts create mode 100644 src/app/modules/input-uses/presentation/forms/input-use-active-ingredient-form/input-use-active-ingredient-form-inputs.tsx create mode 100644 src/app/modules/input-uses/presentation/forms/input-use-active-ingredient-form/input-use-active-ingredient-form.tsx create mode 100644 src/app/modules/input-uses/presentation/forms/input-use-active-ingredient-form/input-use-active-ingredient-initial-form-data.ts create mode 100644 src/app/modules/input-uses/presentation/hooks/input-use-active-ingredient-context.hook.ts create mode 100644 src/app/modules/input-uses/presentation/hooks/queries/all-input-use-active-ingredients-query.hook.ts create mode 100644 src/app/modules/input-uses/presentation/hooks/queries/input-use-active-ingredient-query.hook.ts create mode 100644 src/app/modules/input-uses/presentation/hooks/queries/input-use-active-ingredients-query.hook.ts create mode 100644 src/app/modules/input-uses/presentation/screens/input-use-active-ingredients-screen.tsx create mode 100644 src/app/modules/input-uses/presentation/types/input-use-active-ingredient-types.ts create mode 100644 src/app/modules/input-uses/presentation/validations/input-use-active-ingredient-form-schema.ts diff --git a/src/app/modules/input-uses/presentation/components/input-use-active-ingredient-data-table/index.ts b/src/app/modules/input-uses/presentation/components/input-use-active-ingredient-data-table/index.ts new file mode 100644 index 00000000..d80d9762 --- /dev/null +++ b/src/app/modules/input-uses/presentation/components/input-use-active-ingredient-data-table/index.ts @@ -0,0 +1 @@ +export * from './input-use-active-ingredient-data-table' diff --git a/src/app/modules/input-uses/presentation/components/input-use-active-ingredient-data-table/input-use-active-ingredient-data-table.hook.tsx b/src/app/modules/input-uses/presentation/components/input-use-active-ingredient-data-table/input-use-active-ingredient-data-table.hook.tsx new file mode 100644 index 00000000..ef3ba665 --- /dev/null +++ b/src/app/modules/input-uses/presentation/components/input-use-active-ingredient-data-table/input-use-active-ingredient-data-table.hook.tsx @@ -0,0 +1,94 @@ +import { useMemo, useState } from 'react' + +import { MoreHorizontalIcon, PencilIcon, Trash2Icon } from 'lucide-react' + +import { DropdownMenu } from '@/core/presentation/components/ui' +import { useDebounce } from '@/core/presentation/hooks' + +import { useInputUseActiveIngredientContext } from '../../hooks/input-use-active-ingredient-context.hook' +import { useInputUseActiveIngredientsQuery } from '../../hooks/queries/input-use-active-ingredients-query.hook' + +import type { InputUseActiveIngredientModel } from '../../../domain/models/input-use-active-ingredients-model' +import type { InputUseActiveIngredientSort } from '../../types/input-use-active-ingredient-types' +import type { ColumnDef } from '@tanstack/react-table' + +export function useInputUseActiveIngredientDataTable() { + const { + filters, + openEditInputUseActiveIngredientForm, + openDeleteInputUseActiveIngredientContainer, + } = useInputUseActiveIngredientContext() + + const [page, setPage] = useState(1) + const [sort, setSort] = useState() + + const debouncedFilters = useDebounce({ value: filters }) + + const { isLoading, inputUseActiveIngredients } = + useInputUseActiveIngredientsQuery({ + filters: debouncedFilters, + page, + sort, + }) + + const columns = useMemo[]>( + () => [ + { + accessorKey: 'name', + header: 'Nome', + }, + { + id: 'row-actions', + header: '', + cell: ({ row }) => { + const { original: inputUseActiveIngredient } = row + + return ( + + + + + + + openEditInputUseActiveIngredientForm( + inputUseActiveIngredient + ) + } + > + Editar + + + + openDeleteInputUseActiveIngredientContainer( + inputUseActiveIngredient + ) + } + > + Excluir + + + + ) + }, + }, + ], + [ + openDeleteInputUseActiveIngredientContainer, + openEditInputUseActiveIngredientForm, + ] + ) + + return { + columns, + inputUseActiveIngredients, + isLoading, + page, + sort, + setSort, + setPage, + } +} diff --git a/src/app/modules/input-uses/presentation/components/input-use-active-ingredient-data-table/input-use-active-ingredient-data-table.tsx b/src/app/modules/input-uses/presentation/components/input-use-active-ingredient-data-table/input-use-active-ingredient-data-table.tsx new file mode 100644 index 00000000..c69c24c9 --- /dev/null +++ b/src/app/modules/input-uses/presentation/components/input-use-active-ingredient-data-table/input-use-active-ingredient-data-table.tsx @@ -0,0 +1,37 @@ +import { DataTable } from '@/core/presentation/components/ui' + +import { useInputUseActiveIngredientDataTable } from './input-use-active-ingredient-data-table.hook' + +import type { InputUseActiveIngredientModel } from '../../../domain/models/input-use-active-ingredients-model' + +export function InputUseActiveIngredientDataTable() { + const { + columns, + inputUseActiveIngredients, + isLoading, + page, + sort, + setSort, + setPage, + } = useInputUseActiveIngredientDataTable() + + return ( + + columns={columns} + data={inputUseActiveIngredients.resources} + totalPages={inputUseActiveIngredients.totalPages} + pagination={{ + currentPage: page, + onPageChange: setPage, + }} + sorting={{ + currentSorting: sort, + onSorting: setSort, + }} + loading={isLoading} + /> + ) +} + +InputUseActiveIngredientDataTable.displayName = + 'InputUseActiveIngredientDataTable' diff --git a/src/app/modules/input-uses/presentation/components/input-use-active-ingredient-delete-dialog/index.ts b/src/app/modules/input-uses/presentation/components/input-use-active-ingredient-delete-dialog/index.ts new file mode 100644 index 00000000..37fe5983 --- /dev/null +++ b/src/app/modules/input-uses/presentation/components/input-use-active-ingredient-delete-dialog/index.ts @@ -0,0 +1 @@ +export * from './input-use-active-ingredient-delete-dialog' diff --git a/src/app/modules/input-uses/presentation/components/input-use-active-ingredient-delete-dialog/input-use-active-ingredient-delete-dialog.tsx b/src/app/modules/input-uses/presentation/components/input-use-active-ingredient-delete-dialog/input-use-active-ingredient-delete-dialog.tsx new file mode 100644 index 00000000..27ebeca4 --- /dev/null +++ b/src/app/modules/input-uses/presentation/components/input-use-active-ingredient-delete-dialog/input-use-active-ingredient-delete-dialog.tsx @@ -0,0 +1,83 @@ +import { useCallback } from 'react' + +import { useMutation, useQueryClient } from '@tanstack/react-query' +import toast from 'react-hot-toast' + +import { AlertDialog } from '@/core/presentation/components/ui' + +import { makeRemoteDeleteInputUseActiveIngredientUseCase } from '../../../main/factories/use-cases/input-use-active-ingredients-use-cases' +import { useInputUseActiveIngredientContext } from '../../hooks/input-use-active-ingredient-context.hook' + +export function InputUseActiveIngredientDeleteDialog() { + const deleteInputUseActiveIngredientUseCase = + makeRemoteDeleteInputUseActiveIngredientUseCase() + + const { + selectedInputUseActiveIngredient, + isOpenDeleteInputUseActiveIngredientContainer, + closeDeleteInputUseActiveIngredientContainer, + } = useInputUseActiveIngredientContext() + + const queryClient = useQueryClient() + + const { mutateAsync: mutateHandleDeleteInputUseActiveIngredient } = + useMutation({ + mutationFn: deleteInputUseActiveIngredientUseCase.execute, + }) + + const handleDeleteInputUseActiveIngredient = useCallback(async () => { + if (!selectedInputUseActiveIngredient?.id) { + toast.error('Erro ao remover princípio ativo') + return + } + + try { + await mutateHandleDeleteInputUseActiveIngredient({ + id: selectedInputUseActiveIngredient.id, + }) + + queryClient.invalidateQueries({ + queryKey: ['input-use-active-ingredients'], + exact: false, + }) + + toast.success('Princípio ativo removido com sucesso') + } catch { + toast.error('Erro ao remover princípio ativo') + } finally { + closeDeleteInputUseActiveIngredientContainer() + } + }, [ + closeDeleteInputUseActiveIngredientContainer, + mutateHandleDeleteInputUseActiveIngredient, + queryClient, + selectedInputUseActiveIngredient, + ]) + + return ( + + + + + {`Deseja remover o princípio ativo ${selectedInputUseActiveIngredient?.name}?`} + + + Não será possível desfazer essa ação! + + + + Cancelar + + Remover + + + + + ) +} + +InputUseActiveIngredientDeleteDialog.displayName = + 'InputUseActiveIngredientDeleteDialog' diff --git a/src/app/modules/input-uses/presentation/contexts/input-use-active-ingredient-context.tsx b/src/app/modules/input-uses/presentation/contexts/input-use-active-ingredient-context.tsx new file mode 100644 index 00000000..edb1e977 --- /dev/null +++ b/src/app/modules/input-uses/presentation/contexts/input-use-active-ingredient-context.tsx @@ -0,0 +1,149 @@ +import { + createContext, + useCallback, + useMemo, + useState, + type ReactNode, +} from 'react' + +import type { InputUseActiveIngredientModel } from '../../domain/models/input-use-active-ingredients-model' +import type { Filters } from '@/core/domain/types' + +export type InputUseActiveIngredientContextData = { + selectedInputUseActiveIngredient: InputUseActiveIngredientModel | null + isOpenNewInputUseActiveIngredientForm: boolean + isOpenEditInputUseActiveIngredientForm: boolean + isOpenDeleteInputUseActiveIngredientContainer: boolean + filters: Filters + openNewInputUseActiveIngredientForm: () => void + closeNewInputUseActiveIngredientForm: () => void + openEditInputUseActiveIngredientForm: ( + inputUseActiveIngredient: InputUseActiveIngredientModel + ) => void + closeEditInputUseActiveIngredientForm: () => void + openDeleteInputUseActiveIngredientContainer: ( + inputUseActiveIngredient: InputUseActiveIngredientModel + ) => void + closeDeleteInputUseActiveIngredientContainer: () => void + handleChangeFilters: ( + newFilters: Filters + ) => void + clearFilters: () => void +} + +export const InputUseActiveIngredientContext = + createContext( + {} as InputUseActiveIngredientContextData + ) + +type InputUseActiveIngredientProviderProps = { + children: ReactNode +} + +export function InputUseActiveIngredientProvider({ + children, +}: InputUseActiveIngredientProviderProps) { + const [ + selectedInputUseActiveIngredient, + setSelectedInputUseActiveIngredient, + ] = useState(null) + const [ + isOpenNewInputUseActiveIngredientForm, + setIsOpenNewInputUseActiveIngredientForm, + ] = useState(false) + const [ + isOpenEditInputUseActiveIngredientForm, + setIsOpenEditInputUseActiveIngredientForm, + ] = useState(false) + const [ + isOpenDeleteInputUseActiveIngredientContainer, + setIsOpenDeleteInputUseActiveIngredientContainer, + ] = useState(false) + const [filters, setFilters] = useState< + Filters + >({}) + + const openNewInputUseActiveIngredientForm = useCallback(() => { + setIsOpenNewInputUseActiveIngredientForm(true) + }, []) + + const closeNewInputUseActiveIngredientForm = useCallback(() => { + setIsOpenNewInputUseActiveIngredientForm(false) + }, []) + + const openEditInputUseActiveIngredientForm = useCallback( + (inputUseActiveIngredient: InputUseActiveIngredientModel) => { + setSelectedInputUseActiveIngredient(inputUseActiveIngredient) + setIsOpenEditInputUseActiveIngredientForm(true) + }, + [] + ) + + const closeEditInputUseActiveIngredientForm = useCallback(() => { + setSelectedInputUseActiveIngredient(null) + setIsOpenEditInputUseActiveIngredientForm(false) + }, []) + + const openDeleteInputUseActiveIngredientContainer = useCallback( + (inputUseActiveIngredient: InputUseActiveIngredientModel) => { + setSelectedInputUseActiveIngredient(inputUseActiveIngredient) + setIsOpenDeleteInputUseActiveIngredientContainer(true) + }, + [] + ) + + const closeDeleteInputUseActiveIngredientContainer = useCallback(() => { + setSelectedInputUseActiveIngredient(null) + setIsOpenDeleteInputUseActiveIngredientContainer(false) + }, []) + + const handleChangeFilters = useCallback( + (newFilters: Filters) => { + setFilters((prev) => ({ ...prev, ...newFilters })) + }, + [] + ) + + const clearFilters = useCallback(() => { + setFilters({}) + }, []) + + const value = useMemo( + () => ({ + selectedInputUseActiveIngredient, + isOpenNewInputUseActiveIngredientForm, + isOpenEditInputUseActiveIngredientForm, + isOpenDeleteInputUseActiveIngredientContainer, + filters, + openNewInputUseActiveIngredientForm, + closeNewInputUseActiveIngredientForm, + openEditInputUseActiveIngredientForm, + closeEditInputUseActiveIngredientForm, + openDeleteInputUseActiveIngredientContainer, + closeDeleteInputUseActiveIngredientContainer, + handleChangeFilters, + clearFilters, + }), + [ + selectedInputUseActiveIngredient, + isOpenNewInputUseActiveIngredientForm, + isOpenEditInputUseActiveIngredientForm, + isOpenDeleteInputUseActiveIngredientContainer, + filters, + openNewInputUseActiveIngredientForm, + closeNewInputUseActiveIngredientForm, + openEditInputUseActiveIngredientForm, + closeEditInputUseActiveIngredientForm, + openDeleteInputUseActiveIngredientContainer, + closeDeleteInputUseActiveIngredientContainer, + handleChangeFilters, + clearFilters, + ] + ) + + return ( + + {children} + + ) +} diff --git a/src/app/modules/input-uses/presentation/forms/input-use-active-ingredient-form/create-input-use-active-ingredient-form.tsx b/src/app/modules/input-uses/presentation/forms/input-use-active-ingredient-form/create-input-use-active-ingredient-form.tsx new file mode 100644 index 00000000..88857512 --- /dev/null +++ b/src/app/modules/input-uses/presentation/forms/input-use-active-ingredient-form/create-input-use-active-ingredient-form.tsx @@ -0,0 +1,116 @@ +import { useCallback } from 'react' + +import { zodResolver } from '@hookform/resolvers/zod' +import { useMutation, useQueryClient } from '@tanstack/react-query' +import toast from 'react-hot-toast' + +import { + Button, + Form, + ScrollArea, + Sheet, +} from '@/core/presentation/components/ui' +import { useHookForm } from '@/core/presentation/hooks' + +import { makeRemoteCreateInputUseActiveIngredientUseCase } from '../../../main/factories/use-cases/input-use-active-ingredients-use-cases' +import { useInputUseActiveIngredientContext } from '../../hooks/input-use-active-ingredient-context.hook' +import { + inputUseActiveIngredientFormSchema, + type InputUseActiveIngredientFormSchema, +} from '../../validations/input-use-active-ingredient-form-schema' + +import { InputUseActiveIngredientFormInputs } from './input-use-active-ingredient-form-inputs' +import { INPUT_USE_ACTIVE_INGREDIENT_INITIAL_FORM_DATA } from './input-use-active-ingredient-initial-form-data' + +export function CreateInputUseActiveIngredientForm() { + const { + isOpenNewInputUseActiveIngredientForm, + closeNewInputUseActiveIngredientForm, + } = useInputUseActiveIngredientContext() + + const createInputUseActiveIngredientUseCase = + makeRemoteCreateInputUseActiveIngredientUseCase() + + const queryClient = useQueryClient() + + const form = useHookForm({ + defaultValues: INPUT_USE_ACTIVE_INGREDIENT_INITIAL_FORM_DATA, + resolver: zodResolver(inputUseActiveIngredientFormSchema), + }) + + const { mutateAsync: mutateHandleCreateInputUseActiveIngredient } = + useMutation({ + mutationFn: createInputUseActiveIngredientUseCase.execute, + }) + + const handleCreateInputUseActiveIngredient = useCallback( + async (data: InputUseActiveIngredientFormSchema) => { + try { + await mutateHandleCreateInputUseActiveIngredient({ + inputUseActiveIngredient: data, + }) + + queryClient.invalidateQueries({ + queryKey: ['input-use-active-ingredients'], + exact: false, + }) + + toast.success('Princípio ativo cadastrado com sucesso') + + form.reset(INPUT_USE_ACTIVE_INGREDIENT_INITIAL_FORM_DATA) + + closeNewInputUseActiveIngredientForm() + } catch { + toast.error('Erro ao cadastrar princípio ativo') + } + }, + [ + closeNewInputUseActiveIngredientForm, + form, + mutateHandleCreateInputUseActiveIngredient, + queryClient, + ] + ) + + return ( + + + + Novo Princípio Ativo + + Preencha o formulário para criar um novo princípio ativo + + + + + +
+ + +
+
+ + + + +
+
+ ) +} + +CreateInputUseActiveIngredientForm.displayName = + 'CreateInputUseActiveIngredientForm' diff --git a/src/app/modules/input-uses/presentation/forms/input-use-active-ingredient-form/edit-input-use-active-ingredient-form.tsx b/src/app/modules/input-uses/presentation/forms/input-use-active-ingredient-form/edit-input-use-active-ingredient-form.tsx new file mode 100644 index 00000000..4fbe75ed --- /dev/null +++ b/src/app/modules/input-uses/presentation/forms/input-use-active-ingredient-form/edit-input-use-active-ingredient-form.tsx @@ -0,0 +1,141 @@ +import { useCallback } from 'react' + +import { zodResolver } from '@hookform/resolvers/zod' +import { useMutation, useQueryClient } from '@tanstack/react-query' +import toast from 'react-hot-toast' + +import { + Button, + Form, + Loading, + ScrollArea, + Sheet, +} from '@/core/presentation/components/ui' +import { useHookForm } from '@/core/presentation/hooks' + +import { makeRemoteUpdateInputUseActiveIngredientUseCase } from '../../../main/factories/use-cases/input-use-active-ingredients-use-cases' +import { useInputUseActiveIngredientContext } from '../../hooks/input-use-active-ingredient-context.hook' +import { useInputUseActiveIngredientQuery } from '../../hooks/queries/input-use-active-ingredient-query.hook' +import { + inputUseActiveIngredientFormSchema, + type InputUseActiveIngredientFormSchema, +} from '../../validations/input-use-active-ingredient-form-schema' + +import { InputUseActiveIngredientFormInputs } from './input-use-active-ingredient-form-inputs' +import { INPUT_USE_ACTIVE_INGREDIENT_INITIAL_FORM_DATA } from './input-use-active-ingredient-initial-form-data' + +export function EditInputUseActiveIngredientForm() { + const { + isOpenEditInputUseActiveIngredientForm, + closeEditInputUseActiveIngredientForm, + selectedInputUseActiveIngredient, + } = useInputUseActiveIngredientContext() + + const { isLoading, inputUseActiveIngredient } = + useInputUseActiveIngredientQuery({ + id: selectedInputUseActiveIngredient!.id, + }) + + const updateInputUseActiveIngredientUseCase = + makeRemoteUpdateInputUseActiveIngredientUseCase() + + const queryClient = useQueryClient() + + const form = useHookForm({ + defaultValues: INPUT_USE_ACTIVE_INGREDIENT_INITIAL_FORM_DATA, + ...(inputUseActiveIngredient && { + values: { + ...inputUseActiveIngredient, + }, + }), + resolver: zodResolver(inputUseActiveIngredientFormSchema), + }) + + const { mutateAsync: mutateHandleUpdateInputUseActiveIngredient } = + useMutation({ + mutationFn: updateInputUseActiveIngredientUseCase.execute, + }) + + const handleUpdateInputUseActiveIngredient = useCallback( + async (data: InputUseActiveIngredientFormSchema) => { + try { + if (!selectedInputUseActiveIngredient?.id) { + toast.error('Erro ao atualizar princípio ativo') + return + } + + await mutateHandleUpdateInputUseActiveIngredient({ + inputUseActiveIngredient: { + ...data, + id: selectedInputUseActiveIngredient.id, + }, + }) + + queryClient.invalidateQueries({ + queryKey: ['input-use-active-ingredients'], + exact: false, + }) + + toast.success('Princípio ativo foi editado com sucesso') + form.reset(INPUT_USE_ACTIVE_INGREDIENT_INITIAL_FORM_DATA) + closeEditInputUseActiveIngredientForm() + } catch { + toast.error('Erro ao salvar alterações') + } + }, + [ + closeEditInputUseActiveIngredientForm, + form, + mutateHandleUpdateInputUseActiveIngredient, + queryClient, + selectedInputUseActiveIngredient, + ] + ) + + return ( + + + + {`Editar Princípio Ativo ${selectedInputUseActiveIngredient?.name}`} + + Preencha o formulário para editar o princípio ativo + + + + +
+ {isLoading ? ( +
+ +
+ ) : ( + + )} + +
+
+ + + + +
+
+ ) +} + +EditInputUseActiveIngredientForm.displayName = + 'EditInputUseActiveIngredientForm' diff --git a/src/app/modules/input-uses/presentation/forms/input-use-active-ingredient-form/index.ts b/src/app/modules/input-uses/presentation/forms/input-use-active-ingredient-form/index.ts new file mode 100644 index 00000000..155b97f6 --- /dev/null +++ b/src/app/modules/input-uses/presentation/forms/input-use-active-ingredient-form/index.ts @@ -0,0 +1 @@ +export * from './input-use-active-ingredient-form' diff --git a/src/app/modules/input-uses/presentation/forms/input-use-active-ingredient-form/input-use-active-ingredient-form-inputs.tsx b/src/app/modules/input-uses/presentation/forms/input-use-active-ingredient-form/input-use-active-ingredient-form-inputs.tsx new file mode 100644 index 00000000..4550c224 --- /dev/null +++ b/src/app/modules/input-uses/presentation/forms/input-use-active-ingredient-form/input-use-active-ingredient-form-inputs.tsx @@ -0,0 +1,28 @@ +import { useFormContext } from 'react-hook-form' + +import { Form, Input } from '@/core/presentation/components/ui' + +import type { InputUseActiveIngredientFormSchema } from '../../validations/input-use-active-ingredient-form-schema' + +export function InputUseActiveIngredientFormInputs() { + const form = useFormContext() + + return ( + ( + + Nome* + + + + + + )} + /> + ) +} + +InputUseActiveIngredientFormInputs.displayName = + 'InputUseActiveIngredientFormInputs' diff --git a/src/app/modules/input-uses/presentation/forms/input-use-active-ingredient-form/input-use-active-ingredient-form.tsx b/src/app/modules/input-uses/presentation/forms/input-use-active-ingredient-form/input-use-active-ingredient-form.tsx new file mode 100644 index 00000000..df206810 --- /dev/null +++ b/src/app/modules/input-uses/presentation/forms/input-use-active-ingredient-form/input-use-active-ingredient-form.tsx @@ -0,0 +1,18 @@ +import { CreateInputUseActiveIngredientForm } from './create-input-use-active-ingredient-form' +import { EditInputUseActiveIngredientForm } from './edit-input-use-active-ingredient-form' + +type InputUseActiveIngredientFormProps = { + id?: string | number +} + +export function InputUseActiveIngredientForm({ + id, +}: Readonly) { + if (id) { + return + } + + return +} + +InputUseActiveIngredientForm.displayName = 'InputUseActiveIngredientForm' diff --git a/src/app/modules/input-uses/presentation/forms/input-use-active-ingredient-form/input-use-active-ingredient-initial-form-data.ts b/src/app/modules/input-uses/presentation/forms/input-use-active-ingredient-form/input-use-active-ingredient-initial-form-data.ts new file mode 100644 index 00000000..cc346453 --- /dev/null +++ b/src/app/modules/input-uses/presentation/forms/input-use-active-ingredient-form/input-use-active-ingredient-initial-form-data.ts @@ -0,0 +1,6 @@ +import type { InputUseActiveIngredientFormSchema } from '../../validations/input-use-active-ingredient-form-schema' + +export const INPUT_USE_ACTIVE_INGREDIENT_INITIAL_FORM_DATA: InputUseActiveIngredientFormSchema = + { + name: '', + } diff --git a/src/app/modules/input-uses/presentation/forms/input-use-product-form/input-use-product-form-inputs.tsx b/src/app/modules/input-uses/presentation/forms/input-use-product-form/input-use-product-form-inputs.tsx index 49dede13..b306f873 100644 --- a/src/app/modules/input-uses/presentation/forms/input-use-product-form/input-use-product-form-inputs.tsx +++ b/src/app/modules/input-uses/presentation/forms/input-use-product-form/input-use-product-form-inputs.tsx @@ -4,8 +4,8 @@ import { useFormContext } from 'react-hook-form' import { Combobox, Form, Input } from '@/core/presentation/components/ui' import { useDebounce } from '@/core/presentation/hooks' -import { useAllActiveIngredientsQuery } from '@/core/presentation/hooks/queries/all-active-ingredients-query.hook' +import { useAllInputUseActiveIngredientsQuery } from '../../hooks/queries/all-input-use-active-ingredients-query.hook' import { useAllInputUseProductCategoriesQuery } from '../../hooks/queries/all-input-use-product-categories-query.hook' import type { InputUseProductFormSchema } from '../../validations/input-use-product-form-schema' @@ -31,15 +31,17 @@ export function InputUseProductFormInputs() { }, }) - const { allActiveIngredients, isLoading: isLoadingActiveIngredients } = - useAllActiveIngredientsQuery({ - filters: { - name: { - value: debouncedActiveIngredient, - type: 'LIKE', - }, + const { + allInputUseActiveIngredients: allActiveIngredients, + isLoading: isLoadingActiveIngredients, + } = useAllInputUseActiveIngredientsQuery({ + filters: { + name: { + value: debouncedActiveIngredient, + type: 'LIKE', }, - }) + }, + }) return ( <> diff --git a/src/app/modules/input-uses/presentation/hooks/input-use-active-ingredient-context.hook.ts b/src/app/modules/input-uses/presentation/hooks/input-use-active-ingredient-context.hook.ts new file mode 100644 index 00000000..51492a88 --- /dev/null +++ b/src/app/modules/input-uses/presentation/hooks/input-use-active-ingredient-context.hook.ts @@ -0,0 +1,15 @@ +import { useContext } from 'react' + +import { InputUseActiveIngredientContext } from '../contexts/input-use-active-ingredient-context' + +export function useInputUseActiveIngredientContext() { + const context = useContext(InputUseActiveIngredientContext) + + if (!context) { + throw new Error( + 'useInputUseActiveIngredientContext must be used within an InputUseActiveIngredientProvider' + ) + } + + return context +} diff --git a/src/app/modules/input-uses/presentation/hooks/queries/all-input-use-active-ingredients-query.hook.ts b/src/app/modules/input-uses/presentation/hooks/queries/all-input-use-active-ingredients-query.hook.ts new file mode 100644 index 00000000..537c1fdc --- /dev/null +++ b/src/app/modules/input-uses/presentation/hooks/queries/all-input-use-active-ingredients-query.hook.ts @@ -0,0 +1,49 @@ +import { useEffect } from 'react' + +import { useQuery } from '@tanstack/react-query' +import toast from 'react-hot-toast' + +import { toOption } from '@/core/utils/object/to-option' + +import { makeRemoteGetInputUseActiveIngredientsUseCase } from '../../../main/factories/use-cases/input-use-active-ingredients-use-cases' + +import type { InputUseActiveIngredientFilters } from '../../types/input-use-active-ingredient-types' + +type Props = { + filters: InputUseActiveIngredientFilters +} + +export function useAllInputUseActiveIngredientsQuery({ filters }: Props) { + const getInputUseActiveIngredientsUseCase = + makeRemoteGetInputUseActiveIngredientsUseCase() + + const { + data, + isError, + error, + isLoading, + refetch: refetchAllInputUseActiveIngredients, + } = useQuery({ + queryKey: ['all-input-use-active-ingredients', { filters }], + queryFn: () => + getInputUseActiveIngredientsUseCase.execute({ + filters, + pagination: { + page: 1, + perPage: 30, + }, + }), + }) + + useEffect(() => { + if (isError) + toast.error(error?.message ?? 'Erro ao buscar princípios ativos') + }, [error, isError]) + + return { + allInputUseActiveIngredients: + data?.resources.map((ingredient) => toOption(ingredient, 'name')) ?? [], + isLoading, + refetchAllInputUseActiveIngredients, + } +} diff --git a/src/app/modules/input-uses/presentation/hooks/queries/input-use-active-ingredient-query.hook.ts b/src/app/modules/input-uses/presentation/hooks/queries/input-use-active-ingredient-query.hook.ts new file mode 100644 index 00000000..764c0c4c --- /dev/null +++ b/src/app/modules/input-uses/presentation/hooks/queries/input-use-active-ingredient-query.hook.ts @@ -0,0 +1,37 @@ +import { useEffect } from 'react' + +import { useQuery } from '@tanstack/react-query' +import toast from 'react-hot-toast' + +import { makeRemoteGetInputUseActiveIngredientUseCase } from '../../../main/factories/use-cases/input-use-active-ingredients-use-cases' + +type Props = { + id: number +} + +export function useInputUseActiveIngredientQuery({ id }: Props) { + const getInputUseActiveIngredientUseCase = + makeRemoteGetInputUseActiveIngredientUseCase() + + const { + data: inputUseActiveIngredient, + isError, + error, + isLoading, + refetch: refetchInputUseActiveIngredient, + } = useQuery({ + queryKey: ['input-use-active-ingredient', id], + queryFn: () => getInputUseActiveIngredientUseCase.execute({ id }), + enabled: !!id, + }) + + useEffect(() => { + if (isError) toast.error(error?.message ?? 'Erro ao buscar princípio ativo') + }, [error, isError]) + + return { + inputUseActiveIngredient, + isLoading, + refetchInputUseActiveIngredient, + } +} diff --git a/src/app/modules/input-uses/presentation/hooks/queries/input-use-active-ingredients-query.hook.ts b/src/app/modules/input-uses/presentation/hooks/queries/input-use-active-ingredients-query.hook.ts new file mode 100644 index 00000000..f95505a9 --- /dev/null +++ b/src/app/modules/input-uses/presentation/hooks/queries/input-use-active-ingredients-query.hook.ts @@ -0,0 +1,56 @@ +import { useEffect } from 'react' + +import { useQuery } from '@tanstack/react-query' +import toast from 'react-hot-toast' + +import { makeRemoteGetInputUseActiveIngredientsUseCase } from '../../../main/factories/use-cases/input-use-active-ingredients-use-cases' + +import type { + InputUseActiveIngredientFilters, + InputUseActiveIngredientSort, +} from '../../types/input-use-active-ingredient-types' + +type Props = { + filters: InputUseActiveIngredientFilters + page: number + sort?: InputUseActiveIngredientSort +} + +export function useInputUseActiveIngredientsQuery({ + filters, + page, + sort, +}: Props) { + const getInputUseActiveIngredientsUseCase = + makeRemoteGetInputUseActiveIngredientsUseCase() + + const { + data, + isError, + error, + isLoading, + refetch: refetchInputUseActiveIngredients, + } = useQuery({ + queryKey: ['input-use-active-ingredients', { page, sort, filters }], + queryFn: () => + getInputUseActiveIngredientsUseCase.execute({ + pagination: { page }, + sort, + filters, + }), + }) + + useEffect(() => { + if (isError) + toast.error(error?.message ?? 'Erro ao buscar princípios ativos') + }, [error, isError]) + + return { + inputUseActiveIngredients: data ?? { + resources: [], + totalPages: 1, + }, + isLoading, + refetchInputUseActiveIngredients, + } +} diff --git a/src/app/modules/input-uses/presentation/screens/input-use-active-ingredients-screen.tsx b/src/app/modules/input-uses/presentation/screens/input-use-active-ingredients-screen.tsx new file mode 100644 index 00000000..569a4e49 --- /dev/null +++ b/src/app/modules/input-uses/presentation/screens/input-use-active-ingredients-screen.tsx @@ -0,0 +1,67 @@ +import { Button, Input } from '@/core/presentation/components/ui' + +import { InputUseActiveIngredientDataTable } from '../components/input-use-active-ingredient-data-table' +import { InputUseActiveIngredientDeleteDialog } from '../components/input-use-active-ingredient-delete-dialog' +import { + InputUseActiveIngredientContext, + InputUseActiveIngredientProvider, +} from '../contexts/input-use-active-ingredient-context' +import { InputUseActiveIngredientForm } from '../forms/input-use-active-ingredient-form' + +export function InputUseActiveIngredientsScreen() { + return ( + + + {({ + selectedInputUseActiveIngredient, + isOpenDeleteInputUseActiveIngredientContainer, + isOpenNewInputUseActiveIngredientForm, + isOpenEditInputUseActiveIngredientForm, + filters, + handleChangeFilters, + openNewInputUseActiveIngredientForm, + }) => ( +
+
+ + + { + handleChangeFilters({ + name: { value: target.value, type: 'LIKE' }, + }) + }} + placeholder="Procurar princípio ativo por nome" + /> +
+ + + + {selectedInputUseActiveIngredient && + isOpenDeleteInputUseActiveIngredientContainer && ( + + )} + + {(isOpenNewInputUseActiveIngredientForm || + isOpenEditInputUseActiveIngredientForm) && ( + + )} +
+ )} +
+
+ ) +} + +InputUseActiveIngredientsScreen.displayName = 'InputUseActiveIngredientsScreen' diff --git a/src/app/modules/input-uses/presentation/types/input-use-active-ingredient-types.ts b/src/app/modules/input-uses/presentation/types/input-use-active-ingredient-types.ts new file mode 100644 index 00000000..bad595c4 --- /dev/null +++ b/src/app/modules/input-uses/presentation/types/input-use-active-ingredient-types.ts @@ -0,0 +1,6 @@ +import type { InputUseActiveIngredientModel } from '../../domain/models/input-use-active-ingredients-model' +import type { Filters, Sort } from '@/core/domain/types' + +export type InputUseActiveIngredientFilters = + Filters +export type InputUseActiveIngredientSort = Sort diff --git a/src/app/modules/input-uses/presentation/validations/input-use-active-ingredient-form-schema.ts b/src/app/modules/input-uses/presentation/validations/input-use-active-ingredient-form-schema.ts new file mode 100644 index 00000000..5dfdd6e9 --- /dev/null +++ b/src/app/modules/input-uses/presentation/validations/input-use-active-ingredient-form-schema.ts @@ -0,0 +1,12 @@ +import { z } from 'zod' + +export const inputUseActiveIngredientFormSchema = z.object({ + name: z + .string() + .min(3, 'O nome deve ter no mínimo 3 caracteres') + .max(255, 'O nome deve ter no máximo 255 caracteres'), +}) + +export type InputUseActiveIngredientFormSchema = z.infer< + typeof inputUseActiveIngredientFormSchema +> From c043c7e2a6ea4bff2bf8812810ebd022ac5198e1 Mon Sep 17 00:00:00 2001 From: Guilherme Minozzi Date: Wed, 22 Apr 2026 16:53:07 -0300 Subject: [PATCH 6/7] chore: remove older active ingredients code --- .../active-ingredients-use-cases/index.ts | 1 - ...ote-get-all-active-ingredients-use-case.ts | 66 ------------------ .../domain/models/active-ingredients-model.ts | 10 --- .../get-all-active-ingredients-use-case.ts | 11 --- .../active-ingredients-use-cases/index.ts | 1 - .../active-ingredients-use-cases/index.ts | 1 - ...all-active-ingredients-use-case-factory.ts | 20 ------ src/core/mocks/browser.ts | 16 ++++- .../get-all-active-ingredients-handler.ts | 69 ------------------- .../active-ingredients-handlers/index.ts | 1 - .../all-active-ingredients-query.hook.ts | 49 ------------- 11 files changed, 13 insertions(+), 232 deletions(-) delete mode 100644 src/core/data/use-cases/active-ingredients-use-cases/index.ts delete mode 100644 src/core/data/use-cases/active-ingredients-use-cases/remote-get-all-active-ingredients-use-case.ts delete mode 100644 src/core/domain/models/active-ingredients-model.ts delete mode 100644 src/core/domain/use-cases/active-ingredients-use-cases/get-all-active-ingredients-use-case.ts delete mode 100644 src/core/domain/use-cases/active-ingredients-use-cases/index.ts delete mode 100644 src/core/main/factories/use-cases/active-ingredients-use-cases/index.ts delete mode 100644 src/core/main/factories/use-cases/active-ingredients-use-cases/remote-get-all-active-ingredients-use-case-factory.ts delete mode 100644 src/core/mocks/handlers/active-ingredients-handlers/get-all-active-ingredients-handler.ts delete mode 100644 src/core/mocks/handlers/active-ingredients-handlers/index.ts delete mode 100644 src/core/presentation/hooks/queries/all-active-ingredients-query.hook.ts diff --git a/src/core/data/use-cases/active-ingredients-use-cases/index.ts b/src/core/data/use-cases/active-ingredients-use-cases/index.ts deleted file mode 100644 index c3774c7e..00000000 --- a/src/core/data/use-cases/active-ingredients-use-cases/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './remote-get-all-active-ingredients-use-case' diff --git a/src/core/data/use-cases/active-ingredients-use-cases/remote-get-all-active-ingredients-use-case.ts b/src/core/data/use-cases/active-ingredients-use-cases/remote-get-all-active-ingredients-use-case.ts deleted file mode 100644 index f4c5da6b..00000000 --- a/src/core/data/use-cases/active-ingredients-use-cases/remote-get-all-active-ingredients-use-case.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { type HttpClient, HttpStatusCode } from '@/core/data/protocols/http' -import { - BadRequestError, - ForbiddenError, - NotFoundError, - UnexpectedError, -} from '@/core/domain/errors' - -import type { - ActiveIngredientApiResponse, - ActiveIngredientModel, -} from '@/core/domain/models/active-ingredients-model' -import type { ListApiResponse, MapApiProperties } from '@/core/domain/types' -import type { GetAllActiveIngredientsUseCase } from '@/core/domain/use-cases/active-ingredients-use-cases' - -export class RemoteGetAllActiveIngredientsUseCase - implements GetAllActiveIngredientsUseCase -{ - constructor( - private readonly url: string, - private readonly httpClient: HttpClient< - ActiveIngredientModel, - ActiveIngredientApiResponse, - ListApiResponse - > - ) {} - - execute: GetAllActiveIngredientsUseCase['execute'] = async ({ filters }) => { - const mapApiProperties: MapApiProperties< - ActiveIngredientModel, - ActiveIngredientApiResponse - > = { - id: 'id', - name: 'name', - } - - const { statusCode, body } = await this.httpClient.request({ - url: `${this.url}/search`, - method: 'post', - filters, - mapApiProperties, - }) - - if (statusCode === HttpStatusCode.ok && !!body) { - return { - resources: body.content.map((item) => ({ - id: item.id, - name: item.name, - })), - totalPages: Math.ceil(body.numberOfElements / body.pageable.pageSize), - } - } - - if (statusCode === HttpStatusCode.forbidden) { - throw new ForbiddenError() - } - - if (statusCode === HttpStatusCode.notFound) { - throw new NotFoundError('Princípios Ativos') - } - - if (statusCode === HttpStatusCode.badRequest) throw new BadRequestError() - - throw new UnexpectedError() - } -} diff --git a/src/core/domain/models/active-ingredients-model.ts b/src/core/domain/models/active-ingredients-model.ts deleted file mode 100644 index 39f9381f..00000000 --- a/src/core/domain/models/active-ingredients-model.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { WithId } from '../types' - -// todo: quando for implementado a parte de registros globais de princípios ativos (active-ingredients), mover esse model e tudo referente a active-ingredients para la -export type ActiveIngredientModel = WithId<{ - name: string -}> - -export type ActiveIngredientApiResponse = WithId<{ - name: string -}> diff --git a/src/core/domain/use-cases/active-ingredients-use-cases/get-all-active-ingredients-use-case.ts b/src/core/domain/use-cases/active-ingredients-use-cases/get-all-active-ingredients-use-case.ts deleted file mode 100644 index 7589d243..00000000 --- a/src/core/domain/use-cases/active-ingredients-use-cases/get-all-active-ingredients-use-case.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { ActiveIngredientModel } from '../../models/active-ingredients-model' -import type { - RequestInterface, - ListParams, - ListResponse, -} from '@/core/domain/types' - -export type GetAllActiveIngredientsUseCase = RequestInterface< - ListParams, - ListResponse -> diff --git a/src/core/domain/use-cases/active-ingredients-use-cases/index.ts b/src/core/domain/use-cases/active-ingredients-use-cases/index.ts deleted file mode 100644 index f972bde9..00000000 --- a/src/core/domain/use-cases/active-ingredients-use-cases/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './get-all-active-ingredients-use-case' diff --git a/src/core/main/factories/use-cases/active-ingredients-use-cases/index.ts b/src/core/main/factories/use-cases/active-ingredients-use-cases/index.ts deleted file mode 100644 index 02bab4ad..00000000 --- a/src/core/main/factories/use-cases/active-ingredients-use-cases/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './remote-get-all-active-ingredients-use-case-factory' diff --git a/src/core/main/factories/use-cases/active-ingredients-use-cases/remote-get-all-active-ingredients-use-case-factory.ts b/src/core/main/factories/use-cases/active-ingredients-use-cases/remote-get-all-active-ingredients-use-case-factory.ts deleted file mode 100644 index c3ba0adf..00000000 --- a/src/core/main/factories/use-cases/active-ingredients-use-cases/remote-get-all-active-ingredients-use-case-factory.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { RemoteGetAllActiveIngredientsUseCase } from '@/core/data/use-cases/active-ingredients-use-cases' -import { makeApiHttpClient } from '@/core/main/factories/http' - -import type { - ActiveIngredientApiResponse, - ActiveIngredientModel, -} from '@/core/domain/models/active-ingredients-model' -import type { ListApiResponse } from '@/core/domain/types' -import type { GetAllActiveIngredientsUseCase } from '@/core/domain/use-cases/active-ingredients-use-cases' - -export function makeRemoteGetAllActiveIngredientsUseCase(): GetAllActiveIngredientsUseCase { - return new RemoteGetAllActiveIngredientsUseCase( - 'active-ingredients', - makeApiHttpClient< - ActiveIngredientModel, - ActiveIngredientApiResponse, - ListApiResponse - >() - ) -} diff --git a/src/core/mocks/browser.ts b/src/core/mocks/browser.ts index c44aa1a2..63f0f8d0 100644 --- a/src/core/mocks/browser.ts +++ b/src/core/mocks/browser.ts @@ -130,6 +130,13 @@ import { getImprovementsHandler, updateImprovementHandler, } from '@/app/modules/improvements/mocks/handlers' +import { + createInputUseActiveIngredientHandler, + deleteInputUseActiveIngredientHandler, + getInputUseActiveIngredientHandler, + getInputUseActiveIngredientsHandler, + updateInputUseActiveIngredientHandler, +} from '@/app/modules/input-uses/mocks/handlers/input-use-active-ingredients-handlers' import { createInputUseLocationHandler, deleteInputUseLocationHandler, @@ -174,7 +181,6 @@ import { updatePropertyHandler, } from '@/app/modules/properties/mocks/handlers' -import { getAllActiveIngredientsHandler } from './handlers/active-ingredients-handlers' import { getAllBreedsHandler } from './handlers/breeds-handlers' import { getAllUsersHandler, getMeHandler } from './handlers/users-handlers' @@ -183,8 +189,6 @@ const handlers: HttpHandler[] = [ getAllBreedsHandler, - getAllActiveIngredientsHandler, - getAllUsersHandler, getMeHandler, @@ -316,6 +320,12 @@ const handlers: HttpHandler[] = [ getInputUseLocationsHandler, updateInputUseLocationHandler, + createInputUseActiveIngredientHandler, + deleteInputUseActiveIngredientHandler, + getInputUseActiveIngredientHandler, + getInputUseActiveIngredientsHandler, + updateInputUseActiveIngredientHandler, + createInputUseProductCategoryHandler, deleteInputUseProductCategoryHandler, getInputUseProductCategoryHandler, diff --git a/src/core/mocks/handlers/active-ingredients-handlers/get-all-active-ingredients-handler.ts b/src/core/mocks/handlers/active-ingredients-handlers/get-all-active-ingredients-handler.ts deleted file mode 100644 index 4a7ac838..00000000 --- a/src/core/mocks/handlers/active-ingredients-handlers/get-all-active-ingredients-handler.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { HttpResponse } from 'msw' - -import { HttpStatusCode } from '@/core/data/protocols/http' - -import allActiveIngredientsData from '@database/allActiveIngredientsData.json' - -import { httpWithMiddleware } from '../../lib' -import { withDelay, withAuth } from '../../middleware' -import { filterData } from '../../utils' - -import type { MockParams } from '../../types/mock-params-type' -import type { MockResponse } from '../../types/mock-response-type' -import type { ActiveIngredientApiResponse } from '@/core/domain/models/active-ingredients-model' - -export const getAllActiveIngredientsHandler = httpWithMiddleware< - never, - MockParams, - MockResponse ->({ - routePath: '/api/active-ingredients/search', - method: 'post', - middlewares: [withDelay(), withAuth], - resolver: async ({ request }) => { - const { filters, rows } = await request.json() - - if (!allActiveIngredientsData.length) { - return HttpResponse.json( - { - content: [], - numberOfElements: 0, - pageable: { - pageSize: 0, - }, - }, - { - status: 404, - } - ) - } - - if (filters) { - const activeIngredients = filterData( - filters, - allActiveIngredientsData - ) - return HttpResponse.json( - { - content: activeIngredients, - numberOfElements: activeIngredients.length, - pageable: { - pageSize: rows, - }, - }, - { status: HttpStatusCode.ok } - ) - } - - return HttpResponse.json( - { - content: allActiveIngredientsData, - numberOfElements: allActiveIngredientsData.length, - pageable: { - pageSize: rows, - }, - }, - { status: HttpStatusCode.ok } - ) - }, -}) diff --git a/src/core/mocks/handlers/active-ingredients-handlers/index.ts b/src/core/mocks/handlers/active-ingredients-handlers/index.ts deleted file mode 100644 index 0642d8a6..00000000 --- a/src/core/mocks/handlers/active-ingredients-handlers/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './get-all-active-ingredients-handler' diff --git a/src/core/presentation/hooks/queries/all-active-ingredients-query.hook.ts b/src/core/presentation/hooks/queries/all-active-ingredients-query.hook.ts deleted file mode 100644 index 520023ee..00000000 --- a/src/core/presentation/hooks/queries/all-active-ingredients-query.hook.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { useEffect } from 'react' - -import { useQuery } from '@tanstack/react-query' -import toast from 'react-hot-toast' - -import { makeRemoteGetAllActiveIngredientsUseCase } from '@/core/main/factories/use-cases/active-ingredients-use-cases' -import { toOption } from '@/core/utils/object/to-option' - -import type { ActiveIngredientModel } from '@/core/domain/models/active-ingredients-model' -import type { Filters } from '@/core/domain/types' - -type Props = { - filters: Filters -} - -export function useAllActiveIngredientsQuery({ filters }: Props) { - const getAllActiveIngredients = makeRemoteGetAllActiveIngredientsUseCase() - - const { - data, - isError, - error, - isLoading, - refetch: refetchAllActiveIngredients, - } = useQuery({ - queryKey: ['all-active-ingredients', { filters }], - queryFn: () => - getAllActiveIngredients.execute({ - filters, - pagination: { - page: 1, - perPage: 30, - }, - }), - enabled: !!filters, - }) - - useEffect(() => { - if (isError) - toast.error(error?.message ?? 'Erro ao buscar princípios ativos') - }, [error, isError]) - - return { - allActiveIngredients: - data?.resources.map((resource) => toOption(resource, 'name')) ?? [], - isLoading, - refetchAllActiveIngredients, - } -} From 6fedb8e0ed1ac14f7ed0d807faf9abe9adf03bfe Mon Sep 17 00:00:00 2001 From: Guilherme Minozzi Date: Wed, 22 Apr 2026 16:54:24 -0300 Subject: [PATCH 7/7] feat: register input use active ingredients --- src/app/pages/general-registrations-page.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/app/pages/general-registrations-page.tsx b/src/app/pages/general-registrations-page.tsx index 3e7ad3f7..7c5928c7 100644 --- a/src/app/pages/general-registrations-page.tsx +++ b/src/app/pages/general-registrations-page.tsx @@ -5,6 +5,7 @@ import { Breadcrumb, ScrollArea, Tabs } from '@/core/presentation/components/ui' import { GeneralCultivationDiseasesScreen } from '../modules/general-cultivations/presentation/screens/general-cultivation-diseases-screen' import { GeneralCultivationPestsScreen } from '../modules/general-cultivations/presentation/screens/general-cultivation-pests-screen' import { GeneralCultivationsScreen } from '../modules/general-cultivations/presentation/screens/general-cultivations-screen' +import { InputUseActiveIngredientsScreen } from '../modules/input-uses/presentation/screens/input-use-active-ingredients-screen' import { InputUseLocationsScreen } from '../modules/input-uses/presentation/screens/input-use-locations-screen' import { InputUseProductCategoriesScreen } from '../modules/input-uses/presentation/screens/input-use-product-categories-screen' import { InputUseProductsScreen } from '../modules/input-uses/presentation/screens/input-use-products-screen' @@ -70,6 +71,11 @@ export function GeneralRegistrationsPage() { name: 'Produtos', component: , }, + { + key: 'input-use-active-ingredients', + name: 'Princípios Ativos', + component: , + }, ], }, ],