Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
59809d2
Add linting checks to CI/CD and fix code style issues
marcelorodrigo Jan 1, 2026
217c396
Merge branch 'master' into lint
marcelorodrigo Jan 1, 2026
d5df4d4
style: Improve code formatting and accessibility in AmountInput, defa…
marcelorodrigo Jan 1, 2026
b03af91
chore: Remove unused eslint-stylistic dependency from package.json an…
marcelorodrigo Jan 1, 2026
48a052a
refactor: Use Number.parseFloat for consistency in compoundInterest f…
marcelorodrigo Jan 1, 2026
ff8e71d
refactor: Simplify condition checks for interest, tax, and IOF amount…
marcelorodrigo Jan 1, 2026
e9e5dc8
refactor: Replace parseInt with Number.parseInt for consistency in va…
marcelorodrigo Jan 1, 2026
2d4ea6e
feat: Update update-indexes script to use ES modules and fetch financ…
marcelorodrigo Jan 1, 2026
40c5983
Merge branch 'master' into lint
marcelorodrigo Jan 1, 2026
83e1686
chore: Update cron schedule for index updates and add write permissions
marcelorodrigo Jan 1, 2026
ce811ba
chore: Consolidate permissions for GitHub Pages deployment in publish…
marcelorodrigo Jan 1, 2026
8ad2e76
chore: Update CI permissions to allow read access for contents
marcelorodrigo Jan 1, 2026
8489a3e
refactor: Simplify amount handling in AmountInput component
marcelorodrigo Jan 1, 2026
4dba2e2
refactor: Simplify interest amount check in InvestmentResult component
marcelorodrigo Jan 1, 2026
c5eb674
refactor: Improve validator logic for amount in InvestmentResult comp…
marcelorodrigo Jan 1, 2026
a783752
refactor: Enhance error handling for indicadores.json file access
marcelorodrigo Jan 1, 2026
0f0689c
refactor: Improve error handling for BCB API response in update-index…
marcelorodrigo Jan 1, 2026
a879422
refactor: Add null return for error handling in BCB API response
marcelorodrigo Jan 1, 2026
37dc4ab
refactor: Enhance error handling for BCB API response data validation
marcelorodrigo Jan 1, 2026
529070c
refactor: Update documentation to reflect change from update-indexes.…
marcelorodrigo Jan 1, 2026
fc482ac
refactor: Improve error handling and validation for Selic and CDI val…
marcelorodrigo Jan 1, 2026
ba0eea9
refactor: Clean up error handling and formatting in update-indexes.mj…
marcelorodrigo Jan 1, 2026
c4b97e4
refactor: Remove redundant validation tests and improve input handlin…
marcelorodrigo Jan 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 0 additions & 13 deletions .editorconfig

This file was deleted.

