Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,14 @@ This layer requires a highly specific file organization:
- 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.
**CRITICAL MANDATE:** To guarantee that the generated code has the exact same conditionals, React hooks usage, types, error messages in use-cases, 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.
**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, specific domain fields, and **Custom Error Messages**. This is the only way to ensure 100% fidelity to the project's internal patterns. Pay special attention to change strings like "Local de Uso" or "Praga" in exceptions like `NotFoundError("...")` or `ForbiddenError("Você não tem permissão para...")` to match the newly created entity context.

**Presentation Custom Messages:** You must also strictly adapt all success and error toasts, dialog titles, form titles, and action button labels present in the UI layers (`presentation/components`, `presentation/forms`, `presentation/screens`, `presentation/hooks/queries`) to the new entity name. For example, replace `toast.success('Local de utilização removido com sucesso')` with the appropriate label for the new entity. **Pay special attention to `useEffect` error toasts in query hooks** (`presentation/hooks/queries/{entity}-query.hook.ts`).

**Form Inputs and Placeholders:** Inside `presentation/forms/{entity}-form-inputs.tsx`, you must adapt all `<Form.Label>` and `<Input placeholder="..." />` to match the new entity's fields and context. Avoid leaving generic labels like "Descrição" if the field is "Nome", and update examples in placeholders (e.g., from "Ex: Galpão" to "Ex: Fertilizantes").

**Constants and Variables:** All constant names, like `INITIAL_FORM_DATA` or `FORM_SCHEMA`, should also be renamed to match the new entity prefix (e.g., `PRODUCT_CATEGORY_INITIAL_FORM_DATA`). Ensure that all imports of these constants are also updated accordingly.

**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.
2 changes: 1 addition & 1 deletion scripts/seed/data/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export * from './animal-inseminations.mjs'
export * from './animal-purchases.mjs'
export * from './animal-sales.mjs'
export * from './active-ingredients.mjs'
export * from './product-categories.mjs'
export * from './input-use-product-categories.mjs'
export * from './products.mjs'
export * from './animal-medications.mjs'
export * from './animal-mastitides.mjs'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { faker } from '@faker-js/faker/locale/pt_BR'

