Files
openmonetis/src/features/transactions/components/import/global-fields.tsx
Felipe Coutinho 7d0781b035 refactor: faxina arquitetural — código morto, identificadores em inglês e estrutura padronizada
Refatoração estrutural sem mudanças funcionais. Saldo líquido: −428 linhas.

Removido:
- 14 funções/constantes mortas verificadas via grep no repo todo: validateCategoriaOwnership,
  getInstallmentAnticipationsAction, getAnticipationDetailsAction, formatDecimalForDb,
  currencyFormatterNoCents, optionalDecimalSchema, formatMonthLabel,
  getGoalProgressStatusColorClass, MONTH_PERIOD_PARAM, calculateRemainingInstallments,
  e 5 funções fetch* não usadas em inbox/queries.ts.
- 1 tipo morto (ImportRow) + 2 órfãos consequentes (InstallmentAnticipationWithRelations,
  GoalProgressStatus convertido em interno).
- ~30 export keywords desnecessários (símbolos usados apenas no próprio arquivo).
- Re-exports mortos em barrels: EstablishmentLogoPicker, CategoryReportSkeleton,
  WidgetSkeleton, toNameKey.
- Arquivo features/reports/types.ts (barrel inteiro era órfão).

Padronizado (PT-BR→EN em identificadores expostos):
- 4 constantes globais (LANCAMENTOS_* → TRANSACTIONS_*).
- 12 tipos/interfaces (Lancamento*/Pagador*/Estabelecimento* → equivalentes EN).
- 13 funções/components exportados (fetchPagador*, EstabelecimentoInput, PagadorInfoCard, etc.).
- 5 props cross-file (preLancamentosCount → inboxPendingCount, pagadorAvatarUrl → payerAvatarUrl, etc.).
- Mantidas em PT-BR conforme exceção do CLAUDE.md: variáveis locais (pagador, categoria,
  lancamento), accessor key pagadorName (persistida em preferências), strings de UI.

Reorganizado:
- transactions/: 14 helpers soltos na raiz movidos para lib/; barrel actions.ts reduzido
  de 76 linhas de wrappers para 14 linhas de re-exports puros; anticipation-actions.ts
  movido para actions/anticipation.ts.
- dashboard/: 8 helpers soltos consolidados em dashboard/lib/.
- reports/: 5 query files na raiz consolidados em reports/lib/.
- payers/: detail-actions.ts (21KB) e detail-queries.ts movidos para payers/lib/.
- shared/components/: 9 dos 16 componentes soltos agrupados em brand/, widgets/, feedback/.
- shared/lib/fetch-json.ts movido para shared/utils/fetch-json.ts.

Validação: pnpm exec tsc --noEmit (0 erros), biome check (0 issues), knip (sem unused).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 18:42:54 +00:00

201 lines
5.4 KiB
TypeScript

