feat: categorizar transações no preview de importação
Select de categoria em cada linha da prévia; category_id salvo no BulkInsert. Duplicatas ficam desabilitadas. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
This commit is contained in:
@@ -21,6 +21,7 @@ type ImportRow struct {
|
||||
Amount float64 `json:"amount"`
|
||||
Description string `json:"description"`
|
||||
Type string `json:"type"`
|
||||
CategoryID *int `json:"category_id,omitempty"`
|
||||
ExternalID string `json:"external_id,omitempty"` // FITID from OFX
|
||||
IsDuplicate bool `json:"is_duplicate"`
|
||||
}
|
||||
|
||||
@@ -63,9 +63,9 @@ func (r *transactionRepo) BulkInsert(ctx context.Context, rows []model.ImportRow
|
||||
extID = &row.ExternalID
|
||||
}
|
||||
_, err := tx.Exec(ctx, `
|
||||
INSERT INTO transactions (date, amount, description, type, source, external_id)
|
||||
VALUES ($1, $2, $3, $4, 'import', $5)`,
|
||||
row.Date, row.Amount, row.Description, row.Type, extID)
|
||||
INSERT INTO transactions (date, amount, description, type, source, external_id, category_id)
|
||||
VALUES ($1, $2, $3, $4, 'import', $5, $6)`,
|
||||
row.Date, row.Amount, row.Description, row.Type, extID, row.CategoryID)
|
||||
if err != nil {
|
||||
return count, err
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ export interface ImportRow {
|
||||
amount: number
|
||||
description: string
|
||||
type: string
|
||||
category_id?: number | null
|
||||
external_id?: string
|
||||
is_duplicate: boolean
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { ref, computed, watch, onMounted } from 'vue'
|
||||
import { useImportStore, type CSVMapping } from '@/stores/import'
|
||||
import { useCategoriesStore } from '@/stores/categories'
|
||||
import NeonPanel from '@/components/NeonPanel.vue'
|
||||
|
||||
const store = useImportStore()
|
||||
const catStore = useCategoriesStore()
|
||||
onMounted(() => catStore.fetchAll())
|
||||
|
||||
const fileInput = ref<HTMLInputElement | null>(null)
|
||||
const selectedFile = ref<File | null>(null)
|
||||
@@ -192,6 +195,7 @@ function fmt(amount: number) {
|
||||
<th class="fc-pixel">DESCRIÇÃO</th>
|
||||
<th class="fc-pixel">VALOR</th>
|
||||
<th class="fc-pixel">TIPO</th>
|
||||
<th class="fc-pixel">CATEGORIA</th>
|
||||
<th class="fc-pixel">STATUS</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -203,6 +207,16 @@ function fmt(amount: number) {
|
||||
{{ fmt(row.amount) }}
|
||||
</td>
|
||||
<td class="fc-mono">{{ row.type === 'income' ? 'Receita' : 'Gasto' }}</td>
|
||||
<td>
|
||||
<select
|
||||
v-model="row.category_id"
|
||||
class="fc-select fc-import-cat-select"
|
||||
:disabled="row.is_duplicate"
|
||||
>
|
||||
<option :value="null">—</option>
|
||||
<option v-for="c in catStore.categories" :key="c.id" :value="c.id">{{ c.name }}</option>
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<span v-if="row.is_duplicate" class="fc-pixel fc-import-status fc-import-status--dup">DUP</span>
|
||||
<span v-else class="fc-pixel fc-import-status fc-import-status--new">NOVO</span>
|
||||
@@ -334,6 +348,13 @@ function fmt(amount: number) {
|
||||
.fc-import-status--new { background: rgba(57,255,122,.15); color: var(--fc-green); }
|
||||
.fc-import-status--dup { background: rgba(255,216,77,.15); color: var(--fc-gold); }
|
||||
|
||||
.fc-import-cat-select {
|
||||
font-size: 10px;
|
||||
padding: 2px 4px;
|
||||
min-width: 110px;
|
||||
height: 26px;
|
||||
}
|
||||
|
||||
.fc-import-confirm {
|
||||
display: flex;
|
||||
gap: var(--fc-space-3);
|
||||
|
||||
Reference in New Issue
Block a user