fix reports
This commit is contained in:
+129
-76
@@ -15,104 +15,157 @@
|
||||
configReject = reject;
|
||||
});
|
||||
|
||||
// API endpoint for config - you can change this URL
|
||||
// You can also set it via a data attribute: <script src="/config-loader.js" data-config-url="https://your-api.com/config"></script>
|
||||
//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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -62,15 +62,28 @@
|
||||
{{ row.sts_str || '-' }}
|
||||
</span>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<span
|
||||
class="inline-flex rounded-lg border px-2 py-0.5 text-xs font-600"
|
||||
:class="typeTagClass(row)"
|
||||
>
|
||||
{{ typeLabel(row) }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<MobileStatCard bordered variant="danger" label="مقدار (ریال)" :value="usePrice(row.cash ?? 0, true, '')">
|
||||
<MobileStatCard
|
||||
bordered
|
||||
:variant="amountVariant(row)"
|
||||
label="مقدار"
|
||||
:value="amountLabel(row)"
|
||||
>
|
||||
<template #prefix>
|
||||
<BaseIcon icon="vuesax-linear:empty-wallet" size="18" color="#dc2626" />
|
||||
</template>
|
||||
</MobileStatCard>
|
||||
<MobileStatCard bordered variant="warning" :label="metalLabel.goldUnit()" :value="goldLabel(row)">
|
||||
<template #prefix>
|
||||
<BaseIcon icon="vuesax-linear:coin" size="18" color="#b45309" />
|
||||
<BaseIcon
|
||||
:icon="isMoneyWithdraw(row) ? 'vuesax-linear:empty-wallet' : 'vuesax-linear:coin'"
|
||||
size="18"
|
||||
:color="amountIconColor(row)"
|
||||
/>
|
||||
</template>
|
||||
</MobileStatCard>
|
||||
</div>
|
||||
@@ -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'
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
<div class="flex items-center justify-between gap-2 text-xs">
|
||||
<span class="truncate text-gray-600">{{ noteLabel }}</span>
|
||||
<span class="shrink-0 font-700 text-red-600">{{ cashLabel }}</span>
|
||||
<span class="shrink-0 font-700" :class="amountClass">{{ amountLabel }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</MobileCard>
|
||||
@@ -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'
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -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 <div class="flex justify-center px-6">-</div>
|
||||
}
|
||||
|
||||
return (
|
||||
<div class="flex justify-center">
|
||||
<div class={'font-bold px-6 text-red'}>{usePrice(record.cash ?? 0, true, '')}</div>
|
||||
<CustomTag
|
||||
textColor={isMoneyWithdraw(record) ? '#25855A' : '#ca8a04'}
|
||||
bgColor={isMoneyWithdraw(record) ? '#E3F4E9' : '#fef3c7'}
|
||||
class="font-bold px-6"
|
||||
>
|
||||
{label}
|
||||
</CustomTag>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: metalLabel.goldUnit(),
|
||||
dataIndex: 'gold',
|
||||
key: 'gold',
|
||||
title: 'مقدار',
|
||||
dataIndex: 'amount',
|
||||
key: 'amount',
|
||||
type: 'Number',
|
||||
customRender: ({ record }) => {
|
||||
const colorClass = withdrawAmountColorClass(record)
|
||||
return (
|
||||
<div class="flex justify-center">
|
||||
<div class={`font-bold px-6 ${colorClass}`}>{formatWithdrawAmountDisplay(record)}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'شماره کارت',
|
||||
|
||||
@@ -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<Withdraw, 'cash' | 'gold'>): boolean {
|
||||
const gold = Math.abs(Number(record.gold ?? 0))
|
||||
if (gold > 0) return false
|
||||
return true
|
||||
}
|
||||
|
||||
export function withdrawTypeLabel(record: Pick<Withdraw, 'cash' | 'gold'>): string {
|
||||
if (!isMoneyWithdraw(record)) {
|
||||
return metalLabel.goldUnit()
|
||||
}
|
||||
|
||||
const cash = Math.abs(Number(record.cash ?? 0))
|
||||
return cash > 0 ? 'پول' : '-'
|
||||
}
|
||||
|
||||
export function withdrawAmountValue(record: Pick<Withdraw, 'cash' | 'gold'>): number {
|
||||
if (isMoneyWithdraw(record)) {
|
||||
return Number(record.cash ?? 0)
|
||||
}
|
||||
return Number(record.gold ?? 0)
|
||||
}
|
||||
|
||||
export function formatWithdrawAmountDisplay(record: Pick<Withdraw, 'cash' | 'gold'>): 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<Withdraw, 'cash' | 'gold'>): string {
|
||||
const value = withdrawAmountValue(record)
|
||||
if (!value) return ''
|
||||
return value > 0 ? 'text-green' : 'text-red'
|
||||
}
|
||||
Reference in New Issue
Block a user