Files
boursecu_admin/src/pages/dashboard/index.vue
T
2026-07-27 21:44:32 +03:30

999 lines
31 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="dashboard-v2 box-border min-h-full w-full max-w-full overflow-x-hidden p-2 md:p-3">
<a-spin :spinning="loading">
<PargarStuckOutboundAlert v-if="pargarEnabled" compact class="mb-2" />
<!-- Hero -->
<section
class="dashboard-hero rounded-xl bg-gradient-to-l from-[#0f766e] via-[#1e3a5f] to-[#0f172a] px-3 py-2.5 text-white shadow-md"
>
<div class="dashboard-hero__row">
<div class="dashboard-hero__price min-w-0">
<div class="flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1">
<DashboardLabelWithHelp
label="مظنه"
:help="heroHelp"
size="xs"
text-class="text-[11px]"
label-class="text-white/80"
/>
<span class="text-[11px] text-white/80">/{{ quoteUnitLabel }}</span>
<span class="text-lg font-bold leading-none sm:text-xl">{{ usePrice(master?.price ?? 0, true, 'ریال') }}</span>
<template v-if="showMesghalQuote">
<span class="text-white/35">·</span>
<span class="text-[11px] text-white/80">مثقال</span>
<span class="text-sm font-semibold leading-none sm:text-base">{{ usePrice(master?.pricem ?? 0, true, 'ریال') }}</span>
</template>
</div>
<p v-if="master?.update_date" class="mt-1.5 text-[10px] leading-none text-white/55">
{{ toJalali(master.update_date) }}
</p>
</div>
<div class="dashboard-hero__actions min-w-0">
<DashboardQuickActionsBar
:quick-action-loading="quickActionLoading"
:has-product-snapshot="hasProductSnapshot"
:product-snapshot-count="productSnapshotCount"
@wallet="showQuickWallet = true"
@deactivate-all="deactivateAll"
@restore-products="restoreProducts"
@close-all-buy="closeAllBuy"
@close-all-sell="closeAllSell"
@sync-prices="syncPrices"
@sync-show="syncShow"
/>
</div>
</div>
</section>
<DashboardQuickWalletModal v-model:visible="showQuickWallet" />
<DashboardProfitSplit :unit-price="master?.price" />
<!-- تراز و آمار — ۴ کارت در یک ردیف -->
<section class="dashboard-metrics-row mt-2">
<DashboardStatCard
tone="green"
icon="vuesax-linear:receipt-2"
title="تعداد سفارش"
subtitle="ثبت‌شده در بازه"
:value="String(finance?.orderCount ?? 0)"
:help="copy.kpi.orderCount"
/>
<DashboardStatCard
v-if="metalBalanceItems.length === 1"
tone="blue"
icon="vuesax-linear:empty-wallet"
:title="metalBalanceTitle(metalBalanceItems[0].metal_id, metalBalanceItems[0].label)"
subtitle="تراز وزن — سفارش موفق"
:value="formatGram(metalBalanceItems[0].goldBalance, metalBalanceItems[0].metal_id)"
:help="copy.kpi.goldBalance"
/>
<DashboardStatCard
v-else
tone="blue"
icon="vuesax-linear:empty-wallet"
:title="metalLabel.goldBalance()"
subtitle="تراز وزن — سفارش موفق"
:value="formatGram(balance?.goldBalance)"
:help="copy.kpi.goldBalance"
/>
<DashboardStatCard
tone="rose"
icon="vuesax-linear:dollar-circle"
title="تراز ریالی"
subtitle="ورود/خروج نقد"
:value="formatDashboardAmount(balance?.cashBalance)"
:unit="DASHBOARD_RIAL_UNIT"
:help="copy.kpi.cashBalance"
/>
<DashboardStatCard
tone="violet"
icon="vuesax-linear:chart-2"
title="تراز مظنه"
subtitle="میانگین موزون"
:value="formatDashboardAmount(balance?.mazanehBalance)"
:unit="DASHBOARD_RIAL_UNIT"
:help="copy.kpi.mazanehBalance"
/>
</section>
<!-- بازه گزارش -->
<section class="dashboard-panel mt-2">
<DashboardSectionHeader title="بازه گزارش" :help="copy.dateRange" />
<div class="dashboard-metrics-row mb-2">
<div
v-for="metric in dateRangeMetrics"
:key="metric.label"
class="dashboard-inline-metric rounded-lg px-2.5 py-2"
>
<DashboardLabelWithHelp
:label="metric.label"
:help="metric.help"
size="xs"
text-class="text-[10px]"
label-class="dashboard-text-muted"
/>
<p class="dashboard-text text-sm font-bold">{{ metric.value }}</p>
</div>
</div>
<div class="flex flex-wrap items-center gap-2">
<BaseDatePicker
v-model:value="startDate"
format="YYYY-MM-DD HH:mm:ss"
class="!w-44"
size="small"
/>
<span class="dashboard-text-muted text-xs">تا</span>
<BaseDatePicker
v-model:value="endDate"
format="YYYY-MM-DD HH:mm:ss"
class="!w-44"
size="small"
/>
<div class="flex items-center gap-2">
<a-button type="primary" size="small" @click="applyDateRange">اعمال</a-button>
<a-button size="small" @click="refreshAll">بروزرسانی</a-button>
</div>
</div>
</section>
<!-- Charts row 1 -->
<section class="mt-2 grid grid-cols-1 gap-2 xl:grid-cols-2">
<div class="dashboard-panel">
<DashboardSectionHeader title="ترکیب درآمد" :help="copy.charts.incomeMix" />
<div class="dashboard-chart-wrap">
<apexchart
v-if="(finance?.fullPriceSood ?? 0) + (finance?.fullPriceKarmozd ?? 0) + (finance?.GiftsPrise ?? 0) > 0"
type="donut"
height="220"
:options="financeChartOptions"
:series="financeChartOptions.series"
/>
<div
v-else
class="dashboard-empty flex h-44 items-center justify-center text-xs"
>
در این بازه داده مالی ثبت نشده
</div>
</div>
</div>
<div class="dashboard-panel">
<div class="flex items-start justify-between gap-2">
<DashboardSectionHeader
class="!mb-0 min-w-0 flex-1"
title="حجم خرید و فروش"
:help="copy.charts.buySellVolume"
/>
<a-radio-group
v-model:value="flowRange"
button-style="solid"
size="small"
class="shrink-0"
>
<a-radio-button value="week">۷ روز</a-radio-button>
<a-radio-button value="month">۳۰ روز</a-radio-button>
</a-radio-group>
</div>
<a-spin :spinning="flowLoading">
<apexchart
v-if="flowData.length"
type="bar"
:height="flowChartHeight"
:options="flowChartOptions"
:series="flowChartOptions.series"
/>
<div
v-else
class="dashboard-empty flex h-44 items-center justify-center text-xs"
>
داده‌ای برای نمودار نیست
</div>
</a-spin>
</div>
</section>
<!-- Price chart -->
<section class="dashboard-panel mt-2">
<div class="mb-3 flex flex-wrap items-start justify-between gap-2">
<DashboardSectionHeader
class="!mb-0 min-w-0 flex-1"
title="نمودار مظنه"
:help="copy.charts.priceChart"
/>
<a-radio-group
v-model:value="priceRange"
button-style="solid"
size="small"
class="dashboard-price-range shrink-0"
>
<a-radio-button value="DAILY">24 ساعت اخیر</a-radio-button>
<a-radio-button value="WEEKLY">هفته گذشته</a-radio-button>
<a-radio-button value="MONTHLY">ماه گذشته</a-radio-button>
</a-radio-group>
</div>
<apexchart
v-if="priceSeries.length"
:key="priceRange"
type="area"
height="200"
:options="priceChartOptions"
:series="priceChartOptions.series"
/>
<div
v-else
class="flex h-40 items-center justify-center text-xs dashboard-empty"
>
داده نمودار قیمت در دسترس نیست
</div>
</section>
<!-- Orders + side charts — ارتفاع سفارشات = مجموع دو پنل کناری -->
<section class="dashboard-orders-row mt-2 grid grid-cols-1 gap-2 lg:grid-cols-3 lg:items-stretch">
<div class="dashboard-orders-side flex flex-col gap-2 lg:col-span-1">
<div class="dashboard-panel">
<div class="mb-2 flex items-start justify-between gap-2">
<DashboardSectionHeader
class="!mb-0 min-w-0 flex-1"
title="ساعات شلوغ سفارش"
:help="copy.orders.peakHours"
/>
<a-radio-group
v-model:value="peakHoursRange"
button-style="solid"
size="small"
class="shrink-0"
>
<a-radio-button :value="7">۷ روز</a-radio-button>
<a-radio-button :value="30">۳۰ روز</a-radio-button>
</a-radio-group>
</div>
<div
v-if="peakHoursData?.peak_hour != null && peakHoursData.peak_count > 0"
class="mb-3 flex flex-wrap items-center gap-1.5"
>
<span class="dashboard-peak-badge dashboard-peak-badge--primary inline-flex items-center rounded-full px-2.5 py-1 text-[11px] font-semibold">
شلوغ‌ترین: {{ formatPeakHourLabel(peakHoursData.peak_hour) }}
<span class="mr-1 font-normal opacity-75">({{ peakHoursData.peak_count }} سفارش)</span>
</span>
<span
v-for="slot in topPeakHours.slice(1)"
:key="slot.hour"
class="dashboard-peak-badge dashboard-peak-badge--muted inline-flex items-center rounded-full px-2 py-0.5 text-[10px]"
>
{{ formatPeakHourLabel(slot.hour) }} · {{ slot.total }}
</span>
</div>
<a-spin :spinning="peakHoursLoading">
<apexchart
v-if="peakHoursData?.total_orders"
type="bar"
:height="peakHoursChartHeight"
:options="peakHoursChartOptions"
:series="peakHoursChartOptions.series"
/>
<div
v-else
class="dashboard-empty flex h-44 items-center justify-center text-xs"
>
در این بازه سفارشی ثبت نشده
</div>
</a-spin>
</div>
<div class="dashboard-panel">
<div class="mb-3 flex flex-wrap items-start justify-between gap-2">
<DashboardSectionHeader
class="!mb-0 min-w-0 flex-1"
title="فعالیت محصولات"
:help="copy.charts.productActivity"
/>
<a-radio-group
v-model:value="productActivityRange"
button-style="solid"
size="small"
class="dashboard-product-range shrink-0"
>
<a-radio-button value="day">روزانه</a-radio-button>
<a-radio-button value="week">هفتگی</a-radio-button>
<a-radio-button value="month">ماهانه</a-radio-button>
<a-radio-button value="year">سالانه</a-radio-button>
</a-radio-group>
</div>
<a-spin :spinning="productActivityLoading">
<div v-if="productReportItems.length" class="dashboard-product-chart min-w-0">
<apexchart
:key="`${productActivityRange}-${productChartHeight}-${productChartLabelWidth}`"
type="bar"
:height="productChartHeight"
:options="productChartOptions"
:series="productChartOptions.series"
/>
</div>
<div
v-else
class="dashboard-empty flex h-44 items-center justify-center text-xs"
>
در این بازه معامله‌ای برای محصولات ثبت نشده
</div>
</a-spin>
</div>
</div>
<div class="dashboard-orders-main relative min-h-[22rem] lg:col-span-2 lg:min-h-0">
<div class="dashboard-panel dashboard-orders-panel flex h-full min-h-0 flex-col overflow-hidden lg:absolute lg:inset-0">
<div class="mb-2 flex shrink-0 items-start justify-between gap-2">
<DashboardSectionHeader
class="!mb-0 min-w-0 flex-1"
title="آخرین سفارشات"
:help="copy.orders.recent"
/>
<router-link :to="{ name: 'ordersMain' }">
<a-button type="link" class="!px-0">همه سفارشات</a-button>
</router-link>
</div>
<div class="min-h-0 flex-1 overflow-auto">
<table class="dashboard-table w-full min-w-[52rem] text-sm">
<thead>
<tr class="border-b text-xs">
<th class="px-2 py-2 text-right">کد</th>
<th class="px-2 py-2 text-right">نوع</th>
<th class="px-2 py-2 text-right">مشتری</th>
<th class="px-2 py-2 text-right">مقدار</th>
<th class="px-2 py-2 text-right">مظنه/گرم</th>
<th class="px-2 py-2 text-right">قیمت مشتری</th>
<th class="px-2 py-2 text-right">سود اختلاف قیمت/گرم</th>
<th class="px-2 py-2 text-right">سود کل اختلاف قیمت</th>
<th class="px-2 py-2 text-right">کارمزد</th>
<th class="px-2 py-2 text-right">وضعیت</th>
<th class="px-2 py-2 text-right">تاریخ</th>
</tr>
</thead>
<tbody>
<tr
v-for="order in recentOrders"
:key="order.id"
class="border-b hover:bg-[var(--admin-bg-hover)]"
>
<td class="px-2 py-2.5 font-medium">#{{ order.id }}</td>
<td class="px-2 py-2.5">
<span class="rounded-md px-2 py-0.5 text-xs" :class="directionClass(order.direction)">
{{ directionLabel(order.direction) }}
</span>
</td>
<td class="max-w-[8rem] truncate px-2 py-2.5">{{ order.customer?.name ?? '—' }}</td>
<td class="px-2 py-2.5">{{ formatGram(order.amount) }}</td>
<td class="px-2 py-2.5">{{ usePrice(order.price_original ?? 0, true, 'ریال') }}</td>
<td class="px-2 py-2.5">{{ usePrice(order.price ?? 0, true, 'ریال') }}</td>
<td class="dashboard-profit px-2 py-2.5">
{{ usePrice(order.fee ?? 0, true, 'ریال') }}
</td>
<td class="dashboard-profit px-2 py-2.5 font-semibold">
{{ usePrice(order.total_fee ?? 0, true, 'ریال') }}
</td>
<td class="px-2 py-2.5">
<div class="leading-snug">
<div>{{ orderServiceFee(order).value }}</div>
<div
v-if="orderServiceFee(order).source"
class="dashboard-text-muted text-[11px]"
>
{{ orderServiceFee(order).source }}
</div>
</div>
</td>
<td class="px-2 py-2.5">
<span class="rounded-md px-2 py-0.5 text-xs" :class="stsClass(order.sts)">
{{ stsLabel(order) }}
</span>
</td>
<td class="dashboard-text-muted whitespace-nowrap px-2 py-2.5 text-xs">
{{ order.created_at ? toJalali(order.created_at) : '—' }}
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</section>
<!-- Bottom row -->
<section class="mt-2 grid grid-cols-1 gap-2 xl:grid-cols-2">
<div class="dashboard-panel">
<DashboardSectionHeader title="مشتریان" :help="copy.customers" />
<div class="grid grid-cols-2 gap-2.5 md:grid-cols-4">
<div
v-for="item in customerMetricItems"
:key="item.label"
class="dashboard-metric-cell"
:class="`dashboard-metric-cell--${item.variant}`"
>
<DashboardLabelWithHelp
:label="item.label"
:help="item.help"
size="xs"
text-class="text-[11px]"
label-class="dashboard-text-muted"
root-class="dashboard-metric-label"
/>
<p class="dashboard-metric-value">{{ item.value }}</p>
</div>
</div>
</div>
<div class="dashboard-panel">
<DashboardSectionHeader title="موجودی کیف پول‌ها" :help="copy.wallet" />
<div class="grid grid-cols-1 gap-2.5 sm:grid-cols-3">
<div
v-for="item in walletMetricItems"
:key="item.label"
class="dashboard-metric-cell"
:class="`dashboard-metric-cell--${item.variant}`"
>
<DashboardLabelWithHelp
:label="item.label"
:help="item.help"
size="xs"
text-class="text-[11px]"
label-class="dashboard-text-muted"
root-class="dashboard-metric-label"
/>
<p class="dashboard-metric-value">{{ item.value }}</p>
</div>
</div>
</div>
</section>
</a-spin>
</div>
</template>
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import DashboardLabelWithHelp from '@/pages/dashboard/components/DashboardLabelWithHelp.vue'
import PargarStuckOutboundAlert from '@/pages/menuPages/accounting/components/PargarStuckOutboundAlert.vue'
import DashboardQuickActionsBar from '@/pages/dashboard/components/DashboardQuickActionsBar.vue'
import DashboardQuickWalletModal from '@/pages/dashboard/components/DashboardQuickWalletModal.vue'
import DashboardSectionHeader from '@/pages/dashboard/components/DashboardSectionHeader.vue'
import DashboardStatCard from '@/pages/dashboard/components/DashboardStatCard.vue'
import DashboardProfitSplit from '@/pages/dashboard/components/DashboardProfitSplit.vue'
import {
dashboardCharts,
dashboardCustomerMetrics,
dashboardCustomers,
dashboardDateMetrics,
dashboardDateRange,
dashboardGateway,
dashboardHero,
dashboardKpi,
dashboardOrders,
dashboardWallet,
dashboardWalletMetrics,
} from '@/pages/dashboard/dashboardSectionCopy'
import { DASHBOARD_RIAL_UNIT, formatDashboardAmount } from '@/pages/dashboard/dashboardFormat'
import { useDashboardPage } from '@/pages/dashboard/composables/useDashboardPage'
import { useDashboardQuickActions } from '@/pages/dashboard/composables/useDashboardQuickActions'
import { metalBalanceTitle, metalLabel } from '@/utils/adminMetalLabels'
import { formatOrderServiceFee } from '@/utils/orderServiceFee'
import { getMainMetal } from '@/utils/metalLabels'
import { TRADE_WEIGHT_UNIT_GRAM, TRADE_WEIGHT_UNIT_KILOGRAM } from '@/utils/weightUnit'
import { initAccountingSettingsFromApi, pargarEnabledSetting } from '@/utils/config'
const pargarEnabled = pargarEnabledSetting
onMounted(() => {
void initAccountingSettingsFromApi()
})
const copy = {
hero: dashboardHero,
gateway: dashboardGateway,
kpi: dashboardKpi,
dateRange: dashboardDateRange,
charts: dashboardCharts,
orders: dashboardOrders,
customers: dashboardCustomers,
wallet: dashboardWallet,
}
const {
loading,
flowLoading,
startDate,
endDate,
master,
finance,
balance,
metalBalanceItems,
walletSums,
recentOrders,
peakHoursRange,
peakHoursData,
peakHoursLoading,
topPeakHours,
priceRange,
flowRange,
flowData,
priceSeries,
productActivityRange,
productActivityLoading,
productReportItems,
productChartHeight,
productChartLabelWidth,
refreshAll,
applyDateRange,
loadFlow,
loadProductActivity,
formatGram,
directionLabel,
directionClass,
stsClass,
stsLabel,
financeChartOptions,
flowChartOptions,
flowChartHeight,
peakHoursChartOptions,
peakHoursChartHeight,
formatPeakHourLabel,
priceChartOptions,
productChartOptions,
usePrice,
toJalali,
customerStats,
} = useDashboardPage()
const orderServiceFee = formatOrderServiceFee
const isCopperMainMetal = computed(() => getMainMetal().trim() === 'مس')
const quoteUnitLabel = computed(() =>
isCopperMainMetal.value ? TRADE_WEIGHT_UNIT_KILOGRAM : TRADE_WEIGHT_UNIT_GRAM
)
const showMesghalQuote = computed(() => !isCopperMainMetal.value)
const heroHelp = computed(() =>
isCopperMainMetal.value
? {
description: 'آخرین قیمت پایه هر کیلوگرم مس در سیستم.',
formula: 'مظنه بر حسب کیلوگرم نمایش داده می‌شود؛ مثقال برای مس کاربرد ندارد.',
}
: copy.hero
)
const tradingProfitTotal = computed(() => {
const priceDiff = Number(finance.value?.fullPriceSood ?? 0)
const commission = Number(finance.value?.fullPriceKarmozd ?? finance.value?.fullPriceKaermozd ?? 0)
return Number(finance.value?.totalTradingProfit ?? priceDiff + commission)
})
const {
quickActionLoading,
hasProductSnapshot,
productSnapshotCount,
loadSnapshotStatus,
deactivateAll,
restoreProducts,
closeAllBuy,
closeAllSell,
syncShow,
syncPrices,
} = useDashboardQuickActions(refreshAll)
const showQuickWallet = ref(false)
void loadSnapshotStatus()
const dateRangeMetrics = computed(() => [
{
label: 'واریز بانکی',
value: `${formatDashboardAmount(balance.value?.income_money)} ${DASHBOARD_RIAL_UNIT}`,
help: dashboardDateMetrics.bankDeposit,
},
{
label: 'جمع سود معاملات',
value: `${formatDashboardAmount(tradingProfitTotal.value)} ${DASHBOARD_RIAL_UNIT}`,
help: dashboardDateMetrics.totalTradingProfit,
},
{
label: 'تراز قیمت−پرداخت (پس از هدیه)',
value: `${formatDashboardAmount(finance.value?.sumAll)} ${DASHBOARD_RIAL_UNIT}`,
help: dashboardDateMetrics.netIncome,
},
{
label: 'هدایا (گرم)',
value: `${finance.value?.Gifts ?? '0'} گرم`,
help: dashboardDateMetrics.gifts,
},
])
const customerMetricItems = computed(() => [
{
label: 'کل مشتریان',
value: customerStats.value.total,
help: dashboardCustomerMetrics.total,
variant: 'default',
},
{
label: 'جدید (۳ ماه)',
value: customerStats.value.newCount,
help: dashboardCustomerMetrics.new,
variant: 'default',
},
{
label: 'فعال',
value: customerStats.value.active,
help: dashboardCustomerMetrics.active,
variant: 'success',
},
{
label: 'بدون احراز',
value: customerStats.value.notVerified,
help: dashboardCustomerMetrics.notVerified,
variant: 'danger',
},
])
const walletMetricItems = computed(() => [
{
label: metalLabel.customersGold(),
value: formatGram(walletSums.value?.gold),
help: dashboardWalletMetrics.gold,
variant: 'amber',
},
{
label: 'ریال مشتریان',
value: `${formatDashboardAmount(walletSums.value?.cash)} ${DASHBOARD_RIAL_UNIT}`,
help: dashboardWalletMetrics.cash,
variant: 'sky',
},
{
label: metalLabel.goldValueInRial(),
value: `${formatDashboardAmount(walletSums.value?.goldToCash)} ${DASHBOARD_RIAL_UNIT}`,
help: dashboardWalletMetrics.goldToCash,
variant: 'violet',
},
])
</script>
<style scoped>
.dashboard-panel {
border-radius: 0.75rem;
border: 1px solid var(--admin-border);
background: var(--admin-bg-elevated);
color: var(--admin-text);
padding: 1rem;
}
.dashboard-hero__row {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.dashboard-hero__actions {
width: 100%;
}
@media (min-width: 1280px) {
.dashboard-hero__row {
flex-direction: row;
align-items: center;
justify-content: space-between;
gap: 1rem;
}
.dashboard-hero__price {
order: 1;
min-width: 0;
}
.dashboard-hero__actions {
order: 2;
width: auto;
max-width: min(100%, 56rem);
flex-shrink: 1;
min-width: 0;
}
}
@media (min-width: 768px) and (max-width: 1279px) {
/* دسکتاپ باریک: مظنه بالا، دکمه‌ها زیرش در ردیف مرتب — نه قاطی کنار هم */
.dashboard-hero__row {
flex-direction: column;
align-items: stretch;
gap: 0.875rem;
}
.dashboard-hero__price {
order: 1;
}
.dashboard-hero__actions {
order: 2;
width: 100%;
}
}
/* ارتفاع «آخرین سفارشات» = ارتفاع ستون دو نمودار؛ جدول داخل اسکرول می‌شود */
@media (min-width: 1024px) {
.dashboard-orders-row {
align-items: stretch;
}
.dashboard-orders-main {
/* ارتفاع ردیف را ستون کناری تعیین می‌کند */
min-height: 0;
}
.dashboard-orders-panel {
max-height: 100%;
}
}
.dashboard-metrics-row {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 0.5rem;
}
@media (max-width: 1024px) {
.dashboard-metrics-row {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (max-width: 480px) {
.dashboard-metrics-row {
grid-template-columns: 1fr;
}
}
.dashboard-text {
color: var(--admin-text);
}
.dashboard-text-muted {
color: var(--admin-text-secondary);
}
.dashboard-empty {
color: var(--admin-text-secondary);
}
.dashboard-inline-metric {
background: var(--admin-bg-muted);
min-height: 3.75rem;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.dashboard-profit {
color: #059669;
}
.dashboard-metric-cell {
display: flex;
min-height: 4.75rem;
flex-direction: column;
justify-content: space-between;
border-radius: 0.5rem;
padding: 0.75rem;
background: var(--admin-bg-muted);
color: var(--admin-text);
}
.dashboard-metric-cell--success {
background: rgba(16, 185, 129, 0.12);
border: 1px solid rgba(16, 185, 129, 0.18);
color: #047857;
}
.dashboard-metric-cell--danger {
background: rgba(244, 63, 94, 0.1);
border: 1px solid rgba(244, 63, 94, 0.18);
color: #be123c;
}
.dashboard-metric-cell--amber {
background: rgba(245, 158, 11, 0.12);
border: 1px solid rgba(245, 158, 11, 0.18);
}
.dashboard-metric-cell--sky {
background: rgba(14, 165, 233, 0.12);
border: 1px solid rgba(14, 165, 233, 0.18);
}
.dashboard-metric-cell--violet {
background: rgba(139, 92, 246, 0.12);
border: 1px solid rgba(139, 92, 246, 0.18);
}
.dashboard-peak-badge--primary {
background: rgba(245, 158, 11, 0.14);
color: #b45309;
}
.dashboard-peak-badge--muted {
background: var(--admin-bg-muted);
color: var(--admin-text-muted);
}
.dashboard-table thead tr,
.dashboard-table tbody tr {
border-color: var(--admin-border);
}
.dashboard-table thead tr {
color: var(--admin-text-secondary);
}
.dashboard-table tbody tr {
color: var(--admin-text);
}
.dashboard-badge--buy {
background: rgba(16, 185, 129, 0.14);
color: #047857;
}
.dashboard-badge--sell {
background: rgba(244, 63, 94, 0.14);
color: #be123c;
}
.dashboard-badge--ok {
background: rgba(16, 185, 129, 0.14);
color: #047857;
}
.dashboard-badge--open {
background: rgba(245, 158, 11, 0.14);
color: #b45309;
}
.dashboard-badge--fail {
background: rgba(244, 63, 94, 0.14);
color: #be123c;
}
.dashboard-metric-value {
@apply mt-2 text-base font-bold leading-snug break-words;
}
.dashboard-v2 :deep(.dashboard-metric-label) {
align-items: flex-start;
line-height: 1.4;
}
.dashboard-v2 :deep(.dashboard-metric-label > span:first-child) {
overflow: visible;
text-overflow: unset;
white-space: normal;
line-height: 1.4;
}
.dashboard-chart-wrap {
@apply px-1 pb-1;
}
.dashboard-v2 :deep(.apexcharts-legend) {
padding-inline: 12px !important;
}
.dashboard-v2 :deep(.apexcharts-legend-series) {
margin-inline: 8px !important;
}
.dashboard-v2 :deep(.apexcharts-tooltip) {
direction: rtl;
}
.dashboard-v2 :deep(.dashboard-section-header) {
overflow: visible;
padding-bottom: 2px;
}
.dashboard-v2 :deep(.dashboard-section-title) {
line-height: 1.6;
}
.dashboard-v2 :deep(.dashboard-section-title > span:first-child) {
line-height: 1.6;
overflow: visible;
padding-bottom: 2px;
}
.dashboard-v2 :deep(.dashboard-label-with-help .ant-tooltip-disabled-compatible-wrapper),
.dashboard-v2 :deep(.dashboard-label-with-help .dashboard-help-trigger) {
display: inline-flex !important;
align-items: center !important;
line-height: 1 !important;
vertical-align: baseline !important;
}
.dashboard-product-chart {
@apply min-w-0 px-0.5 md:px-1;
}
.dashboard-product-chart :deep(.apexcharts-yaxis-texts-g text) {
text-anchor: start !important;
direction: rtl;
unicode-bidi: plaintext;
}
.dashboard-product-chart :deep(.apexcharts-yaxis) {
transform: translateX(-2px);
}
</style>
<style lang="less">
html.is-dark {
.dashboard-v2 {
.dashboard-profit {
color: #6ee7b7;
}
.dashboard-metric-cell--success {
background: rgba(16, 185, 129, 0.16);
border-color: rgba(16, 185, 129, 0.28);
color: #6ee7b7;
}
.dashboard-metric-cell--danger {
background: rgba(244, 63, 94, 0.16);
border-color: rgba(244, 63, 94, 0.28);
color: #fda4af;
}
.dashboard-metric-cell--amber {
background: rgba(245, 158, 11, 0.16);
border-color: rgba(245, 158, 11, 0.28);
}
.dashboard-metric-cell--sky {
background: rgba(14, 165, 233, 0.16);
border-color: rgba(14, 165, 233, 0.28);
}
.dashboard-metric-cell--violet {
background: rgba(139, 92, 246, 0.16);
border-color: rgba(139, 92, 246, 0.28);
}
.dashboard-peak-badge--primary {
background: rgba(245, 158, 11, 0.18);
color: #fcd34d;
}
.dashboard-badge--buy {
background: rgba(16, 185, 129, 0.2);
color: #6ee7b7;
}
.dashboard-badge--sell {
background: rgba(244, 63, 94, 0.2);
color: #fda4af;
}
.dashboard-badge--open {
background: rgba(245, 158, 11, 0.2);
color: #fcd34d;
}
.dashboard-badge--fail {
background: rgba(244, 63, 94, 0.2);
color: #fda4af;
}
.dashboard-badge--ok {
background: rgba(16, 185, 129, 0.2);
color: #6ee7b7;
}
}
}
</style>