export const allProductCategoriesData = Array.from(
export const inputUseProductCategoriesData = Array.from(
{
length: faker.number.int({
min: 50,
max: 150,
min: 10,
max: 50,
}),
},
(_, index) => ({
id: index + 1,
description: faker.commerce.department(),
name: faker.commerce.department(),
})
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export * from './remote-create-input-use-product-category-use-case'
export * from './remote-update-input-use-product-category-use-case'
export * from './remote-delete-input-use-product-category-use-case'
export * from './remote-get-input-use-product-category-use-case'
export * from './remote-get-input-use-product-categories-use-case'
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { type HttpClient, HttpStatusCode } from '@/core/data/protocols/http'
import {
BadRequestError,
ForbiddenError,
UnexpectedError,
} from '@/core/domain/errors'

import type { CreateInputUseProductCategoryUseCase } from '../../../domain/use-cases/input-use-product-categories-use-cases'

export class RemoteCreateInputUseProductCategoryUseCase
implements CreateInputUseProductCategoryUseCase
{
constructor(
private readonly url: string,
private readonly httpClient: HttpClient
) {}

execute: CreateInputUseProductCategoryUseCase['execute'] = async ({
inputUseProductCategory,
}) => {
const { statusCode } = await this.httpClient.request({
url: this.url,
method: 'post',
body: inputUseProductCategory,
})

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 uma categoria de produto.'
)
}

throw new UnexpectedError()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { type HttpClient, HttpStatusCode } from '@/core/data/protocols/http'
import {
UnexpectedError,
NotFoundError,
ForbiddenError,
} from '@/core/domain/errors'

import type { DeleteInputUseProductCategoryUseCase } from '../../../domain/use-cases/input-use-product-categories-use-cases'

export class RemoteDeleteInputUseProductCategoryUseCase
implements DeleteInputUseProductCategoryUseCase
{
constructor(
private readonly url: string,
private readonly httpClient: HttpClient
) {}

execute: DeleteInputUseProductCategoryUseCase['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('Categoria de Produto')
}

if (statusCode === HttpStatusCode.forbidden) {
throw new ForbiddenError(
'Você não tem permissão para excluir uma categoria de produto.'
)
}

throw new UnexpectedError()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { type HttpClient, HttpStatusCode } from '@/core/data/protocols/http'
import {
UnexpectedError,
NotFoundError,
ForbiddenError,
} from '@/core/domain/errors'

import type {
InputUseProductCategoryApiResponse,
InputUseProductCategoryModel,
} from '../../../domain/models/input-use-product-categories-model'
import type { GetInputUseProductCategoriesUseCase } from '../../../domain/use-cases/input-use-product-categories-use-cases'
import type { ListApiResponse, MapApiProperties } from '@/core/domain/types'

export class RemoteGetInputUseProductCategoriesUseCase
implements GetInputUseProductCategoriesUseCase
{
constructor(
private readonly url: string,
private readonly httpClient: HttpClient<
InputUseProductCategoryModel,
InputUseProductCategoryApiResponse,
ListApiResponse<InputUseProductCategoryApiResponse[]>
>
) {}

execute: GetInputUseProductCategoriesUseCase['execute'] = async ({
filters,
pagination,
sort,
}) => {
const mapApiProperties: MapApiProperties<
InputUseProductCategoryModel,
InputUseProductCategoryApiResponse
> = {
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('Categoria de Produto')
}

if (statusCode === HttpStatusCode.forbidden) {
throw new ForbiddenError(
'Você não tem permissão para buscar as categorias de produtos.'
)
}

throw new UnexpectedError()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { type HttpClient, HttpStatusCode } from '@/core/data/protocols/http'
import {
BadRequestError,
ForbiddenError,
NotFoundError,
UnexpectedError,
} from '@/core/domain/errors'

import type {
InputUseProductCategoryDetailsApiResponse,
InputUseProductCategoryDetailsModel,
} from '../../../domain/models/input-use-product-categories-model'
import type { GetInputUseProductCategoryUseCase } from '../../../domain/use-cases/input-use-product-categories-use-cases'

export class RemoteGetInputUseProductCategoryUseCase
implements GetInputUseProductCategoryUseCase
{
constructor(
private readonly url: string,
private readonly httpClient: HttpClient<
InputUseProductCategoryDetailsModel,
InputUseProductCategoryDetailsApiResponse
>
) {}

execute: GetInputUseProductCategoryUseCase['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('Categoria de Produto')

if (statusCode === HttpStatusCode.forbidden) {
throw new ForbiddenError(
'Você não tem permissão para acessar os dados desta categoria de produto.'
)
}

throw new UnexpectedError()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { type HttpClient, HttpStatusCode } from '@/core/data/protocols/http'
import {
BadRequestError,
ForbiddenError,
UnexpectedError,
} from '@/core/domain/errors'

import type { UpdateInputUseProductCategoryUseCase } from '../../../domain/use-cases/input-use-product-categories-use-cases'

export class RemoteUpdateInputUseProductCategoryUseCase
implements UpdateInputUseProductCategoryUseCase
{
constructor(
private readonly url: string,
private readonly httpClient: HttpClient
) {}

execute: UpdateInputUseProductCategoryUseCase['execute'] = async ({
inputUseProductCategory: { id, ...inputUseProductCategory },
}) => {
const { statusCode } = await this.httpClient.request({
url: `${this.url}/${id}`,
method: 'patch',
body: inputUseProductCategory,
})

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 uma categoria de produto.'
)
}

throw new UnexpectedError()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { WithId } from '@/core/domain/types'

export type InputUseProductCategoryDetailsModel = {
name: string
}

export type InputUseProductCategoryDetailsApiResponse = {
name: string
}

export type InputUseProductCategoryModel = WithId<{
name: string
}>

export type InputUseProductCategoryApiResponse = WithId<{
name: string
}>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { InputUseProductCategoryDetailsModel } from '../../models/input-use-product-categories-model'
import type { RequestInterface } from '@/core/domain/types'

export type CreateInputUseProductCategoryUseCase = RequestInterface<
{
inputUseProductCategory: InputUseProductCategoryDetailsModel
},
void
>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { RequestInterface } from '@/core/domain/types'

export type DeleteInputUseProductCategoryUseCase = RequestInterface<
{
id: number
},
void
>
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { InputUseProductCategoryModel } from '../../models/input-use-product-categories-model'
import type {
RequestInterface,
ListParams,
ListResponse,
} from '@/core/domain/types'

export type GetInputUseProductCategoriesUseCase = RequestInterface<
ListParams<InputUseProductCategoryModel>,
ListResponse<InputUseProductCategoryModel>
>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { InputUseProductCategoryDetailsModel } from '../../models/input-use-product-categories-model'
import type { RequestInterface } from '@/core/domain/types'

export type GetInputUseProductCategoryUseCase = RequestInterface<
{
id: number
},
InputUseProductCategoryDetailsModel
>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export * from './create-input-use-product-category-use-case'
export * from './delete-input-use-product-category-use-case'
export * from './get-input-use-product-category-use-case'
export * from './get-input-use-product-categories-use-case'
export * from './update-input-use-product-category-use-case'
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { InputUseProductCategoryDetailsModel } from '../../models/input-use-product-categories-model'
import type { RequestInterface, WithId } from '@/core/domain/types'

export type UpdateInputUseProductCategoryUseCase = RequestInterface<
{
inputUseProductCategory: WithId<InputUseProductCategoryDetailsModel>
},
void
>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export * from './remote-create-input-use-product-category-use-case-factory'
export * from './remote-update-input-use-product-category-use-case-factory'
export * from './remote-delete-input-use-product-category-use-case-factory'
export * from './remote-get-input-use-product-category-use-case-factory'
export * from './remote-get-input-use-product-categories-use-case-factory'
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { makeApiHttpClient } from '@/core/main/factories/http'

import { RemoteCreateInputUseProductCategoryUseCase } from '../../../../data/use-cases/input-use-product-categories-use-cases'

import type { CreateInputUseProductCategoryUseCase } from '../../../../domain/use-cases/input-use-product-categories-use-cases'

export function makeRemoteCreateInputUseProductCategoryUseCase(): CreateInputUseProductCategoryUseCase {
return new RemoteCreateInputUseProductCategoryUseCase(
'/input-use-product-categories',
makeApiHttpClient()
)
}
Loading
Loading