From 3708b08bd89a958c1e241220607c8f3bec0dec5b Mon Sep 17 00:00:00 2001
From: Guilherme Minozzi
Date: Tue, 7 Apr 2026 10:39:53 -0300
Subject: [PATCH 1/8] feat: add script to generate fake data
---
scripts/seed/data/index.mjs | 1 +
scripts/seed/data/input-use-locations.mjs | 14 ++++++++++++++
2 files changed, 15 insertions(+)
create mode 100644 scripts/seed/data/input-use-locations.mjs
diff --git a/scripts/seed/data/index.mjs b/scripts/seed/data/index.mjs
index cde707e8..e1e583da 100644
--- a/scripts/seed/data/index.mjs
+++ b/scripts/seed/data/index.mjs
@@ -24,3 +24,4 @@ export * from './cultivation-diseases.mjs'
export * from './general-cultivation-pests.mjs'
export * from './cultivation-pests.mjs'
export * from './nutritional-balancings.mjs'
+export * from './input-use-locations.mjs'
diff --git a/scripts/seed/data/input-use-locations.mjs b/scripts/seed/data/input-use-locations.mjs
new file mode 100644
index 00000000..86fadbab
--- /dev/null
+++ b/scripts/seed/data/input-use-locations.mjs
@@ -0,0 +1,14 @@
+import { faker } from '@faker-js/faker/locale/pt_BR'
+
+export const inputUseLocationsData = Array.from(
+ {
+ length: faker.number.int({
+ min: 50,
+ max: 150,
+ }),
+ },
+ (_, index) => ({
+ id: index + 1,
+ description: `${faker.location.street()}, ${faker.number.int({ min: 1, max: 100 })}`,
+ })
+)
From 41edef962cb7cfb4b0fa8c3565e5cce58f4b94b5 Mon Sep 17 00:00:00 2001
From: Guilherme Minozzi
Date: Tue, 7 Apr 2026 10:40:59 -0300
Subject: [PATCH 2/8] feat: add domain layer
---
.../domain/models/input-use-locations-model.ts | 17 +++++++++++++++++
.../create-input-use-location-use-case.ts | 9 +++++++++
.../delete-input-use-location-use-case.ts | 8 ++++++++
.../get-input-use-location-use-case.ts | 9 +++++++++
.../get-input-use-locations-use-case.ts | 11 +++++++++++
.../input-use-locations-use-cases/index.ts | 5 +++++
.../update-input-use-location-use-case.ts | 9 +++++++++
7 files changed, 68 insertions(+)
create mode 100644 src/app/modules/input-uses/domain/models/input-use-locations-model.ts
create mode 100644 src/app/modules/input-uses/domain/use-cases/input-use-locations-use-cases/create-input-use-location-use-case.ts
create mode 100644 src/app/modules/input-uses/domain/use-cases/input-use-locations-use-cases/delete-input-use-location-use-case.ts
create mode 100644 src/app/modules/input-uses/domain/use-cases/input-use-locations-use-cases/get-input-use-location-use-case.ts
create mode 100644 src/app/modules/input-uses/domain/use-cases/input-use-locations-use-cases/get-input-use-locations-use-case.ts
create mode 100644 src/app/modules/input-uses/domain/use-cases/input-use-locations-use-cases/index.ts
create mode 100644 src/app/modules/input-uses/domain/use-cases/input-use-locations-use-cases/update-input-use-location-use-case.ts
diff --git a/src/app/modules/input-uses/domain/models/input-use-locations-model.ts b/src/app/modules/input-uses/domain/models/input-use-locations-model.ts
new file mode 100644
index 00000000..ccf35559
--- /dev/null
+++ b/src/app/modules/input-uses/domain/models/input-use-locations-model.ts
@@ -0,0 +1,17 @@
+import type { WithId } from '@/core/domain/types'
+
+export type InputUseLocationDetailsModel = {
+ description: string
+}
+
+export type InputUseLocationDetailsApiResponse = {
+ description: string
+}
+
+export type InputUseLocationModel = WithId<{
+ description: string
+}>
+
+export type InputUseLocationApiResponse = WithId<{
+ description: string
+}>
diff --git a/src/app/modules/input-uses/domain/use-cases/input-use-locations-use-cases/create-input-use-location-use-case.ts b/src/app/modules/input-uses/domain/use-cases/input-use-locations-use-cases/create-input-use-location-use-case.ts
new file mode 100644
index 00000000..a73ce83f
--- /dev/null
+++ b/src/app/modules/input-uses/domain/use-cases/input-use-locations-use-cases/create-input-use-location-use-case.ts
@@ -0,0 +1,9 @@
+import type { InputUseLocationDetailsModel } from '../../models/input-use-locations-model'
+import type { RequestInterface } from '@/core/domain/types'
+
+export type CreateInputUseLocationUseCase = RequestInterface<
+ {
+ inputUseLocation: InputUseLocationDetailsModel
+ },
+ void
+>
diff --git a/src/app/modules/input-uses/domain/use-cases/input-use-locations-use-cases/delete-input-use-location-use-case.ts b/src/app/modules/input-uses/domain/use-cases/input-use-locations-use-cases/delete-input-use-location-use-case.ts
new file mode 100644
index 00000000..37596962
--- /dev/null
+++ b/src/app/modules/input-uses/domain/use-cases/input-use-locations-use-cases/delete-input-use-location-use-case.ts
@@ -0,0 +1,8 @@
+import type { RequestInterface } from '@/core/domain/types'
+
+export type DeleteInputUseLocationUseCase = RequestInterface<
+ {
+ id: number
+ },
+ void
+>
diff --git a/src/app/modules/input-uses/domain/use-cases/input-use-locations-use-cases/get-input-use-location-use-case.ts b/src/app/modules/input-uses/domain/use-cases/input-use-locations-use-cases/get-input-use-location-use-case.ts
new file mode 100644
index 00000000..98d541ef
--- /dev/null
+++ b/src/app/modules/input-uses/domain/use-cases/input-use-locations-use-cases/get-input-use-location-use-case.ts
@@ -0,0 +1,9 @@
+import type { InputUseLocationDetailsModel } from '../../models/input-use-locations-model'
+import type { RequestInterface } from '@/core/domain/types'
+
+export type GetInputUseLocationUseCase = RequestInterface<
+ {
+ id: number
+ },
+ InputUseLocationDetailsModel
+>
diff --git a/src/app/modules/input-uses/domain/use-cases/input-use-locations-use-cases/get-input-use-locations-use-case.ts b/src/app/modules/input-uses/domain/use-cases/input-use-locations-use-cases/get-input-use-locations-use-case.ts
new file mode 100644
index 00000000..2cba83f4
--- /dev/null
+++ b/src/app/modules/input-uses/domain/use-cases/input-use-locations-use-cases/get-input-use-locations-use-case.ts
@@ -0,0 +1,11 @@
+import type { InputUseLocationModel } from '../../models/input-use-locations-model'
+import type {
+ RequestInterface,
+ ListParams,
+ ListResponse,
+} from '@/core/domain/types'
+
+export type GetInputUseLocationsUseCase = RequestInterface<
+ ListParams,
+ ListResponse
+>
diff --git a/src/app/modules/input-uses/domain/use-cases/input-use-locations-use-cases/index.ts b/src/app/modules/input-uses/domain/use-cases/input-use-locations-use-cases/index.ts
new file mode 100644
index 00000000..aa6d743c
--- /dev/null
+++ b/src/app/modules/input-uses/domain/use-cases/input-use-locations-use-cases/index.ts
@@ -0,0 +1,5 @@
+export * from './create-input-use-location-use-case'
+export * from './delete-input-use-location-use-case'
+export * from './get-input-use-location-use-case'
+export * from './get-input-use-locations-use-case'
+export * from './update-input-use-location-use-case'
diff --git a/src/app/modules/input-uses/domain/use-cases/input-use-locations-use-cases/update-input-use-location-use-case.ts b/src/app/modules/input-uses/domain/use-cases/input-use-locations-use-cases/update-input-use-location-use-case.ts
new file mode 100644
index 00000000..b0c187b0
--- /dev/null
+++ b/src/app/modules/input-uses/domain/use-cases/input-use-locations-use-cases/update-input-use-location-use-case.ts
@@ -0,0 +1,9 @@
+import type { InputUseLocationDetailsModel } from '../../models/input-use-locations-model'
+import type { RequestInterface, WithId } from '@/core/domain/types'
+
+export type UpdateInputUseLocationUseCase = RequestInterface<
+ {
+ inputUseLocation: WithId
+ },
+ void
+>
From 26f4e4e907dedcd1b74390a6b5139793f14e179e Mon Sep 17 00:00:00 2001
From: Guilherme Minozzi
Date: Tue, 7 Apr 2026 10:42:15 -0300
Subject: [PATCH 3/8] feat: add data layer
---
.../input-use-locations-use-cases/index.ts | 5 ++
...mote-create-input-use-location-use-case.ts | 39 ++++++++++
...mote-delete-input-use-location-use-case.ts | 38 ++++++++++
.../remote-get-input-use-location-use-case.ts | 50 +++++++++++++
...remote-get-input-use-locations-use-case.ts | 73 +++++++++++++++++++
...mote-update-input-use-location-use-case.ts | 39 ++++++++++
6 files changed, 244 insertions(+)
create mode 100644 src/app/modules/input-uses/data/use-cases/input-use-locations-use-cases/index.ts
create mode 100644 src/app/modules/input-uses/data/use-cases/input-use-locations-use-cases/remote-create-input-use-location-use-case.ts
create mode 100644 src/app/modules/input-uses/data/use-cases/input-use-locations-use-cases/remote-delete-input-use-location-use-case.ts
create mode 100644 src/app/modules/input-uses/data/use-cases/input-use-locations-use-cases/remote-get-input-use-location-use-case.ts
create mode 100644 src/app/modules/input-uses/data/use-cases/input-use-locations-use-cases/remote-get-input-use-locations-use-case.ts
create mode 100644 src/app/modules/input-uses/data/use-cases/input-use-locations-use-cases/remote-update-input-use-location-use-case.ts
diff --git a/src/app/modules/input-uses/data/use-cases/input-use-locations-use-cases/index.ts b/src/app/modules/input-uses/data/use-cases/input-use-locations-use-cases/index.ts
new file mode 100644
index 00000000..292d3dc9
--- /dev/null
+++ b/src/app/modules/input-uses/data/use-cases/input-use-locations-use-cases/index.ts
@@ -0,0 +1,5 @@
+export * from './remote-create-input-use-location-use-case'
+export * from './remote-update-input-use-location-use-case'
+export * from './remote-delete-input-use-location-use-case'
+export * from './remote-get-input-use-location-use-case'
+export * from './remote-get-input-use-locations-use-case'
diff --git a/src/app/modules/input-uses/data/use-cases/input-use-locations-use-cases/remote-create-input-use-location-use-case.ts b/src/app/modules/input-uses/data/use-cases/input-use-locations-use-cases/remote-create-input-use-location-use-case.ts
new file mode 100644
index 00000000..a302ac37
--- /dev/null
+++ b/src/app/modules/input-uses/data/use-cases/input-use-locations-use-cases/remote-create-input-use-location-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 { CreateInputUseLocationUseCase } from '../../../domain/use-cases/input-use-locations-use-cases'
+
+export class RemoteCreateInputUseLocationUseCase
+ implements CreateInputUseLocationUseCase
+{
+ constructor(
+ private readonly url: string,
+ private readonly httpClient: HttpClient
+ ) {}
+
+ execute: CreateInputUseLocationUseCase['execute'] = async ({
+ inputUseLocation,
+ }) => {
+ const { statusCode } = await this.httpClient.request({
+ url: this.url,
+ method: 'post',
+ body: inputUseLocation,
+ })
+
+ 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 local de utilização.'
+ )
+ }
+
+ throw new UnexpectedError()
+ }
+}
diff --git a/src/app/modules/input-uses/data/use-cases/input-use-locations-use-cases/remote-delete-input-use-location-use-case.ts b/src/app/modules/input-uses/data/use-cases/input-use-locations-use-cases/remote-delete-input-use-location-use-case.ts
new file mode 100644
index 00000000..6c488517
--- /dev/null
+++ b/src/app/modules/input-uses/data/use-cases/input-use-locations-use-cases/remote-delete-input-use-location-use-case.ts
@@ -0,0 +1,38 @@
+import { type HttpClient, HttpStatusCode } from '@/core/data/protocols/http'
+import {
+ UnexpectedError,
+ NotFoundError,
+ ForbiddenError,
+} from '@/core/domain/errors'
+
+import type { DeleteInputUseLocationUseCase } from '../../../domain/use-cases/input-use-locations-use-cases'
+
+export class RemoteDeleteInputUseLocationUseCase
+ implements DeleteInputUseLocationUseCase
+{
+ constructor(
+ private readonly url: string,
+ private readonly httpClient: HttpClient
+ ) {}
+
+ execute: DeleteInputUseLocationUseCase['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('Local de Utilização')
+ }
+
+ if (statusCode === HttpStatusCode.forbidden) {
+ throw new ForbiddenError(
+ 'Você não tem permissão para excluir um local de utilização.'
+ )
+ }
+
+ throw new UnexpectedError()
+ }
+}
diff --git a/src/app/modules/input-uses/data/use-cases/input-use-locations-use-cases/remote-get-input-use-location-use-case.ts b/src/app/modules/input-uses/data/use-cases/input-use-locations-use-cases/remote-get-input-use-location-use-case.ts
new file mode 100644
index 00000000..6c533c73
--- /dev/null
+++ b/src/app/modules/input-uses/data/use-cases/input-use-locations-use-cases/remote-get-input-use-location-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 {
+ InputUseLocationDetailsApiResponse,
+ InputUseLocationDetailsModel,
+} from '../../../domain/models/input-use-locations-model'
+import type { GetInputUseLocationUseCase } from '../../../domain/use-cases/input-use-locations-use-cases'
+
+export class RemoteGetInputUseLocationUseCase
+ implements GetInputUseLocationUseCase
+{
+ constructor(
+ private readonly url: string,
+ private readonly httpClient: HttpClient<
+ InputUseLocationDetailsModel,
+ InputUseLocationDetailsApiResponse
+ >
+ ) {}
+
+ execute: GetInputUseLocationUseCase['execute'] = async ({ id }) => {
+ const { statusCode, body } = await this.httpClient.request({
+ url: `${this.url}/${id}`,
+ method: 'get',
+ })
+
+ if (statusCode === HttpStatusCode.ok && !!body)
+ return {
+ description: body.description,
+ }
+
+ if (statusCode === HttpStatusCode.badRequest) throw new BadRequestError()
+
+ if (statusCode === HttpStatusCode.notFound)
+ throw new NotFoundError('Local de Utilização')
+
+ if (statusCode === HttpStatusCode.forbidden) {
+ throw new ForbiddenError(
+ 'Você não tem permissão para acessar os dados deste local de utilização.'
+ )
+ }
+
+ throw new UnexpectedError()
+ }
+}
diff --git a/src/app/modules/input-uses/data/use-cases/input-use-locations-use-cases/remote-get-input-use-locations-use-case.ts b/src/app/modules/input-uses/data/use-cases/input-use-locations-use-cases/remote-get-input-use-locations-use-case.ts
new file mode 100644
index 00000000..43ed956c
--- /dev/null
+++ b/src/app/modules/input-uses/data/use-cases/input-use-locations-use-cases/remote-get-input-use-locations-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 {
+ InputUseLocationApiResponse,
+ InputUseLocationModel,
+} from '../../../domain/models/input-use-locations-model'
+import type { GetInputUseLocationsUseCase } from '../../../domain/use-cases/input-use-locations-use-cases'
+import type { ListApiResponse, MapApiProperties } from '@/core/domain/types'
+
+export class RemoteGetInputUseLocationsUseCase
+ implements GetInputUseLocationsUseCase
+{
+ constructor(
+ private readonly url: string,
+ private readonly httpClient: HttpClient<
+ InputUseLocationModel,
+ InputUseLocationApiResponse,
+ ListApiResponse
+ >
+ ) {}
+
+ execute: GetInputUseLocationsUseCase['execute'] = async ({
+ filters,
+ pagination,
+ sort,
+ }) => {
+ const mapApiProperties: MapApiProperties<
+ InputUseLocationModel,
+ InputUseLocationApiResponse
+ > = {
+ id: 'id',
+ description: 'description',
+ }
+
+ 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,
+ description: item.description,
+ }
+ }),
+ totalPages: Math.ceil(body.numberOfElements / body.pageable.pageSize),
+ }
+ }
+
+ if (statusCode === HttpStatusCode.notFound) {
+ throw new NotFoundError('Local de Utilização')
+ }
+
+ if (statusCode === HttpStatusCode.forbidden) {
+ throw new ForbiddenError(
+ 'Você não tem permissão para buscar os locais de utilização.'
+ )
+ }
+
+ throw new UnexpectedError()
+ }
+}
diff --git a/src/app/modules/input-uses/data/use-cases/input-use-locations-use-cases/remote-update-input-use-location-use-case.ts b/src/app/modules/input-uses/data/use-cases/input-use-locations-use-cases/remote-update-input-use-location-use-case.ts
new file mode 100644
index 00000000..5f4e8d6f
--- /dev/null
+++ b/src/app/modules/input-uses/data/use-cases/input-use-locations-use-cases/remote-update-input-use-location-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 { UpdateInputUseLocationUseCase } from '../../../domain/use-cases/input-use-locations-use-cases'
+
+export class RemoteUpdateInputUseLocationUseCase
+ implements UpdateInputUseLocationUseCase
+{
+ constructor(
+ private readonly url: string,
+ private readonly httpClient: HttpClient
+ ) {}
+
+ execute: UpdateInputUseLocationUseCase['execute'] = async ({
+ inputUseLocation: { id, ...inputUseLocation },
+ }) => {
+ const { statusCode } = await this.httpClient.request({
+ url: `${this.url}/${id}`,
+ method: 'patch',
+ body: inputUseLocation,
+ })
+
+ 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 local de utilização.'
+ )
+ }
+
+ throw new UnexpectedError()
+ }
+}
From e832f466092bec0b12b29dd7af22df71755754c5 Mon Sep 17 00:00:00 2001
From: Guilherme Minozzi
Date: Tue, 7 Apr 2026 10:42:41 -0300
Subject: [PATCH 4/8] feat: add main with factories layer
---
.../use-cases/input-use-locations-use-cases/index.ts | 5 +++++
...ote-create-input-use-location-use-case-factory.ts | 12 ++++++++++++
...ote-delete-input-use-location-use-case-factory.ts | 12 ++++++++++++
...remote-get-input-use-location-use-case-factory.ts | 12 ++++++++++++
...emote-get-input-use-locations-use-case-factory.ts | 12 ++++++++++++
...ote-update-input-use-location-use-case-factory.ts | 12 ++++++++++++
6 files changed, 65 insertions(+)
create mode 100644 src/app/modules/input-uses/main/factories/use-cases/input-use-locations-use-cases/index.ts
create mode 100644 src/app/modules/input-uses/main/factories/use-cases/input-use-locations-use-cases/remote-create-input-use-location-use-case-factory.ts
create mode 100644 src/app/modules/input-uses/main/factories/use-cases/input-use-locations-use-cases/remote-delete-input-use-location-use-case-factory.ts
create mode 100644 src/app/modules/input-uses/main/factories/use-cases/input-use-locations-use-cases/remote-get-input-use-location-use-case-factory.ts
create mode 100644 src/app/modules/input-uses/main/factories/use-cases/input-use-locations-use-cases/remote-get-input-use-locations-use-case-factory.ts
create mode 100644 src/app/modules/input-uses/main/factories/use-cases/input-use-locations-use-cases/remote-update-input-use-location-use-case-factory.ts
diff --git a/src/app/modules/input-uses/main/factories/use-cases/input-use-locations-use-cases/index.ts b/src/app/modules/input-uses/main/factories/use-cases/input-use-locations-use-cases/index.ts
new file mode 100644
index 00000000..a47ea0f7
--- /dev/null
+++ b/src/app/modules/input-uses/main/factories/use-cases/input-use-locations-use-cases/index.ts
@@ -0,0 +1,5 @@
+export * from './remote-create-input-use-location-use-case-factory'
+export * from './remote-update-input-use-location-use-case-factory'
+export * from './remote-delete-input-use-location-use-case-factory'
+export * from './remote-get-input-use-location-use-case-factory'
+export * from './remote-get-input-use-locations-use-case-factory'
diff --git a/src/app/modules/input-uses/main/factories/use-cases/input-use-locations-use-cases/remote-create-input-use-location-use-case-factory.ts b/src/app/modules/input-uses/main/factories/use-cases/input-use-locations-use-cases/remote-create-input-use-location-use-case-factory.ts
new file mode 100644
index 00000000..e12b24f8
--- /dev/null
+++ b/src/app/modules/input-uses/main/factories/use-cases/input-use-locations-use-cases/remote-create-input-use-location-use-case-factory.ts
@@ -0,0 +1,12 @@
+import { makeApiHttpClient } from '@/core/main/factories/http'
+
+import { RemoteCreateInputUseLocationUseCase } from '../../../../data/use-cases/input-use-locations-use-cases'
+
+import type { CreateInputUseLocationUseCase } from '../../../../domain/use-cases/input-use-locations-use-cases'
+
+export function makeRemoteCreateInputUseLocationUseCase(): CreateInputUseLocationUseCase {
+ return new RemoteCreateInputUseLocationUseCase(
+ '/input-uses/locations',
+ makeApiHttpClient()
+ )
+}
diff --git a/src/app/modules/input-uses/main/factories/use-cases/input-use-locations-use-cases/remote-delete-input-use-location-use-case-factory.ts b/src/app/modules/input-uses/main/factories/use-cases/input-use-locations-use-cases/remote-delete-input-use-location-use-case-factory.ts
new file mode 100644
index 00000000..e59d511a
--- /dev/null
+++ b/src/app/modules/input-uses/main/factories/use-cases/input-use-locations-use-cases/remote-delete-input-use-location-use-case-factory.ts
@@ -0,0 +1,12 @@
+import { makeApiHttpClient } from '@/core/main/factories/http'
+
+import { RemoteDeleteInputUseLocationUseCase } from '../../../../data/use-cases/input-use-locations-use-cases'
+
+import type { DeleteInputUseLocationUseCase } from '../../../../domain/use-cases/input-use-locations-use-cases'
+
+export function makeRemoteDeleteInputUseLocationUseCase(): DeleteInputUseLocationUseCase {
+ return new RemoteDeleteInputUseLocationUseCase(
+ '/input-uses/locations',
+ makeApiHttpClient()
+ )
+}
diff --git a/src/app/modules/input-uses/main/factories/use-cases/input-use-locations-use-cases/remote-get-input-use-location-use-case-factory.ts b/src/app/modules/input-uses/main/factories/use-cases/input-use-locations-use-cases/remote-get-input-use-location-use-case-factory.ts
new file mode 100644
index 00000000..2c08a5e1
--- /dev/null
+++ b/src/app/modules/input-uses/main/factories/use-cases/input-use-locations-use-cases/remote-get-input-use-location-use-case-factory.ts
@@ -0,0 +1,12 @@
+import { makeApiHttpClient } from '@/core/main/factories/http'
+
+import { RemoteGetInputUseLocationUseCase } from '../../../../data/use-cases/input-use-locations-use-cases'
+
+import type { GetInputUseLocationUseCase } from '../../../../domain/use-cases/input-use-locations-use-cases'
+
+export function makeRemoteGetInputUseLocationUseCase(): GetInputUseLocationUseCase {
+ return new RemoteGetInputUseLocationUseCase(
+ '/input-uses/locations',
+ makeApiHttpClient()
+ )
+}
diff --git a/src/app/modules/input-uses/main/factories/use-cases/input-use-locations-use-cases/remote-get-input-use-locations-use-case-factory.ts b/src/app/modules/input-uses/main/factories/use-cases/input-use-locations-use-cases/remote-get-input-use-locations-use-case-factory.ts
new file mode 100644
index 00000000..966e6040
--- /dev/null
+++ b/src/app/modules/input-uses/main/factories/use-cases/input-use-locations-use-cases/remote-get-input-use-locations-use-case-factory.ts
@@ -0,0 +1,12 @@
+import { makeApiHttpClient } from '@/core/main/factories/http'
+
+import { RemoteGetInputUseLocationsUseCase } from '../../../../data/use-cases/input-use-locations-use-cases'
+
+import type { GetInputUseLocationsUseCase } from '../../../../domain/use-cases/input-use-locations-use-cases'
+
+export function makeRemoteGetInputUseLocationsUseCase(): GetInputUseLocationsUseCase {
+ return new RemoteGetInputUseLocationsUseCase(
+ '/input-uses/locations',
+ makeApiHttpClient()
+ )
+}
diff --git a/src/app/modules/input-uses/main/factories/use-cases/input-use-locations-use-cases/remote-update-input-use-location-use-case-factory.ts b/src/app/modules/input-uses/main/factories/use-cases/input-use-locations-use-cases/remote-update-input-use-location-use-case-factory.ts
new file mode 100644
index 00000000..557bf6d4
--- /dev/null
+++ b/src/app/modules/input-uses/main/factories/use-cases/input-use-locations-use-cases/remote-update-input-use-location-use-case-factory.ts
@@ -0,0 +1,12 @@
+import { makeApiHttpClient } from '@/core/main/factories/http'
+
+import { RemoteUpdateInputUseLocationUseCase } from '../../../../data/use-cases/input-use-locations-use-cases'
+
+import type { UpdateInputUseLocationUseCase } from '../../../../domain/use-cases/input-use-locations-use-cases'
+
+export function makeRemoteUpdateInputUseLocationUseCase(): UpdateInputUseLocationUseCase {
+ return new RemoteUpdateInputUseLocationUseCase(
+ '/input-uses/locations',
+ makeApiHttpClient()
+ )
+}
From cc75e249cab2e0089baf4892b279daad1549bc49 Mon Sep 17 00:00:00 2001
From: Guilherme Minozzi
Date: Tue, 7 Apr 2026 10:43:02 -0300
Subject: [PATCH 5/8] feat: add mocks layer
---
.../create-input-use-location-handler.ts | 19 +++++
.../delete-input-use-location-handler.ts | 17 +++++
.../get-input-use-location-handler.ts | 33 +++++++++
.../get-input-use-locations-handler.ts | 72 +++++++++++++++++++
.../input-use-locations-handlers/index.ts | 5 ++
.../update-input-use-location-handler.ts | 19 +++++
6 files changed, 165 insertions(+)
create mode 100644 src/app/modules/input-uses/mocks/handlers/input-use-locations-handlers/create-input-use-location-handler.ts
create mode 100644 src/app/modules/input-uses/mocks/handlers/input-use-locations-handlers/delete-input-use-location-handler.ts
create mode 100644 src/app/modules/input-uses/mocks/handlers/input-use-locations-handlers/get-input-use-location-handler.ts
create mode 100644 src/app/modules/input-uses/mocks/handlers/input-use-locations-handlers/get-input-use-locations-handler.ts
create mode 100644 src/app/modules/input-uses/mocks/handlers/input-use-locations-handlers/index.ts
create mode 100644 src/app/modules/input-uses/mocks/handlers/input-use-locations-handlers/update-input-use-location-handler.ts
diff --git a/src/app/modules/input-uses/mocks/handlers/input-use-locations-handlers/create-input-use-location-handler.ts b/src/app/modules/input-uses/mocks/handlers/input-use-locations-handlers/create-input-use-location-handler.ts
new file mode 100644
index 00000000..88f3fca1
--- /dev/null
+++ b/src/app/modules/input-uses/mocks/handlers/input-use-locations-handlers/create-input-use-location-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 { InputUseLocationDetailsModel } from '@/app/modules/input-uses/domain/models/input-use-locations-model'
+
+export const createInputUseLocationHandler = httpWithMiddleware<
+ PathParams,
+ InputUseLocationDetailsModel,
+ never
+>({
+ routePath: '/api/input-uses/locations',
+ method: 'post',
+ middlewares: [withDelay(), withAuth],
+ resolver: async () =>
+ HttpResponse.json({}, { status: HttpStatusCode.created }),
+})
diff --git a/src/app/modules/input-uses/mocks/handlers/input-use-locations-handlers/delete-input-use-location-handler.ts b/src/app/modules/input-uses/mocks/handlers/input-use-locations-handlers/delete-input-use-location-handler.ts
new file mode 100644
index 00000000..2f2ad7f6
--- /dev/null
+++ b/src/app/modules/input-uses/mocks/handlers/input-use-locations-handlers/delete-input-use-location-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 deleteInputUseLocationHandler = httpWithMiddleware<
+ PathParams<'id'>,
+ never,
+ never
+>({
+ routePath: '/api/input-uses/locations/: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-locations-handlers/get-input-use-location-handler.ts b/src/app/modules/input-uses/mocks/handlers/input-use-locations-handlers/get-input-use-location-handler.ts
new file mode 100644
index 00000000..c914a1ec
--- /dev/null
+++ b/src/app/modules/input-uses/mocks/handlers/input-use-locations-handlers/get-input-use-location-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 inputUseLocationsData from '@database/inputUseLocationsData.json'
+
+import type { InputUseLocationApiResponse } from '@/app/modules/input-uses/domain/models/input-use-locations-model'
+
+export const getInputUseLocationHandler = httpWithMiddleware<
+ PathParams<'id'>,
+ never,
+ InputUseLocationApiResponse
+>({
+ routePath: '/api/input-uses/locations/:id',
+ method: 'get',
+ middlewares: [withDelay(), withAuth],
+ resolver: async ({ params }) => {
+ const id = Number(params.id)
+ const inputUseLocation = inputUseLocationsData.find(
+ (item) => item.id === id
+ )
+
+ if (!inputUseLocation) {
+ return HttpResponse.json({} as InputUseLocationApiResponse, {
+ status: HttpStatusCode.notFound,
+ })
+ }
+
+ return HttpResponse.json(inputUseLocation, { status: HttpStatusCode.ok })
+ },
+})
diff --git a/src/app/modules/input-uses/mocks/handlers/input-use-locations-handlers/get-input-use-locations-handler.ts b/src/app/modules/input-uses/mocks/handlers/input-use-locations-handlers/get-input-use-locations-handler.ts
new file mode 100644
index 00000000..bbf9b785
--- /dev/null
+++ b/src/app/modules/input-uses/mocks/handlers/input-use-locations-handlers/get-input-use-locations-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 inputUseLocationsData from '@database/inputUseLocationsData.json'
+
+import type { InputUseLocationApiResponse } from '@/app/modules/input-uses/domain/models/input-use-locations-model'
+import type { MockParams } from '@/core/mocks/types/mock-params-type'
+import type { MockResponse } from '@/core/mocks/types/mock-response-type'
+
+export const getInputUseLocationsHandler = httpWithMiddleware<
+ PathParams,
+ MockParams,
+ MockResponse
+>({
+ routePath: '/api/input-uses/locations/search',
+ method: 'post',
+ middlewares: [withDelay(), withAuth],
+ resolver: async ({ request }) => {
+ const { filters, page, rows, sort } = await request.json()
+
+ if (!inputUseLocationsData.length) {
+ return HttpResponse.json(
+ {
+ content: [],
+ numberOfElements: 0,
+ pageable: {
+ pageSize: 0,
+ },
+ },
+ {
+ status: 404,
+ }
+ )
+ }
+
+ let inputUseLocations = inputUseLocationsData
+
+ if (filters) {
+ inputUseLocations = filterData(
+ filters,
+ inputUseLocations
+ )
+ }
+ if (sort) {
+ inputUseLocations = sortData(
+ sort,
+ inputUseLocations
+ )
+ }
+
+ const numberOfElements = inputUseLocations.length
+ inputUseLocations = paginateData(
+ { page, perPage: rows },
+ inputUseLocations
+ )
+
+ return HttpResponse.json(
+ {
+ content: inputUseLocations,
+ numberOfElements,
+ pageable: {
+ pageSize: rows,
+ },
+ },
+ { status: HttpStatusCode.ok }
+ )
+ },
+})
diff --git a/src/app/modules/input-uses/mocks/handlers/input-use-locations-handlers/index.ts b/src/app/modules/input-uses/mocks/handlers/input-use-locations-handlers/index.ts
new file mode 100644
index 00000000..3c0472a0
--- /dev/null
+++ b/src/app/modules/input-uses/mocks/handlers/input-use-locations-handlers/index.ts
@@ -0,0 +1,5 @@
+export * from './create-input-use-location-handler'
+export * from './delete-input-use-location-handler'
+export * from './get-input-use-location-handler'
+export * from './get-input-use-locations-handler'
+export * from './update-input-use-location-handler'
diff --git a/src/app/modules/input-uses/mocks/handlers/input-use-locations-handlers/update-input-use-location-handler.ts b/src/app/modules/input-uses/mocks/handlers/input-use-locations-handlers/update-input-use-location-handler.ts
new file mode 100644
index 00000000..b400e142
--- /dev/null
+++ b/src/app/modules/input-uses/mocks/handlers/input-use-locations-handlers/update-input-use-location-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 { InputUseLocationApiResponse } from '@/app/modules/input-uses/domain/models/input-use-locations-model'
+
+export const updateInputUseLocationHandler = httpWithMiddleware<
+ PathParams<'id'>,
+ Omit,
+ never
+>({
+ routePath: '/api/input-uses/locations/:id',
+ method: 'patch',
+ middlewares: [withDelay(), withAuth],
+ resolver: async () =>
+ HttpResponse.json(undefined, { status: HttpStatusCode.noContent }),
+})
From e9e7e70da1a0725403ed0cb47ff26c11c433c5bf Mon Sep 17 00:00:00 2001
From: Guilherme Minozzi
Date: Tue, 7 Apr 2026 10:44:16 -0300
Subject: [PATCH 6/8] feat: add mocks presentation layer
---
.../input-use-location-data-table/index.ts | 1 +
.../input-use-location-data-table.hook.tsx | 84 +++++++++++
.../input-use-location-data-table.tsx | 36 +++++
.../input-use-location-delete-dialog/index.ts | 1 +
.../input-use-location-delete-dialog.tsx | 82 +++++++++++
.../contexts/input-use-location-context.tsx | 137 +++++++++++++++++
.../create-input-use-location-form.tsx | 112 ++++++++++++++
.../edit-input-use-location-form.tsx | 138 ++++++++++++++++++
.../forms/input-use-location-form/index.ts | 1 +
.../input-use-location-form-inputs.tsx | 27 ++++
.../input-use-location-form.tsx | 18 +++
.../input-use-location-initial-form-data.ts | 3 +
.../hooks/input-use-location-context.hook.ts | 15 ++
.../queries/input-use-location-query.hook.ts | 37 +++++
.../queries/input-use-locations-query.hook.ts | 51 +++++++
.../screens/input-use-locations-screen.tsx | 65 +++++++++
.../types/input-use-location-types.ts | 5 +
.../input-use-location-form-schema.ts | 12 ++
18 files changed, 825 insertions(+)
create mode 100644 src/app/modules/input-uses/presentation/components/input-use-location-data-table/index.ts
create mode 100644 src/app/modules/input-uses/presentation/components/input-use-location-data-table/input-use-location-data-table.hook.tsx
create mode 100644 src/app/modules/input-uses/presentation/components/input-use-location-data-table/input-use-location-data-table.tsx
create mode 100644 src/app/modules/input-uses/presentation/components/input-use-location-delete-dialog/index.ts
create mode 100644 src/app/modules/input-uses/presentation/components/input-use-location-delete-dialog/input-use-location-delete-dialog.tsx
create mode 100644 src/app/modules/input-uses/presentation/contexts/input-use-location-context.tsx
create mode 100644 src/app/modules/input-uses/presentation/forms/input-use-location-form/create-input-use-location-form.tsx
create mode 100644 src/app/modules/input-uses/presentation/forms/input-use-location-form/edit-input-use-location-form.tsx
create mode 100644 src/app/modules/input-uses/presentation/forms/input-use-location-form/index.ts
create mode 100644 src/app/modules/input-uses/presentation/forms/input-use-location-form/input-use-location-form-inputs.tsx
create mode 100644 src/app/modules/input-uses/presentation/forms/input-use-location-form/input-use-location-form.tsx
create mode 100644 src/app/modules/input-uses/presentation/forms/input-use-location-form/input-use-location-initial-form-data.ts
create mode 100644 src/app/modules/input-uses/presentation/hooks/input-use-location-context.hook.ts
create mode 100644 src/app/modules/input-uses/presentation/hooks/queries/input-use-location-query.hook.ts
create mode 100644 src/app/modules/input-uses/presentation/hooks/queries/input-use-locations-query.hook.ts
create mode 100644 src/app/modules/input-uses/presentation/screens/input-use-locations-screen.tsx
create mode 100644 src/app/modules/input-uses/presentation/types/input-use-location-types.ts
create mode 100644 src/app/modules/input-uses/presentation/validations/input-use-location-form-schema.ts
diff --git a/src/app/modules/input-uses/presentation/components/input-use-location-data-table/index.ts b/src/app/modules/input-uses/presentation/components/input-use-location-data-table/index.ts
new file mode 100644
index 00000000..dabf3ac9
--- /dev/null
+++ b/src/app/modules/input-uses/presentation/components/input-use-location-data-table/index.ts
@@ -0,0 +1 @@
+export * from './input-use-location-data-table'
diff --git a/src/app/modules/input-uses/presentation/components/input-use-location-data-table/input-use-location-data-table.hook.tsx b/src/app/modules/input-uses/presentation/components/input-use-location-data-table/input-use-location-data-table.hook.tsx
new file mode 100644
index 00000000..e83a4493
--- /dev/null
+++ b/src/app/modules/input-uses/presentation/components/input-use-location-data-table/input-use-location-data-table.hook.tsx
@@ -0,0 +1,84 @@
+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 { useInputUseLocationContext } from '../../hooks/input-use-location-context.hook'
+import { useInputUseLocationsQuery } from '../../hooks/queries/input-use-locations-query.hook'
+
+import type { InputUseLocationModel } from '../../../domain/models/input-use-locations-model'
+import type { InputUseLocationSort } from '../../types/input-use-location-types'
+import type { ColumnDef } from '@tanstack/react-table'
+
+export function useInputUseLocationDataTable() {
+ const {
+ filters,
+ openEditInputUseLocationForm,
+ openDeleteInputUseLocationContainer,
+ } = useInputUseLocationContext()
+
+ const [page, setPage] = useState(1)
+ const [sort, setSort] = useState()
+
+ const debouncedFilters = useDebounce({ value: filters })
+
+ const { isLoading, inputUseLocations } = useInputUseLocationsQuery({
+ filters: debouncedFilters,
+ page,
+ sort,
+ })
+
+ const columns = useMemo[]>(
+ () => [
+ {
+ accessorKey: 'description',
+ header: 'Descrição',
+ },
+ {
+ id: 'row-actions',
+ header: '',
+ cell: ({ row }) => {
+ const { original: inputUseLocation } = row
+
+ return (
+
+
+
+
+
+ openEditInputUseLocationForm(inputUseLocation)}
+ >
+ Editar
+
+
+
+ openDeleteInputUseLocationContainer(inputUseLocation)
+ }
+ >
+ Excluir
+
+
+
+ )
+ },
+ },
+ ],
+ [openDeleteInputUseLocationContainer, openEditInputUseLocationForm]
+ )
+
+ return {
+ columns,
+ inputUseLocations,
+ isLoading,
+ page,
+ sort,
+ setSort,
+ setPage,
+ }
+}
diff --git a/src/app/modules/input-uses/presentation/components/input-use-location-data-table/input-use-location-data-table.tsx b/src/app/modules/input-uses/presentation/components/input-use-location-data-table/input-use-location-data-table.tsx
new file mode 100644
index 00000000..13968798
--- /dev/null
+++ b/src/app/modules/input-uses/presentation/components/input-use-location-data-table/input-use-location-data-table.tsx
@@ -0,0 +1,36 @@
+import { DataTable } from '@/core/presentation/components/ui'
+
+import { useInputUseLocationDataTable } from './input-use-location-data-table.hook'
+
+import type { InputUseLocationModel } from '../../../domain/models/input-use-locations-model'
+
+export function InputUseLocationDataTable() {
+ const {
+ columns,
+ inputUseLocations,
+ isLoading,
+ page,
+ sort,
+ setSort,
+ setPage,
+ } = useInputUseLocationDataTable()
+
+ return (
+
+ columns={columns}
+ data={inputUseLocations.resources}
+ totalPages={inputUseLocations.totalPages}
+ pagination={{
+ currentPage: page,
+ onPageChange: setPage,
+ }}
+ sorting={{
+ currentSorting: sort,
+ onSorting: setSort,
+ }}
+ loading={isLoading}
+ />
+ )
+}
+
+InputUseLocationDataTable.displayName = 'InputUseLocationDataTable'
diff --git a/src/app/modules/input-uses/presentation/components/input-use-location-delete-dialog/index.ts b/src/app/modules/input-uses/presentation/components/input-use-location-delete-dialog/index.ts
new file mode 100644
index 00000000..506b75bd
--- /dev/null
+++ b/src/app/modules/input-uses/presentation/components/input-use-location-delete-dialog/index.ts
@@ -0,0 +1 @@
+export * from './input-use-location-delete-dialog'
diff --git a/src/app/modules/input-uses/presentation/components/input-use-location-delete-dialog/input-use-location-delete-dialog.tsx b/src/app/modules/input-uses/presentation/components/input-use-location-delete-dialog/input-use-location-delete-dialog.tsx
new file mode 100644
index 00000000..036eaab9
--- /dev/null
+++ b/src/app/modules/input-uses/presentation/components/input-use-location-delete-dialog/input-use-location-delete-dialog.tsx
@@ -0,0 +1,82 @@
+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 { makeRemoteDeleteInputUseLocationUseCase } from '../../../main/factories/use-cases/input-use-locations-use-cases'
+import { useInputUseLocationContext } from '../../hooks/input-use-location-context.hook'
+
+export function InputUseLocationDeleteDialog() {
+ const deleteInputUseLocationUseCase =
+ makeRemoteDeleteInputUseLocationUseCase()
+
+ const {
+ selectedInputUseLocation,
+ isOpenDeleteInputUseLocationContainer,
+ closeDeleteInputUseLocationContainer,
+ } = useInputUseLocationContext()
+
+ const queryClient = useQueryClient()
+
+ const { mutateAsync: mutateHandleDeleteInputUseLocation } = useMutation({
+ mutationFn: deleteInputUseLocationUseCase.execute,
+ })
+
+ const handleDeleteInputUseLocation = useCallback(async () => {
+ if (!selectedInputUseLocation?.id) {
+ toast.error('Erro ao remover local de utilização')
+ return
+ }
+
+ try {
+ await mutateHandleDeleteInputUseLocation({
+ id: selectedInputUseLocation.id,
+ })
+
+ queryClient.invalidateQueries({
+ queryKey: ['input-use-locations'],
+ exact: false,
+ })
+
+ toast.success('Local de utilização removido com sucesso')
+ } catch {
+ toast.error('Erro ao remover local de utilização')
+ } finally {
+ closeDeleteInputUseLocationContainer()
+ }
+ }, [
+ closeDeleteInputUseLocationContainer,
+ mutateHandleDeleteInputUseLocation,
+ queryClient,
+ selectedInputUseLocation,
+ ])
+
+ return (
+
+
+
+
+ {`Deseja remover o local de utilização
+ ${selectedInputUseLocation?.description}?`}
+
+
+ Não será possível desfazer essa ação!
+
+
+
+ Cancelar
+
+ Remover
+
+
+
+
+ )
+}
+
+InputUseLocationDeleteDialog.displayName = 'InputUseLocationDeleteDialog'
diff --git a/src/app/modules/input-uses/presentation/contexts/input-use-location-context.tsx b/src/app/modules/input-uses/presentation/contexts/input-use-location-context.tsx
new file mode 100644
index 00000000..ab59b6b6
--- /dev/null
+++ b/src/app/modules/input-uses/presentation/contexts/input-use-location-context.tsx
@@ -0,0 +1,137 @@
+import {
+ createContext,
+ useCallback,
+ useMemo,
+ useState,
+ type ReactNode,
+} from 'react'
+
+import type { InputUseLocationModel } from '../../domain/models/input-use-locations-model'
+import type { Filters } from '@/core/domain/types'
+
+export type InputUseLocationContextData = {
+ selectedInputUseLocation: InputUseLocationModel | null
+ isOpenNewInputUseLocationForm: boolean
+ isOpenEditInputUseLocationForm: boolean
+ isOpenDeleteInputUseLocationContainer: boolean
+ filters: Filters
+ openNewInputUseLocationForm: () => void
+ closeNewInputUseLocationForm: () => void
+ openEditInputUseLocationForm: (
+ inputUseLocation: InputUseLocationModel
+ ) => void
+ closeEditInputUseLocationForm: () => void
+ openDeleteInputUseLocationContainer: (
+ inputUseLocation: InputUseLocationModel
+ ) => void
+ closeDeleteInputUseLocationContainer: () => void
+ handleChangeFilters: (newFilters: Filters) => void
+ clearFilters: () => void
+}
+
+export const InputUseLocationContext =
+ createContext({} as InputUseLocationContextData)
+
+type InputUseLocationProviderProps = {
+ children: ReactNode
+}
+
+export function InputUseLocationProvider({
+ children,
+}: InputUseLocationProviderProps) {
+ const [selectedInputUseLocation, setSelectedInputUseLocation] =
+ useState(null)
+ const [isOpenNewInputUseLocationForm, setIsOpenNewInputUseLocationForm] =
+ useState(false)
+ const [isOpenEditInputUseLocationForm, setIsOpenEditInputUseLocationForm] =
+ useState(false)
+ const [
+ isOpenDeleteInputUseLocationContainer,
+ setIsOpenDeleteInputUseLocationContainer,
+ ] = useState(false)
+ const [filters, setFilters] = useState>({})
+
+ const openNewInputUseLocationForm = useCallback(() => {
+ setIsOpenNewInputUseLocationForm(true)
+ }, [])
+
+ const closeNewInputUseLocationForm = useCallback(() => {
+ setIsOpenNewInputUseLocationForm(false)
+ }, [])
+
+ const openEditInputUseLocationForm = useCallback(
+ (inputUseLocation: InputUseLocationModel) => {
+ setSelectedInputUseLocation(inputUseLocation)
+ setIsOpenEditInputUseLocationForm(true)
+ },
+ []
+ )
+
+ const closeEditInputUseLocationForm = useCallback(() => {
+ setSelectedInputUseLocation(null)
+ setIsOpenEditInputUseLocationForm(false)
+ }, [])
+
+ const openDeleteInputUseLocationContainer = useCallback(
+ (inputUseLocation: InputUseLocationModel) => {
+ setSelectedInputUseLocation(inputUseLocation)
+ setIsOpenDeleteInputUseLocationContainer(true)
+ },
+ []
+ )
+
+ const closeDeleteInputUseLocationContainer = useCallback(() => {
+ setSelectedInputUseLocation(null)
+ setIsOpenDeleteInputUseLocationContainer(false)
+ }, [])
+
+ const handleChangeFilters = useCallback(
+ (newFilters: Filters) => {
+ setFilters((prev) => ({ ...prev, ...newFilters }))
+ },
+ []
+ )
+
+ const clearFilters = useCallback(() => {
+ setFilters({})
+ }, [])
+
+ const value = useMemo(
+ () => ({
+ selectedInputUseLocation,
+ isOpenNewInputUseLocationForm,
+ isOpenEditInputUseLocationForm,
+ isOpenDeleteInputUseLocationContainer,
+ filters,
+ openNewInputUseLocationForm,
+ closeNewInputUseLocationForm,
+ openEditInputUseLocationForm,
+ closeEditInputUseLocationForm,
+ openDeleteInputUseLocationContainer,
+ closeDeleteInputUseLocationContainer,
+ handleChangeFilters,
+ clearFilters,
+ }),
+ [
+ selectedInputUseLocation,
+ isOpenNewInputUseLocationForm,
+ isOpenEditInputUseLocationForm,
+ isOpenDeleteInputUseLocationContainer,
+ filters,
+ openNewInputUseLocationForm,
+ closeNewInputUseLocationForm,
+ openEditInputUseLocationForm,
+ closeEditInputUseLocationForm,
+ openDeleteInputUseLocationContainer,
+ closeDeleteInputUseLocationContainer,
+ handleChangeFilters,
+ clearFilters,
+ ]
+ )
+
+ return (
+
+ {children}
+
+ )
+}
diff --git a/src/app/modules/input-uses/presentation/forms/input-use-location-form/create-input-use-location-form.tsx b/src/app/modules/input-uses/presentation/forms/input-use-location-form/create-input-use-location-form.tsx
new file mode 100644
index 00000000..090ad35b
--- /dev/null
+++ b/src/app/modules/input-uses/presentation/forms/input-use-location-form/create-input-use-location-form.tsx
@@ -0,0 +1,112 @@
+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 { makeRemoteCreateInputUseLocationUseCase } from '../../../main/factories/use-cases/input-use-locations-use-cases'
+import { useInputUseLocationContext } from '../../hooks/input-use-location-context.hook'
+import {
+ inputUseLocationFormSchema,
+ type InputUseLocationFormSchema,
+} from '../../validations/input-use-location-form-schema'
+
+import { InputUseLocationFormInputs } from './input-use-location-form-inputs'
+import { INPUT_USE_LOCATION_INITIAL_FORM_DATA } from './input-use-location-initial-form-data'
+
+export function CreateInputUseLocationForm() {
+ const { isOpenNewInputUseLocationForm, closeNewInputUseLocationForm } =
+ useInputUseLocationContext()
+
+ const createInputUseLocationUseCase =
+ makeRemoteCreateInputUseLocationUseCase()
+
+ const queryClient = useQueryClient()
+
+ const form = useHookForm({
+ defaultValues: INPUT_USE_LOCATION_INITIAL_FORM_DATA,
+ resolver: zodResolver(inputUseLocationFormSchema),
+ })
+
+ const { mutateAsync: mutateHandleCreateInputUseLocation } = useMutation({
+ mutationFn: createInputUseLocationUseCase.execute,
+ })
+
+ const handleCreateInputUseLocation = useCallback(
+ async (data: InputUseLocationFormSchema) => {
+ try {
+ await mutateHandleCreateInputUseLocation({
+ inputUseLocation: data,
+ })
+
+ queryClient.invalidateQueries({
+ queryKey: ['input-use-locations'],
+ exact: false,
+ })
+
+ toast.success('Local de utilização cadastrado com sucesso')
+
+ form.reset(INPUT_USE_LOCATION_INITIAL_FORM_DATA)
+
+ closeNewInputUseLocationForm()
+ } catch {
+ toast.error('Erro ao cadastrar local de utilização')
+ }
+ },
+ [
+ closeNewInputUseLocationForm,
+ form,
+ mutateHandleCreateInputUseLocation,
+ queryClient,
+ ]
+ )
+
+ return (
+
+
+
+ Novo Local de Utilização
+
+ Preencha o formulário para criar um novo local de utilização
+
+
+
+
+
+
+
+
+
+
+
+ Criar
+
+
+
+
+ )
+}
+
+CreateInputUseLocationForm.displayName = 'CreateInputUseLocationForm'
diff --git a/src/app/modules/input-uses/presentation/forms/input-use-location-form/edit-input-use-location-form.tsx b/src/app/modules/input-uses/presentation/forms/input-use-location-form/edit-input-use-location-form.tsx
new file mode 100644
index 00000000..95d23ac2
--- /dev/null
+++ b/src/app/modules/input-uses/presentation/forms/input-use-location-form/edit-input-use-location-form.tsx
@@ -0,0 +1,138 @@
+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 { makeRemoteUpdateInputUseLocationUseCase } from '../../../main/factories/use-cases/input-use-locations-use-cases'
+import { useInputUseLocationContext } from '../../hooks/input-use-location-context.hook'
+import { useInputUseLocationQuery } from '../../hooks/queries/input-use-location-query.hook'
+import {
+ inputUseLocationFormSchema,
+ type InputUseLocationFormSchema,
+} from '../../validations/input-use-location-form-schema'
+
+import { InputUseLocationFormInputs } from './input-use-location-form-inputs'
+import { INPUT_USE_LOCATION_INITIAL_FORM_DATA } from './input-use-location-initial-form-data'
+
+export function EditInputUseLocationForm() {
+ const {
+ isOpenEditInputUseLocationForm,
+ closeEditInputUseLocationForm,
+ selectedInputUseLocation,
+ } = useInputUseLocationContext()
+
+ const { isLoading, inputUseLocation } = useInputUseLocationQuery({
+ id: selectedInputUseLocation!.id,
+ })
+
+ const updateInputUseLocationUseCase =
+ makeRemoteUpdateInputUseLocationUseCase()
+
+ const queryClient = useQueryClient()
+
+ const form = useHookForm({
+ defaultValues: INPUT_USE_LOCATION_INITIAL_FORM_DATA,
+ ...(inputUseLocation && {
+ values: {
+ ...inputUseLocation,
+ },
+ }),
+ resolver: zodResolver(inputUseLocationFormSchema),
+ })
+
+ const { mutateAsync: mutateHandleUpdateInputUseLocation } = useMutation({
+ mutationFn: updateInputUseLocationUseCase.execute,
+ })
+
+ const handleUpdateInputUseLocation = useCallback(
+ async (data: InputUseLocationFormSchema) => {
+ try {
+ if (!selectedInputUseLocation?.id) {
+ toast.error('Erro ao atualizar local de utilização')
+ return
+ }
+
+ await mutateHandleUpdateInputUseLocation({
+ inputUseLocation: {
+ ...data,
+ id: selectedInputUseLocation.id,
+ },
+ })
+
+ queryClient.invalidateQueries({
+ queryKey: ['input-use-locations'],
+ exact: false,
+ })
+
+ toast.success('Local de utilização foi editado com sucesso')
+ form.reset(INPUT_USE_LOCATION_INITIAL_FORM_DATA)
+ closeEditInputUseLocationForm()
+ } catch {
+ toast.error('Erro ao salvar alterações')
+ }
+ },
+ [
+ closeEditInputUseLocationForm,
+ form,
+ mutateHandleUpdateInputUseLocation,
+ queryClient,
+ selectedInputUseLocation,
+ ]
+ )
+
+ return (
+
+
+
+ Editar Local de Utilização
+
+ Preencha o formulário para editar o local de utilização
+
+
+
+
+
+
+
+
+
+
+ Salvar
+
+
+
+
+ )
+}
+
+EditInputUseLocationForm.displayName = 'EditInputUseLocationForm'
diff --git a/src/app/modules/input-uses/presentation/forms/input-use-location-form/index.ts b/src/app/modules/input-uses/presentation/forms/input-use-location-form/index.ts
new file mode 100644
index 00000000..4be44b23
--- /dev/null
+++ b/src/app/modules/input-uses/presentation/forms/input-use-location-form/index.ts
@@ -0,0 +1 @@
+export * from './input-use-location-form'
diff --git a/src/app/modules/input-uses/presentation/forms/input-use-location-form/input-use-location-form-inputs.tsx b/src/app/modules/input-uses/presentation/forms/input-use-location-form/input-use-location-form-inputs.tsx
new file mode 100644
index 00000000..d8d41242
--- /dev/null
+++ b/src/app/modules/input-uses/presentation/forms/input-use-location-form/input-use-location-form-inputs.tsx
@@ -0,0 +1,27 @@
+import { useFormContext } from 'react-hook-form'
+
+import { Form, Input } from '@/core/presentation/components/ui'
+
+import type { InputUseLocationFormSchema } from '../../validations/input-use-location-form-schema'
+
+export function InputUseLocationFormInputs() {
+ const form = useFormContext()
+
+ return (
+ (
+
+ Descrição
+
+
+
+
+
+ )}
+ />
+ )
+}
+
+InputUseLocationFormInputs.displayName = 'InputUseLocationFormInputs'
diff --git a/src/app/modules/input-uses/presentation/forms/input-use-location-form/input-use-location-form.tsx b/src/app/modules/input-uses/presentation/forms/input-use-location-form/input-use-location-form.tsx
new file mode 100644
index 00000000..86777479
--- /dev/null
+++ b/src/app/modules/input-uses/presentation/forms/input-use-location-form/input-use-location-form.tsx
@@ -0,0 +1,18 @@
+import { CreateInputUseLocationForm } from './create-input-use-location-form'
+import { EditInputUseLocationForm } from './edit-input-use-location-form'
+
+type InputUseLocationFormProps = {
+ id?: string | number
+}
+
+export function InputUseLocationForm({
+ id,
+}: Readonly) {
+ if (id) {
+ return
+ }
+
+ return
+}
+
+InputUseLocationForm.displayName = 'InputUseLocationForm'
diff --git a/src/app/modules/input-uses/presentation/forms/input-use-location-form/input-use-location-initial-form-data.ts b/src/app/modules/input-uses/presentation/forms/input-use-location-form/input-use-location-initial-form-data.ts
new file mode 100644
index 00000000..0624b9ad
--- /dev/null
+++ b/src/app/modules/input-uses/presentation/forms/input-use-location-form/input-use-location-initial-form-data.ts
@@ -0,0 +1,3 @@
+export const INPUT_USE_LOCATION_INITIAL_FORM_DATA = {
+ description: '',
+}
diff --git a/src/app/modules/input-uses/presentation/hooks/input-use-location-context.hook.ts b/src/app/modules/input-uses/presentation/hooks/input-use-location-context.hook.ts
new file mode 100644
index 00000000..a0a80d23
--- /dev/null
+++ b/src/app/modules/input-uses/presentation/hooks/input-use-location-context.hook.ts
@@ -0,0 +1,15 @@
+import { useContext } from 'react'
+
+import { InputUseLocationContext } from '../contexts/input-use-location-context'
+
+export function useInputUseLocationContext() {
+ const context = useContext(InputUseLocationContext)
+
+ if (!context) {
+ throw new Error(
+ 'useInputUseLocationContext must be used within a InputUseLocationProvider'
+ )
+ }
+
+ return context
+}
diff --git a/src/app/modules/input-uses/presentation/hooks/queries/input-use-location-query.hook.ts b/src/app/modules/input-uses/presentation/hooks/queries/input-use-location-query.hook.ts
new file mode 100644
index 00000000..4bbdc845
--- /dev/null
+++ b/src/app/modules/input-uses/presentation/hooks/queries/input-use-location-query.hook.ts
@@ -0,0 +1,37 @@
+import { useEffect } from 'react'
+
+import { useQuery } from '@tanstack/react-query'
+import toast from 'react-hot-toast'
+
+import { makeRemoteGetInputUseLocationUseCase } from '../../../main/factories/use-cases/input-use-locations-use-cases'
+
+type Props = {
+ id: number
+}
+
+export function useInputUseLocationQuery({ id }: Props) {
+ const getInputUseLocationUseCase = makeRemoteGetInputUseLocationUseCase()
+
+ const {
+ data: inputUseLocation,
+ isError,
+ error,
+ isLoading,
+ refetch: refetchInputUseLocation,
+ } = useQuery({
+ queryKey: ['input-use-location', id],
+ queryFn: () => getInputUseLocationUseCase.execute({ id }),
+ enabled: !!id,
+ })
+
+ useEffect(() => {
+ if (isError)
+ toast.error(error?.message ?? 'Erro ao buscar local de utilização')
+ }, [error, isError])
+
+ return {
+ inputUseLocation,
+ isLoading,
+ refetchInputUseLocation,
+ }
+}
diff --git a/src/app/modules/input-uses/presentation/hooks/queries/input-use-locations-query.hook.ts b/src/app/modules/input-uses/presentation/hooks/queries/input-use-locations-query.hook.ts
new file mode 100644
index 00000000..0bab69b4
--- /dev/null
+++ b/src/app/modules/input-uses/presentation/hooks/queries/input-use-locations-query.hook.ts
@@ -0,0 +1,51 @@
+import { useEffect } from 'react'
+
+import { useQuery } from '@tanstack/react-query'
+import toast from 'react-hot-toast'
+
+import { makeRemoteGetInputUseLocationsUseCase } from '../../../main/factories/use-cases/input-use-locations-use-cases'
+
+import type {
+ InputUseLocationFilters,
+ InputUseLocationSort,
+} from '../../types/input-use-location-types'
+
+type Props = {
+ filters: InputUseLocationFilters
+ page: number
+ sort?: InputUseLocationSort
+}
+
+export function useInputUseLocationsQuery({ filters, page, sort }: Props) {
+ const getInputUseLocationsUseCase = makeRemoteGetInputUseLocationsUseCase()
+
+ const {
+ data,
+ isError,
+ error,
+ isLoading,
+ refetch: refetchInputUseLocations,
+ } = useQuery({
+ queryKey: ['input-use-locations', { page, sort, filters }],
+ queryFn: () =>
+ getInputUseLocationsUseCase.execute({
+ pagination: { page },
+ sort,
+ filters,
+ }),
+ })
+
+ useEffect(() => {
+ if (isError)
+ toast.error(error?.message ?? 'Erro ao buscar locais de uso de insumo')
+ }, [error, isError])
+
+ return {
+ inputUseLocations: data ?? {
+ resources: [],
+ totalPages: 1,
+ },
+ isLoading,
+ refetchInputUseLocations,
+ }
+}
diff --git a/src/app/modules/input-uses/presentation/screens/input-use-locations-screen.tsx b/src/app/modules/input-uses/presentation/screens/input-use-locations-screen.tsx
new file mode 100644
index 00000000..694128c9
--- /dev/null
+++ b/src/app/modules/input-uses/presentation/screens/input-use-locations-screen.tsx
@@ -0,0 +1,65 @@
+import { Button, Input } from '@/core/presentation/components/ui'
+
+import { InputUseLocationDataTable } from '../components/input-use-location-data-table'
+import { InputUseLocationDeleteDialog } from '../components/input-use-location-delete-dialog'
+import {
+ InputUseLocationContext,
+ InputUseLocationProvider,
+} from '../contexts/input-use-location-context'
+import { InputUseLocationForm } from '../forms/input-use-location-form'
+
+export function InputUseLocationsScreen() {
+ return (
+
+
+ {({
+ selectedInputUseLocation,
+ isOpenDeleteInputUseLocationContainer,
+ isOpenNewInputUseLocationForm,
+ isOpenEditInputUseLocationForm,
+ filters,
+ handleChangeFilters,
+ openNewInputUseLocationForm,
+ }) => (
+
+ )}
+
+
+ )
+}
+
+InputUseLocationsScreen.displayName = 'InputUseLocationsScreen'
diff --git a/src/app/modules/input-uses/presentation/types/input-use-location-types.ts b/src/app/modules/input-uses/presentation/types/input-use-location-types.ts
new file mode 100644
index 00000000..402fb032
--- /dev/null
+++ b/src/app/modules/input-uses/presentation/types/input-use-location-types.ts
@@ -0,0 +1,5 @@
+import type { InputUseLocationModel } from '../../domain/models/input-use-locations-model'
+import type { Filters, Sort } from '@/core/domain/types'
+
+export type InputUseLocationFilters = Filters
+export type InputUseLocationSort = Sort
diff --git a/src/app/modules/input-uses/presentation/validations/input-use-location-form-schema.ts b/src/app/modules/input-uses/presentation/validations/input-use-location-form-schema.ts
new file mode 100644
index 00000000..43e524f9
--- /dev/null
+++ b/src/app/modules/input-uses/presentation/validations/input-use-location-form-schema.ts
@@ -0,0 +1,12 @@
+import { z } from 'zod'
+
+export const inputUseLocationFormSchema = z.object({
+ description: z
+ .string()
+ .min(3, 'A descrição deve ter no mínimo 3 caracteres')
+ .max(255, 'A descrição deve ter no máximo 255 caracteres'),
+})
+
+export type InputUseLocationFormSchema = z.infer<
+ typeof inputUseLocationFormSchema
+>
From 5a80f1e80645406088957738df7230995c19d010 Mon Sep 17 00:00:00 2001
From: Guilherme Minozzi
Date: Tue, 7 Apr 2026 10:46:00 -0300
Subject: [PATCH 7/8] feat: register input use locations module
---
src/app/pages/general-registrations-page.tsx | 43 +++++++++++++++-----
src/core/mocks/browser.ts | 13 ++++++
2 files changed, 46 insertions(+), 10 deletions(-)
diff --git a/src/app/pages/general-registrations-page.tsx b/src/app/pages/general-registrations-page.tsx
index 7a759dcf..ff88017a 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 { InputUseLocationsScreen } from '../modules/input-uses/presentation/screens/input-use-locations-screen'
type Tab =
| {
@@ -48,12 +49,32 @@ export function GeneralRegistrationsPage() {
},
],
},
+ {
+ key: 'input-uses',
+ name: 'Utilização de Insumos',
+ subTabs: [
+ {
+ key: 'input-use-locations',
+ name: 'Locais de Utilização',
+ component: ,
+ },
+ ],
+ },
],
[]
)
- const [activeTab, setActiveTab] = useState('general-registrations')
- const [activeSubTab, setActiveSubTab] = useState('general-cultivations')
+ const getFirstSubTabKey = useCallback(
+ (tabKey: string) => {
+ return tabs.find((tab) => tab.key === tabKey)?.subTabs?.[0]?.key ?? ''
+ },
+ [tabs]
+ )
+
+ const [activeTab, setActiveTab] = useState(() => tabs[0]?.key ?? '')
+ const [activeSubTab, setActiveSubTab] = useState(() =>
+ getFirstSubTabKey(tabs[0]?.key ?? '')
+ )
const subTabs = useMemo(() => {
const tab = tabs.find((tab) => tab.key === activeTab)
@@ -68,9 +89,13 @@ export function GeneralRegistrationsPage() {
return subTabs.find((subTab) => subTab.key === activeSubTab)
}, [activeSubTab, subTabs])
- const handleTabChange = useCallback((tab: string) => {
- setActiveTab(tab)
- }, [])
+ const handleTabChange = useCallback(
+ (tab: string) => {
+ setActiveTab(tab)
+ setActiveSubTab(getFirstSubTabKey(tab))
+ },
+ [getFirstSubTabKey]
+ )
const handleSubTabChange = useCallback((subTab: string) => {
setActiveSubTab(subTab)
@@ -87,7 +112,7 @@ export function GeneralRegistrationsPage() {
-
+
{tabs.map((tab) => (
@@ -127,11 +152,9 @@ export function GeneralRegistrationsPage() {
- setActiveTab('general-registrations')
- }
+ onClick={() => handleTabChange(activeTab)}
>
- Vegetais
+ {tab?.name}
diff --git a/src/core/mocks/browser.ts b/src/core/mocks/browser.ts
index 603ea6cf..b7275544 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 {
+ createInputUseLocationHandler,
+ deleteInputUseLocationHandler,
+ getInputUseLocationHandler,
+ getInputUseLocationsHandler,
+ updateInputUseLocationHandler,
+} from '@/app/modules/input-uses/mocks/handlers/input-use-locations-handlers'
import {
createMachineHandler,
deleteMachineHandler,
@@ -295,6 +302,12 @@ const handlers: HttpHandler[] = [
getCultivationPestsHandler,
updateCultivationPestHandler,
+ createInputUseLocationHandler,
+ deleteInputUseLocationHandler,
+ getInputUseLocationHandler,
+ getInputUseLocationsHandler,
+ updateInputUseLocationHandler,
+
createNutritionalBalancingHandler,
deleteNutritionalBalancingHandler,
getLastVisitNutritionalBalancingsHandler,
From 0982625f5f54fc845b7f6c8fc96d990f5a20439d Mon Sep 17 00:00:00 2001
From: Guilherme Minozzi
Date: Tue, 7 Apr 2026 10:46:21 -0300
Subject: [PATCH 8/8] feat: add idr web module generator agent skill
---
.../idr-web-module-generator.skill | Bin 0 -> 2600 bytes
.../idr-web-module-generator/SKILL.md | 114 ++++++++++++++++++
2 files changed, 114 insertions(+)
create mode 100644 .agents/skills/idr-web-module-generator/idr-web-module-generator.skill
create mode 100644 .agents/skills/idr-web-module-generator/idr-web-module-generator/SKILL.md
diff --git a/.agents/skills/idr-web-module-generator/idr-web-module-generator.skill b/.agents/skills/idr-web-module-generator/idr-web-module-generator.skill
new file mode 100644
index 0000000000000000000000000000000000000000..48abcd49509d06cc7ba7a0d7d6f52a9d70f9113c
GIT binary patch
literal 2600
zcmZ|RcQhM}769;QQ2SGqs8O|7iRj|lR72DrpT;JLk{YEpr9Lf++Tycf#Asv3-lM1$
zHQFFrHJTWqL{aK%-}~d8^Uitqo_p>+_uTvc&lCov=K%l!OaNQ_Uv@Q`nUgWh0KgZ}
z?~Ne90%l-jq~PObWyuTxp18~)e^+2ID}avvkPZO&_h;Fk3;qJFmm)T7idGHpTJF{7
zxjFucn}MWxH;mQ`*<*;!bRJq<1Ya<#lO6$n{Qd8pp#6qg|Ifvs?2=?
zi#t|bXEl<8HDgrL6CVaXDyMYZXft;$RP??P;@x3(jZ!t$#RLrGOG;oBunew6R9drI
z$Os7=6=TxR#z-i2M3v{4Hmv&*qpLc@+ld;gLPqsSSc_oB8>bm>yNb>qxADS{Pws?n
zEV-W=a_ssA>`ZLWWiDEo!9ilM+SEMEUYq3~nefc=9D%iQ>Py-RrLhjN2efn8h29tC
z;PK@k-n&$CCm1Da8X#YDX=T`q3)-XWkzU<^U
zR>>$A;1J@)xUX%3n0F^*jy+eLz{73c5fU)IYE`D+Do8w&w!Gh{3x))^=KPrBb6txi=V8Vh3}KS`tIpOao=Am#QiwPo*(C2
zGT!*vq~@)pX6rT33He;Q09%CavDJclu9_<5_^V^KNtJ~cDLQ?7xhrps8$Rt9<2H*w
zu=i`GU3sN<=8;n|HE26I$SRl%t<$WTjEx0Bn(F%&g}B?EE!n}17QfAvJLHI}xp
zXLK3r#d413b;#jQzGrvbsdUZw!89UCwk2Fzqa@j!DamQ>TzA#0o$gp#8Qm6)U`%$4
zRC7hG>=(|;c)sbfE)TnNsWcKrX_1d#K3KGtSCjBh2X}{8<;Uxpap{&{+f1>p9K{|S7?GF)9NGOhX}7PPrP?cyNm_4k?TSaIN1FXuF`
z{f<@{!#Y9+RdBQTJoIRKV7??rvP*dE#@=8~7kE>;2=F!N^Q-3}+@i)l9ZyW@Y$|i{
z66656=<}(&D>RAm5imU+nur0iPOMS{N&QAmH{IF!ndz!ifqr}OCw}cb*yaA^wN2l6wi}JY
zQywJQ%k_?zl%ZUhm$Z-!TX`#G-DXzV`4*#y70$i@tgc|~7A#NnveRw?aI<6^d7Y}|
zkSbT3EEzRYKD1_71N`1MQ2f%GRRsxcL%u*ILMh&}M+`~m
zAJD+XD_y3c+Sx%Bb&E{F7IY-30Z-dOw<>!Gz;t@8zs}%!lZG#NivD@l^^~s4A$|41coS4w3H~SIT&QDW1
zgIfHBxeh3Ce@TW}ro2qHZXVgaSgDhfb>
zxG;1z5+cq_U)Q>eQ&_BOgII}esT4y!qx#_ab{f6WG^tpi2Q!G2b$Zz*?l^A~d_yG6
z*%|8ST>Qm9YjO403W!jv_GxpT!~@cgEV6Hbn?U=555x@q`sXRmp=TUtXjQ}
zKdds)!G0w!6hOV3I}H+j(qyNCrAF#FRPT1eJ^dT;+7mtVdWZRqTUx}lX2&F%n3K4s
z^oPq0vnjm8&c^8hJ!FA{~-0$O7V+{dcl?R%380I`4zmb)P#K>5AEXPx<0DHtXC0F)?lrlfL?ZbG-5^Kk#Bp1~7bo=MsX1
zCC+k44Kr?t+G=nIMMACwEU0Pn^9{a0-%=`H*VCP7oRr~{1<3m;m9R-
zl#A89eI?Wo3;iF`5oTEhk7Lki{!V&aN2U$QYuw`^&UCzn@@^h}2vE{l{G4whvaOLI
zDg$4=6^W9
zdEAAs-i2hOB~(l=kjOdl-@^8BA1;6TXrRBK<5!^^r-2nud)_qTb+tUerUn7A7}!zQ
zGh8lcG5valfHMq{=ecV(BNQv2LWF;l-E)6WhrO4adsCt_FJQqtPR*OS6XdLzGD6uX
zj6C69$4sRnlc?SN`xeZei7@@ra|Vt*8`beFINBkNA0JaM-W1kVSy8K4m+BIPp0(;U
z5-Z6qo4y}eGPfOxEhNhYGLR>*I@=!x1W~+v3$SDf%i1UwpvOXp^FVoLUfQyAR^Vc`E$+%L-gieFj-ls^4E{nvC1|1sVFc+M2Y!1())?pGTB>I1(#
H2mt&8N^1Jl
literal 0
HcmV?d00001
diff --git a/.agents/skills/idr-web-module-generator/idr-web-module-generator/SKILL.md b/.agents/skills/idr-web-module-generator/idr-web-module-generator/SKILL.md
new file mode 100644
index 00000000..2806b96f
--- /dev/null
+++ b/.agents/skills/idr-web-module-generator/idr-web-module-generator/SKILL.md
@@ -0,0 +1,114 @@
+---
+name: idr-web-module-generator
+description: Guide for creating a new module from scratch following IDR Web Clean Architecture standards. Use this skill when asked to create a new module, entity, feature, or CRUD operations in the IDR Web project.
+---
+
+# IDR Web Module Generator
+
+This skill contains the procedural knowledge required to build a new module from scratch in the IDR Web project. It ensures that the generated code strictly follows the project's Clean Architecture conventions and specific module structures.
+
+## Core Mandates
+
+The IDR Web project strictly follows a layered Clean Architecture structure. Every module typically consists of these layers:
+- `domain`: Models, Types, and Use Cases interfaces.
+- `data`: Implementations of domain use cases (e.g., `RemoteGet...`).
+- `main`: Factories that instantiate the use cases to be consumed by the UI.
+- `presentation`: UI components (Screens, Components, Forms, Contexts, Hooks, Types, Validations).
+- `mocks`: Mock Service Worker handlers for local development.
+
+## Step-by-Step Workflow
+
+When asked to create a new module (e.g., "machines"), follow these steps precisely:
+
+### 1. Domain Layer (`src/app/modules/{module-name}/domain`)
+- Create `models/{module-name}-model.ts` with `WithId` type.
+- Create `use-cases/{module-name}-use-cases/` directory.
+- Define `RequestInterface` for all CRUD operations:
+ - `create-{entity}-use-case.ts`
+ - `update-{entity}-use-case.ts`
+ - `delete-{entity}-use-case.ts`
+ - `get-{entity}-use-case.ts` (Get One)
+ - `get-{entities}-use-case.ts` (Get All/Paginated list)
+- Export everything via `index.ts`.
+
+### 2. Data Layer (`src/app/modules/{module-name}/data`)
+- Create `use-cases/{module-name}-use-cases/` directory.
+- Implement the domain interfaces using `HttpClient` from `@/core/data/protocols/http`:
+ - `remote-create-{entity}-use-case.ts`
+ - `remote-update-{entity}-use-case.ts`
+ - `remote-delete-{entity}-use-case.ts`
+ - `remote-get-{entity}-use-case.ts`
+ - `remote-get-{entities}-use-case.ts`
+- Throw custom errors on failure (e.g., `BadRequestError`, `ForbiddenError`, `UnexpectedError`).
+- Export everything via `index.ts`.
+
+### 3. Main Layer (`src/app/modules/{module-name}/main`)
+- Create `factories/use-cases/{module-name}-use-cases/` directory.
+- Create factory functions utilizing `makeApiHttpClient()` to instantiate the Data layer classes:
+ - `remote-create-{entity}-use-case-factory.ts`
+ - `remote-update-{entity}-use-case-factory.ts`
+ - `remote-delete-{entity}-use-case-factory.ts`
+ - `remote-get-{entity}-use-case-factory.ts`
+ - `remote-get-{entities}-use-case-factory.ts`
+- Export everything via `index.ts`.
+
+### 4. Mocks Layer (`src/app/modules/{module-name}/mocks`)
+- Create `handlers/{module-name}-handlers/` directory.
+- Implement MSW handlers for all operations:
+ - `create-{entity}-handler.ts`
+ - `update-{entity}-handler.ts`
+ - `delete-{entity}-handler.ts`
+ - `get-{entity}-handler.ts`
+ - `get-{entities}-handler.ts` (Implement pagination and sorting logic from `mock/utils`)
+- Export handlers via `index.ts`.
+- Important: Register the new handlers inside `src/core/mocks/browser.ts`.
+
+### 5. Presentation Layer (`src/app/modules/{module-name}/presentation`)
+This layer requires a highly specific file organization:
+
+#### Validations and Types
+- Create `validations/{entity}-form-schema.ts` with `zod`.
+- Create `types/{entity}-types.ts` defining `Filters` and `Sort` types.
+
+#### Queries Hooks
+- Create `hooks/queries/{entities}-query.hook.ts` using `@tanstack/react-query` to fetch lists. Handle `toast.error` for errors.
+- Create `hooks/queries/{entity}-query.hook.ts` for fetching single items.
+- Always use the factories from the `main` layer.
+
+#### Context API
+- Create `contexts/{entity}-context.tsx` controlling:
+ - Form open/close states (New and Edit).
+ - Delete dialog open/close states.
+ - Selected item state.
+ - Filter and sort states.
+- Create `hooks/{entity}-context.hook.ts` to consume the context securely.
+
+#### UI Components
+- **Data Table**: `components/{entity}-data-table/`
+ - Implement a hook (`.hook.tsx`) mapping columns and returning `react-table` configuration.
+ - Include an action column with a `DropdownMenu` for "Editar" and "Excluir".
+ - Implement the table (`.tsx`) using the generic ` ` component from `@/core/presentation/components/ui`.
+- **Delete Dialog**: `components/{entity}-delete-dialog/`
+ - Implement `.tsx` with ` ` and a `useMutation` calling the delete factory.
+ - Invalidates react-query cache on success.
+- **Forms**: `forms/{entity}-form/`
+ - Needs 5 files:
+ - `create-{entity}-form.tsx` (using ` `)
+ - `edit-{entity}-form.tsx` (fetches the item via Get One hook, displays ` ` while fetching)
+ - `{entity}-form-inputs.tsx` (UI inputs mapped to `react-hook-form` via `useFormContext`)
+ - `{entity}-initial-form-data.ts` (Empty initial data object)
+ - `{entity}-form.tsx` (Wrapper rendering Create or Edit based on `id` prop presence).
+
+#### Screens
+- Create `screens/{entities}-screen.tsx` which glues everything together:
+ - Wraps content in `{Entity}Provider` and `{Entity}Context.Consumer`.
+ - Header with a "Add" button and a search ` `.
+ - Renders `<{Entity}DataTable />`.
+ - Conditionally renders `<{Entity}DeleteDialog />` and `<{Entity}Form />`.
+
+## Example and Reference Check
+**CRITICAL MANDATE:** To guarantee that the generated code has the exact same conditionals, React hooks usage, types, and architectural structure as the rest of the project, you **MUST ALWAYS** read the files in the `src/app/modules/input-uses` or `src/app/modules/cultivations` modules before writing any code.
+
+**DO NOT** rely on your general knowledge to generate the file contents. You must treat the existing modules as strict templates. For every single file you create (e.g., the Delete Dialog, the Create Form, the Data Table, the Use Cases), you must read its equivalent in the reference module, copy its exact implementation logic, and adapt only the entity names, variables, and specific domain fields. This is the only way to ensure 100% fidelity to the project's internal patterns.
+
+**Once the files are created:** Always run formatting (`pnpm format:fix`) and linting (`pnpm lint:fix`) for the newly created module folder, and run `pnpm type:check` to ensure no errors were introduced.