diff --git a/.claude/skills/nova-tab/SKILL.md b/.claude/skills/nova-tab/SKILL.md index ea078cd2..5b5e52d5 100644 --- a/.claude/skills/nova-tab/SKILL.md +++ b/.claude/skills/nova-tab/SKILL.md @@ -11,7 +11,7 @@ Cria a interface completa de uma etapa do fluxo do `Project` (Tab + Schemas), no Pergunte ao usuário (ou infira do pedido): -- **Nome da etapa em inglês, PascalCase** — ex. `Payment`, `Inspection`. Define os nomes de arquivos (`Tab.vue`, `Schemas//`). +- **Nome da etapa em inglês, PascalCase** — ex. `Payment`, `Inspection`. Define os nomes de arquivos (`Tab.vue`, identificador da tab no catálogo de visualização e export `FormSections`). - **Nome da relação no model `Project`** — ex. `payment`. Define `props.project.` e os nomes de rotas (`projects..store|update`). Depois confira se o backend já existe: @@ -30,13 +30,13 @@ Estes arquivos do repo são a fonte de verdade do padrão. Leia todos: |---|---| | `resources/js/Pages/ProjectDetails/Partials/Tabs/FormalizationTab.vue` | Tab completa: useForm, init no onMounted, validação, upload, submit, tramitação | | `resources/js/Pages/ProjectDetails/Partials/Tabs/MonitoringTab.vue` | Tab que consome viewSections via SectionContent | -| `resources/js/Schemas/Monitoring/viewSections.js` | Estrutura de campos `{label, key|compute, format}` e uso do useDate | -| `resources/js/Schemas/Monitoring/formSections.js` e `index.js` | formSections `{title, key}` e barrel export | +| `resources/js/Schemas/viewSections.js` | Estrutura de campos `{label, key|compute, format}` e uso do useDate | +| `resources/js/Schemas/formSections.js` | formSections `{title, key}` com exports por etapa | | `resources/js/Components/SplitScreenTab.vue`, `SectionContent.vue`, `SectionForm.vue`, `SectionChips.vue` | Contrato de props e slots | ## Passo 3 — Criar os Schemas -Criar `resources/js/Schemas//`: +Editar os dois arquivos únicos em `resources/js/Schemas/`; não criar cópias por etapa: 1. **`viewSections.js`** — array de seções `{ title, fields: [...] }`. Cada field: - `{ label: 'Texto em português', key: 'relacao.campo' }` — acesso por dot notation @@ -45,8 +45,8 @@ Criar `resources/js/Schemas//`: - `addDaysTo('campo', dias)` — data + N dias (combine com `format: 'datetime'`) - `daysBetween('relacao.inicio', 'relacao.fim')` — dias entre duas datas - Exceção conhecida: a relação de **Formalization** chama-se **`formalizations`** (plural, mesmo sendo `HasOne`) no JSON — use esse nome só para essa etapa. Para todas as outras etapas, use o nome de relação definido com o usuário no Passo 1 (`props.project.`), nunca `formalizations`. -2. **`formSections.js`** — `export const formSections = [{ title: '...', key: 'slug-da-secao' }]` -3. **`index.js`** — barrel export: `export { viewSections } from './viewSections'; export { formSections } from './formSections';` +2. **`formSections.js`** — `export const FormSections = [{ title: '...', key: 'slug-da-secao' }]` +3. No catálogo `viewSections`, cada seção tem `key` único e `tabs` com as etapas pertinentes. Reutilizar seções existentes adicionando a etapa a `tabs`. Na Tab, usar `import { getViewSections } from '@/Schemas/viewSections';` e `const viewSections = getViewSections('');`. A ordem segue o catálogo. Importar o array de formulário com alias `formSections`. ## Passo 4 — Criar a Tab diff --git a/CLAUDE.md b/CLAUDE.md index 1b3183e5..f8b28b66 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -105,11 +105,11 @@ Dois workers separados por responsabilidade: ### Tabs de etapa e Schemas -As telas de cada etapa do Project são Tabs em `resources/js/Pages/ProjectDetails/Partials/Tabs/` (registradas em `ProcessTabs.vue`), construídas sobre `SplitScreenTab` + `SectionChips` + `SectionContent` (visualização) + `SectionForm` (edição). Os campos exibidos/editados são declarados em Schemas (`resources/js/Schemas//`): +As telas de cada etapa do Project são Tabs em `resources/js/Pages/ProjectDetails/Partials/Tabs/` (registradas em `ProcessTabs.vue`), construídas sobre `SplitScreenTab` + `SectionChips` + `SectionContent` (visualização) + `SectionForm` (edição). Os campos exibidos/editados são declarados em Schemas (`resources/js/Schemas/`): - `viewSections.js` — seções `{ title, fields }`; cada field usa `{ label, key }` (dot notation) ou `{ label, compute }` para valores calculados. - `formSections.js` — `{ title, key }`; o `SectionForm` renderiza um slot por `section.key`. -- `index.js` — barrel export. +Cada arquivo é único no projeto. `viewSections.js` exporta um catálogo de seções com `key` e `tabs`; cada Tab usa `getViewSections('opening')` (ou sua etapa) para selecionar as seções pertinentes na ordem do catálogo. Reutilizar as seções compartilhadas e preservar variantes quando os campos diferirem. `formSections.js` exporta arrays por etapa (ex.: `openingFormSections`), importados com o alias `formSections`. Não criar cópias em pastas por etapa. Para datas nos Schemas, usar o composable `useDate`: `getDate(key)` formata, `addDaysTo(key, dias)` soma dias, `daysBetween(startKey, endKey)` retorna o intervalo em dias. diff --git a/docs/skills/README.md b/docs/skills/README.md index e6b39876..67a7e2a9 100644 --- a/docs/skills/README.md +++ b/docs/skills/README.md @@ -19,7 +19,7 @@ As skills ficam em `.claude/skills/` na raiz do projeto: | Skill | Quando usar | O que faz | |---|---|---| -| `/nova-tab` | Criar a tela de uma nova etapa do fluxo do Project (ex.: PaymentTab) | Cria `Schemas//` (viewSections, formSections, index), a `Tab.vue` no padrão SplitScreenTab e registra no `ProcessTabs.vue`. Se o backend da etapa não existir, oferece o fluxo guiado do `checklist-backend.md` | +| `/nova-tab` | Criar a tela de uma nova etapa do fluxo do Project (ex.: PaymentTab) | Configura a visibilidade da etapa no catálogo `Schemas/viewSections.js` e adiciona seu export em `Schemas/formSections.js`, a `Tab.vue` no padrão SplitScreenTab e registra no `ProcessTabs.vue`. Se o backend da etapa não existir, oferece o fluxo guiado do `checklist-backend.md` | | `/novo-componente` | Criar componente Vue reutilizável em `resources/js/Components/` | Verifica se já existe componente similar (prefere estender/compor), usa `TextField.vue`/`FormField.vue` como referência e aplica o checklist (props tipadas, v-model, Vuetify outlined + Tailwind) | | `/novo-composable` | Extrair lógica reutilizável para `resources/js/Composables/` | Confere os 12 composables existentes para não duplicar (ex.: nova função de data vai no `useDate`, não num composable novo), segue o padrão `export function useXxx()` | | `/tratamento-erros` | Lançar/capturar erros, criar Service/Controller novo, tratar erro de API no Vue | Aplica a hierarquia `AppException` (backend) e o padrão `useErrorHandler` (frontend) descritos em `docs/error_handling.md` | diff --git a/resources/js/Components/DocumentViewerDialog.vue b/resources/js/Components/DocumentViewerDialog.vue index e04c0786..1c53e8b1 100644 --- a/resources/js/Components/DocumentViewerDialog.vue +++ b/resources/js/Components/DocumentViewerDialog.vue @@ -2,7 +2,7 @@ import { computed } from 'vue'; import DocumentDownloadMenu from '@/Components/DocumentDownloadMenu.vue'; import SanitizedHtml from '@/Components/SanitizedHtml.vue'; -import { getBudgetAllocation } from '@/Schemas/getBudgetAllocation'; +import { getBudgetAllocation } from '@/Schemas/schemaUtils'; const props = defineProps({ modelValue: { type: Boolean, default: false }, diff --git a/resources/js/Pages/ProjectDetails/Partials/Tabs/BudgetTab.vue b/resources/js/Pages/ProjectDetails/Partials/Tabs/BudgetTab.vue index 01f7c114..d17bf4d4 100644 --- a/resources/js/Pages/ProjectDetails/Partials/Tabs/BudgetTab.vue +++ b/resources/js/Pages/ProjectDetails/Partials/Tabs/BudgetTab.vue @@ -15,7 +15,8 @@ import TextField from '@/Components/TextField.vue'; import TramitButton from '@/Pages/ProjectDetails/Partials/Tabs/Actions/TramitButton.vue'; import SaveButton from '@/Pages/ProjectDetails/Partials/Tabs/Actions/SaveButton.vue'; -import { viewSections, formSections } from '@/Schemas/Budget'; +import { getViewSections } from '@/Schemas/viewSections'; +import { budgetFormSections as formSections } from '@/Schemas/formSections'; import { useDate } from '@/Composables/useDate'; import { useSnackbar } from '@/Composables/useSnackbar'; @@ -23,6 +24,8 @@ import { useAlert } from '@/Composables/useAlert'; import { useSaveShortcut } from '@/Composables/useSaveShortcut'; import { useStageAdvance } from '@/Composables/useStageAdvance'; +const viewSections = getViewSections('budget'); + const props = defineProps({ project: { type: Object, required: true }, canReturn: { type: Boolean, default: false }, diff --git a/resources/js/Pages/ProjectDetails/Partials/Tabs/FormalizationTab.vue b/resources/js/Pages/ProjectDetails/Partials/Tabs/FormalizationTab.vue index 3244c735..0f1422a0 100644 --- a/resources/js/Pages/ProjectDetails/Partials/Tabs/FormalizationTab.vue +++ b/resources/js/Pages/ProjectDetails/Partials/Tabs/FormalizationTab.vue @@ -15,8 +15,8 @@ import SelectField from '@/Components/SelectField.vue'; import TramitButton from '@/Pages/ProjectDetails/Partials/Tabs/Actions/TramitButton.vue'; import SaveButton from '@/Pages/ProjectDetails/Partials/Tabs/Actions/SaveButton.vue'; -import { viewSections } from '@/Schemas/Opening'; -import { formSections } from '@/Schemas/Formalization'; +import { getViewSections } from '@/Schemas/viewSections'; +import { formalizationFormSections as formSections } from '@/Schemas/formSections'; import { useDate } from '@/Composables/useDate'; import { useSnackbar } from '@/Composables/useSnackbar'; @@ -25,6 +25,8 @@ import { useSaveShortcut } from '@/Composables/useSaveShortcut'; import { useStageAdvance } from '@/Composables/useStageAdvance'; import { useAuth } from '@/Composables/useAuth'; +const viewSections = getViewSections('formalization'); + const props = defineProps({ project: { type: Object, required: true }, canReturn: { type: Boolean, default: false }, diff --git a/resources/js/Pages/ProjectDetails/Partials/Tabs/LegalAnalysisTab.vue b/resources/js/Pages/ProjectDetails/Partials/Tabs/LegalAnalysisTab.vue index c221e186..c6b15cdf 100644 --- a/resources/js/Pages/ProjectDetails/Partials/Tabs/LegalAnalysisTab.vue +++ b/resources/js/Pages/ProjectDetails/Partials/Tabs/LegalAnalysisTab.vue @@ -8,10 +8,12 @@ import StepActions from '@/Components/StepActions.vue'; import DocumentEvaluationList from '@/Components/DocumentEvaluationList.vue'; import ReturnProcessAction from '@/Components/ReturnProcessAction.vue'; import TramitButton from '@/Pages/ProjectDetails/Partials/Tabs/Actions/TramitButton.vue'; -import { viewSections } from '@/Schemas/Opening'; +import { getViewSections } from '@/Schemas/viewSections'; import { useLegalAnalysis } from '@/Composables/useLegalAnalysis'; import { useStageAdvance } from '@/Composables/useStageAdvance'; +const viewSections = getViewSections('legalAnalysis'); + const props = defineProps({ project: { type: Object, required: true }, canReturn: { type: Boolean, default: false }, diff --git a/resources/js/Pages/ProjectDetails/Partials/Tabs/MonitoringTab.vue b/resources/js/Pages/ProjectDetails/Partials/Tabs/MonitoringTab.vue index 5b643fcf..0bb2f67e 100644 --- a/resources/js/Pages/ProjectDetails/Partials/Tabs/MonitoringTab.vue +++ b/resources/js/Pages/ProjectDetails/Partials/Tabs/MonitoringTab.vue @@ -12,13 +12,16 @@ import ReturnProcessAction from '@/Components/ReturnProcessAction.vue'; import DiligenceChat from '@/Components/DiligenceChat.vue'; import TramitButton from '@/Pages/ProjectDetails/Partials/Tabs/Actions/TramitButton.vue'; import SaveButton from '@/Pages/ProjectDetails/Partials/Tabs/Actions/SaveButton.vue'; -import { viewSections, formSections } from '@/Schemas/Monitoring'; +import { getViewSections } from '@/Schemas/viewSections'; +import { monitoringFormSections as formSections } from '@/Schemas/formSections'; import { useSnackbar } from '@/Composables/useSnackbar'; import { useAlert } from '@/Composables/useAlert'; import { sanitizeExternalUrl } from '@/Composables/useExternalLink'; import { useAuth } from '@/Composables/useAuth'; import { useStageAdvance } from '@/Composables/useStageAdvance'; +const viewSections = getViewSections('monitoring'); + const props = defineProps({ project: { type: Object, required: true }, canReturn: { type: Boolean, default: false }, diff --git a/resources/js/Pages/ProjectDetails/Partials/Tabs/OpeningTab.vue b/resources/js/Pages/ProjectDetails/Partials/Tabs/OpeningTab.vue index fdad5eb1..c307c09e 100644 --- a/resources/js/Pages/ProjectDetails/Partials/Tabs/OpeningTab.vue +++ b/resources/js/Pages/ProjectDetails/Partials/Tabs/OpeningTab.vue @@ -5,8 +5,9 @@ import SectionChips from '@/Components/SectionChips.vue'; import SectionContent from '@/Components/SectionContent.vue'; import SectionForm from '@/Components/SectionForm.vue'; import AuxLinks from '@/Components/AuxLinks.vue'; +import { getViewSections } from '@/Schemas/viewSections'; +import { openingFormSections as formSections } from '@/Schemas/formSections'; import StepActions from '@/Components/StepActions.vue'; -import { viewSections, formSections } from '@/Schemas/Opening'; import { router, useForm } from '@inertiajs/vue3'; import { onMounted } from 'vue'; import { useSnackbar } from '@/Composables/useSnackbar'; @@ -22,6 +23,8 @@ import { useSaveShortcut } from '@/Composables/useSaveShortcut'; import { useMask } from '@/Composables/useMask'; import { useStageAdvance } from '@/Composables/useStageAdvance'; +const viewSections = getViewSections('opening'); + const { showSnackbar } = useSnackbar(); const { normalizeDate } = useDate(); const { showAlert } = useAlert(); diff --git a/resources/js/Pages/ProjectDetails/Partials/Tabs/PaymentTab.vue b/resources/js/Pages/ProjectDetails/Partials/Tabs/PaymentTab.vue index 850ce034..7c09f493 100644 --- a/resources/js/Pages/ProjectDetails/Partials/Tabs/PaymentTab.vue +++ b/resources/js/Pages/ProjectDetails/Partials/Tabs/PaymentTab.vue @@ -15,7 +15,8 @@ import StepActions from '@/Components/StepActions.vue'; import TramitButton from '@/Pages/ProjectDetails/Partials/Tabs/Actions/TramitButton.vue'; import SaveButton from '@/Pages/ProjectDetails/Partials/Tabs/Actions/SaveButton.vue'; -import { viewSections, formSections } from '@/Schemas/Payment'; +import { getViewSections } from '@/Schemas/viewSections'; +import { paymentFormSections as formSections } from '@/Schemas/formSections'; import { useDate } from '@/Composables/useDate'; import { useSnackbar } from '@/Composables/useSnackbar'; @@ -23,6 +24,8 @@ import { useAlert } from '@/Composables/useAlert'; import { useInstallmentStatus } from '@/Composables/useInstallments'; import { useStageAdvance } from '@/Composables/useStageAdvance'; +const viewSections = getViewSections('payment'); + const props = defineProps({ project: { type: Object, diff --git a/resources/js/Schemas/Budget/formSections.js b/resources/js/Schemas/Budget/formSections.js deleted file mode 100644 index f030dca6..00000000 --- a/resources/js/Schemas/Budget/formSections.js +++ /dev/null @@ -1,5 +0,0 @@ -export const formSections = [ - { title: 'Informação de parcelas', key: 'installments' }, - { title: 'Datas', key: 'dates' }, - { title: 'Despacho orçamentário', key: 'budget_report' }, -]; diff --git a/resources/js/Schemas/Budget/index.js b/resources/js/Schemas/Budget/index.js deleted file mode 100644 index 0d72eee6..00000000 --- a/resources/js/Schemas/Budget/index.js +++ /dev/null @@ -1,4 +0,0 @@ -import { viewSections } from './viewSections'; -import { formSections } from './formSections'; - -export { viewSections, formSections }; diff --git a/resources/js/Schemas/Budget/viewSections.js b/resources/js/Schemas/Budget/viewSections.js deleted file mode 100644 index 3c2a076d..00000000 --- a/resources/js/Schemas/Budget/viewSections.js +++ /dev/null @@ -1,88 +0,0 @@ -import { getBudgetAllocation } from '../getBudgetAllocation'; -import { getLegalType } from '../getLegalType'; -import { useMask } from '@/Composables/useMask'; - -const { maskProcessNumber } = useMask(); - -function getInstallmentJustification(project) { - const agentName = project?.agent?.name; - const noticeName = project?.notice?.name; - const categoryName = project?.category?.name; - const openingNup = maskProcessNumber(project?.opening_nup); - - if (!agentName || !noticeName || !categoryName || !openingNup) return null; - - return `Repasse de recurso para o/a proponente ${agentName ?? ''}, selecionado no ${noticeName ?? ''}, na categoria - ${categoryName ?? ''}, conforme ${openingNup ?? ''}`; -} - -export const viewSections = [ - { - title: 'Dados do agente e do projeto', - fields: [ - { label: 'Nome social / Nome fantasia', key: 'agent.name' }, - { label: 'Personalidade jurídica', compute: (project) => getLegalType(project) }, - { label: 'CPF / CNPJ do agente cultural', key: 'agent.latest_snapshot.cpf_cnpj' }, - { label: 'Área / linguagem / ciclo', key: 'notice.name' }, - { label: 'Categoria de inscrição', key: 'category.name' }, - { label: 'Título do projeto', key: 'title_project' }, - { label: 'N° da inscrição', key: 'number' }, - { - label: 'Endereço completo', - compute: (project) => { - const s = project?.agent?.latest_snapshot; - if (!s) return null; - return [s.street, s.number, s.neighborhood, s.city, s.state, s.postal_code] - .filter(Boolean) - .join(', '); - }, - }, - ], - }, - { - title: 'Dados Bancários', - fields: [ - { label: 'Banco', key: 'opening.bank.name' }, - { label: 'Conta', key: 'opening.account' }, - { label: 'Agência', key: 'opening.branch' }, - { label: 'Tipo de Conta', key: 'opening.account_type' }, - ], - }, - { - title: 'Dotação orçamentária', - fields: [ - { label: 'Código da Dotação', compute: (project) => getBudgetAllocation(project)?.allocation_code }, - { label: 'Dotação orçamentária', compute: (project) => getBudgetAllocation(project)?.allocation_number }, - { label: 'Cidade do agente', compute: (project) => project?.agent?.latest_snapshot?.city }, - { label: 'Macrorregião', compute: (project) => project?.agent?.latest_snapshot?.macroregion }, - ], - }, - { - title: 'Justificativa da parcela', - fields: [ - { - label: 'Texto da justificativa', - compute: (project) => getInstallmentJustification(project), - fullWidth: true, - }, - ], - }, - { - title: 'Informações da LOA (Lei Orçamentária Anual)', - fields: [ - { label: 'Unidade orçamentária', compute: (project) => getBudgetAllocation(project)?.management_unit }, - { label: 'Programa de trabalho', compute: (project) => getBudgetAllocation(project)?.work_program }, - { label: 'Objetivo', compute: (project) => getBudgetAllocation(project)?.objective }, - { label: 'Entrega', compute: (project) => getBudgetAllocation(project)?.deliverable }, - { label: 'Função', compute: (project) => getBudgetAllocation(project)?.budget_function }, - { label: 'Subfunção', compute: (project) => getBudgetAllocation(project)?.budget_subfunction }, - { label: 'Projeto/atividade', compute: (project) => getBudgetAllocation(project)?.project_activity }, - { label: 'Elemento de despesa', compute: (project) => getBudgetAllocation(project)?.expense_element }, - { label: 'Fonte de recursos', compute: (project) => getBudgetAllocation(project)?.funding_source }, - { label: 'MAPP', compute: (project) => getBudgetAllocation(project)?.mapp }, - { - label: 'Projeto finalistico - INTERIOR', - compute: (project) => getBudgetAllocation(project)?.finalistic_project, - }, - ], - }, -]; diff --git a/resources/js/Schemas/Formalization/formSections.js b/resources/js/Schemas/Formalization/formSections.js deleted file mode 100644 index 1ea89a2c..00000000 --- a/resources/js/Schemas/Formalization/formSections.js +++ /dev/null @@ -1,7 +0,0 @@ -export const formSections = [ - { title: 'Datas e status do processo', key: 'dates_and_status_process' }, - { title: 'Termo', key: 'signing_term' }, - { title: 'SACC', key: 'sacc' }, - { title: 'Diário oficial', key: 'official_gazette' }, - { title: 'Vigência do instrumento', key: 'validity_instrument' }, -]; diff --git a/resources/js/Schemas/Formalization/index.js b/resources/js/Schemas/Formalization/index.js deleted file mode 100644 index 1b2c7baa..00000000 --- a/resources/js/Schemas/Formalization/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import { formSections } from './formSections'; - -export { formSections }; diff --git a/resources/js/Schemas/Monitoring/formSections.js b/resources/js/Schemas/Monitoring/formSections.js deleted file mode 100644 index c1a9dee9..00000000 --- a/resources/js/Schemas/Monitoring/formSections.js +++ /dev/null @@ -1,4 +0,0 @@ -export const formSections = [ - { title: 'Parecer técnico', key: 'opinion' }, - { title: 'Observações', key: 'observations' }, -]; diff --git a/resources/js/Schemas/Monitoring/index.js b/resources/js/Schemas/Monitoring/index.js deleted file mode 100644 index 4a3db074..00000000 --- a/resources/js/Schemas/Monitoring/index.js +++ /dev/null @@ -1,2 +0,0 @@ -export { viewSections } from './viewSections'; -export { formSections } from './formSections'; diff --git a/resources/js/Schemas/Monitoring/viewSections.js b/resources/js/Schemas/Monitoring/viewSections.js deleted file mode 100644 index 2996a8b4..00000000 --- a/resources/js/Schemas/Monitoring/viewSections.js +++ /dev/null @@ -1,114 +0,0 @@ -import { useDate } from '@/Composables/useDate'; - -const { addDaysTo, getDate, daysBetween, daysBetweenDates, formatDate } = useDate(); - -function getCurrentInstallment(project) { - const installments = project.budgets?.installments ?? []; - return installments.find( - (installment) => Number(installment.installment_number) === Number(project.current_installment_cycle ?? 1) - ); -} - -export const viewSections = [ - { - title: 'Prazos e datas', - fields: [ - { - label: 'Data de solicitação do relatório de monitoramento', - compute: (project) => { - const paymentDate = getCurrentInstallment(project)?.payment_date; - const signedAt = project.formalizations?.signed_by_office_at; - - if (!paymentDate || !signedAt) return null; - - const daysBetweenSignedAndPayment = daysBetweenDates(signedAt, paymentDate); - const requestDeadlineDays = Number(project.notice?.monitoring_report_request_deadline_days); - - const date = new Date(paymentDate); - date.setDate(date.getDate() + requestDeadlineDays + daysBetweenSignedAndPayment); - - return formatDate(date); - }, - }, - { - label: 'Data prevista para envio do relatório de monitoramento', - compute: (project) => formatDate(addDaysTo('formalizations.validity_end_at', 365)(project)), - }, - { - label: 'Prazo final para análise e emissão do parecer do monitoramento', - compute: addDaysTo('sent_timestamp', 30), - format: 'datetime', - }, - - { - label: 'Data do pagamento', - compute: (project) => formatDate(getCurrentInstallment(project)?.payment_date), - }, - { - label: 'Período entre publicação e pagamento', - compute: (project) => { - const paymentDate = getCurrentInstallment(project)?.payment_date; - const publishedAt = project.formalizations?.official_gazette_published_at; - - const days = daysBetweenDates(publishedAt, paymentDate); - - return days === null ? null : `${days} dia(s)`; - }, - }, - { - label: 'Data de início de vigência do instrumento', - compute: getDate('formalizations.validity_start_at'), - }, - { - label: 'Data de término de vigência do instrumento', - compute: getDate('formalizations.validity_end_at'), - }, - { - label: 'Quantidade de dias da vigência inicial', - compute: (project) => { - const days = daysBetween( - 'formalizations.validity_start_at', - 'formalizations.validity_end_at' - )(project); - - return days === null ? null : `${days} dia(s)`; - }, - }, - { - label: 'Data da publicação do DOE', - compute: getDate('formalizations.official_gazette_published_at'), - }, - { - label: 'Quantidade de dias da vigência inicial prorrogadas por atraso no pagamento', - compute: (project) => { - const paymentDate = getCurrentInstallment(project)?.payment_date; - const signedAt = project.formalizations?.signed_by_office_at; - - const days = daysBetweenDates(signedAt, paymentDate); - - return days === null ? null : `${days} dias`; - }, - }, - ], - }, - { - title: 'Dados do agente', - fields: [ - { label: 'CPF / CNPJ', key: 'agent.latest_snapshot.cpf_cnpj' }, - { label: 'Área / linguagem / eixo', key: 'notice.name' }, - { label: 'Categoria de inscrição', key: 'category.name' }, - { label: 'Título do projeto', key: 'title_project' }, - { label: 'Telefone', key: 'agent.director_phone' }, - { label: 'E-mail', key: 'agent.latest_snapshot.email' }, - ], - }, - { - title: 'Dados do fiscal', - fields: [ - { label: 'Fiscal titular', key: 'opening.supervisors.0.user.name' }, - { label: 'Matrícula titular', key: 'opening.supervisors.0.user.registration_number' }, - { label: 'Fiscal suplente', key: 'opening.supervisors.1.user.name' }, - { label: 'Matrícula suplente', key: 'opening.supervisors.1.user.registration_number' }, - ], - }, -]; diff --git a/resources/js/Schemas/Opening/formSections.js b/resources/js/Schemas/Opening/formSections.js deleted file mode 100644 index cf6e4c6f..00000000 --- a/resources/js/Schemas/Opening/formSections.js +++ /dev/null @@ -1,8 +0,0 @@ -export const formSections = [ - { title: 'Certidão e-parcerias', key: 'certificate' }, - { title: 'Dados bancários', key: 'bank' }, - { title: 'Dados da Abertura', key: 'opening' }, - { title: 'Dados do fiscal', key: 'supervisors' }, - { title: 'Dados do Agente', key: 'agent' }, - { title: 'Credor', key: 'creditor' }, -]; diff --git a/resources/js/Schemas/Opening/index.js b/resources/js/Schemas/Opening/index.js deleted file mode 100644 index 0d72eee6..00000000 --- a/resources/js/Schemas/Opening/index.js +++ /dev/null @@ -1,4 +0,0 @@ -import { viewSections } from './viewSections'; -import { formSections } from './formSections'; - -export { viewSections, formSections }; diff --git a/resources/js/Schemas/Opening/viewSections.js b/resources/js/Schemas/Opening/viewSections.js deleted file mode 100644 index 3ad046b1..00000000 --- a/resources/js/Schemas/Opening/viewSections.js +++ /dev/null @@ -1,102 +0,0 @@ -import { useDate } from '@/Composables/useDate'; -import { useMask } from '@/Composables/useMask'; -import { getLegalType } from '@/Schemas/getLegalType'; -import { parseRegistrationFieldValue } from '@/Schemas/parseRegistrationFieldValue'; - -const { getDate } = useDate(); -const { getCpfCnpj } = useMask(); - -export const viewSections = [ - { - title: 'Dados do agente e do projeto', - fields: [ - { label: 'Nome social / Nome fantasia', key: 'agent.name' }, - { label: 'Personalidade jurídica', compute: getLegalType }, - { label: 'CPF / CNPJ do agente cultural', compute: getCpfCnpj('agent.latest_snapshot.cpf_cnpj') }, - { label: 'Área / linguagem / ciclo', key: 'notice.name' }, - { label: 'Categoria de inscrição', key: 'category.name' }, - { label: 'Título do projeto', key: 'title_project' }, - { label: 'N° da inscrição', key: 'number' }, - ], - }, - { - title: 'Endereço', - fields: [ - { - label: 'Endereço completo', - compute: (project) => { - const s = project?.agent?.latest_snapshot; - if (!s) return null; - - const hasDetailedAddress = [s.number, s.complement, s.postal_code, s.neighborhood].some((val) => - Boolean(val && String(val).trim()) - ); - - if (!hasDetailedAddress) { - return s.street || null; - } - - return [s.street, s.number, s.complement, s.neighborhood, s.city, s.state, s.postal_code] - .filter(Boolean) - .join(', '); - }, - }, - { label: 'Macrorregião', key: 'agent.latest_snapshot.macroregion' }, - { label: 'CEP', key: 'agent.latest_snapshot.postal_code' }, - { label: 'Município', key: 'agent.latest_snapshot.city' }, - ], - }, - { - title: 'Campos adicionais', - fields: [ - { label: 'Nome completo do proponente', key: 'agent.name' }, - { label: 'Cargo do proponente', key: 'agent.director_position' }, - { label: 'Telefone do proponente', key: 'agent.latest_snapshot.phone' }, - { label: 'CPF do proponente', compute: getCpfCnpj('agent.latest_snapshot.cpf_cnpj') }, - { label: 'E-mail do proponente', key: 'agent.latest_snapshot.email' }, - { label: 'E-mail secundário', key: 'agent.latest_snapshot.secondary_email' }, - { label: 'Telefone secundário', key: 'agent.latest_snapshot.secondary_phone' }, - ], - }, - { - title: 'Perfil socioeconômico', - fields: [ - { label: 'Data de nascimento', compute: getDate('agent.latest_snapshot.birth_date') }, - { label: 'Escolaridade', key: 'agent.latest_snapshot.education' }, - { label: 'Raça / cor', key: 'agent.latest_snapshot.race' }, - { label: 'Orientação sexual', key: 'agent.latest_snapshot.sexual_orientation' }, - { label: 'Possui deficiência?', key: 'agent.latest_snapshot.has_disability' }, - { label: 'Gênero', key: 'agent.latest_snapshot.gender' }, - ], - }, - { - title: 'Dados do fiscal', - fields: [ - { label: 'Fiscal titular', key: 'opening.supervisors.0.user.name' }, - { label: 'Matrícula titular', key: 'opening.supervisors.0.user.registration_number' }, - { label: 'Fiscal suplente', key: 'opening.supervisors.1.user.name' }, - { label: 'Matrícula suplente', key: 'opening.supervisors.1.user.registration_number' }, - ], - }, - { - title: 'Campos Extras e Documentos', - fields: [{ label: 'Campos extras', key: 'extra_fields' }], - }, - { - title: 'Ficha de Inscrição (Mapas Cultural)', - fields: [ - { - label: 'Campos da inscrição', - fullWidth: true, - items: (project) => { - const fields = project.opening?.registration_data?.fields ?? []; - - return fields.map((f) => ({ - label: f.titleField, - value: parseRegistrationFieldValue(f.valueField), - })); - }, - }, - ], - }, -]; diff --git a/resources/js/Schemas/Payment/formSections.js b/resources/js/Schemas/Payment/formSections.js deleted file mode 100644 index 923958c5..00000000 --- a/resources/js/Schemas/Payment/formSections.js +++ /dev/null @@ -1,4 +0,0 @@ -export const formSections = [ - { title: 'Data de tramitação', key: 'tramit_date' }, - { title: 'Dados de pagamentos', key: 'payment_date' }, -]; diff --git a/resources/js/Schemas/Payment/index.js b/resources/js/Schemas/Payment/index.js deleted file mode 100644 index 0d72eee6..00000000 --- a/resources/js/Schemas/Payment/index.js +++ /dev/null @@ -1,4 +0,0 @@ -import { viewSections } from './viewSections'; -import { formSections } from './formSections'; - -export { viewSections, formSections }; diff --git a/resources/js/Schemas/Payment/viewSections.js b/resources/js/Schemas/Payment/viewSections.js deleted file mode 100644 index 0a8b6adc..00000000 --- a/resources/js/Schemas/Payment/viewSections.js +++ /dev/null @@ -1,64 +0,0 @@ -import { getBudgetAllocation } from '../getBudgetAllocation'; -import { getLegalType } from '../getLegalType'; - -export const viewSections = [ - { - title: 'Dados do agente e do projeto', - fields: [ - { label: 'Nome social / Nome fantasia', key: 'agent.name' }, - { label: 'Personalidade jurídica', compute: (project) => getLegalType(project) }, - { label: 'CPF / CNPJ do agente cultural', key: 'agent.latest_snapshot.cpf_cnpj' }, - { label: 'Área / linguagem / ciclo', key: 'notice.name' }, - { label: 'Categoria de inscrição', key: 'category.name' }, - { label: 'Título do projeto', key: 'title_project' }, - { label: 'N° da inscrição', key: 'number' }, - { - label: 'Endereço completo', - compute: (project) => { - const s = project?.agent?.latest_snapshot; - if (!s) return null; - return [s.street, s.number, s.neighborhood, s.city, s.state, s.postal_code] - .filter(Boolean) - .join(', '); - }, - }, - ], - }, - { - title: 'Dados Bancários', - fields: [ - { label: 'Banco', key: 'opening.bank.name' }, - { label: 'Conta', key: 'opening.account' }, - { label: 'Agência', key: 'opening.branch' }, - { label: 'Tipo de Conta', key: 'opening.account_type' }, - ], - }, - { - title: 'Dotação orçamentária', - fields: [ - { label: 'Código da Dotação', compute: (project) => getBudgetAllocation(project)?.allocation_code }, - { label: 'Dotação orçamentária', compute: (project) => getBudgetAllocation(project)?.allocation_number }, - { label: 'Cidade do agente', compute: (project) => project?.agent?.latest_snapshot?.city }, - { label: 'Macrorregião', compute: (project) => project?.agent?.latest_snapshot?.macroregion }, - ], - }, - { - title: 'Informações da LOA (Lei Orçamentária Anual)', - fields: [ - { label: 'Unidade orçamentária', compute: (project) => getBudgetAllocation(project)?.management_unit }, - { label: 'Programa de trabalho', compute: (project) => getBudgetAllocation(project)?.work_program }, - { label: 'Objetivo', compute: (project) => getBudgetAllocation(project)?.objective }, - { label: 'Entrega', compute: (project) => getBudgetAllocation(project)?.deliverable }, - { label: 'Função', compute: (project) => getBudgetAllocation(project)?.budget_function }, - { label: 'Subfunção', compute: (project) => getBudgetAllocation(project)?.budget_subfunction }, - { label: 'Projeto/atividade', compute: (project) => getBudgetAllocation(project)?.project_activity }, - { label: 'Elemento de despesa', compute: (project) => getBudgetAllocation(project)?.expense_element }, - { label: 'Fonte de recursos', compute: (project) => getBudgetAllocation(project)?.funding_source }, - { label: 'MAPP', compute: (project) => getBudgetAllocation(project)?.mapp }, - { - label: 'Projeto finalistico - INTERIOR', - compute: (project) => getBudgetAllocation(project)?.finalistic_project, - }, - ], - }, -]; diff --git a/resources/js/Schemas/formSections.js b/resources/js/Schemas/formSections.js new file mode 100644 index 00000000..735d2568 --- /dev/null +++ b/resources/js/Schemas/formSections.js @@ -0,0 +1,32 @@ +export const openingFormSections = [ + { title: 'Certidão e-parcerias', key: 'certificate' }, + { title: 'Dados bancários', key: 'bank' }, + { title: 'Dados da Abertura', key: 'opening' }, + { title: 'Dados do fiscal', key: 'supervisors' }, + { title: 'Dados do Agente', key: 'agent' }, + { title: 'Credor', key: 'creditor' }, +]; + +export const formalizationFormSections = [ + { title: 'Datas e status do processo', key: 'dates_and_status_process' }, + { title: 'Termo', key: 'signing_term' }, + { title: 'SACC', key: 'sacc' }, + { title: 'Diário oficial', key: 'official_gazette' }, + { title: 'Vigência do instrumento', key: 'validity_instrument' }, +]; + +export const budgetFormSections = [ + { title: 'Informação de parcelas', key: 'installments' }, + { title: 'Datas', key: 'dates' }, + { title: 'Despacho orçamentário', key: 'budget_report' }, +]; + +export const paymentFormSections = [ + { title: 'Data de tramitação', key: 'tramit_date' }, + { title: 'Dados de pagamentos', key: 'payment_date' }, +]; + +export const monitoringFormSections = [ + { title: 'Parecer técnico', key: 'opinion' }, + { title: 'Observações', key: 'observations' }, +]; diff --git a/resources/js/Schemas/getBudgetAllocation.js b/resources/js/Schemas/getBudgetAllocation.js deleted file mode 100644 index 0943f77c..00000000 --- a/resources/js/Schemas/getBudgetAllocation.js +++ /dev/null @@ -1,45 +0,0 @@ -const normalizeMacroregion = (value) => - String(value ?? '') - .normalize('NFD') - .replace(/[\u0300-\u036f]/g, '') - .replace(/^\s*\d+\s*[-–—]?\s*/, '') - .replace(/[^a-zA-Z0-9]+/g, '') - .toLowerCase(); - -const asArray = (value) => { - if (Array.isArray(value)) return value; - return value ? [value] : []; -}; - -export const getBudgetAllocation = (project) => { - if (!project) return null; - - const noticeAllocations = asArray(project.notice?.budget_allocations); - const noticeFallback = noticeAllocations[0] ?? null; - const currentBudget = asArray(project.budgets).at(-1); - const installment = asArray(currentBudget?.installments).find( - (item) => Number(item.installment_number) === Number(project.current_installment_cycle) - ); - - if (installment?.budget_allocation) return installment.budget_allocation; - - if (installment?.budget_allocation_id) { - const linkedAllocation = noticeAllocations.find( - (allocation) => Number(allocation.id) === Number(installment.budget_allocation_id) - ); - - if (linkedAllocation) return linkedAllocation; - } - - const macroregion = normalizeMacroregion(project.agent?.latest_snapshot?.macroregion); - - if (macroregion) { - const match = noticeAllocations.find( - (allocation) => normalizeMacroregion(allocation.planning_macroregion) === macroregion - ); - - if (match) return match; - } - - return noticeFallback; -}; diff --git a/resources/js/Schemas/getLegalType.js b/resources/js/Schemas/getLegalType.js deleted file mode 100644 index 2d059d5b..00000000 --- a/resources/js/Schemas/getLegalType.js +++ /dev/null @@ -1,12 +0,0 @@ -export function getLegalType(project) { - const cpfCnpj = project?.agent?.latest_snapshot?.cpf_cnpj; - - if (!cpfCnpj) return null; - - const digits = String(cpfCnpj).replace(/\D/g, ''); - - if (digits.length === 11) return 'PESSOA FÍSICA'; - if (digits.length === 14) return 'PESSOA JURÍDICA'; - - return null; -} diff --git a/resources/js/Schemas/parseRegistrationFieldValue.js b/resources/js/Schemas/parseRegistrationFieldValue.js deleted file mode 100644 index c57404dc..00000000 --- a/resources/js/Schemas/parseRegistrationFieldValue.js +++ /dev/null @@ -1,24 +0,0 @@ -export function parseRegistrationFieldValue(raw) { - if (raw === null || raw === undefined) return '—'; - - const value = typeof raw === 'string' ? tryParseJson(raw) : raw; - - return formatValue(value); -} - -function tryParseJson(text) { - try { - return JSON.parse(text); - } catch { - return text; - } -} - -function formatValue(value) { - if (value === null || value === undefined) return '—'; - if (typeof value === 'string') return value; - if (Array.isArray(value)) return value.map(formatValue).join(', '); - if (typeof value === 'object') return JSON.stringify(value); - - return String(value); -} diff --git a/resources/js/Schemas/schemaUtils.js b/resources/js/Schemas/schemaUtils.js new file mode 100644 index 00000000..9ea5823c --- /dev/null +++ b/resources/js/Schemas/schemaUtils.js @@ -0,0 +1,122 @@ +import { useMask } from '@/Composables/useMask'; + +const { maskProcessNumber } = useMask(); + +export function getFullAddress(project) { + const s = project?.agent?.latest_snapshot; + if (!s) return null; + + const hasDetailedAddress = [s.number, s.complement, s.postal_code, s.neighborhood].some((val) => + Boolean(val && String(val).trim()) + ); + + if (!hasDetailedAddress) { + return s.street || null; + } + + return [s.street, s.number, s.complement, s.neighborhood, s.city, s.state, s.postal_code] + .filter(Boolean) + .join(', '); +} + +export function getInstallmentJustification(project) { + const agentName = project?.agent?.name; + const noticeName = project?.notice?.name; + const categoryName = project?.category?.name; + const openingNup = maskProcessNumber(project?.opening_nup); + + if (!agentName || !noticeName || !categoryName || !openingNup) return null; + + return `Repasse de recurso para o/a proponente ${agentName ?? ''}, selecionado no ${noticeName ?? ''}, na categoria - ${categoryName ?? ''}, conforme ${openingNup ?? ''}`; +} + +export function getCurrentInstallment(project) { + const installments = project.budgets?.installments ?? []; + return installments.find( + (installment) => Number(installment.installment_number) === Number(project.current_installment_cycle ?? 1) + ); +} + +export function parseRegistrationFieldValue(raw) { + if (raw === null || raw === undefined) return '—'; + + const value = typeof raw === 'string' ? tryParseJson(raw) : raw; + + return formatValue(value); +} + +function tryParseJson(text) { + try { + return JSON.parse(text); + } catch { + return text; + } +} + +function formatValue(value) { + if (value === null || value === undefined) return '—'; + if (typeof value === 'string') return value; + if (Array.isArray(value)) return value.map(formatValue).join(', '); + if (typeof value === 'object') return JSON.stringify(value); + + return String(value); +} + +export function getLegalType(project) { + const cpfCnpj = project?.agent?.latest_snapshot?.cpf_cnpj; + + if (!cpfCnpj) return null; + + const digits = String(cpfCnpj).replace(/\D/g, ''); + + if (digits.length === 11) return 'PESSOA FÍSICA'; + if (digits.length === 14) return 'PESSOA JURÍDICA'; + + return null; +} + +const normalizeMacroregion = (value) => + String(value ?? '') + .normalize('NFD') + .replace(/[\u0300-\u036f]/g, '') + .replace(/^\s*\d+\s*[-–—]?\s*/, '') + .replace(/[^a-zA-Z0-9]+/g, '') + .toLowerCase(); + +const asArray = (value) => { + if (Array.isArray(value)) return value; + return value ? [value] : []; +}; + +export const getBudgetAllocation = (project) => { + if (!project) return null; + + const noticeAllocations = asArray(project.notice?.budget_allocations); + const noticeFallback = noticeAllocations[0] ?? null; + const currentBudget = asArray(project.budgets).at(-1); + const installment = asArray(currentBudget?.installments).find( + (item) => Number(item.installment_number) === Number(project.current_installment_cycle) + ); + + if (installment?.budget_allocation) return installment.budget_allocation; + + if (installment?.budget_allocation_id) { + const linkedAllocation = noticeAllocations.find( + (allocation) => Number(allocation.id) === Number(installment.budget_allocation_id) + ); + + if (linkedAllocation) return linkedAllocation; + } + + const macroregion = normalizeMacroregion(project.agent?.latest_snapshot?.macroregion); + + if (macroregion) { + const match = noticeAllocations.find( + (allocation) => normalizeMacroregion(allocation.planning_macroregion) === macroregion + ); + + if (match) return match; + } + + return noticeFallback; +}; diff --git a/resources/js/Schemas/viewSections.js b/resources/js/Schemas/viewSections.js new file mode 100644 index 00000000..0e68fbd4 --- /dev/null +++ b/resources/js/Schemas/viewSections.js @@ -0,0 +1,281 @@ +import { useDate } from '@/Composables/useDate'; +import { useMask } from '@/Composables/useMask'; +import { + getCurrentInstallment, + getInstallmentJustification, + parseRegistrationFieldValue, + getLegalType, + getBudgetAllocation, + getFullAddress, +} from './schemaUtils'; + +const { addDaysTo, getDate, daysBetween, daysBetweenDates, formatDate } = useDate(); +const { getCpfCnpj } = useMask(); + +export const viewSections = [ + { + key: 'monitoringDates', + tabs: ['monitoring'], + title: 'Prazos e datas', + fields: [ + { + label: 'Data de solicitação do relatório de monitoramento', + compute: (project) => { + const paymentDate = getCurrentInstallment(project)?.payment_date; + const signedAt = project.formalizations?.signed_by_office_at; + + if (!paymentDate || !signedAt) return null; + + const daysBetweenSignedAndPayment = daysBetweenDates(signedAt, paymentDate); + const requestDeadlineDays = Number(project.notice?.monitoring_report_request_deadline_days); + + const date = new Date(paymentDate); + date.setDate(date.getDate() + requestDeadlineDays + daysBetweenSignedAndPayment); + + return formatDate(date); + }, + }, + { + label: 'Data prevista para envio do relatório de monitoramento', + compute: (project) => formatDate(addDaysTo('formalizations.validity_end_at', 365)(project)), + }, + { + label: 'Prazo final para análise e emissão do parecer do monitoramento', + compute: addDaysTo('sent_timestamp', 30), + format: 'datetime', + }, + + { + label: 'Data do pagamento', + compute: (project) => formatDate(getCurrentInstallment(project)?.payment_date), + }, + { + label: 'Período entre publicação e pagamento', + compute: (project) => { + const paymentDate = getCurrentInstallment(project)?.payment_date; + const publishedAt = project.formalizations?.official_gazette_published_at; + + const days = daysBetweenDates(publishedAt, paymentDate); + + return days === null ? null : `${days} dia(s)`; + }, + }, + { + label: 'Data de início de vigência do instrumento', + compute: getDate('formalizations.validity_start_at'), + }, + { + label: 'Data de término de vigência do instrumento', + compute: getDate('formalizations.validity_end_at'), + }, + { + label: 'Quantidade de dias da vigência inicial', + compute: (project) => { + const days = daysBetween( + 'formalizations.validity_start_at', + 'formalizations.validity_end_at' + )(project); + + return days === null ? null : `${days} dia(s)`; + }, + }, + { + label: 'Data da publicação do DOE', + compute: getDate('formalizations.official_gazette_published_at'), + }, + { + label: 'Quantidade de dias da vigência inicial prorrogadas por atraso no pagamento', + compute: (project) => { + const paymentDate = getCurrentInstallment(project)?.payment_date; + const signedAt = project.formalizations?.signed_by_office_at; + + const days = daysBetweenDates(signedAt, paymentDate); + + return days === null ? null : `${days} dias`; + }, + }, + ], + }, + { + key: 'monitoringAgent', + tabs: ['monitoring'], + title: 'Dados do agente', + fields: [ + { label: 'CPF / CNPJ', key: 'agent.latest_snapshot.cpf_cnpj' }, + { label: 'Área / linguagem / eixo', key: 'notice.name' }, + { label: 'Categoria de inscrição', key: 'category.name' }, + { label: 'Título do projeto', key: 'title_project' }, + { label: 'Telefone', key: 'agent.director_phone' }, + { label: 'E-mail', key: 'agent.latest_snapshot.email' }, + ], + }, + { + key: 'agentProject', + tabs: ['opening', 'formalization', 'legalAnalysis'], + title: 'Dados do agente e do projeto', + fields: [ + { label: 'Nome social / Nome fantasia', key: 'agent.name' }, + { label: 'Personalidade jurídica', compute: getLegalType }, + { label: 'CPF / CNPJ do agente cultural', compute: getCpfCnpj('agent.latest_snapshot.cpf_cnpj') }, + { label: 'Área / linguagem / ciclo', key: 'notice.name' }, + { label: 'Categoria de inscrição', key: 'category.name' }, + { label: 'Título do projeto', key: 'title_project' }, + { label: 'N° da inscrição', key: 'number' }, + ], + }, + { + key: 'address', + tabs: ['opening', 'formalization', 'legalAnalysis'], + title: 'Endereço', + fields: [ + { + label: 'Endereço completo', + compute: getFullAddress, + }, + { label: 'Macrorregião', key: 'agent.latest_snapshot.macroregion' }, + { label: 'CEP', key: 'agent.latest_snapshot.postal_code' }, + { label: 'Município', key: 'agent.latest_snapshot.city' }, + ], + }, + { + key: 'additionalFields', + tabs: ['opening', 'formalization', 'legalAnalysis'], + title: 'Campos adicionais', + fields: [ + { label: 'Nome completo do proponente', key: 'agent.name' }, + { label: 'Cargo do proponente', key: 'agent.director_position' }, + { label: 'Telefone do proponente', key: 'agent.latest_snapshot.phone' }, + { label: 'CPF do proponente', compute: getCpfCnpj('agent.latest_snapshot.cpf_cnpj') }, + { label: 'E-mail do proponente', key: 'agent.latest_snapshot.email' }, + { label: 'E-mail secundário', key: 'agent.latest_snapshot.secondary_email' }, + { label: 'Telefone secundário', key: 'agent.latest_snapshot.secondary_phone' }, + ], + }, + { + key: 'socioeconomic', + tabs: ['opening', 'formalization', 'legalAnalysis'], + title: 'Perfil socioeconômico', + fields: [ + { label: 'Data de nascimento', compute: getDate('agent.latest_snapshot.birth_date') }, + { label: 'Escolaridade', key: 'agent.latest_snapshot.education' }, + { label: 'Raça / cor', key: 'agent.latest_snapshot.race' }, + { label: 'Orientação sexual', key: 'agent.latest_snapshot.sexual_orientation' }, + { label: 'Possui deficiência?', key: 'agent.latest_snapshot.has_disability' }, + { label: 'Gênero', key: 'agent.latest_snapshot.gender' }, + ], + }, + { + key: 'supervisors', + tabs: ['opening', 'formalization', 'legalAnalysis', 'monitoring'], + title: 'Dados do fiscal', + fields: [ + { label: 'Fiscal titular', key: 'opening.supervisors.0.user.name' }, + { label: 'Matrícula titular', key: 'opening.supervisors.0.user.registration_number' }, + { label: 'Fiscal suplente', key: 'opening.supervisors.1.user.name' }, + { label: 'Matrícula suplente', key: 'opening.supervisors.1.user.registration_number' }, + ], + }, + { + key: 'extraFields', + tabs: ['opening', 'formalization', 'legalAnalysis'], + title: 'Campos Extras e Documentos', + fields: [{ label: 'Campos extras', key: 'extra_fields' }], + }, + { + key: 'registration', + tabs: ['opening', 'formalization', 'legalAnalysis'], + title: 'Ficha de Inscrição (Mapas Cultural)', + fields: [ + { + label: 'Campos da inscrição', + fullWidth: true, + items: (project) => { + const fields = project.opening?.registration_data?.fields ?? []; + + return fields.map((f) => ({ + label: f.titleField, + value: parseRegistrationFieldValue(f.valueField), + })); + }, + }, + ], + }, + { + key: 'agentProjectWithAddress', + tabs: ['budget', 'payment'], + title: 'Dados do agente e do projeto', + fields: [ + { label: 'Nome social / Nome fantasia', key: 'agent.name' }, + { label: 'Personalidade jurídica', compute: getLegalType }, + { label: 'CPF / CNPJ do agente cultural', compute: getCpfCnpj('agent.latest_snapshot.cpf_cnpj') }, + { label: 'Área / linguagem / ciclo', key: 'notice.name' }, + { label: 'Categoria de inscrição', key: 'category.name' }, + { label: 'Título do projeto', key: 'title_project' }, + { label: 'N° da inscrição', key: 'number' }, + { + label: 'Endereço completo', + compute: getFullAddress, + }, + ], + }, + { + key: 'bank', + tabs: ['formalization', 'budget', 'payment'], + title: 'Dados Bancários', + fields: [ + { label: 'Número do credor', key: 'opening.creditor_number' }, + { label: 'Banco', key: 'opening.bank.name' }, + { label: 'Conta', key: 'opening.account' }, + { label: 'Agência', key: 'opening.branch' }, + { label: 'Tipo de Conta', key: 'opening.account_type' }, + ], + }, + { + key: 'budgetAllocation', + tabs: ['budget', 'payment'], + title: 'Dotação orçamentária', + fields: [ + { label: 'Código da Dotação', compute: (project) => getBudgetAllocation(project)?.allocation_code }, + { label: 'Dotação orçamentária', compute: (project) => getBudgetAllocation(project)?.allocation_number }, + { label: 'Cidade do agente', compute: (project) => project?.agent?.latest_snapshot?.city }, + { label: 'Macrorregião', compute: (project) => project?.agent?.latest_snapshot?.macroregion }, + ], + }, + { + key: 'installmentJustification', + tabs: ['budget'], + title: 'Justificativa da parcela', + fields: [ + { + label: 'Texto da justificativa', + compute: (project) => getInstallmentJustification(project), + fullWidth: true, + }, + ], + }, + { + key: 'loa', + tabs: ['budget', 'payment'], + title: 'Informações da LOA (Lei Orçamentária Anual)', + fields: [ + { label: 'Unidade orçamentária', compute: (project) => getBudgetAllocation(project)?.management_unit }, + { label: 'Programa de trabalho', compute: (project) => getBudgetAllocation(project)?.work_program }, + { label: 'Objetivo', compute: (project) => getBudgetAllocation(project)?.objective }, + { label: 'Entrega', compute: (project) => getBudgetAllocation(project)?.deliverable }, + { label: 'Função', compute: (project) => getBudgetAllocation(project)?.budget_function }, + { label: 'Subfunção', compute: (project) => getBudgetAllocation(project)?.budget_subfunction }, + { label: 'Projeto/atividade', compute: (project) => getBudgetAllocation(project)?.project_activity }, + { label: 'Elemento de despesa', compute: (project) => getBudgetAllocation(project)?.expense_element }, + { label: 'Fonte de recursos', compute: (project) => getBudgetAllocation(project)?.funding_source }, + { label: 'MAPP', compute: (project) => getBudgetAllocation(project)?.mapp }, + { + label: 'Projeto finalistico - INTERIOR', + compute: (project) => getBudgetAllocation(project)?.finalistic_project, + }, + ], + }, +]; + +export function getViewSections(tab) { + return viewSections.filter((section) => section.tabs.includes(tab)); +}