Files
boursecu_admin/src/pages/dashboard/components/DashboardQuickWalletModal.vue
T

282 lines
7.9 KiB
Vue
Raw Normal View History

2026-06-21 21:55:54 +03:30
<template>
<BasicActionSheet
v-model:visible="visibleProxy"
:title="modalTitle"
modal-width="22rem"
drawer-height="auto"
compact
:loading="loading"
:show-button="false"
@close="handleClose"
>
<div class="dashboard-wallet-modal">
<div class="dashboard-wallet-modal__section">
<label class="dashboard-wallet-modal__label">مشتری</label>
<a-select
v-model:value="customerId"
show-search
allow-clear
placeholder="انتخاب کنید"
class="dashboard-wallet-modal__select w-full"
size="large"
:options="customerOptions"
:filter-option="filterCustomer"
/>
</div>
<div
v-if="selectedCustomer"
class="dashboard-wallet-modal__balances"
>
<div>
<span class="text-slate-500">طلایی</span>
<p class="font-semibold text-slate-800 dark:text-slate-100">{{ formatGold(selectedCustomer) }}</p>
</div>
<div>
<span class="text-slate-500">ریالی</span>
<p class="font-semibold text-slate-800 dark:text-slate-100">{{ formatCash(selectedCustomer) }}</p>
</div>
</div>
<div class="dashboard-wallet-modal__section">
<label class="dashboard-wallet-modal__label">نوع کیف پول</label>
<a-radio-group v-model:value="walletVariant" button-style="solid" class="flex w-full">
<a-radio-button value="money" class="w-1/2 text-center">ریالی</a-radio-button>
<a-radio-button value="gold" class="w-1/2 text-center">طلایی</a-radio-button>
</a-radio-group>
</div>
<WalletChargeForm
ref="chargeFormRef"
class="dashboard-wallet-modal__form"
:model-value="formCharge"
:variant="walletVariant"
/>
<div class="dashboard-wallet-modal__footer">
<a-button danger size="large" block @click="closeModal">
بستن
</a-button>
<a-button
type="primary"
:danger="formCharge.direction === 'discharge'"
size="large"
block
:loading="submitting"
@click="submit"
>
{{ okText }}
</a-button>
</div>
</div>
</BasicActionSheet>
</template>
<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import { message } from 'ant-design-vue'
import BasicActionSheet from '@/components/BasicActionSheet.vue'
import WalletChargeForm from '@/pages/menuPages/customers/base/components/WalletChargeForm.vue'
import {
buildChargePayload,
createEmptyChargeForm,
walletChargeOkText,
walletChargeSuccessMessage,
walletChargeTitle,
} from '@/pages/menuPages/customers/base/utils/walletChargeForm'
import { getCustomersList } from '@/api/customer'
import { chargeWallet } from '@/api/wallet'
import type { Customer } from '@/model/customer'
import type { Charge, Wallet } from '@/model/wallet'
import { usePrice } from '@core/utils/usePrice'
const props = defineProps<{ visible: boolean }>()
const emit = defineEmits<{ (e: 'update:visible', value: boolean): void }>()
const visibleProxy = computed({
get: () => props.visible,
set: (value: boolean) => emit('update:visible', value),
})
const loading = ref(false)
const submitting = ref(false)
const customers = ref<Customer[]>([])
const walletVariant = ref<'money' | 'gold'>('money')
const formCharge = ref<Charge>(createEmptyChargeForm())
const chargeFormRef = ref<InstanceType<typeof WalletChargeForm>>()
const customerId = computed({
get: () => formCharge.value.customer_id ?? undefined,
set: (value: number | undefined) => {
formCharge.value.customer_id = value
},
})
const customerOptions = computed(() =>
customers.value.map((customer) => ({
value: customer.id,
label: `${customer.name ?? ''} — ${customer.mobile ?? ''}`.trim(),
})),
)
const selectedCustomer = computed(() =>
customers.value.find((customer) => customer.id === formCharge.value.customer_id),
)
const modalTitle = computed(() =>
walletChargeTitle(walletVariant.value, formCharge.value.direction),
)
const okText = computed(() => walletChargeOkText(formCharge.value.direction))
function filterCustomer(input: string, option?: { label?: string }) {
return String(option?.label ?? '').toLowerCase().includes(input.toLowerCase())
}
function customerWallet(customer: Customer): Wallet | undefined {
const wallet = customer.wallet as Wallet | Wallet[] | undefined
return Array.isArray(wallet) ? wallet[0] : wallet
}
function formatGold(customer: Customer) {
const gold = customerWallet(customer)?.gold ?? customer.wallet_gold ?? customer.gold
return `${Number(gold ?? 0).toLocaleString('fa-IR', { maximumFractionDigits: 3 })} گرم`
}
function formatCash(customer: Customer) {
const cash = customerWallet(customer)?.cash ?? customer.wallet_cash
return usePrice(Number(cash ?? 0), true, '')
}
async function loadCustomers() {
loading.value = true
try {
customers.value = (await getCustomersList()) ?? []
} catch (e) {
console.error(e)
message.error('خطا در بارگذاری لیست مشتریان')
} finally {
loading.value = false
}
}
function resetForm() {
walletVariant.value = 'money'
formCharge.value = createEmptyChargeForm()
}
function handleClose() {
resetForm()
}
function closeModal() {
visibleProxy.value = false
resetForm()
}
async function submit() {
if (!formCharge.value.customer_id) {
message.warning('مشتری را انتخاب کنید')
return
}
try {
await chargeFormRef.value?.validateFields?.()
} catch {
return
}
submitting.value = true
try {
await chargeWallet(buildChargePayload(formCharge.value), walletVariant.value)
message.success(walletChargeSuccessMessage(formCharge.value.direction))
closeModal()
} catch (e) {
console.error(e)
message.error('خطا در ثبت عملیات')
} finally {
submitting.value = false
}
}
watch(
() => props.visible,
(open) => {
if (open) {
void loadCustomers()
}
},
)
</script>
<style scoped>
.dashboard-wallet-modal {
@apply flex flex-col gap-4 px-4 pb-2 pt-1;
}
.dashboard-wallet-modal__select :deep(.ant-select-selector) {
@apply !px-3;
}
.dashboard-wallet-modal__select :deep(.ant-select-selection-placeholder) {
@apply text-slate-400;
}
.dashboard-wallet-modal__section {
@apply flex flex-col gap-1.5;
}
.dashboard-wallet-modal__label {
@apply text-xs font-semibold text-slate-600 dark:text-slate-300;
}
.dashboard-wallet-modal__balances {
@apply grid grid-cols-2 gap-3 rounded-lg border border-slate-200 bg-slate-50 px-3 py-2.5 text-xs dark:border-slate-600 dark:bg-slate-800/60;
}
.dashboard-wallet-modal__footer {
@apply sticky bottom-0 grid grid-cols-2 gap-2 border-t border-slate-200 bg-white pt-3 dark:border-slate-600 dark:bg-slate-900;
}
.dashboard-wallet-modal__form :deep(.ant-form-item) {
margin-bottom: 0.85rem;
}
.dashboard-wallet-modal__form :deep(.ant-form-item-label > label) {
@apply text-xs font-semibold text-slate-600 dark:text-slate-300;
}
.dashboard-wallet-modal__form :deep(.wallet-charge-form__direction .ant-radio-button-wrapper) {
height: 2.5rem;
line-height: 2.4rem;
}
.dashboard-wallet-modal__form :deep(.wallet-charge-form__preview) {
margin-bottom: 0.75rem;
padding: 0.65rem 0.85rem;
font-size: 0.8125rem;
}
.dashboard-wallet-modal__form :deep(.wallet-charge-form__preview--charge) {
color: #166534;
background: #f0fdf4;
border-color: #bbf7d0;
}
.dashboard-wallet-modal__form :deep(.wallet-charge-form__preview--charge .wallet-charge-form__preview-sign) {
color: #15803d;
background: #dcfce7;
}
.dashboard-wallet-modal__form :deep(.wallet-charge-form__preview--discharge) {
color: #991b1b;
background: #fef2f2;
border-color: #fecaca;
}
.dashboard-wallet-modal__form :deep(.wallet-charge-form__preview--discharge .wallet-charge-form__preview-sign) {
color: #b91c1c;
background: #fee2e2;
}
</style>