AI Skill Report Card

Generating Select Components

A-85·May 7, 2026·Source: Web

Generate Select Components

15 / 15

Provide entity name in kebab-case (e.g., banco, centro-custo, sindicato) and I'll generate:

  1. src/models/selects/select-<entity>.ts — Entity-specific model
  2. src/components/selects/select-<entity>.tsx — Select component

Example:

Entity: sindicato
Controller: getSindicatosController
Endpoint: /api/pessoal/sindicatos/
Recommendation
Reduce verbosity in templates - they're quite long for a skill that assumes Claude is smart

I'll check for:

  1. Attachments: typings.d.ts or files with entity names
  2. Editor context: Current file selection (e.g., Planejamento.Cadastro.Area)
  3. User message: Entity name in request

If unclear, I'll ask for:

  • Entity name (kebab-case)
  • Controller function and import path
  • API endpoint
  • Label mapping (r.label or composite)
TypeScript
import { defaultPaginationValues } from '@/constants/pagination'; import useLoading from '@/hooks/use-loading'; import { getUpdateSelectOptions } from '@/utils/get-update-select-options'; import { message } from 'antd'; import { useState } from 'react'; import { get<Entity>Controller } from '<controller-path>'; export default () => { const [options, setOptions] = useState<App.Pagination<EntityType>>( defaultPaginationValues, ); const [queryStringOptionsParams, setQueryStringOptionsParams] = useState<App.QueryStringParams>({ page: 1, page_size: 10, order_by: '<order-field>', }); const { loading, startLoading, stopLoading } = useLoading(); const addMissedOptions = async (ids: number[]) => { try { startLoading(); const params: App.QueryStringParams = { ...queryStringOptionsParams, page: 1, page_size: 1000, id: ids, }; const response = await get<Entity>Controller(params); const { data } = response; setOptions((previousData) => { const results: any[] = getUpdateSelectOptions( data.results, previousData.results, ); return { ...previousData, results }; }); } catch (error) { message.error('Não foi possível obter os dados'); } finally { stopLoading(); } }; const getOptions = async (...args: Parameters<typeof get<Entity>Controller>) => { try { startLoading(); const response = await get<Entity>Controller(...args); const { data } = response; setOptions((previousData) => { const results: any[] = getUpdateSelectOptions( data.results, previousData.results, ); return { ...data, results }; }); } catch (error) { message.error('Não foi possível obter os dados'); return Promise.reject(); } finally { stopLoading(); } }; const getOptionsNewPage = async () => { try { startLoading(); if (!(queryStringOptionsParams.page + 1 <= options.total_pages)) return; const params: App.QueryStringParams = { ...queryStringOptionsParams, page: queryStringOptionsParams.page + 1, }; const response = await get<Entity>Controller(params); const { data } = response; setOptions((previousData) => { const results: any[] = getUpdateSelectOptions( data.results, previousData.results, ); return { ...data, results }; }); setQueryStringOptionsParams(params); } catch (error) { message.error('Não foi possível obter os dados'); } finally { stopLoading(); } }; return { selectLoading: loading, addMissedOptions, options, getOptions, getOptionsNewPage, queryStringOptionsParams, }; };
TypeScript
import { defaultPaginationValues } from '@/constants/pagination'; import useLoading from '@/hooks/use-loading'; import { getSelectController } from '@/services/select.controller'; import { getUpdateSelectOptions } from '@/utils/get-update-select-options'; import { useModel } from '@umijs/max'; import { Select, SelectProps } from 'antd'; import { message } from 'antd/lib'; import { debounce } from 'lodash'; import { useEffect, useMemo, useState } from 'react'; interface Props { value?: any; onChange?: (val: any, option: any) => void; multiple?: boolean; allowClear?: boolean; style?: any; placeholder?: string; disabled?: boolean; labelInValue?: boolean; isInstanceForColumnFilter?: boolean; } export default (props: Props) => { const [needFillMissedOptions, setNeedFillMissedOptions] = useState<boolean>(true); const [searchValue, setSearchValue] = useState<string>(); const [isSearching, setIsSearching] = useState<boolean>(false); const [searchedOptions, setSearchedOptions] = useState<App.Pagination<EntityType>>( defaultPaginationValues, ); const { selectLoading, options, getOptions, getOptionsNewPage, queryStringOptionsParams, addMissedOptions, } = useModel('selects.select-<entity>'); const debouncedSearch = useMemo( () => debounce((value: string) => { if (value) { setIsSearching(true); const params = { reset: true, params: { page: 1, page_size: 10, order_by: '<order-field>', search: value }, }; getSearchedOptions(params.reset, '<endpoint>', params.params); } else { setIsSearching(false); } }, 700), [], ); const optionsMemo = useMemo(() => { return (options.results || []).map((r: any) => ({ label: <label-mapping>, // r.label or `${r.codigo} - ${r.nome}` value: r.id, })); }, [options.results]); // ... search, scroll, and option filling logic return ( <Select searchValue={searchValue} onSearch={(value) => { setSearchValue(value); debouncedSearch(value); }} options={isSearching ? optionsSearched : optionsMemo} placeholder={props.placeholder || 'Selecione'} showSearch optionFilterProp="label" onPopupScroll={isSearching ? handleScrollSearched : handleScroll} loading={selectLoading || loading} value={props.value} onChange={props.onChange} mode={props.multiple ? 'multiple' : undefined} allowClear={props.allowClear} maxTagCount={3} disabled={props.disabled} labelInValue={props.labelInValue} {...columnFilterProps} /> ); };

Model Placeholders:

  • get<Entity>Controller → Real controller name (e.g., getSindicatosController)
  • <controller-path> → Import path
  • <order-field> → Sort field ('label' or 'codigo')
  • EntityType → TypeScript type from typings.d.ts

Component Placeholders:

  • 'selects.select-<entity>' → Model key (e.g., 'selects.select-sindicato')
  • '<endpoint>' → API endpoint (e.g., '/api/pessoal/sindicatos/')
  • <label-mapping> → Simple: r.label | Composite: `${r.codigo} - ${r.nome}`
18 / 20

Simple label:

Entity: sindicato
Model: src/models/selects/select-sindicato.ts
Component: src/components/selects/select-sindicato.tsx
Controller: getSindicatosController
Endpoint: '/api/pessoal/sindicatos/'
Order: 'label'
Label: r.label

Composite label:

Entity: colaborador  
Controller: getColaboradoresEquipeController
Endpoint: '/api/pessoal/colaboradores/equipe/'
Order: 'codigo'
Label: `${r.codigo} - ${r.nome}`
Recommendation
The workflow section could be clearer about the step-by-step process rather than jumping between identification and templates
  • Each entity has its own model and component
  • Remove select logic from main entity models
  • Use addMissedOptions for pre-selected values
  • Implement infinite scroll and debounced search
  • Support labelInValue for column filters
  • Handle dependencies with useUpdateEffect
  • Don't share models between entities
  • Don't duplicate select logic in entity models
  • Always implement missing option filling
  • Remember to replace all placeholders
  • Test with pre-selected values and search functionality
0
Grade A-AI Skill Framework
Scorecard
Criteria Breakdown
Quick Start
15/15
Workflow
14/15
Examples
18/20
Completeness
20/20
Format
15/15
Conciseness
12/15