15 changes: 13 additions & 2 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,16 @@ const periodMultiplier = {
- Pinia: Store initialized in `index.vue` page via `store.initializeStore()`
- Tailwind CSS + Vite integration (check `@tailwindcss/vite` in config)

### Linting & Formatting (Nuxt 4/Vue 3 Best Practice)
- Project uses **ESLint flat config** with official `@nuxt/eslint` module. See `eslint.config.mjs` (auto-generated).
- Formatting rules are enforced by the **ESLint Stylistic** plugin—no `.editorconfig` file is needed or used.
- No Prettier: all stylistic and layout concerns handled via ESLint rules.
- **Lint scripts:**
- `pnpm lint` — Check entire codebase for lint and style violations
- `pnpm lint:fix` — Auto-fix fixable issues (recommended before commit)
- **CI/CD:** Lint runs automatically in GitHub Actions (`.github/workflows/ci.yml` and `publish.yml`). Commits/PRs failing lint will cause the pipeline to fail.
- TypeScript and Vue SFCs are deeply integrated in lint setup. Prefer explicit, non-`any` types.

## Testing Patterns
- Unit tests in `test/unit/src/` use **Vitest** with `describe`/`it` blocks
- Example: `finance.spec.ts` tests IR brackets and compound interest edge cases
Expand All @@ -88,8 +98,9 @@ const periodMultiplier = {
- **SonarCloud**: CI/CD quality gates (see badges in README)

## Key Files Reference
- `package.json`: Build/test scripts and dependencies
- `nuxt.config.ts`: Tailwind, SEO meta, static generation config
- `package.json`: Build/test/lint scripts and dependencies
- `nuxt.config.ts`: Tailwind, SEO meta, static generation and lint config
- `eslint.config.mjs`: ESLint flat config entry point (auto-generated)
- `vitest.config.ts`: Test runner configuration
- `tsconfig.json`: TypeScript strict mode (recommended)
- `app/assets/indicadores.json`: Runtime market data (do not commit hardcoded values)
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ jobs:
uses: actions/checkout@v6
with:
fetch-depth: 0
show-progress: false
- uses: pnpm/action-setup@v4
name: Configure PNPM
- name: Setup node env 🏗
Expand All @@ -19,6 +20,8 @@ jobs:
cache: 'pnpm'
- name: Install dependencies 👨🏻‍💻
run: pnpm install --frozen-lockfile
- name: Lint check ✅
run: pnpm lint
- name: Tests 🧑‍🔬
run: pnpm test
- name: Deploy 😎
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ jobs:
cache: 'pnpm'
- name: Install dependencies 👨🏻‍💻
run: pnpm install --frozen-lockfile
- name: Lint check ✅
run: pnpm lint
- name: Deploy 😎
run: pnpm generate
- name: Publish 🍻
Expand Down
5 changes: 3 additions & 2 deletions app/components/InvestmentInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ import PeriodTypeInput from './investment/PeriodTypeInput.vue'
<template>
<div class="bg-white rounded-lg shadow-md">
<div class="px-6 py-4 border-b border-gray-200">
<h2 class="text-lg font-semibold text-gray-900">Investimento</h2>
<h2 class="text-lg font-semibold text-gray-900">
Investimento
</h2>
</div>
<div class="px-6 py-4">
<form @submit.prevent>
Expand All @@ -37,4 +39,3 @@ import PeriodTypeInput from './investment/PeriodTypeInput.vue'
gap: 1rem;
}
</style>

122 changes: 78 additions & 44 deletions app/components/InvestmentResult.vue
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
<template>
<div class="bg-white rounded-xl shadow-lg overflow-hidden border border-gray-100 hover:shadow-xl transition-shadow duration-300">
<!-- Header with gradient background -->
<div class="px-6 py-4 bg-gradient-to-r" :class="headerGradientClass">
<h3 class="text-xl font-bold text-white drop-shadow-sm">{{ name }}</h3>
<div
class="px-6 py-4 bg-gradient-to-r"
:class="headerGradientClass"
>
<h3 class="text-xl font-bold text-white drop-shadow-sm">
{{ name }}
</h3>
</div>

<div class="p-6">
Expand All @@ -11,64 +16,89 @@
<!-- Total Amount Card -->
<div class="bg-gradient-to-br from-blue-50 to-blue-100 rounded-lg p-4 border border-blue-200">
<div class="flex items-center gap-2 mb-2">
<div class="w-3 h-3 rounded-full" :class="colorDotClass"></div>
<p class="text-sm font-medium text-gray-600">Valor Total Líquido</p>
<div
class="w-3 h-3 rounded-full"
:class="colorDotClass"
/>
<p class="text-sm font-medium text-gray-600">
Valor Total Líquido
</p>
</div>
<p class="text-2xl font-bold text-gray-900">{{ totalAmountDisplay }}</p>
<p class="text-2xl font-bold text-gray-900">
{{ totalAmountDisplay }}
</p>
</div>

<!-- Liquid Profit Card -->
<div class="bg-gradient-to-br from-green-50 to-green-100 rounded-lg p-4 border border-green-200">
<div class="flex items-center gap-2 mb-2">
<div class="w-3 h-3 rounded-full bg-green-500"></div>
<p class="text-sm font-medium text-gray-600">Rendimento Líquido</p>
<div class="w-3 h-3 rounded-full bg-green-500" />
<p class="text-sm font-medium text-gray-600">
Rendimento Líquido
</p>
</div>
<p class="text-2xl font-bold text-green-700">{{ liquidAmountDisplay }}</p>
<p class="text-2xl font-bold text-green-700">
{{ liquidAmountDisplay }}
</p>
</div>
</div>

<!-- Progress bar showing profitability -->
<div class="mb-6">
<div class="flex items-center justify-between mb-2">
<span class="text-sm font-medium text-gray-600">Rentabilidade</span>
<span class="text-sm font-bold" :class="profitabilityTextColor">{{ totalProfitPercentageDisplay }}</span>
<span
class="text-sm font-bold"
:class="profitabilityTextColor"
>{{ totalProfitPercentageDisplay }}</span>
</div>
<div class="relative h-3 bg-gray-200 rounded-full overflow-hidden">
<div
class="absolute inset-y-0 left-0 transition-all duration-500 ease-out rounded-full"
:class="progressBarColor"
:style="{ width: `${Math.min(totalProfitPercentage, 100)}%` }"
></div>
/>
</div>
</div>

<!-- Detailed breakdown section -->
<div>
<div class="flex items-center justify-between border-b border-gray-100">
<div class="flex items-center gap-2">
<div class="w-2 h-2 rounded-full bg-blue-500"></div>
<div class="w-2 h-2 rounded-full bg-blue-500" />
<span class="text-sm text-gray-600">Valor Investido</span>
</div>
<span class="text-sm font-semibold text-gray-900">{{ amountDisplay }}</span>
</div>

<div v-if="!!interestAmount" class="flex items-center justify-between py-2 border-b border-gray-100">
<div
v-if="!!interestAmount"
class="flex items-center justify-between py-2 border-b border-gray-100"
>
<div class="flex items-center gap-2">
<div class="w-2 h-2 rounded-full bg-green-500"></div>
<div class="w-2 h-2 rounded-full bg-green-500" />
<span class="text-sm text-gray-600">Rendimento Bruto</span>
</div>
<span class="text-sm font-semibold text-green-700">{{ interestAmountDisplay }}</span>
</div>
</div>

<!-- Deductions section - highlighted -->
<div v-if="hasDeductions" class="bg-red-50 rounded-lg p-4 border border-red-100 space-y-3">
<div
v-if="hasDeductions"
class="bg-red-50 rounded-lg p-4 border border-red-100 space-y-3"
>
<div class="flex items-center gap-2 mb-2">
<div class="w-3 h-3 rounded-full bg-red-500"></div>
<p class="text-sm font-semibold text-gray-700">Deduções</p>
<div class="w-3 h-3 rounded-full bg-red-500" />
<p class="text-sm font-semibold text-gray-700">
Deduções
</p>
</div>

<div v-if="!!taxAmount" class="flex items-center justify-between">
<div
v-if="!!taxAmount"
class="flex items-center justify-between"
>
<div class="flex items-center gap-2 flex-1">
<span class="text-sm text-gray-600">Imposto de Renda</span>
<span
Expand All @@ -81,7 +111,10 @@
<span class="text-sm font-semibold text-red-700">-{{ taxAmountDisplay }}</span>
</div>

<div v-if="!!iofAmount" class="flex items-center justify-between">
<div
v-if="!!iofAmount"
class="flex items-center justify-between"
>
<span class="text-sm text-gray-600">IOF</span>
<span class="text-sm font-semibold text-red-700">-{{ iofAmountDisplay }}</span>
</div>
Expand All @@ -106,50 +139,51 @@ const filters = {
style: 'currency',
currency: 'BRL',
currencyDisplay: 'symbol',
minimumFractionDigits: 2
minimumFractionDigits: 2,
})
}
},
}

