|
|
@@ -1,23 +1,38 @@
|
|
|
<template>
|
|
|
- <q-dialog ref="dialogRef" persistent maximized transition-show="slide-left" transition-hide="slide-right">
|
|
|
+ <q-dialog ref="dialogRef" maximized persistent transition-hide="slide-right" transition-show="slide-left">
|
|
|
<div class="bg-page full-height no-shadow">
|
|
|
<div class="row items-center q-px-md q-pb-sm bg-white shadow-profile bg-surface dialog-safe-header">
|
|
|
- <q-btn v-close-popup icon="mdi-chevron-left" flat round dense color="primary" />
|
|
|
+ <q-btn v-close-popup color dense flat icon="mdi-chevron-left" round="primary" />
|
|
|
+
|
|
|
<q-space />
|
|
|
- <span class="gradient-diarista font16 fontbold">{{ $t('profile.bank_data.title') }}</span>
|
|
|
+
|
|
|
+ <span class="gradient-diarista font16 fontbold">
|
|
|
+ {{ $t('profile.bank_data.title') }}
|
|
|
+ </span>
|
|
|
+
|
|
|
<q-space />
|
|
|
+
|
|
|
<div style="width: 32px"></div>
|
|
|
</div>
|
|
|
|
|
|
<q-card-section class="col">
|
|
|
<div class="text-text">
|
|
|
- <div class="gradient-diarista font14 fontbold">{{ $t('profile.bank_data.my_bank_data_title') }}</div>
|
|
|
- <div class="text-grey-7 q-mb-md font12 fontregular">{{ $t('profile.bank_data.subtitle') }}</div>
|
|
|
+ <div class="gradient-diarista font14 fontbold">
|
|
|
+ {{ $t('profile.bank_data.my_bank_data_title') }}
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="text-grey-7 q-mb-md font12 fontregular">
|
|
|
+ {{ $t('profile.bank_data.subtitle') }}
|
|
|
+ </div>
|
|
|
|
|
|
<div class="pix-warning q-pa-md q-mb-md row items-start q-col-gutter-sm">
|
|
|
- <q-icon name="mdi-alert-outline" color="warning" size="20px" class="q-mt-xs" />
|
|
|
+ <q-icon class="q-mt-xs" color="warning" name="mdi-alert-outline" size="20px" />
|
|
|
+
|
|
|
<div class="col font12">
|
|
|
- <span class="fontbold">{{ $t('profile.bank_data.warning_title') }}</span>
|
|
|
+ <span class="fontbold">
|
|
|
+ {{ $t('profile.bank_data.warning_title') }}
|
|
|
+ </span>
|
|
|
+
|
|
|
{{ $t('profile.bank_data.warning_message') }}
|
|
|
</div>
|
|
|
</div>
|
|
|
@@ -26,27 +41,29 @@
|
|
|
<div class="text-center text-text q-mb-sm font14 fontbold">
|
|
|
<div>
|
|
|
{{ $t('profile.bank_data.pix_title') }}
|
|
|
- <q-badge color="grey-5" class="q-ml-xs" :label="$t('profile.bank_data.in_development')" />
|
|
|
+
|
|
|
+ <q-badge class="q-ml-xs" color="grey-5" :label="$t('profile.bank_data.in_development')" />
|
|
|
</div>
|
|
|
</div>
|
|
|
+
|
|
|
<q-input
|
|
|
- outlined
|
|
|
+ class="q-mb-sm"
|
|
|
dense
|
|
|
disable
|
|
|
input-class="text-text"
|
|
|
+ outlined
|
|
|
:placeholder="$t('profile.bank_data.pix_key_placeholder')"
|
|
|
- class="q-mb-sm"
|
|
|
/>
|
|
|
|
|
|
<q-btn
|
|
|
- unelevated
|
|
|
- rounded
|
|
|
- no-caps
|
|
|
- color="grey-4"
|
|
|
- text-color="grey-7"
|
|
|
class="full-width"
|
|
|
- padding="4px 12px"
|
|
|
+ color="grey-4"
|
|
|
disable
|
|
|
+ no-caps
|
|
|
+ padding="4px 12px"
|
|
|
+ rounded
|
|
|
+ text-color="grey-7"
|
|
|
+ unelevated
|
|
|
:label="$t('profile.bank_data.in_development')"
|
|
|
/>
|
|
|
</q-card>
|
|
|
@@ -56,93 +73,153 @@
|
|
|
{{ $t('profile.bank_data.bank_account_title') }}
|
|
|
</div>
|
|
|
|
|
|
- <q-input
|
|
|
+ <q-select
|
|
|
+ ref="bankSelectRef"
|
|
|
v-model="bankForm.bank"
|
|
|
- outlined
|
|
|
+ behavior="menu"
|
|
|
+ class="q-mb-sm col-12"
|
|
|
dense
|
|
|
- inputmode="numeric"
|
|
|
- maxlength="3"
|
|
|
+ emit-value
|
|
|
+ fill-input
|
|
|
+ hide-dropdown-icon
|
|
|
+ hide-selected
|
|
|
input-class="text-text"
|
|
|
+ input-debounce="0"
|
|
|
+ map-options
|
|
|
+ maxlength="3"
|
|
|
+ menu-anchor="bottom left"
|
|
|
+ menu-self="top left"
|
|
|
+ options-dense
|
|
|
+ use-input
|
|
|
+ outlined
|
|
|
+ popup-content-class="bank-select-menu"
|
|
|
+ stack-label
|
|
|
:label="$t('profile.bank_data.bank_code')"
|
|
|
+ :loading="loadingBanks"
|
|
|
+ :menu-offset="[0, 4]"
|
|
|
+ :options="bankOptions"
|
|
|
+ :options-dark="false"
|
|
|
:placeholder="$t('profile.bank_data.bank_code_placeholder')"
|
|
|
- class="q-mb-sm col-12"
|
|
|
- />
|
|
|
+ :popup-content-style="bankPopupStyle"
|
|
|
+ @blur="clearInvalidBankInput"
|
|
|
+ @filter="filterBanks"
|
|
|
+ @input-value="onBankInputValue"
|
|
|
+ @popup-show="updateBankSelectWidth"
|
|
|
+ >
|
|
|
+ <template #append>
|
|
|
+ <q-icon color="grey-7" name="mdi-magnify" />
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <template #option="scope">
|
|
|
+ <q-item v-bind="scope.itemProps">
|
|
|
+ <q-item-section class="bank-select-menu__code" side>
|
|
|
+ {{ scope.opt.code }}
|
|
|
+ </q-item-section>
|
|
|
+
|
|
|
+ <q-item-section class="bank-select-menu__name">
|
|
|
+ <q-item-label class="ellipsis">
|
|
|
+ {{ scope.opt.name }}
|
|
|
+ </q-item-label>
|
|
|
+ </q-item-section>
|
|
|
+ </q-item>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <template #no-option>
|
|
|
+ <q-item>
|
|
|
+ <q-item-section class="text-grey">
|
|
|
+ {{ $t("http.errors.no_records_found") }}
|
|
|
+ </q-item-section>
|
|
|
+ </q-item>
|
|
|
+ </template>
|
|
|
+ </q-select>
|
|
|
|
|
|
<div class="row q-mb-sm q-my-auto col-12 row items-center">
|
|
|
<q-radio
|
|
|
v-model="bankForm.type"
|
|
|
- val="checking"
|
|
|
- :label="$t('profile.bank_data.checking')"
|
|
|
+ class="q-mr-lg text-text"
|
|
|
color="primary"
|
|
|
keep-color
|
|
|
- class="q-mr-lg text-text"
|
|
|
+ val="checking"
|
|
|
+ :label="$t('profile.bank_data.checking')"
|
|
|
/>
|
|
|
+
|
|
|
<q-radio
|
|
|
v-model="bankForm.type"
|
|
|
- val="savings"
|
|
|
- :label="$t('profile.bank_data.savings')"
|
|
|
+ class="text-text"
|
|
|
color="primary"
|
|
|
keep-color
|
|
|
- class="text-text"
|
|
|
+ val="savings"
|
|
|
+ :label="$t('profile.bank_data.savings')"
|
|
|
/>
|
|
|
</div>
|
|
|
|
|
|
<div class="row col-12 q-col-gutter-sm q-mb-sm">
|
|
|
<q-input
|
|
|
v-model="bankForm.branch_number"
|
|
|
- outlined
|
|
|
+ class="col-8"
|
|
|
dense
|
|
|
- inputmode="numeric"
|
|
|
input-class="text-text"
|
|
|
+ inputmode="numeric"
|
|
|
+ outlined
|
|
|
+ stack-label
|
|
|
:label="$t('profile.bank_data.agency')"
|
|
|
- class="col-8"
|
|
|
+ :placeholder="$t('profile.bank_data.agency_placeholder')"
|
|
|
/>
|
|
|
+
|
|
|
<q-input
|
|
|
v-model="bankForm.branch_check_digit"
|
|
|
- outlined
|
|
|
+ class="col-4"
|
|
|
dense
|
|
|
- maxlength="2"
|
|
|
input-class="text-text"
|
|
|
+ maxlength="2"
|
|
|
+ outlined
|
|
|
+ stack-label
|
|
|
:label="$t('profile.bank_data.agency_digit')"
|
|
|
- class="col-4"
|
|
|
+ :placeholder="$t('profile.bank_data.digit_placeholder')"
|
|
|
/>
|
|
|
</div>
|
|
|
|
|
|
<div class="row col-12 q-col-gutter-sm q-mb-sm">
|
|
|
<q-input
|
|
|
v-model="bankForm.account_number"
|
|
|
- outlined
|
|
|
+ class="col-8"
|
|
|
dense
|
|
|
- inputmode="numeric"
|
|
|
input-class="text-text"
|
|
|
+ inputmode="numeric"
|
|
|
+ outlined
|
|
|
+ stack-label
|
|
|
:label="$t('profile.bank_data.account')"
|
|
|
- class="col-8"
|
|
|
+ :placeholder="$t('profile.bank_data.account_placeholder')"
|
|
|
/>
|
|
|
+
|
|
|
<q-input
|
|
|
v-model="bankForm.account_check_digit"
|
|
|
- outlined
|
|
|
+ class="col-4"
|
|
|
dense
|
|
|
- maxlength="2"
|
|
|
input-class="text-text"
|
|
|
+ maxlength="2"
|
|
|
+ outlined
|
|
|
+ stack-label
|
|
|
:label="$t('profile.bank_data.account_digit')"
|
|
|
- class="col-4"
|
|
|
+ :placeholder="$t('profile.bank_data.digit_placeholder')"
|
|
|
/>
|
|
|
</div>
|
|
|
|
|
|
<q-btn
|
|
|
- unelevated
|
|
|
- rounded
|
|
|
- no-caps
|
|
|
+ class="full-width"
|
|
|
color="primary"
|
|
|
+ no-caps
|
|
|
padding="8px 16px"
|
|
|
- class="full-width"
|
|
|
+ rounded
|
|
|
+ unelevated
|
|
|
+ :disable="!canSaveBank"
|
|
|
:label="bankAlreadyRegistered ? $t('profile.bank_data.att_bank') : $t('profile.bank_data.save_account')"
|
|
|
:loading="savingBank"
|
|
|
- :disable="!canSaveBank"
|
|
|
@click="saveBank"
|
|
|
/>
|
|
|
</q-card>
|
|
|
</div>
|
|
|
+
|
|
|
<div class="q-pb-xl"></div>
|
|
|
</q-card-section>
|
|
|
</div>
|
|
|
@@ -150,11 +227,12 @@
|
|
|
</template>
|
|
|
|
|
|
<script setup>
|
|
|
-import { ref, reactive, computed, onMounted } from 'vue';
|
|
|
-import { useDialogPluginComponent, useQuasar } from 'quasar';
|
|
|
+import { computed, nextTick, onBeforeUnmount, onMounted, reactive, ref } from 'vue';
|
|
|
+import { getBanks } from 'src/api/banks';
|
|
|
import { updateProviderBankAccount } from 'src/api/providerPaymentMethod';
|
|
|
-import { userStore } from 'src/stores/user';
|
|
|
+import { useDialogPluginComponent, useQuasar } from 'quasar';
|
|
|
import { useI18n } from 'vue-i18n';
|
|
|
+import { userStore } from 'src/stores/user';
|
|
|
|
|
|
defineEmits([...useDialogPluginComponent.emits]);
|
|
|
|
|
|
@@ -163,10 +241,13 @@ const $q = useQuasar();
|
|
|
const { t } = useI18n();
|
|
|
const user = userStore();
|
|
|
|
|
|
-const savingBank = ref(false);
|
|
|
+const banks = ref([]);
|
|
|
const bankAlreadyRegistered = ref(false);
|
|
|
-
|
|
|
-const providerId = computed(() => user.user?.provider?.id ?? user.user?.provider_id);
|
|
|
+const bankOptions = ref([]);
|
|
|
+const bankSelectRef = ref(null);
|
|
|
+const bankSelectWidth = ref(null);
|
|
|
+const loadingBanks = ref(false);
|
|
|
+const savingBank = ref(false);
|
|
|
|
|
|
const bankForm = reactive({
|
|
|
bank: '',
|
|
|
@@ -177,22 +258,150 @@ const bankForm = reactive({
|
|
|
account_check_digit: '',
|
|
|
});
|
|
|
|
|
|
+const bankPopupStyle = computed(() => ({
|
|
|
+ maxHeight: '188px',
|
|
|
+ overflowY: 'auto',
|
|
|
+ width: bankSelectWidth.value ? `${bankSelectWidth.value}px` : undefined,
|
|
|
+ maxWidth: bankSelectWidth.value ? `${bankSelectWidth.value}px` : undefined,
|
|
|
+}));
|
|
|
+
|
|
|
+const providerId = computed(() => user.user?.provider?.id ?? user.user?.provider_id);
|
|
|
+
|
|
|
+const isSelectedBankValid = (code) => {
|
|
|
+ if (!code) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ return banks.value.some((bank) => getBankCode(bank) === code);
|
|
|
+};
|
|
|
+
|
|
|
const canSaveBank = computed(
|
|
|
() =>
|
|
|
!!providerId.value &&
|
|
|
- !!bankForm.bank &&
|
|
|
+ isSelectedBankValid(bankForm.bank) &&
|
|
|
!!bankForm.branch_number &&
|
|
|
!!bankForm.account_number &&
|
|
|
!!bankForm.account_check_digit,
|
|
|
);
|
|
|
|
|
|
+const getBankCode = (bank) => {
|
|
|
+ const code = bank?.code ?? bank?.bank_code ?? bank?.number ?? bank?.value ?? '';
|
|
|
+ return String(code).padStart(3, '0');
|
|
|
+};
|
|
|
+
|
|
|
+const getBankName = (bank) => bank?.name ?? bank?.label ?? bank?.title ?? '';
|
|
|
+
|
|
|
+const getBankList = (response) => {
|
|
|
+ if (Array.isArray(response)) {
|
|
|
+ return response;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (Array.isArray(response?.data)) {
|
|
|
+ return response.data;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (Array.isArray(response?.payload)) {
|
|
|
+ return response.payload;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (response && typeof response === 'object') {
|
|
|
+ return Object.entries(response).map(([code, name]) => ({
|
|
|
+ code,
|
|
|
+ name,
|
|
|
+ }));
|
|
|
+ }
|
|
|
+
|
|
|
+ return [];
|
|
|
+};
|
|
|
+
|
|
|
+//
|
|
|
+
|
|
|
+const filterBanks = (val, update) => {
|
|
|
+ const sanitized = onlyBankCodeDigits(val);
|
|
|
+
|
|
|
+ if (val !== sanitized) {
|
|
|
+ bankSelectRef.value?.updateInputValue(sanitized, true);
|
|
|
+ }
|
|
|
+
|
|
|
+ update(() => {
|
|
|
+ const needle = normalizeText(sanitized);
|
|
|
+
|
|
|
+ if (!needle) {
|
|
|
+ setBankOptions(banks.value);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const filtered = banks.value.filter((bank) => {
|
|
|
+ const code = getBankCode(bank);
|
|
|
+
|
|
|
+ return code.includes(needle);
|
|
|
+ });
|
|
|
+
|
|
|
+ setBankOptions(filtered);
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+const formatBankOption = (bank) => {
|
|
|
+ const code = getBankCode(bank);
|
|
|
+ const name = getBankName(bank);
|
|
|
+
|
|
|
+ return {
|
|
|
+ code,
|
|
|
+ name,
|
|
|
+ label: code,
|
|
|
+ value: code,
|
|
|
+ };
|
|
|
+};
|
|
|
+
|
|
|
+const normalizeText = (text) =>
|
|
|
+ String(text ?? '')
|
|
|
+ .normalize('NFD')
|
|
|
+ .replace(/[\u0300-\u036f]/g, '')
|
|
|
+ .toLowerCase();
|
|
|
+
|
|
|
+const onlyBankCodeDigits = (value) => String(value ?? '').replace(/\D/g, '').slice(0, 3);
|
|
|
+
|
|
|
+const setBankOptions = (list) => {
|
|
|
+ bankOptions.value = list.map(formatBankOption).filter((bank) => bank.code);
|
|
|
+};
|
|
|
+
|
|
|
+const updateBankSelectWidth = async () => {
|
|
|
+ await nextTick();
|
|
|
+
|
|
|
+ bankSelectWidth.value = bankSelectRef.value?.$el?.getBoundingClientRect()?.width ?? null;
|
|
|
+};
|
|
|
+
|
|
|
+//
|
|
|
+
|
|
|
+const clearInvalidBankInput = () => {
|
|
|
+ if (!isSelectedBankValid(bankForm.bank)) {
|
|
|
+ bankForm.bank = '';
|
|
|
+
|
|
|
+ bankSelectRef.value?.updateInputValue('', true);
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const onBankInputValue = (value) => {
|
|
|
+ const sanitized = onlyBankCodeDigits(value);
|
|
|
+
|
|
|
+ if (value !== sanitized) {
|
|
|
+ bankSelectRef.value?.updateInputValue(sanitized, true);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (sanitized !== bankForm.bank) {
|
|
|
+ bankForm.bank = '';
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
const saveBank = async () => {
|
|
|
if (!canSaveBank.value) {
|
|
|
$q.notify({ type: 'negative', message: t('profile.bank_data.bank_fields_required') });
|
|
|
+
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
savingBank.value = true;
|
|
|
+
|
|
|
try {
|
|
|
// Holder (nome/CPF) é derivado do prestador no backend; aqui só os campos digitados.
|
|
|
const bankAccount = {
|
|
|
@@ -205,23 +414,42 @@ const saveBank = async () => {
|
|
|
};
|
|
|
|
|
|
await updateProviderBankAccount(providerId.value, bankAccount);
|
|
|
+
|
|
|
await user.fetchUser();
|
|
|
|
|
|
bankAlreadyRegistered.value = true;
|
|
|
+
|
|
|
$q.notify({ type: 'positive', message: t('profile.bank_data.account_saved') });
|
|
|
} catch (error) {
|
|
|
$q.notify({ type: 'negative', message: t('profile.bank_data.save_error') });
|
|
|
+
|
|
|
console.log(error);
|
|
|
} finally {
|
|
|
savingBank.value = false;
|
|
|
}
|
|
|
};
|
|
|
|
|
|
-onMounted(() => {
|
|
|
+const loadBanks = async () => {
|
|
|
+ loadingBanks.value = true;
|
|
|
+
|
|
|
+ try {
|
|
|
+ const response = await getBanks();
|
|
|
+
|
|
|
+ banks.value = getBankList(response);
|
|
|
+
|
|
|
+ setBankOptions(banks.value);
|
|
|
+ } catch (error) {
|
|
|
+ console.log(error);
|
|
|
+ } finally {
|
|
|
+ loadingBanks.value = false;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+onMounted(async () => {
|
|
|
const bank = user.user?.provider?.recipient_default_bank_account;
|
|
|
|
|
|
if (bank && Object.keys(bank).length) {
|
|
|
- bankForm.bank = bank.bank || '';
|
|
|
+ bankForm.bank = bank.bank ? String(bank.bank).padStart(3, '0') : '';
|
|
|
bankForm.type = bank.type || 'checking';
|
|
|
bankForm.branch_number = bank.branch_number || '';
|
|
|
bankForm.branch_check_digit = bank.branch_check_digit || '';
|
|
|
@@ -229,6 +457,16 @@ onMounted(() => {
|
|
|
bankForm.account_check_digit = bank.account_check_digit || '';
|
|
|
bankAlreadyRegistered.value = true;
|
|
|
}
|
|
|
+
|
|
|
+ await loadBanks();
|
|
|
+
|
|
|
+ await updateBankSelectWidth();
|
|
|
+
|
|
|
+ window.addEventListener('resize', updateBankSelectWidth);
|
|
|
+});
|
|
|
+
|
|
|
+onBeforeUnmount(() => {
|
|
|
+ window.removeEventListener('resize', updateBankSelectWidth);
|
|
|
});
|
|
|
</script>
|
|
|
|
|
|
@@ -238,3 +476,31 @@ onMounted(() => {
|
|
|
border-radius: 12px;
|
|
|
}
|
|
|
</style>
|
|
|
+
|
|
|
+<style>
|
|
|
+.bank-select-menu {
|
|
|
+ background: #FFFFFF !important;
|
|
|
+ color: #1F1F1F !important;
|
|
|
+ border: 1px solid #E7E7E7;
|
|
|
+ box-shadow: 0 6px 18px rgba(0, 0, 0, 0.14);
|
|
|
+ max-height: 188px !important;
|
|
|
+ overflow-y: auto !important;
|
|
|
+}
|
|
|
+
|
|
|
+.bank-select-menu .q-item {
|
|
|
+ min-height: 32px;
|
|
|
+ padding: 6px 10px;
|
|
|
+ color: #1F1F1F;
|
|
|
+}
|
|
|
+
|
|
|
+.bank-select-menu__name {
|
|
|
+ min-width: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.bank-select-menu__code {
|
|
|
+ min-width: 36px;
|
|
|
+ color: #8C8C8C;
|
|
|
+ font-family: monospace;
|
|
|
+ padding-right: 8px;
|
|
|
+}
|
|
|
+</style>
|