Frontend
Estrutura de Pastas
Como o código é organizado dentro de src/.
Visão geral
src/
├── app/ # rotas (Next.js App Router)
│ ├── (public)/ # login, telas de erro
│ └── (private)/ # área logada, com Sidebar/Header
├── features/ # um diretório por domínio de negócio
└── shared/ # componentes, hooks e utils reutilizáveis entre featuresAnatomia de uma feature
Cada domínio (candidates, vacancy, client, user, role, trash, ...) segue a mesma convenção:
src/features/<feature>/
├── <feature>.types.ts # interfaces (ex: ICandidate, ICandidateFilters)
├── constants/
│ └── <feature>.keys.ts # query key factory do TanStack Query
├── services/
│ └── <feature>Service.ts # chamadas axios cruas (api.get/post/put/delete)
├── hooks/
│ ├── useList<Feature>.ts # useApiQuery
│ ├── useCreate<Feature>.ts # useApiMutation
│ └── useForceDelete<Feature>.ts # etc.
├── components/
│ └── <Feature>Table.tsx # usa ScreenTable
└── pages/
└── <Feature>Page.tsx # componente de página, importado pela rotaA rota em src/app/(private)/<feature>/page.tsx é só um wrapper fino que importa a página da feature:
// src/app/(private)/users/page.tsx
import { UserPage } from '@/features/user/pages/UserPage';
export default function AppUsersPage() {
return <UserPage />;
}Query key factory
Todas as features seguem o mesmo padrão hierárquico de chaves do TanStack Query:
export const candidateQueryKeys = {
all: ['candidates'] as const,
lists: () => [...candidateQueryKeys.all, 'list'] as const,
list: (params: object) => [...candidateQueryKeys.lists(), params] as const,
details: () => [...candidateQueryKeys.all, 'detail'] as const,
detail: (id: string) => [...candidateQueryKeys.details(), id] as const,
};Isso permite invalidar por granularidade (lists() invalida todas as listagens; detail(id) invalida só um
registro).