reduce dialog margins and make the action buttons always at the bottom of the dialog

This commit is contained in:
MaysWind
2025-12-13 21:04:43 +08:00
parent e9c3001c28
commit b2fab42170
26 changed files with 657 additions and 677 deletions
@@ -1,9 +1,9 @@
<template>
<v-dialog :persistent="!!persistent" v-model="showState">
<v-card class="pa-6 pa-sm-10 pa-md-12">
<v-card class="pa-sm-1 pa-md-2">
<template #title>
<div class="d-flex align-center justify-center">
<div class="d-flex w-100 align-center justify-center">
<div class="d-flex w-100 align-center">
<h4 class="text-h4">{{ tt('Import Transactions') }}</h4>
<v-progress-circular indeterminate size="22" class="ms-2" v-if="loading"></v-progress-circular>
</div>
@@ -79,182 +79,185 @@
</div>
</template>
<div class="mt-4 cursor-default">
<steps-bar min-width="700" :clickable="false" :steps="allSteps" :current-step="currentStep" />
</div>
<v-card-text>
<div class="cursor-default">
<steps-bar min-width="700" :clickable="false" :steps="allSteps" :current-step="currentStep" />
</div>
<v-window class="disable-tab-transition" v-model="currentStep">
<v-window-item value="uploadFile">
<v-row>
<v-col cols="12" md="12">
<two-column-select primary-key-field="displayCategoryName"
primary-value-field="displayCategoryName"
primary-title-field="displayCategoryName"
primary-sub-items-field="fileTypes"
secondary-key-field="type"
secondary-value-field="type"
secondary-title-field="displayName"
:disabled="submitting"
:enable-filter="true"
:filter-placeholder="tt('Find file type')"
:filter-no-items-text="tt('No available file type')"
:label="tt('File Type')"
:placeholder="tt('File Type')"
:items="allSupportedImportFileCategoryAndTypes"
:auto-update-menu-position="true"
v-model="fileType">
</two-column-select>
</v-col>
<v-window class="disable-tab-transition" v-model="currentStep">
<v-window-item value="uploadFile">
<v-row class="pt-2">
<v-col cols="12" md="12">
<two-column-select primary-key-field="displayCategoryName"
primary-value-field="displayCategoryName"
primary-title-field="displayCategoryName"
primary-sub-items-field="fileTypes"
secondary-key-field="type"
secondary-value-field="type"
secondary-title-field="displayName"
:disabled="submitting"
:enable-filter="true"
:filter-placeholder="tt('Find file type')"
:filter-no-items-text="tt('No available file type')"
:label="tt('File Type')"
:placeholder="tt('File Type')"
:items="allSupportedImportFileCategoryAndTypes"
:auto-update-menu-position="true"
v-model="fileType">
</two-column-select>
</v-col>
<v-col cols="12" md="12" v-if="allFileSubTypes">
<v-select
item-title="displayName"
item-value="type"
:disabled="submitting"
:label="tt('Format')"
:placeholder="tt('Format')"
:items="allFileSubTypes"
v-model="fileSubType"
/>
</v-col>
<v-col cols="12" md="12" v-if="allFileSubTypes">
<v-select
item-title="displayName"
item-value="type"
:disabled="submitting"
:label="tt('Format')"
:placeholder="tt('Format')"
:items="allFileSubTypes"
v-model="fileSubType"
/>
</v-col>
<v-col cols="12" md="12" v-if="!isImportDataFromTextbox && allSupportedEncodings">
<v-select
item-title="displayName"
item-value="encoding"
:disabled="submitting"
:label="tt('File Encoding')"
:placeholder="tt('File Encoding')"
:items="allSupportedEncodings"
v-model="fileEncoding"
/>
</v-col>
<v-col cols="12" md="12" v-if="!isImportDataFromTextbox && allSupportedEncodings">
<v-select
item-title="displayName"
item-value="encoding"
:disabled="submitting"
:label="tt('File Encoding')"
:placeholder="tt('File Encoding')"
:items="allSupportedEncodings"
v-model="fileEncoding"
/>
</v-col>
<v-col cols="12" md="12" v-if="fileType === 'dsv' || fileType === 'dsv_data'">
<v-select
item-title="displayName"
item-value="type"
:disabled="submitting"
:label="tt('Handling Method')"
:placeholder="tt('Handling Method')"
:items="[
{ displayName: tt('Column Mapping'), type: ImportDSVProcessMethod.ColumnMapping },
{ displayName: tt('Custom Script'), type: ImportDSVProcessMethod.CustomScript }
]"
v-model="processDSVMethod"
/>
</v-col>
<v-col cols="12" md="12" v-if="fileType === 'dsv' || fileType === 'dsv_data'">
<v-select
item-title="displayName"
item-value="type"
:disabled="submitting"
:label="tt('Handling Method')"
:placeholder="tt('Handling Method')"
:items="[
{ displayName: tt('Column Mapping'), type: ImportDSVProcessMethod.ColumnMapping },
{ displayName: tt('Custom Script'), type: ImportDSVProcessMethod.CustomScript }
]"
v-model="processDSVMethod"
/>
</v-col>
<v-col cols="12" md="12" v-if="supportedAdditionalOptions">
<v-select
:disabled="submitting"
:label="tt('Additional Options')"
:placeholder="tt('Additional Options')"
v-model="fileType"
v-model:menu="additionalOptionsMenuState"
>
<template #selection>
<span class="cursor-pointer">{{ displaySelectedAdditionalOptions }}</span>
</template>
<v-col cols="12" md="12" v-if="supportedAdditionalOptions">
<v-select
:disabled="submitting"
:label="tt('Additional Options')"
:placeholder="tt('Additional Options')"
v-model="fileType"
v-model:menu="additionalOptionsMenuState"
>
<template #selection>
<span class="cursor-pointer">{{ displaySelectedAdditionalOptions }}</span>
</template>
<template #no-data>
<v-list class="py-0">
<template v-for="item in allSupportedAdditionalOptions">
<v-list-item :key="item.key"
:append-icon="importAdditionalOptions[item.key] ? mdiCheck : undefined"
@click="importAdditionalOptions[item.key] = !importAdditionalOptions[item.key]"
v-if="isDefined(supportedAdditionalOptions[item.key])">{{ tt(item.name) }}</v-list-item>
</template>
</v-list>
</template>
</v-select>
</v-col>
<template #no-data>
<v-list class="py-0">
<template v-for="item in allSupportedAdditionalOptions">
<v-list-item :key="item.key"
:append-icon="importAdditionalOptions[item.key] ? mdiCheck : undefined"
@click="importAdditionalOptions[item.key] = !importAdditionalOptions[item.key]"
v-if="isDefined(supportedAdditionalOptions[item.key])">{{ tt(item.name) }}</v-list-item>
</template>
</v-list>
</template>
</v-select>
</v-col>
<v-col cols="12" md="12" v-if="!isImportDataFromTextbox">
<v-text-field
readonly
persistent-placeholder
type="text"
class="always-cursor-pointer"
:disabled="submitting"
:label="tt('Data File')"
:placeholder="tt('format.misc.clickToSelectedFile', { extensions: supportedImportFileExtensions })"
v-model="fileName"
@click="showOpenFileDialog"
/>
</v-col>
<v-col cols="12" md="12" v-if="!isImportDataFromTextbox">
<v-text-field
readonly
persistent-placeholder
type="text"
class="always-cursor-pointer"
:disabled="submitting"
:label="tt('Data File')"
:placeholder="tt('format.misc.clickToSelectedFile', { extensions: supportedImportFileExtensions })"
v-model="fileName"
@click="showOpenFileDialog"
/>
</v-col>
<v-col cols="12" md="12" v-if="isImportDataFromTextbox">
<v-textarea
type="text"
persistent-placeholder
rows="5"
:disabled="submitting"
:placeholder="tt('Data to import')"
v-model="importData"
/>
</v-col>
<v-col cols="12" md="12" v-if="isImportDataFromTextbox">
<v-textarea
type="text"
persistent-placeholder
rows="5"
:disabled="submitting"
:placeholder="tt('Data to import')"
v-model="importData"
/>
</v-col>
<v-col cols="12" md="12" class="mb-0 pb-0" v-if="exportFileGuideDocumentUrl">
<a :href="exportFileGuideDocumentUrl" :class="{ 'disabled': submitting }" target="_blank">
<v-icon :icon="mdiHelpCircleOutline" size="16" />
<span class="ms-1" v-if="fileType === 'dsv' || fileType === 'dsv_data'">{{ tt('How to import this file?') }}</span>
<span class="ms-1" v-if="fileType !== 'dsv' && fileType !== 'dsv_data'">{{ tt('How to export this file?') }}</span>
<span class="ms-1" v-if="exportFileGuideDocumentLanguageName">[{{ exportFileGuideDocumentLanguageName }}]</span>
</a>
</v-col>
</v-row>
</v-window-item>
<v-window-item value="defineColumn">
<import-transaction-define-column-tab
ref="importTransactionDefineColumnTab"
:parsed-file-data="parsedFileData"
:disabled="loading || submitting"
/>
</v-window-item>
<v-window-item value="executeCustomScript">
<import-transaction-execute-custom-script-tab
ref="importTransactionExecuteCustomScriptTab"
:parsed-file-data="parsedFileData"
:disabled="loading || submitting"
/>
</v-window-item>
<v-window-item value="checkData">
<import-transaction-check-data-tab
ref="importTransactionCheckDataTab"
:import-transactions="importTransactions"
:disabled="loading || submitting"
/>
</v-window-item>
<v-window-item value="finalResult">
<h4 class="text-h4 mb-1">{{ tt('Data Import Completed') }}</h4>
<p class="my-5">{{ tt('format.misc.importTransactionResult', { count: getDisplayCount(importedCount || 0) }) }}</p>
</v-window-item>
</v-window>
<div class="d-flex justify-sm-space-between gap-4 flex-wrap justify-center mt-10">
<v-btn color="secondary" variant="tonal" :disabled="loading || submitting"
:prepend-icon="mdiClose" @click="close(false)"
v-if="currentStep !== 'finalResult'">{{ tt('Cancel') }}</v-btn>
<v-btn class="button-icon-with-direction" color="primary"
:disabled="loading || submitting || (!isImportDataFromTextbox && !importFile) || (isImportDataFromTextbox && !importData) || (!isImportDataFromTextbox && allSupportedEncodings && fileEncoding === 'auto' && !autoDetectedFileEncoding)"
:append-icon="!submitting ? mdiArrowRight : undefined" @click="parseData"
v-if="currentStep === 'defineColumn' || currentStep === 'executeCustomScript' || currentStep === 'uploadFile'">
{{ tt('Next') }}
<v-progress-circular indeterminate size="22" class="ms-2" v-if="submitting"></v-progress-circular>
</v-btn>
<v-btn class="button-icon-with-direction" color="teal"
:disabled="submitting || importTransactionCheckDataTab?.isEditing || !importTransactionCheckDataTab?.canImport"
:append-icon="!submitting ? mdiArrowRight : undefined" @click="submit"
v-if="currentStep === 'checkData'">
{{ (submitting && importProcess > 0 ? tt('format.misc.importingTransactions', { process: formatNumberToLocalizedNumerals(importProcess, 2) }) : tt('Import')) }}
<v-progress-circular indeterminate size="22" class="ms-2" v-if="submitting"></v-progress-circular>
</v-btn>
<v-btn color="secondary" variant="tonal"
:append-icon="mdiCheck"
@click="close(true)"
v-if="currentStep === 'finalResult'">{{ tt('Close') }}</v-btn>
</div>
<v-col cols="12" md="12" v-if="exportFileGuideDocumentUrl">
<a :href="exportFileGuideDocumentUrl" :class="{ 'disabled': submitting }" target="_blank">
<v-icon :icon="mdiHelpCircleOutline" size="16" />
<span class="ms-1" v-if="fileType === 'dsv' || fileType === 'dsv_data'">{{ tt('How to import this file?') }}</span>
<span class="ms-1" v-if="fileType !== 'dsv' && fileType !== 'dsv_data'">{{ tt('How to export this file?') }}</span>
<span class="ms-1" v-if="exportFileGuideDocumentLanguageName">[{{ exportFileGuideDocumentLanguageName }}]</span>
</a>
</v-col>
</v-row>
</v-window-item>
<v-window-item value="defineColumn">
<import-transaction-define-column-tab
ref="importTransactionDefineColumnTab"
:parsed-file-data="parsedFileData"
:disabled="loading || submitting"
/>
</v-window-item>
<v-window-item value="executeCustomScript">
<import-transaction-execute-custom-script-tab
ref="importTransactionExecuteCustomScriptTab"
:parsed-file-data="parsedFileData"
:disabled="loading || submitting"
/>
</v-window-item>
<v-window-item value="checkData">
<import-transaction-check-data-tab
ref="importTransactionCheckDataTab"
:import-transactions="importTransactions"
:disabled="loading || submitting"
/>
</v-window-item>
<v-window-item value="finalResult">
<h4 class="text-h4 mb-1">{{ tt('Data Import Completed') }}</h4>
<p class="my-5">{{ tt('format.misc.importTransactionResult', { count: getDisplayCount(importedCount || 0) }) }}</p>
</v-window-item>
</v-window>
</v-card-text>
<v-card-text>
<div class="d-flex justify-center justify-sm-space-between flex-wrap mt-sm-1 mt-md-2 gap-4">
<v-btn color="secondary" variant="tonal" :disabled="loading || submitting"
:prepend-icon="mdiClose" @click="close(false)"
v-if="currentStep !== 'finalResult'">{{ tt('Cancel') }}</v-btn>
<v-btn class="button-icon-with-direction" color="primary"
:disabled="loading || submitting || (!isImportDataFromTextbox && !importFile) || (isImportDataFromTextbox && !importData) || (!isImportDataFromTextbox && allSupportedEncodings && fileEncoding === 'auto' && !autoDetectedFileEncoding)"
:append-icon="!submitting ? mdiArrowRight : undefined" @click="parseData"
v-if="currentStep === 'defineColumn' || currentStep === 'executeCustomScript' || currentStep === 'uploadFile'">
{{ tt('Next') }}
<v-progress-circular indeterminate size="22" class="ms-2" v-if="submitting"></v-progress-circular>
</v-btn>
<v-btn class="button-icon-with-direction" color="teal"
:disabled="submitting || importTransactionCheckDataTab?.isEditing || !importTransactionCheckDataTab?.canImport"
:append-icon="!submitting ? mdiArrowRight : undefined" @click="submit"
v-if="currentStep === 'checkData'">
{{ (submitting && importProcess > 0 ? tt('format.misc.importingTransactions', { process: formatNumberToLocalizedNumerals(importProcess, 2) }) : tt('Import')) }}
<v-progress-circular indeterminate size="22" class="ms-2" v-if="submitting"></v-progress-circular>
</v-btn>
<v-btn color="secondary" variant="tonal"
:append-icon="mdiCheck"
@click="close(true)"
v-if="currentStep === 'finalResult'">{{ tt('Close') }}</v-btn>
</div>
</v-card-text>
</v-card>
</v-dialog>
@@ -1,14 +1,13 @@
<template>
<v-dialog width="600" :persistent="submitting || !!selectedNames.length" v-model="showState">
<v-card class="pa-2 pa-sm-4 pa-md-4">
<v-card class="pa-sm-1 pa-md-2">
<template #title>
<div class="d-flex align-center justify-center">
<div class="d-flex w-100 align-center justify-center">
<h4 class="text-h4" v-if="type === 'expenseCategory'">{{ tt('Create Nonexistent Expense Categories') }}</h4>
<h4 class="text-h4" v-if="type === 'incomeCategory'">{{ tt('Create Nonexistent Income Categories') }}</h4>
<h4 class="text-h4" v-if="type === 'transferCategory'">{{ tt('Create Nonexistent Transfer Categories') }}</h4>
<h4 class="text-h4" v-if="type === 'tag'">{{ tt('Create Nonexistent Transaction Tags') }}</h4>
</div>
<div class="d-flex flex-wrap">
<h4 class="text-h4 text-wrap" v-if="type === 'expenseCategory'">{{ tt('Create Nonexistent Expense Categories') }}</h4>
<h4 class="text-h4 text-wrap" v-if="type === 'incomeCategory'">{{ tt('Create Nonexistent Income Categories') }}</h4>
<h4 class="text-h4 text-wrap" v-if="type === 'transferCategory'">{{ tt('Create Nonexistent Transfer Categories') }}</h4>
<h4 class="text-h4 text-wrap" v-if="type === 'tag'">{{ tt('Create Nonexistent Transaction Tags') }}</h4>
<v-spacer/>
<v-btn density="comfortable" color="default" variant="text" class="ms-2"
:disabled="submitting || !invalidItems || !invalidItems.length" :icon="true">
<v-icon :icon="mdiDotsVertical" />
@@ -31,12 +30,12 @@
</v-btn>
</div>
</template>
<v-card-text class="my-md-4 w-100 d-flex justify-center">
<v-card-text class="d-flex flex-column flex-md-row flex-grow-1 overflow-y-auto">
<v-row>
<v-col cols="12">
<v-col cols="12" class="px-0">
<v-list class="py-0" density="compact" select-strategy="classic"
:disabled="submitting" v-model:selected="selectedNames">
<v-list-item class="py-0"
<v-list-item class="mx-1 px-2 py-0"
:key="item.value" :value="item.name" :title="item.name"
v-for="item in invalidItems">
<template #prepend="{ isActive }">
@@ -50,8 +49,8 @@
</v-col>
</v-row>
</v-card-text>
<v-card-text class="overflow-y-visible">
<div class="w-100 d-flex justify-center gap-4">
<v-card-text>
<div class="w-100 d-flex justify-center flex-wrap mt-sm-1 mt-md-2 gap-4">
<v-btn :disabled="submitting || !selectedNames || !selectedNames.length" @click="confirm">
{{ tt('OK') }}
<v-progress-circular indeterminate size="22" class="ms-2" v-if="submitting"></v-progress-circular>
@@ -1,10 +1,10 @@
<template>
<v-dialog width="1000" :persistent="loading || !!rules.length || !!newRule.targetId" v-model="showState">
<v-card class="pa-2 pa-sm-4 pa-md-4">
<v-card class="pa-sm-1 pa-md-2">
<template #title>
<div class="d-flex align-center justify-center">
<div class="d-flex w-100 align-center justify-center">
<h4 class="text-h4">{{ tt('Batch Replace Categories / Accounts / Tags') }}</h4>
<div class="d-flex flex-wrap align-center justify-center">
<div class="d-flex flex-wrap align-center">
<h4 class="text-h4 text-wrap">{{ tt('Batch Replace Categories / Accounts / Tags') }}</h4>
<v-btn density="compact" color="default" variant="text" size="24"
class="ms-2" :icon="true" :disabled="loading"
:loading="loading" @click="reload">
@@ -15,6 +15,7 @@
<v-tooltip activator="parent">{{ tt('Refresh') }}</v-tooltip>
</v-btn>
</div>
<v-spacer/>
<v-btn density="comfortable" color="default" variant="text" class="ms-2"
:icon="true" :disabled="loading">
<v-icon :icon="mdiDotsVertical" />
@@ -31,10 +32,10 @@
</v-btn>
</div>
</template>
<v-card-text class="my-md-4 w-100 d-flex justify-center">
<v-card-text class="w-100 d-flex justify-center">
<v-row>
<v-col cols="12">
<v-table fixed-header fixed-footer height="400" striped="even">
<v-table density="comfortable" fixed-header fixed-footer height="400" striped="even">
<thead>
<tr>
<th class="text-left">{{ tt('Type') }}</th>
@@ -57,7 +58,7 @@
<tfoot>
<tr style="background-color: rgb(var(--v-theme-surface))">
<td>
<v-select class="w-100" density="compact" variant="outlined"
<v-select class="w-100" density="compact" variant="underlined"
item-title="name"
item-value="value"
:disabled="loading"
@@ -88,7 +89,7 @@
/>
</td>
<td>
<v-autocomplete class="w-100" density="compact" variant="outlined"
<v-autocomplete class="w-100" density="compact" variant="underlined"
item-title="name" item-value="value" persistent-placeholder
:disabled="loading" :items="sourceItems"
:no-data-text="noSourceItemText"
@@ -96,7 +97,7 @@
</v-autocomplete>
</td>
<td>
<two-column-select density="compact" variant="outlined"
<two-column-select density="compact" variant="underlined"
primary-key-field="id" primary-value-field="id" primary-title-field="name"
primary-icon-field="icon" primary-icon-type="category" primary-color-field="color"
primary-hidden-field="hidden" primary-sub-items-field="subCategories"
@@ -112,7 +113,7 @@
v-model="newRule.targetId"
v-if="newRule.dataType === 'expenseCategory'">
</two-column-select>
<two-column-select density="compact" variant="outlined"
<two-column-select density="compact" variant="underlined"
primary-key-field="id" primary-value-field="id" primary-title-field="name"
primary-icon-field="icon" primary-icon-type="category" primary-color-field="color"
primary-hidden-field="hidden" primary-sub-items-field="subCategories"
@@ -128,7 +129,7 @@
v-model="newRule.targetId"
v-if="newRule.dataType === 'incomeCategory'">
</two-column-select>
<two-column-select density="compact" variant="outlined"
<two-column-select density="compact" variant="underlined"
primary-key-field="id" primary-value-field="id" primary-title-field="name"
primary-icon-field="icon" primary-icon-type="category" primary-color-field="color"
primary-hidden-field="hidden" primary-sub-items-field="subCategories"
@@ -144,7 +145,7 @@
v-model="newRule.targetId"
v-if="newRule.dataType === 'transferCategory'">
</two-column-select>
<two-column-select density="compact" variant="outlined"
<two-column-select density="compact" variant="underlined"
primary-key-field="id" primary-value-field="category"
primary-title-field="name" primary-footer-field="displayBalance"
primary-icon-field="icon" primary-icon-type="account"
@@ -160,7 +161,7 @@
v-model="newRule.targetId"
v-if="newRule.dataType === 'account'">
</two-column-select>
<v-autocomplete density="compact" variant="outlined"
<v-autocomplete density="compact" variant="underlined"
item-title="name" item-value="id"
persistent-placeholder chips
:disabled="loading" :items="allTags"
@@ -196,8 +197,8 @@
</v-col>
</v-row>
</v-card-text>
<v-card-text class="overflow-y-visible">
<div class="w-100 d-flex justify-center gap-4">
<v-card-text>
<div class="w-100 d-flex justify-center flex-wrap mt-sm-1 mt-md-2 gap-4">
<v-btn :disabled="loading" @click="confirm">{{ tt('OK') }}</v-btn>
<v-btn color="secondary" variant="tonal" :disabled="loading" @click="cancel">{{ tt('Cancel') }}</v-btn>
</div>
@@ -1,20 +1,20 @@
<template>
<v-dialog width="600" :persistent="loading || (mode === 'replaceInvalidItems' && !!sourceItem) || !!targetItem" v-model="showState">
<v-card class="pa-2 pa-sm-4 pa-md-4">
<v-card class="pa-sm-1 pa-md-2">
<template #title>
<div class="d-flex align-center justify-center">
<h4 class="text-h4" v-if="mode === 'batchReplace' && type === 'expenseCategory'">{{ tt('Batch Replace Selected Expense Categories') }}</h4>
<h4 class="text-h4" v-if="mode === 'batchReplace' && type === 'incomeCategory'">{{ tt('Batch Replace Selected Income Categories') }}</h4>
<h4 class="text-h4" v-if="mode === 'batchReplace' && type === 'transferCategory'">{{ tt('Batch Replace Selected Transfer Categories') }}</h4>
<h4 class="text-h4" v-if="mode === 'batchReplace' && type === 'account'">{{ tt('Batch Replace Selected Accounts') }}</h4>
<h4 class="text-h4" v-if="mode === 'batchReplace' && type === 'destinationAccount'">{{ tt('Batch Replace Selected Destination Accounts') }}</h4>
<h4 class="text-h4" v-if="mode === 'batchReplace' && type === 'tag'">{{ tt('Batch Replace Selected Transaction Tags') }}</h4>
<h4 class="text-h4" v-if="mode === 'batchAdd' && type === 'tag'">{{ tt('Batch Add Transaction Tags') }}</h4>
<h4 class="text-h4" v-if="mode === 'replaceInvalidItems' && type === 'expenseCategory'">{{ tt('Replace Invalid Expense Categories') }}</h4>
<h4 class="text-h4" v-if="mode === 'replaceInvalidItems' && type === 'incomeCategory'">{{ tt('Replace Invalid Income Categories') }}</h4>
<h4 class="text-h4" v-if="mode === 'replaceInvalidItems' && type === 'transferCategory'">{{ tt('Replace Invalid Transfer Categories') }}</h4>
<h4 class="text-h4" v-if="mode === 'replaceInvalidItems' && type === 'account'">{{ tt('Replace Invalid Accounts') }}</h4>
<h4 class="text-h4" v-if="mode === 'replaceInvalidItems' && type === 'tag'">{{ tt('Replace Invalid Transaction Tags') }}</h4>
<div class="d-flex flex-wrap align-center">
<h4 class="text-h4 text-wrap" v-if="mode === 'batchReplace' && type === 'expenseCategory'">{{ tt('Batch Replace Selected Expense Categories') }}</h4>
<h4 class="text-h4 text-wrap" v-if="mode === 'batchReplace' && type === 'incomeCategory'">{{ tt('Batch Replace Selected Income Categories') }}</h4>
<h4 class="text-h4 text-wrap" v-if="mode === 'batchReplace' && type === 'transferCategory'">{{ tt('Batch Replace Selected Transfer Categories') }}</h4>
<h4 class="text-h4 text-wrap" v-if="mode === 'batchReplace' && type === 'account'">{{ tt('Batch Replace Selected Accounts') }}</h4>
<h4 class="text-h4 text-wrap" v-if="mode === 'batchReplace' && type === 'destinationAccount'">{{ tt('Batch Replace Selected Destination Accounts') }}</h4>
<h4 class="text-h4 text-wrap" v-if="mode === 'batchReplace' && type === 'tag'">{{ tt('Batch Replace Selected Transaction Tags') }}</h4>
<h4 class="text-h4 text-wrap" v-if="mode === 'batchAdd' && type === 'tag'">{{ tt('Batch Add Transaction Tags') }}</h4>
<h4 class="text-h4 text-wrap" v-if="mode === 'replaceInvalidItems' && type === 'expenseCategory'">{{ tt('Replace Invalid Expense Categories') }}</h4>
<h4 class="text-h4 text-wrap" v-if="mode === 'replaceInvalidItems' && type === 'incomeCategory'">{{ tt('Replace Invalid Income Categories') }}</h4>
<h4 class="text-h4 text-wrap" v-if="mode === 'replaceInvalidItems' && type === 'transferCategory'">{{ tt('Replace Invalid Transfer Categories') }}</h4>
<h4 class="text-h4 text-wrap" v-if="mode === 'replaceInvalidItems' && type === 'account'">{{ tt('Replace Invalid Accounts') }}</h4>
<h4 class="text-h4 text-wrap" v-if="mode === 'replaceInvalidItems' && type === 'tag'">{{ tt('Replace Invalid Transaction Tags') }}</h4>
<v-btn density="compact" color="default" variant="text" size="24"
class="ms-2" :icon="true" :disabled="loading"
:loading="loading" @click="reload">
@@ -26,7 +26,7 @@
</v-btn>
</div>
</template>
<v-card-text class="my-md-4 w-100 d-flex justify-center" v-if="type === 'expenseCategory' || type === 'incomeCategory' || type === 'transferCategory'">
<v-card-text class="w-100 d-flex justify-center" v-if="type === 'expenseCategory' || type === 'incomeCategory' || type === 'transferCategory'">
<v-row>
<v-col cols="12" v-if="mode === 'replaceInvalidItems'">
<v-autocomplete
@@ -96,7 +96,7 @@
</v-col>
</v-row>
</v-card-text>
<v-card-text class="my-md-4 w-100 d-flex justify-center" v-if="type === 'account' || type === 'destinationAccount'">
<v-card-text class="w-100 d-flex justify-center" v-if="type === 'account' || type === 'destinationAccount'">
<v-row>
<v-col cols="12" v-if="mode === 'replaceInvalidItems'">
<v-autocomplete
@@ -131,7 +131,7 @@
</v-col>
</v-row>
</v-card-text>
<v-card-text class="my-md-4 w-100 d-flex justify-center" v-if="type === 'tag'">
<v-card-text class="w-100 d-flex justify-center" v-if="type === 'tag'">
<v-row>
<v-col cols="12" v-if="mode === 'batchReplace'">
<v-autocomplete
@@ -196,8 +196,8 @@
</v-col>
</v-row>
</v-card-text>
<v-card-text class="overflow-y-visible">
<div class="w-100 d-flex justify-center gap-4">
<v-card-text>
<div class="w-100 d-flex justify-center flex-wrap mt-sm-1 mt-md-2 gap-4">
<v-btn :disabled="loading || ((mode === 'replaceInvalidItems' || (mode === 'batchReplace' && type === 'tag')) && !sourceItem && sourceItem !== '') || (!targetItem && targetItem !== '' && !removeTag)" @click="confirm">{{ tt('OK') }}</v-btn>
<v-btn color="secondary" variant="tonal" :disabled="loading" @click="cancel">{{ tt('Cancel') }}</v-btn>
</div>
@@ -328,14 +328,14 @@
</v-data-table>
<v-dialog width="640" v-model="showCustomAmountFilterDialog">
<v-card class="pa-2 pa-sm-4 pa-md-4">
<v-card class="pa-sm-1 pa-md-2">
<template #title>
<div class="d-flex align-center justify-center">
<div class="d-flex align-center">
<h4 class="text-h4">{{ tt('Filter Amount') }}</h4>
</div>
</template>
<v-card-text class="mb-md-4 w-100 d-flex justify-center">
<div class="ms-2 me-2 d-flex flex-column justify-center" v-if="currentAmountFilterType">
<v-card-text class="w-100 d-flex justify-center">
<div class="me-2 d-flex flex-column justify-center" v-if="currentAmountFilterType">
{{ tt(currentAmountFilterType.name) }}
</div>
<amount-input :currency="defaultCurrency"
@@ -347,8 +347,8 @@
v-model="currentAmountFilterValue2"
v-if="currentAmountFilterType && currentAmountFilterType.paramCount === 2"/>
</v-card-text>
<v-card-text class="overflow-y-visible">
<div class="w-100 d-flex justify-center gap-4">
<v-card-text>
<div class="w-100 d-flex justify-center flex-wrap mt-sm-1 mt-md-2 gap-4">
<v-btn @click="showCustomAmountFilterDialog = false; filters.amount = currentAmountFilterType?.toTextualFilter(currentAmountFilterValue1, currentAmountFilterValue2) ?? null">{{ tt('OK') }}</v-btn>
<v-btn color="secondary" variant="tonal" @click="showCustomAmountFilterDialog = false">{{ tt('Cancel') }}</v-btn>
</div>
@@ -357,13 +357,13 @@
</v-dialog>
<v-dialog width="640" v-model="showCustomDescriptionDialog">
<v-card class="pa-2 pa-sm-4 pa-md-4">
<v-card class="pa-sm-1 pa-md-2">
<template #title>
<div class="d-flex align-center justify-center">
<div class="d-flex align-center">
<h4 class="text-h4">{{ tt('Filter Description') }}</h4>
</div>
</template>
<v-card-text class="mb-md-4 w-100 d-flex justify-center">
<v-card-text class="w-100 d-flex justify-center">
<v-text-field
type="text"
persistent-placeholder
@@ -372,8 +372,8 @@
v-model="currentDescriptionFilterValue"
/>
</v-card-text>
<v-card-text class="overflow-y-visible">
<div class="w-100 d-flex justify-center gap-4">
<v-card-text>
<div class="w-100 d-flex justify-center flex-wrap mt-sm-1 mt-md-2 gap-4">
<v-btn :disabled="!currentDescriptionFilterValue" @click="showCustomDescriptionDialog = false; filters.description = currentDescriptionFilterValue">{{ tt('OK') }}</v-btn>
<v-btn color="secondary" variant="tonal" @click="showCustomDescriptionDialog = false; currentDescriptionFilterValue = ''">{{ tt('Cancel') }}</v-btn>
</div>
@@ -449,9 +449,9 @@ import {
mdiPencilOutline,
mdiAlertOutline,
mdiPound,
mdiFindReplace,
mdiTextBoxEditOutline,
mdiShapePlusOutline,
mdiTransfer,
mdiPencilBoxMultipleOutline,
mdiNumericPositive1,
mdiNumericNegative1
} from '@mdi/js';
@@ -749,85 +749,85 @@ const filterMenus = computed<ImportTransactionCheckDataMenuGroup[]>(() => [
const toolMenus = computed<ImportTransactionCheckDataMenu[]>(() => [
{
prependIcon: mdiFindReplace,
prependIcon: mdiTextBoxEditOutline,
title: tt('Batch Replace Categories / Accounts / Tags'),
disabled: isEditing.value,
onClick: showReplaceAllTypesDialog
},
{
prependIcon: mdiTextBoxEditOutline,
title: tt('Batch Replace Selected Expense Categories'),
disabled: isEditing.value || selectedExpenseTransactionCount.value < 1,
divider: true,
onClick: () => showBatchReplaceDialog('expenseCategory')
},
{
prependIcon: mdiFindReplace,
prependIcon: mdiTextBoxEditOutline,
title: tt('Batch Replace Selected Income Categories'),
disabled: isEditing.value || selectedIncomeTransactionCount.value < 1,
onClick: () => showBatchReplaceDialog('incomeCategory')
},
{
prependIcon: mdiFindReplace,
prependIcon: mdiTextBoxEditOutline,
title: tt('Batch Replace Selected Transfer Categories'),
disabled: isEditing.value || selectedTransferTransactionCount.value < 1,
onClick: () => showBatchReplaceDialog('transferCategory')
},
{
prependIcon: mdiFindReplace,
prependIcon: mdiTextBoxEditOutline,
title: tt('Batch Replace Selected Accounts'),
disabled: isEditing.value || selectedImportTransactionCount.value < 1,
onClick: () => showBatchReplaceDialog('account')
},
{
prependIcon: mdiFindReplace,
prependIcon: mdiTextBoxEditOutline,
title: tt('Batch Replace Selected Destination Accounts'),
disabled: isEditing.value || selectedTransferTransactionCount.value < 1,
onClick: () => showBatchReplaceDialog('destinationAccount')
},
{
prependIcon: mdiFindReplace,
prependIcon: mdiTextBoxEditOutline,
title: tt('Batch Replace Selected Transaction Tags'),
disabled: isEditing.value || selectedImportTransactionCount.value < 1,
onClick: () => showBatchReplaceDialog('tag', allOriginalTransactionTagNames.value)
},
{
prependIcon: mdiFindReplace,
prependIcon: mdiTextBoxEditOutline,
title: tt('Batch Add Transaction Tags'),
disabled: isEditing.value || selectedImportTransactionCount.value < 1,
onClick: () => showBatchAddDialog('tag')
},
{
prependIcon: mdiFindReplace,
prependIcon: mdiTextBoxEditOutline,
title: tt('Replace Invalid Expense Categories'),
disabled: isEditing.value || !allInvalidExpenseCategoryNames.value || allInvalidExpenseCategoryNames.value.length < 1,
divider: true,
onClick: () => showReplaceInvalidItemDialog('expenseCategory', allInvalidExpenseCategoryNames.value)
},
{
prependIcon: mdiFindReplace,
prependIcon: mdiTextBoxEditOutline,
title: tt('Replace Invalid Income Categories'),
disabled: isEditing.value || !allInvalidIncomeCategoryNames.value || allInvalidIncomeCategoryNames.value.length < 1,
onClick: () => showReplaceInvalidItemDialog('incomeCategory', allInvalidIncomeCategoryNames.value)
},
{
prependIcon: mdiFindReplace,
prependIcon: mdiTextBoxEditOutline,
title: tt('Replace Invalid Transfer Categories'),
disabled: isEditing.value || !allInvalidTransferCategoryNames.value || allInvalidTransferCategoryNames.value.length < 1,
onClick: () => showReplaceInvalidItemDialog('transferCategory', allInvalidTransferCategoryNames.value)
},
{
prependIcon: mdiFindReplace,
prependIcon: mdiTextBoxEditOutline,
title: tt('Replace Invalid Accounts'),
disabled: isEditing.value || !allInvalidAccountNames.value || allInvalidAccountNames.value.length < 1,
onClick: () => showReplaceInvalidItemDialog('account', allInvalidAccountNames.value)
},
{
prependIcon: mdiFindReplace,
prependIcon: mdiTextBoxEditOutline,
title: tt('Replace Invalid Transaction Tags'),
disabled: isEditing.value || !allInvalidTransactionTagNames.value || allInvalidTransactionTagNames.value.length < 1,
onClick: () => showReplaceInvalidItemDialog('tag', allInvalidTransactionTagNames.value)
},
{
prependIcon: mdiFindReplace,
title: tt('Batch Replace Categories / Accounts / Tags'),
disabled: isEditing.value,
divider: true,
onClick: showReplaceAllTypesDialog
},
{
prependIcon: mdiShapePlusOutline,
title: tt('Create Nonexistent Expense Categories'),
@@ -854,38 +854,38 @@ const toolMenus = computed<ImportTransactionCheckDataMenu[]>(() => [
onClick: () => showBatchCreateInvalidItemDialog('tag', allInvalidTransactionTagNames.value)
},
{
prependIcon: mdiTransfer,
prependIcon: mdiPencilBoxMultipleOutline,
title: tt('Batch Convert Expense Transaction to Income Transaction'),
disabled: isEditing.value || selectedExpenseTransactionCount.value < 1,
divider: true,
onClick: () => convertTransactionType(TransactionType.Expense, TransactionType.Income)
},
{
prependIcon: mdiTransfer,
prependIcon: mdiPencilBoxMultipleOutline,
title: tt('Batch Convert Expense Transaction to Transfer Transaction'),
disabled: isEditing.value || selectedExpenseTransactionCount.value < 1,
onClick: () => convertTransactionType(TransactionType.Expense, TransactionType.Transfer)
},
{
prependIcon: mdiTransfer,
prependIcon: mdiPencilBoxMultipleOutline,
title: tt('Batch Convert Income Transaction to Expense Transaction'),
disabled: isEditing.value || selectedIncomeTransactionCount.value < 1,
onClick: () => convertTransactionType(TransactionType.Income, TransactionType.Expense)
},
{
prependIcon: mdiTransfer,
prependIcon: mdiPencilBoxMultipleOutline,
title: tt('Batch Convert Income Transaction to Transfer Transaction'),
disabled: isEditing.value || selectedIncomeTransactionCount.value < 1,
onClick: () => convertTransactionType(TransactionType.Income, TransactionType.Transfer)
},
{
prependIcon: mdiTransfer,
prependIcon: mdiPencilBoxMultipleOutline,
title: tt('Batch Convert Transfer Transaction to Expense Transaction'),
disabled: isEditing.value || selectedTransferTransactionCount.value < 1,
onClick: () => convertTransactionType(TransactionType.Transfer, TransactionType.Expense)
},
{
prependIcon: mdiTransfer,
prependIcon: mdiPencilBoxMultipleOutline,
title: tt('Batch Convert Transfer Transaction to Income Transaction'),
disabled: isEditing.value || selectedTransferTransactionCount.value < 1,
onClick: () => convertTransactionType(TransactionType.Transfer, TransactionType.Income)
@@ -909,7 +909,7 @@ const importTransactionsTableHeight = computed<number | undefined>(() => {
if (countPerPage.value <= 10 || !props.importTransactions || props.importTransactions.length <= 10) {
return undefined;
} else {
return 400;
return 380;
}
});
@@ -1,20 +1,18 @@
<template>
<v-dialog width="800" :persistent="loading || recognizing || !!imageFile" v-model="showState" @paste="onPaste">
<v-card class="pa-2 pa-sm-4 pa-md-4">
<v-card class="pa-sm-1 pa-md-2">
<template #title>
<div class="d-flex align-center justify-center">
<h4 class="text-h4">{{ tt('AI Image Recognition') }}</h4>
</div>
<h4 class="text-h4">{{ tt('AI Image Recognition') }}</h4>
</template>
<v-card-text class="d-flex justify-center w-100 my-md-4 pt-0">
<div class="w-100 border position-relative"
<v-card-text class="d-flex flex-column flex-md-row flex-grow-1 overflow-y-auto" style="height: 480px">
<div class="w-100 h-100 border position-relative"
@dragenter.prevent="onDragEnter"
@dragover.prevent
@dragleave.prevent="onDragLeave"
@drop.prevent="onDrop">
<div class="d-flex w-100 fill-height justify-center align-center justify-content-center text-center px-4"
:class="{ 'dropzone': true, 'dropzone-dragover': isDragOver }" style="height: 480px">
<div class="d-flex w-100 h-100 justify-center align-center justify-content-center text-center px-4"
:class="{ 'dropzone': true, 'dropzone-dragover': isDragOver }">
<div class="d-inline-flex flex-column" v-if="!loading && !imageFile && !isDragOver">
<h3 :class="{ 'pa-2': true, 'bg-grey-200': !isDarkMode, 'bg-grey-100': isDarkMode }">{{ tt('You can drag and drop, paste or click to select a receipt or transaction image') }}</h3>
<span :class="{ 'pa-2': true, 'bg-grey-200': !isDarkMode, 'bg-grey-100': isDarkMode }">{{ tt('Uploaded image and personal data will be sent to the large language model, please be aware of potential privacy risks.') }}</span>
@@ -23,17 +21,17 @@
<h3 class="pa-2" v-else-if="loading">{{ tt('Loading image...') }}</h3>
<h3 :class="{ 'pa-2': true, 'bg-grey-200': !isDarkMode, 'bg-grey-100': isDarkMode }" v-else-if="recognizing">{{ tt('AI can make mistakes. Check important info.') }}</h3>
</div>
<v-img height="480px" :class="{ 'cursor-pointer': !loading && !recognizing && !isDragOver }"
<v-img :class="{ 'cursor-pointer': !loading && !recognizing && !isDragOver, 'h-100': true }"
:src="imageSrc" @click="showOpenImageDialog">
<template #placeholder>
<div :class="{ 'w-100 fill-height': true, 'bg-grey-200': !isDarkMode, 'bg-grey-100': isDarkMode }"></div>
<div :class="{ 'w-100 h-100': true, 'bg-grey-200': !isDarkMode, 'bg-grey-100': isDarkMode }"></div>
</template>
</v-img>
</div>
</v-card-text>
<v-card-text class="overflow-y-visible">
<div ref="buttonContainer" class="w-100 d-flex justify-center gap-4">
<v-card-text>
<div class="w-100 d-flex justify-center flex-wrap mt-sm-1 mt-md-2 gap-4">
<v-btn :disabled="loading || recognizing || !imageFile" @click="recognize">
{{ tt('Recognize') }}
<v-progress-circular indeterminate size="22" class="ms-2" v-if="recognizing"></v-progress-circular>
@@ -1,12 +1,13 @@
<template>
<v-dialog width="1000" :persistent="isTransactionModified" v-model="showState">
<v-card class="pa-2 pa-sm-4 pa-md-8">
<v-card class="pa-sm-1 pa-md-2">
<template #title>
<div class="d-flex align-center justify-center">
<div class="d-flex w-100 align-center justify-center">
<div class="d-flex align-center">
<h4 class="text-h4">{{ tt(title) }}</h4>
<v-progress-circular indeterminate size="22" class="ms-2" v-if="loading"></v-progress-circular>
</div>
<v-spacer/>
<v-btn density="comfortable" color="default" variant="text" class="ms-2" :icon="true"
:disabled="loading || submitting" v-if="mode !== TransactionEditPageMode.View && (activeTab === 'basicInfo' || (activeTab === 'map' && isSupportGetGeoLocationByClick()))">
<v-icon :icon="mdiDotsVertical" />
@@ -49,7 +50,7 @@
</v-btn>
</div>
</template>
<v-card-text class="d-flex flex-column flex-md-row mt-md-4 pt-0">
<v-card-text class="d-flex flex-column flex-md-row flex-grow-1 overflow-y-auto">
<div class="mb-4">
<v-tabs class="v-tabs-pill" direction="vertical" :class="{ 'readonly': type === TransactionEditPageType.Transaction && mode !== TransactionEditPageMode.Add }"
:disabled="loading || submitting" v-model="transaction.type">
@@ -448,8 +449,8 @@
</v-window-item>
</v-window>
</v-card-text>
<v-card-text class="overflow-y-visible">
<div class="w-100 d-flex justify-center flex-wrap mt-2 mt-sm-4 mt-md-6 gap-4">
<v-card-text>
<div class="w-100 d-flex justify-center flex-wrap mt-sm-1 mt-md-2 gap-4">
<v-tooltip :disabled="!inputIsEmpty" :text="inputEmptyProblemMessage ? tt(inputEmptyProblemMessage) : ''">
<template v-slot:activator="{ props }">
<div v-bind="props" class="d-inline-block">
@@ -1260,48 +1261,36 @@ defineExpose({
@media (min-height: 630px) {
.transaction-edit-map-view {
height: 300px;
height: 390px;
}
@media (min-width: 960px) {
.transaction-pictures {
min-height: 300px;
min-height: 414px;
}
}
}
@media (min-height: 700px) {
.transaction-edit-map-view {
height: 350px;
height: 460px;
}
@media (min-width: 960px) {
.transaction-pictures {
min-height: 350px;
min-height: 484px;
}
}
}
@media (min-height: 800px) {
@media (min-height: 780px) {
.transaction-edit-map-view {
height: 450px;
height: 538px;
}
@media (min-width: 960px) {
.transaction-pictures {
min-height: 450px;
}
}
}
@media (min-height: 900px) {
.transaction-edit-map-view {
height: 550px;
}
@media (min-width: 960px) {
.transaction-pictures {
min-height: 550px;
min-height: 562px;
}
}
}