Fix boursecu asset info modals with per-product copper breakdown.

Show total product wallet sum alongside the clicked product inventory in readable multi-line modals on mobile and desktop.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-16 10:51:55 +03:30
co-authored by Cursor
parent 31caa8cca7
commit 17e4f14e46
4 changed files with 208 additions and 37 deletions
+166 -29
View File
@@ -105,21 +105,21 @@ export interface HomeAssetRow {
info: HomeAssetRowInfo info: HomeAssetRowInfo
/** boursecu — متن مودال توضیحات (info) */ /** boursecu — متن مودال توضیحات (info) */
modalText?: string modalText?: string
/** boursecu — عنوان مودال (مثلاً کل دارایی مس) */
modalTitle?: string
/** boursecu — محتوای چندخطی مودال */
modalContent?: string
} }
function formatInventoryCardProductLabel(card: WalletInventoryCard): string { function formatInventoryCardProductLabel(card: WalletInventoryCard): string {
return `دارایی ${card.title}` return `دارایی ${card.title}`
} }
function formatBoursecuProductModalText(card: WalletInventoryCard): string {
return `${formatInventoryCardProductLabel(card)} = موجودی ${card.title}`
}
/** boursecu — متن مودال برای ردیف‌های metal_totals */ /** boursecu — متن مودال برای ردیف‌های metal_totals */
export function formatBoursecuMetalModalText(metal: WalletMetalTotal): string { export function formatBoursecuMetalModalText(metal: WalletMetalTotal): string {
switch (metal.metal_id) { switch (metal.metal_id) {
case METAL_COPPER: case METAL_COPPER:
return 'مجموع دارایی مس = جمع کاتد و مفتول و شمش' return getTotalMetalLabel(METAL_COPPER)
case METAL_GOLD: case METAL_GOLD:
return 'دارایی شمش مس = موجودی شمش مس' return 'دارایی شمش مس = موجودی شمش مس'
case METAL_SILVER: case METAL_SILVER:
@@ -129,6 +129,121 @@ export function formatBoursecuMetalModalText(metal: WalletMetalTotal): string {
} }
} }
/** boursecu — جمع موجودی کاتد، مفتول و شمش از کارت‌های دارایی صفحه اصلی */
function sumBoursecuHomeProductInventories(
inventoryCards: WalletInventoryCard[],
countProducts: WalletInventoryCard[]
): number {
let sum = 0
for (const card of inventoryCards) {
if (card.display_type === 'count') {
continue
}
sum += Number(card.quantity ?? 0)
}
for (const card of countProducts) {
const quantity = Number(card.quantity ?? 0)
const unitWeight = Number(card.unit_weight ?? 0)
if (unitWeight > 0) {
sum += quantity * unitWeight
continue
}
if (card.unit?.includes('کیلو')) {
sum += quantity
}
}
return sum
}
type BoursecuModalProduct = {
title: string
quantity: number
unit: string
}
function toBoursecuModalProduct(card: WalletInventoryCard): BoursecuModalProduct {
return {
title: card.title,
quantity: Number(card.quantity ?? 0),
unit: card.unit ?? (card.display_type === 'count' ? 'عدد' : 'کیلوگرم'),
}
}
function resolveBoursecuMetalModalProduct(
metal: WalletMetalTotal,
countProducts: WalletInventoryCard[]
): BoursecuModalProduct {
const countCard =
countProducts.find((card) => Number(card.metal_id) === metal.metal_id) ??
countProducts.find((card) => card.title?.includes('شمش'))
if (countCard) {
return toBoursecuModalProduct(countCard)
}
if (metal.metal_id === METAL_GOLD) {
return {
title: 'شمش مس',
quantity: Number(metal.total_weight ?? 0),
unit: 'عدد',
}
}
return {
title: metal.label,
quantity: Number(metal.total_weight ?? 0),
unit: metal.unit ?? 'کیلوگرم',
}
}
/** boursecu — جزئیات کل دارایی مس در مودال info */
export function formatBoursecuCopperAssetsModalContent(
metal: WalletMetalTotal,
inventoryCards: WalletInventoryCard[],
countProducts: WalletInventoryCard[],
product: BoursecuModalProduct
): string {
const weightUnit = metal.unit || 'کیلوگرم'
const productWeight = usePriceWithoutType(
sumBoursecuHomeProductInventories(inventoryCards, countProducts),
false
)
const clickedQuantity = usePriceWithoutType(product.quantity, false)
return [
`مجموع کیف محصولات: ${productWeight} ${weightUnit}`,
`موجودی ${product.title}: ${clickedQuantity} ${product.unit}`,
].join('\n')
}
function withBoursecuCopperModal(
row: Omit<HomeAssetRow, 'modalTitle' | 'modalContent'>,
copperTotal: WalletMetalTotal | undefined,
inventoryCards: WalletInventoryCard[],
countProducts: WalletInventoryCard[],
product: BoursecuModalProduct
): HomeAssetRow {
if (!copperTotal) {
return row
}
return {
...row,
metal: copperTotal,
modalTitle: getTotalMetalLabel(METAL_COPPER),
modalContent: formatBoursecuCopperAssetsModalContent(
copperTotal,
inventoryCards,
countProducts,
product
),
}
}
function findMetalTotalById(metalTotals: WalletMetalTotal[], metalId: number) { function findMetalTotalById(metalTotals: WalletMetalTotal[], metalId: number) {
return metalTotals.find((metal) => metal.metal_id === metalId) return metalTotals.find((metal) => metal.metal_id === metalId)
} }
@@ -158,20 +273,28 @@ export function buildBoursecuHomeAssetRows(
const metalTotals = summary.metal_totals ?? [] const metalTotals = summary.metal_totals ?? []
const inventoryCards = summary.inventory_cards ?? [] const inventoryCards = summary.inventory_cards ?? []
const countProducts = summary.count_products ?? [] const countProducts = summary.count_products ?? []
const copperTotal = findMetalTotalById(metalTotals, METAL_COPPER)
for (const card of inventoryCards) { for (const card of inventoryCards) {
if (card.display_type === 'count') { if (card.display_type === 'count') {
continue continue
} }
rows.push({ rows.push(
id: `inventory-${card.product_id}`, withBoursecuCopperModal(
label: formatInventoryCardProductLabel(card), {
value: formatInventoryCardValue(card), id: `inventory-${card.product_id}`,
metal: findMetalTotalById(metalTotals, card.metal_id), label: formatInventoryCardProductLabel(card),
info: 'metal', value: formatInventoryCardValue(card),
modalText: formatBoursecuProductModalText(card), metal: findMetalTotalById(metalTotals, card.metal_id),
}) info: 'metal',
},
copperTotal,
inventoryCards,
countProducts,
toBoursecuModalProduct(card)
)
)
} }
for (const metal of metalTotals) { for (const metal of metalTotals) {
@@ -179,25 +302,39 @@ export function buildBoursecuHomeAssetRows(
continue continue
} }
rows.push({ rows.push(
id: `metal-${metal.metal_id}`, withBoursecuCopperModal(
label: resolveMetalAssetsDisplayLabel(metal, true), {
value: formatBoursecuMetalRowValue(metal), id: `metal-${metal.metal_id}`,
metal, label: resolveMetalAssetsDisplayLabel(metal, true),
info: 'metal', value: formatBoursecuMetalRowValue(metal),
modalText: formatBoursecuMetalModalText(metal), metal,
}) info: 'metal',
},
copperTotal,
inventoryCards,
countProducts,
resolveBoursecuMetalModalProduct(metal, countProducts)
)
)
} }
for (const card of countProducts) { for (const card of countProducts) {
rows.push({ rows.push(
id: `count-${card.product_id}`, withBoursecuCopperModal(
label: formatInventoryCardProductLabel(card), {
value: formatInventoryCardValue(card), id: `count-${card.product_id}`,
metal: findMetalTotalById(metalTotals, card.metal_id), label: formatInventoryCardProductLabel(card),
info: 'metal', value: formatInventoryCardValue(card),
modalText: formatBoursecuProductModalText(card), metal: findMetalTotalById(metalTotals, card.metal_id),
}) info: 'metal',
},
copperTotal,
inventoryCards,
countProducts,
toBoursecuModalProduct(card)
)
)
} }
rows.push({ rows.push({
@@ -94,7 +94,7 @@ const props = withDefaults(
) )
const emit = defineEmits<{ const emit = defineEmits<{
openGoldModal: [metal: WalletMetalTotal, modalText?: string] openGoldModal: [metal: WalletMetalTotal, modalText?: string, modalTitle?: string, modalContent?: string]
openRialModal: [] openRialModal: []
}>() }>()
@@ -136,7 +136,7 @@ function handleRowInfo(row: HomeAssetRow) {
} }
if (row.metal) { if (row.metal) {
emit('openGoldModal', row.metal, row.modalText) emit('openGoldModal', row.metal, row.modalText, row.modalTitle, row.modalContent)
} }
} }
</script> </script>
+34 -4
View File
@@ -1,4 +1,4 @@
import { computed, inject, onMounted, onUnmounted, provide, ref, type InjectionKey } from 'vue' import { computed, h, inject, onMounted, onUnmounted, provide, ref, type InjectionKey } from 'vue'
import router from '@/router' import router from '@/router'
import { loading, removeToken } from '@/utils' import { loading, removeToken } from '@/utils'
import { useUserStore } from '@/store/userStore' import { useUserStore } from '@/store/userStore'
@@ -41,7 +41,12 @@ export interface HomePageContext {
setChartMetal: (metal: ChartMetalType) => void setChartMetal: (metal: ChartMetalType) => void
isBoursecuHomeLayout: ReturnType<typeof computed<boolean>> isBoursecuHomeLayout: ReturnType<typeof computed<boolean>>
handleBannerClick: (banner: { typeClick?: string; modalText?: string; link?: string }) => void handleBannerClick: (banner: { typeClick?: string; modalText?: string; link?: string }) => void
openGoldModal: (metal: WalletMetalTotal, modalText?: string) => void openGoldModal: (
metal: WalletMetalTotal,
modalText?: string,
modalTitle?: string,
modalContent?: string
) => void
openRialModal: () => void openRialModal: () => void
} }
@@ -147,8 +152,32 @@ export function useHomePage() {
window.location.reload() window.location.reload()
} }
function openGoldModal(metal: WalletMetalTotal, modalText?: string) { function renderModalLines(text?: string) {
if (!text) {
return undefined
}
return h(
'div',
{ style: { lineHeight: '1.9' } },
text.split('\n').map((line, index) =>
h(
'div',
{ key: index, style: index > 0 ? { marginTop: '6px' } : undefined },
line
)
)
)
}
function openGoldModal(
metal: WalletMetalTotal,
modalText?: string,
modalTitle?: string,
modalContent?: string
) {
const title = const title =
modalTitle ??
modalText ?? modalText ??
(isBoursecuHomeLayout.value (isBoursecuHomeLayout.value
? formatBoursecuMetalModalText(metal) ? formatBoursecuMetalModalText(metal)
@@ -156,6 +185,7 @@ export function useHomePage() {
Modal.confirm({ Modal.confirm({
title, title,
content: renderModalLines(modalContent),
okText: 'متوجه شدم', okText: 'متوجه شدم',
centered: true, centered: true,
cancelButtonProps: { style: { display: 'none' } }, cancelButtonProps: { style: { display: 'none' } },
@@ -168,7 +198,7 @@ export function useHomePage() {
const metalRial = resolveAssetsRialValue(summary, getTradeBuyPrice(lastPrice.value)) const metalRial = resolveAssetsRialValue(summary, getTradeBuyPrice(lastPrice.value))
Modal.confirm({ Modal.confirm({
title: 'دارایی ریالی', title: 'دارایی ریالی',
content: formatRialAssetsInfoText(cash, metalRial), content: renderModalLines(formatRialAssetsInfoText(cash, metalRial)),
okText: 'متوجه شدم', okText: 'متوجه شدم',
centered: true, centered: true,
cancelButtonProps: { style: { display: 'none' } }, cancelButtonProps: { style: { display: 'none' } },
@@ -8,7 +8,7 @@ import { buildBoursecuHomeAssetRows, resolveSummaryMetalWeight } from '@/utils/p
import { useHomePageContext } from '../../composables/useHomePage' import { useHomePageContext } from '../../composables/useHomePage'
const emit = defineEmits<{ const emit = defineEmits<{
openGoldModal: [metal: WalletMetalTotal, modalText?: string] openGoldModal: [metal: WalletMetalTotal, modalText?: string, modalTitle?: string, modalContent?: string]
openRialModal: [] openRialModal: []
}>() }>()
@@ -30,6 +30,8 @@ type MetricItem = {
value: string value: string
metal?: WalletMetalTotal metal?: WalletMetalTotal
modalText?: string modalText?: string
modalTitle?: string
modalContent?: string
showInfo?: boolean showInfo?: boolean
onInfo?: 'gold' | 'rial' onInfo?: 'gold' | 'rial'
} }
@@ -53,6 +55,8 @@ const metrics = computed<MetricItem[]>(() => {
value: row.value, value: row.value,
metal: row.metal, metal: row.metal,
modalText: row.modalText, modalText: row.modalText,
modalTitle: row.modalTitle,
modalContent: row.modalContent,
showInfo: row.info !== 'none', showInfo: row.info !== 'none',
onInfo: row.info === 'rial' ? 'rial' : 'gold', onInfo: row.info === 'rial' ? 'rial' : 'gold',
}) })
@@ -104,7 +108,7 @@ function handleInfo(metric: MetricItem) {
return return
} }
if (metric.metal) { if (metric.metal) {
emit('openGoldModal', metric.metal, metric.modalText) emit('openGoldModal', metric.metal, metric.modalText, metric.modalTitle, metric.modalContent)
} }
} }
</script> </script>