const props = defineProps({
loading: Boolean,
name: {
type: String,
required: true,
validator: (content: string) => !!content
validator: (content: string) => !!content,
},
amount: {
type: Number,
required: true,
validator: (value: number) => parseFloat(value.toString()) > 0
validator: (value: number) => parseFloat(value.toString()) > 0,
},
interestAmount: {
type: Number,
required: true
required: true,
},
taxAmount: {
type: Number,
required: false,
default: null
default: null,
},
taxPercentage: {
type: Number,
required: false,
default: null,
validator: (value: number | null) => (value !== null ? parseInt(value.toString()) > 0 : true)
validator: (value: number | null) => (value !== null ? parseInt(value.toString()) > 0 : true),
},
iofAmount: {
type: Number,
required: false,
default: null
default: null,
},
color: {
type: String,
required: false,
default: 'amber'
}
default: 'amber',
},
})

// eslint-disable-next-line @typescript-eslint/no-unused-vars
const hasAmount = computed(() => !!props.amount)
const hasDeductions = computed(() => !!props.taxAmount || !!props.iofAmount)

Expand All @@ -168,40 +202,40 @@ const totalProfitPercentageDisplay = computed(() => filters.percent(totalProfitP

const progressBarColor = computed(() => {
const colorMap: Record<string, string> = {
'amber': 'bg-amber-400',
'green': 'bg-green-400',
'blue': 'bg-blue-400',
'red': 'bg-red-400',
amber: 'bg-amber-400',
green: 'bg-green-400',
blue: 'bg-blue-400',
red: 'bg-red-400',
}
return colorMap[props.color] || 'bg-amber-400'
})

const headerGradientClass = computed(() => {
const gradientMap: Record<string, string> = {
'amber': 'from-amber-400 to-amber-500',
'green': 'from-green-400 to-green-500',
'blue': 'from-blue-400 to-blue-500',
'red': 'from-red-400 to-red-500',
amber: 'from-amber-400 to-amber-500',
green: 'from-green-400 to-green-500',
blue: 'from-blue-400 to-blue-500',
red: 'from-red-400 to-red-500',
}
return gradientMap[props.color] || 'from-amber-400 to-amber-500'
})

const colorDotClass = computed(() => {
const dotMap: Record<string, string> = {
'amber': 'bg-amber-500',
'green': 'bg-green-500',
'blue': 'bg-blue-500',
'red': 'bg-red-500',
amber: 'bg-amber-500',
green: 'bg-green-500',
blue: 'bg-blue-500',
red: 'bg-red-500',
}
return dotMap[props.color] || 'bg-amber-500'
})

const profitabilityTextColor = computed(() => {
const textColorMap: Record<string, string> = {
'amber': 'text-amber-700',
'green': 'text-green-700',
'blue': 'text-blue-700',
'red': 'text-red-700',
amber: 'text-amber-700',
green: 'text-green-700',
blue: 'text-blue-700',
red: 'text-red-700',
}
return textColorMap[props.color] || 'text-amber-700'
})
Expand Down
Loading