From 78ce8ad4b4a1a42fdf551a05c2880ba3280bedb0 Mon Sep 17 00:00:00 2001 From: mohammad sedighi Date: Wed, 24 Jun 2026 01:46:30 +0330 Subject: [PATCH] fix reports --- src/config-loader.js | 205 +++++++++++------- .../components/WithdrawMobileDetailSheet.vue | 69 +++++- .../components/WithdrawMobileListItem.vue | 15 +- .../withdraw/hooks/withdrawColumns.tsx | 44 +++- .../withdraw/utils/withdrawDisplay.ts | 63 ++++++ 5 files changed, 295 insertions(+), 101 deletions(-) create mode 100644 src/pages/menuPages/withdraw/utils/withdrawDisplay.ts diff --git a/src/config-loader.js b/src/config-loader.js index 788aae1..cbe5011 100644 --- a/src/config-loader.js +++ b/src/config-loader.js @@ -15,104 +15,157 @@ configReject = reject; }); - // API endpoint for config - you can change this URL - // You can also set it via a data attribute: - //const scriptTag = document.currentScript || document.querySelector('script[src*="config-loader.js"]'); - //const customUrl = scriptTag ? scriptTag.getAttribute('data-config-url') : null; - const CONFIG_URL = 'https://nerkh.gold/get_user' //customUrl || window.location.origin + '/api/config'; + const VERSION_URL = 'https://abshode-service.montgit.ir/server/get_version'; + const CONFIG_URLS = [ + 'https://nerkh.gold/get_user', + 'https://abshode-service.montgit.ir/server/get_user' + ]; + const CONFIG_SUCCESS_URL_KEY = 'CONFIG_Success_URL'; - // Timeout to ensure main.ts loads even if config fails (5 seconds) - const CONFIG_TIMEOUT = 5000; + // Timeout to ensure main.ts loads even if config fails + const CONFIG_TIMEOUT = 12000; let configTimeoutId; let mainScriptLoaded = false; - // Fetch config from server - async function loadConfig() { + function getProtocol() { + return window.location.protocol === 'https:' ? 'https:' : 'http:'; + } + + function sortConfigUrls(urls) { + const lastSuccessUrl = localStorage.getItem(CONFIG_SUCCESS_URL_KEY); + + return [...urls].sort((a, b) => { + if (a === lastSuccessUrl) return -1; + if (b === lastSuccessUrl) return 1; + return 0; + }); + } + + async function fetchJson(url, timeout = 3500) { + const controller = new AbortController(); + const timeoutId = window.setTimeout(() => controller.abort(), timeout); + try { - const response = await fetch(CONFIG_URL, { + const response = await fetch(url, { method: 'GET', headers: { - 'Accept': 'application/json', + Accept: 'application/json' }, - cache: 'no-cache' + cache: 'no-store', + signal: controller.signal }); if (!response.ok) { - throw new Error('Failed to fetch config: ' + response.status); + throw new Error('Request failed: ' + response.status); } - // Check if response is actually JSON - const contentType = response.headers.get('content-type'); - if (!contentType || !contentType.includes('application/json')) { + const contentType = response.headers.get('content-type') || ''; + + if (!contentType.includes('application/json')) { const text = await response.text(); throw new Error('Response is not JSON. Received: ' + text.substring(0, 100)); } - const config = await response.json(); + return await response.json(); + } finally { + window.clearTimeout(timeoutId); + } + } - // Store config in window object - window.__APP_CONFIG__ = config; - window.__CONFIG_READY__ = true; - if (config?.user?.name_fa) { - document.title = 'پنل مدیریت '+ '-'+config.user.name_fa; - } - // ---------- Dynamic Favicon ---------- - if (config?.user?.avatar) { - const faviconUrl = config.user.avatar; - - // remove existing favicon(s) - const existingIcons = document.querySelectorAll('link[rel*="icon"]'); - existingIcons.forEach(icon => icon.remove()); - - // create new favicon - const link = document.createElement('link'); - link.rel = 'icon'; - link.type = 'image/png'; // یا image/x-icon - link.href = faviconUrl + '?v=' + Date.now(); - - document.head.appendChild(link); - } - // Resolve the promise - if (configResolve) { - configResolve(config); - } - - // Dispatch custom event to notify that config is ready - window.dispatchEvent(new CustomEvent('configReady', { detail: config })); - - console.log('Config loaded successfully:', config); - - // Clear timeout if config loaded successfully - if (configTimeoutId) { - clearTimeout(configTimeoutId); - configTimeoutId = null; - } - - // Load main.ts after config is ready - loadMainScript(); + async function loadVersion() { + try { + await fetchJson(VERSION_URL, 3500); + console.log('Version loaded from:', VERSION_URL); } catch (error) { - console.warn('Config loader: Could not fetch config from', CONFIG_URL); + console.warn('Version check failed from', VERSION_URL, error); + } + } + + async function fetchConfig() { + const protocol = getProtocol(); + const urls = sortConfigUrls( + CONFIG_URLS.map((url) => url.replace(/^https?:/, protocol)) + ); + + for (let index = 0; index < urls.length; index++) { + const url = urls[index]; + const timeout = index === urls.length - 1 ? 5000 : 3500; + + try { + const config = await fetchJson(url, timeout); + + if (!config || typeof config !== 'object') { + throw new Error('Config response is invalid'); + } + + localStorage.setItem(CONFIG_SUCCESS_URL_KEY, url); + return { config, url }; + } catch (error) { + console.warn('Config failed from', url, error); + } + } + + throw new Error('Cannot load config from any server'); + } + + function applyConfig(config) { + window.__APP_CONFIG__ = config; + window.__CONFIG_READY__ = true; + + if (config?.user?.name_fa) { + document.title = 'پنل مدیریت ' + '-' + config.user.name_fa; + } + + if (config?.user?.avatar) { + const faviconUrl = config.user.avatar; + const existingIcons = document.querySelectorAll('link[rel*="icon"]'); + existingIcons.forEach((icon) => icon.remove()); + + const link = document.createElement('link'); + link.rel = 'icon'; + link.type = 'image/png'; + link.href = faviconUrl + '?v=' + Date.now(); + document.head.appendChild(link); + } + } + + function finishConfigLoad(config, error) { + if (configResolve) { + configResolve(config); + } + + if (error) { + window.dispatchEvent(new CustomEvent('configError', { detail: error })); + } else { + window.dispatchEvent(new CustomEvent('configReady', { detail: config })); + } + + if (configTimeoutId) { + clearTimeout(configTimeoutId); + configTimeoutId = null; + } + + loadMainScript(); + } + + // Fetch config from server + async function loadConfig() { + try { + await loadVersion(); + + const { config, url } = await fetchConfig(); + applyConfig(config); + + console.log('Config loaded successfully from', url, config); + finishConfigLoad(config); + } catch (error) { + console.warn('Config loader: Could not fetch config from any endpoint'); console.warn('Error details:', error.message); - console.warn('This is normal if the endpoint does not exist yet. App will continue with empty config.'); - // Set default empty config on error + console.warn('App will continue with empty config.'); + window.__APP_CONFIG__ = {}; window.__CONFIG_READY__ = true; - - // Resolve the promise even on error (with empty config) - if (configResolve) { - configResolve({}); - } - - window.dispatchEvent(new CustomEvent('configError', { detail: error })); - - // Clear timeout if error occurred - if (configTimeoutId) { - clearTimeout(configTimeoutId); - configTimeoutId = null; - } - - // Load main.ts even on error (with empty config) - loadMainScript(); + finishConfigLoad({}, error); } } diff --git a/src/pages/menuPages/withdraw/components/WithdrawMobileDetailSheet.vue b/src/pages/menuPages/withdraw/components/WithdrawMobileDetailSheet.vue index 2537c73..5b65d12 100644 --- a/src/pages/menuPages/withdraw/components/WithdrawMobileDetailSheet.vue +++ b/src/pages/menuPages/withdraw/components/WithdrawMobileDetailSheet.vue @@ -62,15 +62,28 @@ {{ row.sts_str || '-' }} +
+ + {{ typeLabel(row) }} + +
+
- + - - -
@@ -104,10 +117,14 @@ import MobileInfoGrid from '@/components/mobile/MobileInfoGrid.vue' import MobileSheetHeader from '@/components/mobile/MobileSheetHeader.vue' import MobileStatCard from '@/components/mobile/MobileStatCard.vue' import { status } from '@/pages/menuPages/withdraw/information/hooks/status' +import { + formatWithdrawAmountDisplay, + isMoneyWithdraw, + withdrawAmountValue, + withdrawTypeLabel +} from '@/pages/menuPages/withdraw/utils/withdrawDisplay' import { isMobile } from '@core/utils/is' import { toJalali } from '@core/utils/useDataTime' -import { usePrice } from '@core/utils/usePrice' -import { metalLabel } from '@/utils/adminMetalLabels' import { toNumber } from 'lodash' import { useRouter } from 'vue-router' @@ -155,8 +172,36 @@ function noteLabel(row: Withdraw) { return String(row.note) } -function goldLabel(row: Withdraw) { - if (row.gold == null) return '-' - return String(row.gold) +function typeLabel(row: Withdraw) { + return withdrawTypeLabel(row) +} + +function typeTagClass(row: Withdraw) { + if (!isMoneyWithdraw(row)) { + return 'border-amber-300 bg-amber-50 text-amber-700' + } + return withdrawAmountValue(row) > 0 + ? 'border-green-300 bg-green-50 text-green-700' + : withdrawAmountValue(row) < 0 + ? 'border-red-300 bg-red-50 text-red-700' + : 'border-gray-300 bg-gray-50 text-gray-700' +} + +function amountLabel(row: Withdraw) { + return formatWithdrawAmountDisplay(row) +} + +function amountVariant(row: Withdraw) { + const value = withdrawAmountValue(row) + if (value > 0) return 'success' + if (value < 0) return 'danger' + return 'warning' +} + +function amountIconColor(row: Withdraw) { + const value = withdrawAmountValue(row) + if (value > 0) return '#15803d' + if (value < 0) return '#dc2626' + return '#b45309' } diff --git a/src/pages/menuPages/withdraw/components/WithdrawMobileListItem.vue b/src/pages/menuPages/withdraw/components/WithdrawMobileListItem.vue index a6dd13f..8b634f9 100644 --- a/src/pages/menuPages/withdraw/components/WithdrawMobileListItem.vue +++ b/src/pages/menuPages/withdraw/components/WithdrawMobileListItem.vue @@ -8,7 +8,7 @@
{{ noteLabel }} - {{ cashLabel }} + {{ amountLabel }}
@@ -18,7 +18,10 @@ import type { Withdraw } from '@/model/wallet' import MobileCard from '@/components/mobile/MobileCard.vue' import { toJalaliNoTime } from '@core/utils/useDataTime' -import { usePrice } from '@core/utils/usePrice' +import { + formatWithdrawAmountDisplay, + withdrawAmountValue +} from '@/pages/menuPages/withdraw/utils/withdrawDisplay' const props = defineProps<{ item: Withdraw @@ -35,5 +38,11 @@ const noteLabel = computed(() => { const customerName = computed(() => props.item.customer?.name || '-') const dateLabel = computed(() => (props.item.created_at ? toJalaliNoTime(props.item.created_at) : '-')) -const cashLabel = computed(() => usePrice(props.item.cash ?? 0, true, '')) +const amountLabel = computed(() => formatWithdrawAmountDisplay(props.item)) +const amountClass = computed(() => { + const value = withdrawAmountValue(props.item) + if (value > 0) return 'text-green-600' + if (value < 0) return 'text-red-600' + return 'text-gray-600' +}) diff --git a/src/pages/menuPages/withdraw/hooks/withdrawColumns.tsx b/src/pages/menuPages/withdraw/hooks/withdrawColumns.tsx index cc1882a..75961ee 100644 --- a/src/pages/menuPages/withdraw/hooks/withdrawColumns.tsx +++ b/src/pages/menuPages/withdraw/hooks/withdrawColumns.tsx @@ -1,9 +1,13 @@ import type { BaseTableColumn } from '@core/type/baseTable' import { toJalali, toJalaliNoTime, toTime } from '@core/utils/useDataTime' import CustomTag from '@/components/CustomTag.vue' -import { usePrice } from '@core/utils/usePrice' import WithdrawStatus from "@/pages/menuPages/withdraw/components/WithdrawStatus.vue"; -import { metalLabel } from '@/utils/adminMetalLabels' +import { + formatWithdrawAmountDisplay, + isMoneyWithdraw, + withdrawAmountColorClass, + withdrawTypeLabel +} from '@/pages/menuPages/withdraw/utils/withdrawDisplay' /*import BudgetSelect from '@/components/BudgetSelect.vue' import { useStaticApiListsStore } from '@/store/staticApisStore' import type { FilterSelect } from '@/model/filterSelect' @@ -71,23 +75,43 @@ export function getWithdrawColumns(): BaseTableColumn[] { type: 'Text' }, { - title: 'مقدار (ریال)', - dataIndex: 'cash', - key: 'cash', - type: 'Number', + title: 'نوع', + dataIndex: 'asset_type', + key: 'asset_type', + filterable: false, + sortable: false, customRender: ({ record }) => { + const label = withdrawTypeLabel(record) + if (label === '-') { + return
-
+ } + return (
-
{usePrice(record.cash ?? 0, true, '')}
+ + {label} +
) } }, { - title: metalLabel.goldUnit(), - dataIndex: 'gold', - key: 'gold', + title: 'مقدار', + dataIndex: 'amount', + key: 'amount', type: 'Number', + customRender: ({ record }) => { + const colorClass = withdrawAmountColorClass(record) + return ( +
+
{formatWithdrawAmountDisplay(record)}
+
+ ) + } }, { title: 'شماره کارت', diff --git a/src/pages/menuPages/withdraw/utils/withdrawDisplay.ts b/src/pages/menuPages/withdraw/utils/withdrawDisplay.ts new file mode 100644 index 0000000..546bfa1 --- /dev/null +++ b/src/pages/menuPages/withdraw/utils/withdrawDisplay.ts @@ -0,0 +1,63 @@ +import type { Withdraw } from '@/model/wallet' +import { metalLabel } from '@/utils/adminMetalLabels' +import { getMainMetal } from '@/utils/metalLabels' +import { + formatMetalDisplayValue, + METAL_ID_COPPER, + METAL_ID_GOLD, + METAL_ID_SILVER, + metalDisplayUnit +} from '@/utils/metalWeight' +import { usePrice } from '@core/utils/usePrice' + +export function resolveWithdrawMetalId(): number { + const main = getMainMetal() + if (main.includes('مس')) return METAL_ID_COPPER + if (main.includes('نقره')) return METAL_ID_SILVER + return METAL_ID_GOLD +} + +export function isMoneyWithdraw(record: Pick): boolean { + const gold = Math.abs(Number(record.gold ?? 0)) + if (gold > 0) return false + return true +} + +export function withdrawTypeLabel(record: Pick): string { + if (!isMoneyWithdraw(record)) { + return metalLabel.goldUnit() + } + + const cash = Math.abs(Number(record.cash ?? 0)) + return cash > 0 ? 'پول' : '-' +} + +export function withdrawAmountValue(record: Pick): number { + if (isMoneyWithdraw(record)) { + return Number(record.cash ?? 0) + } + return Number(record.gold ?? 0) +} + +export function formatWithdrawAmountDisplay(record: Pick): string { + const value = withdrawAmountValue(record) + + if (isMoneyWithdraw(record)) { + if (!value) return `0 ریال` + const formatted = usePrice(Math.abs(value), true, '') + return value > 0 ? `${formatted} ریال` : `-${formatted} ریال` + } + + const metalId = resolveWithdrawMetalId() + const unit = metalDisplayUnit(metalId) + if (!value) return `0 ${unit}` + + const formatted = formatMetalDisplayValue(Math.abs(value), metalId) + return value > 0 ? formatted : `-${formatted}` +} + +export function withdrawAmountColorClass(record: Pick): string { + const value = withdrawAmountValue(record) + if (!value) return '' + return value > 0 ? 'text-green' : 'text-red' +}