AI Skill Report Card
Generating Ant Select Components
Quick Start
Provide the entity name in kebab-case (e.g., banco, centro-custo, sindicato) and I'll generate both the model and component files with all required functionality.
Workflow
-
Entity Identification
- Check attachments for
typings.d.tsor entity definitions - Analyze editor context for namespace like
Planejamento.Cadastro.Area - Extract entity name from user request
- Check attachments for
-
Generate Model (
src/models/selects/select-<entidade>.ts)- Import specific controller for the entity
- Set appropriate
order_byfield - Include
addMissedOptions,getOptions,getOptionsNewPage
-
Generate Component (
src/components/selects/select-<entidade>.tsx)- Connect to entity-specific model via
useModel('selects.select-<entidade>') - Configure API endpoint for search
- Set label mapping (simple
r.labelor composite)
- Connect to entity-specific model via
Progress:
- Identify entity name and controller
- Generate model file
- Generate component file
- Verify all placeholders replaced
Examples
Example 1: Input: "Create select for sindicato entity" Output:
TypeScript// src/models/selects/select-sindicato.ts import { getSindicatosController } from '@/services/global/sindicato.controller'; // ... (complete model with getSindicatosController) // src/components/selects/select-sindicato.tsx const { ... } = useModel('selects.select-sindicato'); // ... (complete component with '/api/pessoal/sindicatos/')
Example 2: Input: "Generate select for colaborador with composite label" Output:
TypeScript// Label mapping in component: label: `${r.codigo} - ${r.nome}` // order_by: 'codigo' instead of 'label'
Model Template
TypeScriptimport { 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<any>>(defaultPaginationValues); const [queryStringOptionsParams, setQueryStringOptionsParams] = useState<App.QueryStringParams>({ page: 1, page_size: 10, order_by: 'label', // or 'codigo' for composite labels }); 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(); } }; // ... getOptions and getOptionsNewPage methods return { selectLoading: loading, addMissedOptions, options, getOptions, getOptionsNewPage, queryStringOptionsParams, }; };
Component Template
TypeScriptimport { useModel } from '@umijs/max'; import { Select, SelectProps } from 'antd'; 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 { selectLoading, options, getOptions, getOptionsNewPage, queryStringOptionsParams, addMissedOptions, } = useModel('selects.select-<entity>'); // Debounced search with getSelectController('<API_ENDPOINT>', params) const debouncedSearch = useMemo(() => debounce((value: string) => { if (value) { setIsSearching(true); // Search logic with API endpoint } else { setIsSearching(false); } }, 700), []); // Options mapping: r.label or `${r.codigo} - ${r.nome}` const optionsMemo = useMemo(() => { return (options.results || []).map((r: any) => ({ label: r.label, // or composite label value: r.id, })); }, [options.results]); // Fill missing options on mount useEffect(() => { // addMissedOptions logic for pre-filled values }, [value]); return ( <Select searchValue={searchValue} onSearch={(value) => { setSearchValue(value); debouncedSearch(value); }} options={isSearching ? optionsSearched : optionsMemo} placeholder={props.placeholder || 'Selecione'} showSearch onPopupScroll={handleScroll} loading={selectLoading} value={props.value} onChange={props.onChange} mode={props.multiple ? 'multiple' : undefined} allowClear={props.allowClear} disabled={props.disabled} labelInValue={props.labelInValue} // ... other props /> ); };
Best Practices
- Always create separate models: Each entity gets its own model file, never share generic models
- Use correct useModel key: Format is always
'selects.select-<entity>' - Set appropriate order_by: Use
'label'for simple labels,'codigo'for composite ones - Configure API endpoint: Replace
'ENDPOINT_AQUI'with actual API path - Handle missing options:
addMissedOptionsensures pre-filled values display correctly - Debounce search: 700ms delay prevents excessive API calls
Common Pitfalls
- Don't create generic shared models - each entity needs its own
- Don't forget to replace placeholder controller imports
- Don't use wrong useModel key format
- Don't miss API endpoint configuration for search functionality
- Don't forget label mapping in options memo
Placeholder Replacements
| Placeholder | Replace With | Example |
|---|---|---|
<entity> | Entity name in kebab-case | sindicato, centro-custo |
get<Entity>Controller | Actual controller function | getSindicatosController |
<controller-path> | Controller import path | @/services/global/sindicato.controller |
'<API_ENDPOINT>' | API endpoint string | '/api/pessoal/sindicatos/' |
r.label | Label mapping | r.label or ${r.codigo} - ${r.nome} |