416 lines
13 KiB
Vue
416 lines
13 KiB
Vue
<template>
|
||
<div class="customer-wallet-balance" :class="compact ? 'space-y-1.5' : 'space-y-3'">
|
||
<div
|
||
v-if="loading"
|
||
class="rounded-xl border border-gray-200 bg-gray-50 text-center text-sm text-gray-500"
|
||
:class="compact ? 'px-3 py-3' : 'px-4 py-6'"
|
||
>
|
||
در حال بارگذاری موجودی…
|
||
</div>
|
||
|
||
<div
|
||
v-else-if="error"
|
||
class="rounded-xl border border-red-200 bg-red-50 text-sm text-red-700"
|
||
:class="compact ? 'px-3 py-2' : 'px-4 py-3'"
|
||
>
|
||
{{ error }}
|
||
</div>
|
||
|
||
<div v-else-if="summary && compact" class="customer-wallet-balance__minimal">
|
||
<template v-if="chargeTargets.length">
|
||
<button
|
||
v-for="target in chargeTargets"
|
||
:key="target.key"
|
||
type="button"
|
||
class="customer-wallet-balance__minimal-item"
|
||
:class="[
|
||
targetToneClass(target),
|
||
{ 'customer-wallet-balance__minimal-item--inactive': target.active === false },
|
||
{ 'customer-wallet-balance__minimal-item--selected': target.key === selectedChargeKey },
|
||
]"
|
||
:title="target.label"
|
||
@click="emit('selectChargeTarget', target.key)"
|
||
>
|
||
<span class="customer-wallet-balance__minimal-label">{{ minimalTargetLabel(target) }}</span>
|
||
<span class="customer-wallet-balance__minimal-value">{{ balanceForTarget(target) }}</span>
|
||
</button>
|
||
</template>
|
||
<template v-else>
|
||
<div
|
||
v-for="metal in summary.metal_totals"
|
||
:key="metal.metal_id"
|
||
class="customer-wallet-balance__minimal-item"
|
||
:class="minimalMetalToneClass(metal)"
|
||
>
|
||
<span class="customer-wallet-balance__minimal-label">{{ minimalMetalLabel(metal) }}</span>
|
||
<span class="customer-wallet-balance__minimal-value">{{ formatMetalTotal(metal) }}</span>
|
||
</div>
|
||
<div class="customer-wallet-balance__minimal-item customer-wallet-balance__minimal-item--cash">
|
||
<span class="customer-wallet-balance__minimal-label">نقدی</span>
|
||
<span class="customer-wallet-balance__minimal-value">{{ usePrice(summary.cash_wallet ?? 0, true, '') }}</span>
|
||
</div>
|
||
</template>
|
||
</div>
|
||
|
||
<template v-else-if="summary">
|
||
<div
|
||
v-for="metal in summary.metal_totals"
|
||
:key="metal.metal_id"
|
||
class="overflow-hidden rounded-xl border border-gray-200"
|
||
>
|
||
<div class="flex items-center justify-between gap-3 bg-emerald-50 px-4 py-3">
|
||
<div class="flex min-w-0 items-center gap-2">
|
||
<BaseIcon icon="vuesax-linear:coin" size="18" color="#15803d" />
|
||
<span class="truncate font-700 text-emerald-900">{{ metal.label }}</span>
|
||
</div>
|
||
<span class="shrink-0 font-800 text-emerald-800">{{ formatMetalTotal(metal) }}</span>
|
||
</div>
|
||
|
||
<div v-if="linesForMetal(metal.metal_id).length" class="divide-y divide-gray-100 bg-white">
|
||
<div
|
||
v-for="line in linesForMetal(metal.metal_id)"
|
||
:key="line.key"
|
||
class="flex items-center justify-between gap-3 px-4 py-2.5 text-sm"
|
||
>
|
||
<span class="min-w-0 truncate text-gray-600">{{ line.label }}</span>
|
||
<span class="shrink-0 font-600 text-gray-900">{{ line.value }}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="overflow-hidden rounded-xl border border-gray-200">
|
||
<div class="flex items-center justify-between gap-3 bg-sky-50 px-4 py-3">
|
||
<div class="flex items-center gap-2">
|
||
<BaseIcon icon="vuesax-linear:empty-wallet" size="18" color="#0369a1" />
|
||
<span class="font-700 text-sky-900">کل دارایی ریالی</span>
|
||
</div>
|
||
<span class="font-800 text-sky-800">{{ usePrice(summary.total_rial_value ?? 0, true, '') }}</span>
|
||
</div>
|
||
|
||
<div class="divide-y divide-gray-100 bg-white">
|
||
<div class="flex items-center justify-between gap-3 px-4 py-2.5 text-sm">
|
||
<span class="text-gray-600">کیف پول ریالی</span>
|
||
<span class="font-600">{{ usePrice(summary.cash_wallet ?? 0, true, '') }}</span>
|
||
</div>
|
||
<div
|
||
v-if="Number(summary.assets_rial_value ?? 0) > 0"
|
||
class="flex items-center justify-between gap-3 px-4 py-2.5 text-sm"
|
||
>
|
||
<span class="text-gray-600">ارزش ریالی فلزات</span>
|
||
<span class="font-600">{{ usePrice(summary.assets_rial_value ?? 0, true, '') }}</span>
|
||
</div>
|
||
<div class="flex items-center justify-between gap-3 px-4 py-2.5 text-sm font-700">
|
||
<span>مجموع کل</span>
|
||
<span>{{ usePrice(summary.total_rial_value ?? 0, true, '') }}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, watch } from 'vue'
|
||
import { getCustomerAssetsSummary } from '@/api/customer'
|
||
import type { ChargeTarget } from '@/model/wallet'
|
||
import type { WalletAssetsSummary, WalletInventoryCard, WalletMetalTotal } from '@/model/walletAssets'
|
||
import { usePrice } from '@core/utils/usePrice'
|
||
import { formatMetalDisplayValue } from '@/utils/metalWeight'
|
||
|
||
const props = withDefaults(
|
||
defineProps<{
|
||
customerId?: number | null
|
||
/** نمایش مینیمال برای مودالها */
|
||
compact?: boolean
|
||
/** گزینههای شارژ — برای نمایش موجودی هر محصول */
|
||
chargeTargets?: ChargeTarget[]
|
||
selectedChargeKey?: string
|
||
}>(),
|
||
{
|
||
compact: false,
|
||
chargeTargets: () => [],
|
||
selectedChargeKey: '',
|
||
},
|
||
)
|
||
|
||
const emit = defineEmits<{
|
||
(e: 'selectChargeTarget', key: string): void
|
||
}>()
|
||
|
||
function minimalMetalLabel(metal: WalletMetalTotal) {
|
||
switch (Number(metal.metal_id)) {
|
||
case 1:
|
||
return 'طلا'
|
||
case 2:
|
||
return 'نقره'
|
||
case 3:
|
||
return 'مس'
|
||
default: {
|
||
const raw = (metal.metal_label || metal.label || '').trim()
|
||
return raw.replace(/^کل\s*دارایی\s*/u, '') || raw || 'فلز'
|
||
}
|
||
}
|
||
}
|
||
|
||
function minimalMetalToneClass(metal: WalletMetalTotal) {
|
||
switch (Number(metal.metal_id)) {
|
||
case 1:
|
||
return 'customer-wallet-balance__minimal-item--gold'
|
||
case 2:
|
||
return 'customer-wallet-balance__minimal-item--silver'
|
||
case 3:
|
||
return 'customer-wallet-balance__minimal-item--copper'
|
||
default:
|
||
return ''
|
||
}
|
||
}
|
||
|
||
function minimalTargetLabel(target: ChargeTarget) {
|
||
if (target.kind === 'money') return 'نقدی'
|
||
const label = target.label.replace(/\s*\(کیف اصلی\)\s*$/u, '').trim()
|
||
if (label.length <= 14) return label
|
||
return `${label.slice(0, 13)}…`
|
||
}
|
||
|
||
function targetToneClass(target: ChargeTarget) {
|
||
if (target.kind === 'money') return 'customer-wallet-balance__minimal-item--cash'
|
||
switch (Number(target.metal_id)) {
|
||
case 1:
|
||
return 'customer-wallet-balance__minimal-item--gold'
|
||
case 2:
|
||
return 'customer-wallet-balance__minimal-item--silver'
|
||
case 3:
|
||
return 'customer-wallet-balance__minimal-item--copper'
|
||
default:
|
||
return 'customer-wallet-balance__minimal-item--product'
|
||
}
|
||
}
|
||
|
||
function inventoryCards() {
|
||
if (!summary.value) return [] as WalletInventoryCard[]
|
||
return [...(summary.value.inventory_cards ?? []), ...(summary.value.count_products ?? [])]
|
||
}
|
||
|
||
function balanceForTarget(target: ChargeTarget) {
|
||
if (!summary.value) return '—'
|
||
if (target.kind === 'money') {
|
||
return usePrice(summary.value.cash_wallet ?? 0, true, '')
|
||
}
|
||
|
||
const card = inventoryCards().find((row) => Number(row.product_id) === Number(target.product_id))
|
||
if (card) return formatCard(card)
|
||
|
||
const unit = target.unit || (target.trade_by_quantity ? 'عدد' : 'گرم')
|
||
if (target.trade_by_quantity) {
|
||
return `۰ ${unit}`
|
||
}
|
||
return formatMetalDisplayValue(0, target.metal_id ?? 1)
|
||
}
|
||
|
||
const loading = ref(false)
|
||
const error = ref<string | null>(null)
|
||
const summary = ref<WalletAssetsSummary | null>(null)
|
||
|
||
watch(
|
||
() => props.customerId,
|
||
(id) => {
|
||
if (id) {
|
||
void load(id)
|
||
} else {
|
||
summary.value = null
|
||
error.value = null
|
||
}
|
||
},
|
||
{ immediate: true }
|
||
)
|
||
|
||
async function load(customerId: number) {
|
||
loading.value = true
|
||
error.value = null
|
||
try {
|
||
summary.value = await getCustomerAssetsSummary(customerId)
|
||
} catch {
|
||
error.value = 'بارگذاری جزئیات موجودی ناموفق بود'
|
||
summary.value = null
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
function formatMetalTotal(metal: WalletMetalTotal) {
|
||
return formatMetalDisplayValue(Number(metal.total_weight ?? 0), metal.metal_id)
|
||
}
|
||
|
||
function formatCard(card: WalletInventoryCard) {
|
||
const qty = Number(card.quantity ?? 0)
|
||
if (card.display_type === 'count') {
|
||
return `${qty.toLocaleString('fa-IR')} ${card.unit || 'عدد'}`
|
||
}
|
||
return `${qty.toLocaleString('fa-IR', { maximumFractionDigits: 3 })} ${card.unit || 'گرم'}`
|
||
}
|
||
|
||
function linesForMetal(metalId: number) {
|
||
const cards = summary.value?.inventory_cards ?? []
|
||
const countCards = summary.value?.count_products ?? []
|
||
const metal = summary.value?.metal_totals?.find((row) => row.metal_id === metalId)
|
||
|
||
const productLines = [...cards, ...countCards]
|
||
.filter((card) => Number(card.metal_id) === metalId && Number(card.quantity ?? 0) > 0)
|
||
.map((card) => ({
|
||
key: `card-${card.product_id}`,
|
||
label: card.title,
|
||
value: formatCard(card)
|
||
}))
|
||
|
||
const lines: Array<{ key: string; label: string; value: string }> = [...productLines]
|
||
|
||
if (metal && Number(metal.melted_weight ?? 0) > 0 && !productLines.some((line) => line.label.includes('آبشده'))) {
|
||
lines.push({
|
||
key: 'melted',
|
||
label: 'موجودی آبشده / کیف اصلی',
|
||
value: formatMetalDisplayValue(Number(metal.melted_weight), metalId)
|
||
})
|
||
}
|
||
|
||
if (metal && lines.length > 1) {
|
||
lines.push({
|
||
key: 'total',
|
||
label: 'مجموع',
|
||
value: formatMetalTotal(metal)
|
||
})
|
||
}
|
||
|
||
return lines
|
||
}
|
||
|
||
defineExpose({ reload: () => props.customerId && load(props.customerId) })
|
||
</script>
|
||
|
||
<style scoped>
|
||
.customer-wallet-balance__minimal {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(4.75rem, 1fr));
|
||
gap: 0.35rem;
|
||
padding: 0.45rem;
|
||
border-radius: 0.6rem;
|
||
border: 1px solid #e2e8f0;
|
||
background: #f8fafc;
|
||
max-height: 14rem;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.customer-wallet-balance__minimal-item {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 0.1rem;
|
||
min-width: 0;
|
||
padding: 0.35rem 0.25rem;
|
||
border-radius: 0.45rem;
|
||
background: #fff;
|
||
text-align: center;
|
||
border: 1px solid transparent;
|
||
cursor: default;
|
||
}
|
||
|
||
button.customer-wallet-balance__minimal-item {
|
||
cursor: pointer;
|
||
transition: box-shadow 0.15s ease, transform 0.15s ease;
|
||
}
|
||
|
||
button.customer-wallet-balance__minimal-item:hover {
|
||
transform: translateY(-1px);
|
||
}
|
||
|
||
.customer-wallet-balance__minimal-item--selected {
|
||
box-shadow: 0 0 0 2px #2563eb;
|
||
}
|
||
|
||
.customer-wallet-balance__minimal-item--inactive {
|
||
opacity: 0.72;
|
||
}
|
||
|
||
.customer-wallet-balance__minimal-item--gold {
|
||
background: #fffbeb;
|
||
border-color: #fde68a;
|
||
}
|
||
|
||
.customer-wallet-balance__minimal-item--silver {
|
||
background: #f8fafc;
|
||
border-color: #cbd5e1;
|
||
}
|
||
|
||
.customer-wallet-balance__minimal-item--copper {
|
||
background: #fff7ed;
|
||
border-color: #fdba74;
|
||
}
|
||
|
||
.customer-wallet-balance__minimal-item--cash {
|
||
background: #eff6ff;
|
||
border-color: #93c5fd;
|
||
}
|
||
|
||
.customer-wallet-balance__minimal-item--product {
|
||
background: #f8fafc;
|
||
border-color: #e2e8f0;
|
||
}
|
||
|
||
.customer-wallet-balance__minimal-label {
|
||
font-size: 0.6875rem;
|
||
font-weight: 600;
|
||
color: #64748b;
|
||
max-width: 100%;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.customer-wallet-balance__minimal-value {
|
||
font-size: 0.75rem;
|
||
font-weight: 700;
|
||
color: #0f172a;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.customer-wallet-balance__minimal-item--gold .customer-wallet-balance__minimal-label {
|
||
color: #92400e;
|
||
}
|
||
|
||
.customer-wallet-balance__minimal-item--gold .customer-wallet-balance__minimal-value {
|
||
color: #b45309;
|
||
}
|
||
|
||
.customer-wallet-balance__minimal-item--silver .customer-wallet-balance__minimal-label {
|
||
color: #64748b;
|
||
}
|
||
|
||
.customer-wallet-balance__minimal-item--silver .customer-wallet-balance__minimal-value {
|
||
color: #334155;
|
||
}
|
||
|
||
.customer-wallet-balance__minimal-item--copper .customer-wallet-balance__minimal-label {
|
||
color: #c2410c;
|
||
}
|
||
|
||
.customer-wallet-balance__minimal-item--copper .customer-wallet-balance__minimal-value {
|
||
color: #9a3412;
|
||
}
|
||
|
||
.customer-wallet-balance__minimal-item--cash .customer-wallet-balance__minimal-label {
|
||
color: #0369a1;
|
||
}
|
||
|
||
.customer-wallet-balance__minimal-item--cash .customer-wallet-balance__minimal-value {
|
||
color: #0284c7;
|
||
}
|
||
|
||
.customer-wallet-balance__minimal-item--product .customer-wallet-balance__minimal-label {
|
||
color: #475569;
|
||
}
|
||
|
||
.customer-wallet-balance__minimal-item--product .customer-wallet-balance__minimal-value {
|
||
color: #1e293b;
|
||
}
|
||
</style>
|