"use client";
import {
AccountCardSelectContent,
CategorySelectContent,
PayerSelectContent,
} from "@/features/transactions/components/select-items";
import type { SelectOption } from "@/features/transactions/components/types";
import { PeriodPicker } from "@/shared/components/period-picker";
import { Label } from "@/shared/components/ui/label";
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectSeparator,
SelectTrigger,
SelectValue,
} from "@/shared/components/ui/select";
type AccountCardValue = `card:${string}` | `account:${string}`;
export function encodeAccountCard(
type: "card" | "account",
id: string,
): AccountCardValue {
return `${type}:${id}` as AccountCardValue;
}
export function decodeAccountCard(value: string): {
type: "card" | "account";
id: string;
} | null {
if (value.startsWith("card:")) return { type: "card", id: value.slice(5) };
if (value.startsWith("account:"))
return { type: "account", id: value.slice(8) };
return null;
}
interface GlobalFieldsProps {
accountOptions: SelectOption[];
cardOptions: SelectOption[];
payerOptions: SelectOption[];
categoryOptions: SelectOption[];
accountCardValue: string | null;
payerId: string | null;
invoicePeriod: string | null;
onAccountCardChange: (value: string | null) => void;
onPayerChange: (value: string | null) => void;
onInvoicePeriodChange: (value: string | null) => void;
onBulkCategoryChange: (categoryId: string) => void;
}
export function GlobalFields({
accountOptions,
cardOptions,
payerOptions,
categoryOptions,
accountCardValue,
payerId,
invoicePeriod,
onAccountCardChange,
onPayerChange,
onInvoicePeriodChange,
onBulkCategoryChange,
}: GlobalFieldsProps) {
const isCard = accountCardValue?.startsWith("card:") ?? false;
const expenseCategories = categoryOptions.filter(
(o) => o.group === "despesa",
);
const incomeCategories = categoryOptions.filter((o) => o.group === "receita");
return (
<div className="flex flex-col gap-2">
<p className="text-muted-foreground text-sm">
Aplicado a todos os lançamentos importados.
</p>
<div className="flex flex-wrap gap-4">
<div className="flex min-w-44 flex-col gap-1.5">
<Label>Conta / Cartão</Label>
<Select
value={accountCardValue ?? ""}
onValueChange={(v) => onAccountCardChange(v || null)}
>
<SelectTrigger>
<SelectValue placeholder="Selecionar conta ou cartão…" />
</SelectTrigger>
<SelectContent>
{cardOptions.length > 0 && (
<SelectGroup>
<SelectLabel>Cartões</SelectLabel>
{cardOptions.map((opt) => (
<SelectItem key={opt.value} value={`card:${opt.value}`}>
<AccountCardSelectContent
label={opt.label}
logo={opt.logo}
isCartao
/>
</SelectItem>
))}
</SelectGroup>
)}
{cardOptions.length > 0 && accountOptions.length > 0 && (
<SelectSeparator />
)}
{accountOptions.length > 0 && (
<SelectGroup>
<SelectLabel>Contas</SelectLabel>
{accountOptions.map((opt) => (
<SelectItem key={opt.value} value={`account:${opt.value}`}>
<AccountCardSelectContent
label={opt.label}
logo={opt.logo}
isCartao={false}
/>
</SelectItem>
))}
</SelectGroup>
)}
</SelectContent>
</Select>
</div>
<div className="flex min-w-44 flex-col gap-1.5">
<Label>Pessoa</Label>
<Select
value={payerId ?? ""}
onValueChange={(v) => onPayerChange(v || null)}
>
<SelectTrigger>
<SelectValue placeholder="Selecionar pessoa…" />
</SelectTrigger>
<SelectContent>
{payerOptions.map((opt) => (
<SelectItem key={opt.value} value={opt.value}>
<PayerSelectContent
label={opt.label}
avatarUrl={opt.avatarUrl}
/>
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="flex min-w-44 flex-col gap-1.5">
<Label>Categoria</Label>
<Select onValueChange={onBulkCategoryChange}>
<SelectTrigger>
<SelectValue placeholder="Aplicar a todas selecionadas…" />
</SelectTrigger>
<SelectContent>
{expenseCategories.length > 0 && (
<SelectGroup>
<SelectLabel>Despesa</SelectLabel>
{expenseCategories.map((opt) => (
<SelectItem key={opt.value} value={opt.value}>
<CategorySelectContent
label={opt.label}
icon={opt.icon}
/>
</SelectItem>
))}
</SelectGroup>
)}
{expenseCategories.length > 0 && incomeCategories.length > 0 && (
<SelectSeparator />
)}
{incomeCategories.length > 0 && (
<SelectGroup>
<SelectLabel>Receita</SelectLabel>
{incomeCategories.map((opt) => (
<SelectItem key={opt.value} value={opt.value}>
<CategorySelectContent
label={opt.label}
icon={opt.icon}
/>
</SelectItem>
))}
</SelectGroup>
)}
</SelectContent>
</Select>
</div>
{isCard && (
<div className="flex min-w-44 flex-col gap-1.5">
<Label>Fatura</Label>
<PeriodPicker
value={invoicePeriod ?? ""}
onChange={(v) => onInvoicePeriodChange(v || null)}
placeholder="Selecionar fatura…"
/>
</div>
)}
</div>
</div>
);
}