ui: update ProductPerformanceProfitability page to enhance table views and tabs management
This commit is contained in:
@@ -356,7 +356,7 @@ const menuItems = [
|
||||
},
|
||||
|
||||
{
|
||||
label: 'Ürün Fiyatlandırma',
|
||||
label: 'Ürün Fiyatlandırma ve Analiz',
|
||||
icon: 'sell',
|
||||
|
||||
children: [
|
||||
|
||||
@@ -32,38 +32,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<q-card flat bordered class="q-mb-sm">
|
||||
<q-card-section class="row q-col-gutter-sm items-end">
|
||||
<div class="col-12 col-md-3">
|
||||
<q-input v-model="filters.q" dense outlined clearable label="Ürün / açıklama ara" @keyup.enter="reload" />
|
||||
</div>
|
||||
<div class="col-6 col-md-2">
|
||||
<q-input v-model="filters.product_code" dense outlined clearable label="Ürün kodu" @keyup.enter="reload" />
|
||||
</div>
|
||||
<div class="col-6 col-md-2">
|
||||
<q-input v-model="filters.kategori" dense outlined clearable label="Kategori" @keyup.enter="reload" />
|
||||
</div>
|
||||
<div class="col-6 col-md-2">
|
||||
<q-input v-model="filters.seri" dense outlined clearable label="Ürün Ana Grubu" @keyup.enter="reload" />
|
||||
</div>
|
||||
<div class="col-6 col-md-2">
|
||||
<q-select
|
||||
v-model="filters.bucket"
|
||||
dense
|
||||
outlined
|
||||
clearable
|
||||
emit-value
|
||||
map-options
|
||||
label="Durum"
|
||||
:options="bucketOptions"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-12 col-md-1">
|
||||
<q-btn class="full-width" color="secondary" outline icon="search" label="Ara" @click="reload" />
|
||||
</div>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
|
||||
<q-tabs
|
||||
v-model="activeTab"
|
||||
dense
|
||||
@@ -73,6 +41,9 @@
|
||||
align="left"
|
||||
>
|
||||
<q-tab name="products" icon="inventory_2" label="Ürün KPI" />
|
||||
<q-tab name="general" icon="analytics" label="KPI Genel" />
|
||||
<q-tab name="order_product_customers" icon="assignment_ind" label="Ürün > Müşteri Sipariş" />
|
||||
<q-tab name="order_market_details" icon="account_tree" label="Piyasa > Sipariş Detay" />
|
||||
<q-tab name="idle" icon="warning" label="Atıl Stok / Maliyet" />
|
||||
<q-tab name="markets" icon="query_stats" label="Piyasa Grup Kıyası" />
|
||||
<q-tab name="countries" icon="public" label="Ülke / Segment" />
|
||||
@@ -80,7 +51,121 @@
|
||||
</q-tabs>
|
||||
|
||||
<q-table
|
||||
v-if="activeTab === 'products'"
|
||||
v-if="activeTab === 'general'"
|
||||
flat
|
||||
bordered
|
||||
row-key="row_key"
|
||||
class="performance-table bg-white"
|
||||
:rows="generalRows"
|
||||
:columns="generalColumns"
|
||||
:loading="loading"
|
||||
:pagination="{ rowsPerPage: 100, sortBy: 'performance_score', descending: true }"
|
||||
>
|
||||
<template #body-cell-image="props">
|
||||
<q-td :props="props" class="product-image-cell">
|
||||
<button type="button" class="product-thumb-button" @click.stop="openProductImageDialog(props.row)">
|
||||
<q-img
|
||||
v-if="getCachedProductImageUrl(props.row)"
|
||||
:src="getCachedProductImageUrl(props.row)"
|
||||
fit="contain"
|
||||
class="product-thumb"
|
||||
no-spinner
|
||||
/>
|
||||
<div v-else class="product-thumb-placeholder">
|
||||
<q-icon name="image" size="22px" />
|
||||
</div>
|
||||
<q-tooltip>Foto</q-tooltip>
|
||||
</button>
|
||||
</q-td>
|
||||
</template>
|
||||
<template #body-cell-performance_bucket="props">
|
||||
<q-td :props="props">
|
||||
<q-badge :color="bucketColor(props.row.performance_bucket)">
|
||||
{{ bucketLabel(props.row.performance_bucket) }}
|
||||
</q-badge>
|
||||
</q-td>
|
||||
</template>
|
||||
</q-table>
|
||||
|
||||
<q-table
|
||||
v-else-if="activeTab === 'order_product_customers'"
|
||||
flat
|
||||
bordered
|
||||
row-key="row_key"
|
||||
class="performance-table bg-white"
|
||||
:rows="orderProductCustomerRows"
|
||||
:columns="orderProductCustomerColumns"
|
||||
:loading="loading"
|
||||
:pagination="{ rowsPerPage: 100, sortBy: 'order_usd', descending: true }"
|
||||
>
|
||||
<template #body-cell-image="props">
|
||||
<q-td :props="props" class="product-image-cell">
|
||||
<button type="button" class="product-thumb-button" @click.stop="openProductImageDialog(props.row)">
|
||||
<q-img
|
||||
v-if="getCachedProductImageUrl(props.row)"
|
||||
:src="getCachedProductImageUrl(props.row)"
|
||||
fit="contain"
|
||||
class="product-thumb"
|
||||
no-spinner
|
||||
/>
|
||||
<div v-else class="product-thumb-placeholder">
|
||||
<q-icon name="image" size="22px" />
|
||||
</div>
|
||||
<q-tooltip>Foto</q-tooltip>
|
||||
</button>
|
||||
</q-td>
|
||||
</template>
|
||||
<template #body-cell-performance_bucket="props">
|
||||
<q-td :props="props">
|
||||
<q-badge :color="bucketColor(props.row.performance_bucket)">
|
||||
{{ bucketLabel(props.row.performance_bucket) }}
|
||||
</q-badge>
|
||||
</q-td>
|
||||
</template>
|
||||
<template #body-cell-net_stock_after_order="props">
|
||||
<q-td :props="props" class="text-right" :class="Number(props.row.net_stock_after_order || 0) < 0 ? 'text-negative text-weight-bold' : ''">
|
||||
{{ formatNumber(props.row.net_stock_after_order, 0) }}
|
||||
</q-td>
|
||||
</template>
|
||||
<template #body-cell-expected_profit_cost_usd="props">
|
||||
<q-td :props="props" class="text-right" :class="Number(props.row.expected_profit_cost_usd || 0) < 0 ? 'text-negative text-weight-bold' : 'text-positive'">
|
||||
{{ formatMoney(props.row.expected_profit_cost_usd, 'USD') }}
|
||||
</q-td>
|
||||
</template>
|
||||
</q-table>
|
||||
|
||||
<q-table
|
||||
v-else-if="activeTab === 'order_market_details'"
|
||||
flat
|
||||
bordered
|
||||
row-key="row_key"
|
||||
class="performance-table bg-white"
|
||||
:rows="orderMarketDetailRows"
|
||||
:columns="orderMarketDetailColumns"
|
||||
:loading="loading"
|
||||
:pagination="{ rowsPerPage: 100, sortBy: 'order_date', descending: true }"
|
||||
>
|
||||
<template #body-cell-performance_bucket="props">
|
||||
<q-td :props="props">
|
||||
<q-badge :color="bucketColor(props.row.performance_bucket)">
|
||||
{{ bucketLabel(props.row.performance_bucket) }}
|
||||
</q-badge>
|
||||
</q-td>
|
||||
</template>
|
||||
<template #body-cell-net_stock_after_order="props">
|
||||
<q-td :props="props" class="text-right" :class="Number(props.row.net_stock_after_order || 0) < 0 ? 'text-negative text-weight-bold' : ''">
|
||||
{{ formatNumber(props.row.net_stock_after_order, 0) }}
|
||||
</q-td>
|
||||
</template>
|
||||
<template #body-cell-expected_profit_cost_usd="props">
|
||||
<q-td :props="props" class="text-right" :class="Number(props.row.expected_profit_cost_usd || 0) < 0 ? 'text-negative text-weight-bold' : 'text-positive'">
|
||||
{{ formatMoney(props.row.expected_profit_cost_usd, 'USD') }}
|
||||
</q-td>
|
||||
</template>
|
||||
</q-table>
|
||||
|
||||
<q-table
|
||||
v-else-if="activeTab === 'products'"
|
||||
flat
|
||||
bordered
|
||||
row-key="row_key"
|
||||
@@ -99,15 +184,22 @@
|
||||
</q-tr>
|
||||
<q-tr class="table-filter-row">
|
||||
<q-th v-for="col in props.cols" :key="`filter-${col.name}`">
|
||||
<q-input
|
||||
<q-select
|
||||
v-if="isColumnFilterable(col.name)"
|
||||
v-model="columnFilters[col.name]"
|
||||
:options="columnFilterOptions(col.name)"
|
||||
dense
|
||||
borderless
|
||||
clearable
|
||||
debounce="250"
|
||||
multiple
|
||||
use-chips
|
||||
emit-value
|
||||
map-options
|
||||
options-dense
|
||||
class="header-filter"
|
||||
placeholder="Filtre"
|
||||
popup-content-class="header-filter-popup"
|
||||
placeholder="Seç"
|
||||
@click.stop
|
||||
/>
|
||||
</q-th>
|
||||
</q-tr>
|
||||
@@ -135,7 +227,10 @@
|
||||
Ort.Satış {{ formatMoney(props.row.avg_price_usd_90d, 'USD') }} ·
|
||||
Taban {{ formatMoney(props.row.avg_base_price_usd, 'USD') }} ·
|
||||
Çıplak {{ formatMoney(props.row.avg_cost_price_usd, 'USD') }} ·
|
||||
Marj {{ formatPercent(props.row.avg_gross_margin_90d) }}
|
||||
90G K/Z {{ formatMoney(props.row.gross_profit_usd_90d, 'USD') }} ·
|
||||
90G Marj {{ formatPercent(props.row.gross_margin_90d) }} ·
|
||||
180G K/Z {{ formatMoney(props.row.gross_profit_usd_180d, 'USD') }} ·
|
||||
180G Marj {{ formatPercent(props.row.gross_margin_180d) }}
|
||||
</span>
|
||||
</q-td>
|
||||
</q-tr>
|
||||
@@ -400,7 +495,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
import { computed, onMounted, reactive, ref, watch } from 'vue'
|
||||
import { Notify } from 'quasar'
|
||||
import api from 'src/services/api'
|
||||
import { formatMoney, formatNumber, formatPercent } from 'src/utils/formatters'
|
||||
@@ -412,6 +507,16 @@ const canUpdate = computed(() => perm.hasApiPermission('pricing:update'))
|
||||
const loading = ref(false)
|
||||
const refreshing = ref(false)
|
||||
const rows = ref([])
|
||||
const generalRows = ref([])
|
||||
const orderAnalysisRows = ref([])
|
||||
const orderMarketRows = ref([])
|
||||
const orderCustomerRows = ref([])
|
||||
const orderProductCustomerRows = ref([])
|
||||
const orderMarketDetailRows = ref([])
|
||||
const orderAnalysisLoaded = ref(false)
|
||||
const orderProductCustomersLoaded = ref(false)
|
||||
const orderMarketDetailsLoaded = ref(false)
|
||||
const orderAnalysisMode = ref('product')
|
||||
const marketRows = ref([])
|
||||
const countryRows = ref([])
|
||||
const customerRows = ref([])
|
||||
@@ -433,13 +538,6 @@ const detailLoading = ref(false)
|
||||
const detailSalesRows = ref([])
|
||||
const detailStockSizes = ref([])
|
||||
|
||||
const filters = reactive({
|
||||
q: '',
|
||||
product_code: '',
|
||||
kategori: '',
|
||||
seri: '',
|
||||
bucket: ''
|
||||
})
|
||||
const columnFilters = reactive({})
|
||||
const expandedGroups = ref({})
|
||||
|
||||
@@ -510,6 +608,8 @@ const columns = [
|
||||
{ name: 'unit_profit_cost_90d', label: '90G P.Başı Çıplak K/Z', field: row => formatMoney(row.unit_profit_cost_90d, 'USD'), align: 'right', sortable: true },
|
||||
{ name: 'unit_profit_base_180d', label: '180G P.Başı Taban K/Z', field: row => formatMoney(row.unit_profit_base_180d, 'USD'), align: 'right', sortable: true },
|
||||
{ name: 'unit_profit_cost_180d', label: '180G P.Başı Çıplak K/Z', field: row => formatMoney(row.unit_profit_cost_180d, 'USD'), align: 'right', sortable: true },
|
||||
{ name: 'gross_profit_usd_90d', label: '90G Toplam K/Z', field: row => formatMoney(row.gross_profit_usd_90d, 'USD'), align: 'right', sortable: true },
|
||||
{ name: 'gross_profit_usd_180d', label: '180G Toplam K/Z', field: row => formatMoney(row.gross_profit_usd_180d, 'USD'), align: 'right', sortable: true },
|
||||
{ name: 'gross_margin_90d', label: 'Marj', field: 'gross_margin_90d', align: 'right', sortable: true },
|
||||
{ name: 'gross_margin_180d', label: '180G Marj', field: 'gross_margin_180d', align: 'right', sortable: true },
|
||||
{ name: 'market_count_90d', label: '90G Piyasa', field: row => formatNumber(row.market_count_90d, 0), align: 'right', sortable: true },
|
||||
@@ -520,6 +620,155 @@ const columns = [
|
||||
{ name: 'recommendation', label: 'Öneri', field: 'recommendation', align: 'left' }
|
||||
]
|
||||
|
||||
const generalColumns = [
|
||||
{ name: 'image', label: 'Foto', field: 'image', align: 'center' },
|
||||
{ name: 'product_code', label: 'Ürün', field: 'product_code', align: 'left', sortable: true },
|
||||
{ name: 'color_code', label: 'Renk', field: 'color_code', align: 'left', sortable: true },
|
||||
{ name: 'yaka_kodu', label: 'Yaka', field: 'yaka_kodu', align: 'left', sortable: true },
|
||||
{ name: 'item_description', label: 'Açıklama', field: 'item_description', align: 'left', sortable: true },
|
||||
{ name: 'kategori', label: 'Kategori', field: 'kategori', align: 'left', sortable: true },
|
||||
{ name: 'urun_ilk_grubu', label: 'Ürün İlk Grubu', field: row => row.urun_ilk_grubu || row.yas_grubu || '', align: 'left', sortable: true },
|
||||
{ name: 'askili_yan', label: 'Askılı/Yan', field: 'askili_yan', align: 'left', sortable: true },
|
||||
{ name: 'urun_ana_grubu', label: 'Ürün Ana Grubu', field: row => row.urun_ana_grubu || row.seri || '', align: 'left', sortable: true },
|
||||
{ name: 'urun_alt_grubu', label: 'Ürün Alt Grubu', field: 'urun_alt_grubu', align: 'left', sortable: true },
|
||||
{ name: 'market_key', label: 'Piyasa', field: 'market_key', align: 'left', sortable: true },
|
||||
{ name: 'period_start', label: 'Başlangıç', field: 'period_start', align: 'left', sortable: true },
|
||||
{ name: 'period_end', label: 'Bitiş', field: 'period_end', align: 'left', sortable: true },
|
||||
{ name: 'stock_qty', label: 'Stok', field: row => formatNumber(row.stock_qty, 0), align: 'right', sortable: true },
|
||||
{ name: 'sales_qty_total', label: 'Toplam Satış', field: row => formatNumber(row.sales_qty_total, 0), align: 'right', sortable: true },
|
||||
{ name: 'sales_usd_total', label: 'Toplam USD', field: row => formatMoney(row.sales_usd_total, 'USD'), align: 'right', sortable: true },
|
||||
{ name: 'avg_daily_sales_total', label: 'Günlük Ort.', field: row => formatNumber(row.avg_daily_sales_total, 3), align: 'right', sortable: true },
|
||||
{ name: 'stock_days_total', label: 'Genel Stok Gün', field: row => formatNumber(row.stock_days_total, 1), align: 'right', sortable: true },
|
||||
{ name: 'avg_price_usd_total', label: 'Genel Ort. USD', field: row => formatMoney(row.avg_price_usd_total, 'USD'), align: 'right', sortable: true },
|
||||
{ name: 'base_price_usd', label: 'USD Taban Maliyeti', field: row => formatMoney(row.base_price_usd, 'USD'), align: 'right', sortable: true },
|
||||
{ name: 'cost_price_usd', label: 'USD Çıplak Maliyeti', field: row => formatMoney(row.cost_price_usd, 'USD'), align: 'right', sortable: true },
|
||||
{ name: 'unit_profit_base_total', label: 'P.Başı Taban K/Z', field: row => formatMoney(row.unit_profit_base_total, 'USD'), align: 'right', sortable: true },
|
||||
{ name: 'unit_profit_cost_total', label: 'P.Başı Çıplak K/Z', field: row => formatMoney(row.unit_profit_cost_total, 'USD'), align: 'right', sortable: true },
|
||||
{ name: 'gross_profit_usd_total', label: 'Toplam K/Z', field: row => formatMoney(row.gross_profit_usd_total, 'USD'), align: 'right', sortable: true },
|
||||
{ name: 'gross_margin_total', label: 'Genel Marj', field: row => formatPercent(row.gross_margin_total), align: 'right', sortable: true },
|
||||
{ name: 'market_count_total', label: 'Toplam Piyasa', field: row => formatNumber(row.market_count_total, 0), align: 'right', sortable: true },
|
||||
{ name: 'customer_count_total', label: 'Toplam Müşteri', field: row => formatNumber(row.customer_count_total, 0), align: 'right', sortable: true },
|
||||
{ name: 'invoice_count_total', label: 'Fatura', field: row => formatNumber(row.invoice_count_total, 0), align: 'right', sortable: true },
|
||||
{ name: 'sales_index_total', label: 'Genel Endeks', field: row => formatNumber(row.sales_index_total, 2), align: 'right', sortable: true },
|
||||
{ name: 'performance_score', label: 'Genel Skor', field: row => formatNumber(row.performance_score, 1), align: 'right', sortable: true },
|
||||
{ name: 'performance_bucket', label: 'Durum', field: 'performance_bucket', align: 'left', sortable: true },
|
||||
{ name: 'first_sale_date', label: 'İlk Satış', field: 'first_sale_date', align: 'left', sortable: true },
|
||||
{ name: 'last_sale_date', label: 'Son Satış', field: 'last_sale_date', align: 'left', sortable: true },
|
||||
{ name: 'recommendation', label: 'Öneri', field: 'recommendation', align: 'left' }
|
||||
]
|
||||
|
||||
const orderAnalysisColumns = [
|
||||
{ name: 'image', label: 'Foto', field: 'image', align: 'center' },
|
||||
{ name: 'product_code', label: 'Ürün', field: 'product_code', align: 'left', sortable: true },
|
||||
{ name: 'color_code', label: 'Renk', field: 'color_code', align: 'left', sortable: true },
|
||||
{ name: 'yaka_kodu', label: 'Yaka', field: 'yaka_kodu', align: 'left', sortable: true },
|
||||
{ name: 'item_description', label: 'Açıklama', field: 'item_description', align: 'left', sortable: true },
|
||||
{ name: 'kategori', label: 'Kategori', field: 'kategori', align: 'left', sortable: true },
|
||||
{ name: 'urun_ilk_grubu', label: 'Ürün İlk Grubu', field: row => row.urun_ilk_grubu || row.yas_grubu || '', align: 'left', sortable: true },
|
||||
{ name: 'askili_yan', label: 'Askılı/Yan', field: 'askili_yan', align: 'left', sortable: true },
|
||||
{ name: 'urun_ana_grubu', label: 'Ürün Ana Grubu', field: row => row.urun_ana_grubu || row.seri || '', align: 'left', sortable: true },
|
||||
{ name: 'urun_alt_grubu', label: 'Ürün Alt Grubu', field: 'urun_alt_grubu', align: 'left', sortable: true },
|
||||
{ name: 'market_key', label: 'Piyasa', field: 'market_key', align: 'left', sortable: true },
|
||||
{ name: 'order_qty', label: 'Açık Sipariş', field: row => formatNumber(row.order_qty, 0), align: 'right', sortable: true },
|
||||
{ name: 'order_usd', label: 'Sipariş USD', field: row => formatMoney(row.order_usd, 'USD'), align: 'right', sortable: true },
|
||||
{ name: 'avg_order_price_usd', label: 'Ort. Sipariş USD', field: row => formatMoney(row.avg_order_price_usd, 'USD'), align: 'right', sortable: true },
|
||||
{ name: 'stock_qty', label: 'Stok', field: row => formatNumber(row.stock_qty, 0), align: 'right', sortable: true },
|
||||
{ name: 'net_stock_after_order', label: 'Stok - Sipariş', field: 'net_stock_after_order', align: 'right', sortable: true },
|
||||
{ name: 'base_price_usd', label: 'USD Taban Maliyeti', field: row => formatMoney(row.base_price_usd, 'USD'), align: 'right', sortable: true },
|
||||
{ name: 'cost_price_usd', label: 'USD Çıplak Maliyeti', field: row => formatMoney(row.cost_price_usd, 'USD'), align: 'right', sortable: true },
|
||||
{ name: 'unit_profit_base_usd', label: 'P.Başı Taban K/Z', field: row => formatMoney(row.unit_profit_base_usd, 'USD'), align: 'right', sortable: true },
|
||||
{ name: 'unit_profit_cost_usd', label: 'P.Başı Çıplak K/Z', field: row => formatMoney(row.unit_profit_cost_usd, 'USD'), align: 'right', sortable: true },
|
||||
{ name: 'expected_profit_base_usd', label: 'Taban Toplam K/Z', field: row => formatMoney(row.expected_profit_base_usd, 'USD'), align: 'right', sortable: true },
|
||||
{ name: 'expected_profit_cost_usd', label: 'Çıplak Toplam K/Z', field: 'expected_profit_cost_usd', align: 'right', sortable: true },
|
||||
{ name: 'expected_margin_cost', label: 'Beklenen Marj', field: row => formatPercent(row.expected_margin_cost), align: 'right', sortable: true },
|
||||
{ name: 'market_count', label: 'Piyasa', field: row => formatNumber(row.market_count, 0), align: 'right', sortable: true },
|
||||
{ name: 'customer_count', label: 'Müşteri', field: row => formatNumber(row.customer_count, 0), align: 'right', sortable: true },
|
||||
{ name: 'order_count', label: 'Sipariş', field: row => formatNumber(row.order_count, 0), align: 'right', sortable: true },
|
||||
{ name: 'line_count', label: 'Satır', field: row => formatNumber(row.line_count, 0), align: 'right', sortable: true },
|
||||
{ name: 'earliest_due_date', label: 'İlk Termin', field: 'earliest_due_date', align: 'left', sortable: true },
|
||||
{ name: 'overdue_qty', label: 'Geciken Adet', field: row => formatNumber(row.overdue_qty, 0), align: 'right', sortable: true },
|
||||
{ name: 'performance_bucket', label: 'Durum', field: 'performance_bucket', align: 'left', sortable: true },
|
||||
{ name: 'recommendation', label: 'Yorum', field: 'recommendation', align: 'left' }
|
||||
]
|
||||
|
||||
const orderGroupColumns = [
|
||||
{ name: 'group_key', label: 'Kırılım', field: row => orderGroupLabel(row), align: 'left', sortable: true },
|
||||
{ name: 'market_key', label: 'Piyasa', field: 'market_key', align: 'left', sortable: true },
|
||||
{ name: 'customer_code', label: 'Müşteri Kodu', field: 'customer_code', align: 'left', sortable: true },
|
||||
{ name: 'customer_name', label: 'Müşteri', field: 'customer_name', align: 'left', sortable: true },
|
||||
{ name: 'product_count', label: 'Ürün', field: row => formatNumber(row.product_count, 0), align: 'right', sortable: true },
|
||||
{ name: 'order_qty', label: 'Açık Sipariş', field: row => formatNumber(row.order_qty, 0), align: 'right', sortable: true },
|
||||
{ name: 'order_usd', label: 'Sipariş USD', field: row => formatMoney(row.order_usd, 'USD'), align: 'right', sortable: true },
|
||||
{ name: 'avg_order_price_usd', label: 'Ort. USD', field: row => formatMoney(row.avg_order_price_usd, 'USD'), align: 'right', sortable: true },
|
||||
{ name: 'base_cost_usd', label: 'Taban Maliyet USD', field: row => formatMoney(row.base_cost_usd, 'USD'), align: 'right', sortable: true },
|
||||
{ name: 'cost_amount_usd', label: 'Çıplak Maliyet USD', field: row => formatMoney(row.cost_amount_usd, 'USD'), align: 'right', sortable: true },
|
||||
{ name: 'expected_profit_base_usd', label: 'Brüt K/Z', field: row => formatMoney(row.expected_profit_base_usd, 'USD'), align: 'right', sortable: true },
|
||||
{ name: 'expected_profit_cost_usd', label: 'Yakl. Net K/Z', field: 'expected_profit_cost_usd', align: 'right', sortable: true },
|
||||
{ name: 'expected_margin_base', label: 'Brüt Marj', field: row => formatPercent(row.expected_margin_base), align: 'right', sortable: true },
|
||||
{ name: 'expected_margin_cost', label: 'Yakl. Net Marj', field: row => formatPercent(row.expected_margin_cost), align: 'right', sortable: true },
|
||||
{ name: 'stock_qty', label: 'Stok', field: row => formatNumber(row.stock_qty, 0), align: 'right', sortable: true },
|
||||
{ name: 'net_stock_after_order', label: 'Stok - Sipariş', field: 'net_stock_after_order', align: 'right', sortable: true },
|
||||
{ name: 'market_count', label: 'Piyasa', field: row => formatNumber(row.market_count, 0), align: 'right', sortable: true },
|
||||
{ name: 'customer_count', label: 'Müşteri', field: row => formatNumber(row.customer_count, 0), align: 'right', sortable: true },
|
||||
{ name: 'order_count', label: 'Sipariş', field: row => formatNumber(row.order_count, 0), align: 'right', sortable: true },
|
||||
{ name: 'line_count', label: 'Satır', field: row => formatNumber(row.line_count, 0), align: 'right', sortable: true },
|
||||
{ name: 'overdue_qty', label: 'Geciken Adet', field: row => formatNumber(row.overdue_qty, 0), align: 'right', sortable: true },
|
||||
{ name: 'performance_bucket', label: 'Durum', field: 'performance_bucket', align: 'left', sortable: true },
|
||||
{ name: 'recommendation', label: 'Yorum', field: 'recommendation', align: 'left' }
|
||||
]
|
||||
|
||||
const orderProductCustomerColumns = [
|
||||
{ name: 'image', label: 'Foto', field: 'image', align: 'center' },
|
||||
{ name: 'product_code', label: 'Ürün', field: 'product_code', align: 'left', sortable: true },
|
||||
{ name: 'color_code', label: 'Renk', field: 'color_code', align: 'left', sortable: true },
|
||||
{ name: 'yaka_kodu', label: 'Yaka', field: 'yaka_kodu', align: 'left', sortable: true },
|
||||
{ name: 'item_description', label: 'Açıklama', field: 'item_description', align: 'left', sortable: true },
|
||||
{ name: 'market_key', label: 'Piyasa', field: 'market_key', align: 'left', sortable: true },
|
||||
{ name: 'customer_code', label: 'Müşteri Kodu', field: 'customer_code', align: 'left', sortable: true },
|
||||
{ name: 'customer_name', label: 'Müşteri', field: 'customer_name', align: 'left', sortable: true },
|
||||
{ name: 'order_qty', label: 'Açık Sipariş', field: row => formatNumber(row.order_qty, 0), align: 'right', sortable: true },
|
||||
{ name: 'order_usd', label: 'Sipariş USD', field: row => formatMoney(row.order_usd, 'USD'), align: 'right', sortable: true },
|
||||
{ name: 'avg_order_price_usd', label: 'Ort. USD', field: row => formatMoney(row.avg_order_price_usd, 'USD'), align: 'right', sortable: true },
|
||||
{ name: 'stock_qty', label: 'Stok', field: row => formatNumber(row.stock_qty, 0), align: 'right', sortable: true },
|
||||
{ name: 'net_stock_after_order', label: 'Stok - Sipariş', field: 'net_stock_after_order', align: 'right', sortable: true },
|
||||
{ name: 'base_price_usd', label: 'Taban USD', field: row => formatMoney(row.base_price_usd, 'USD'), align: 'right', sortable: true },
|
||||
{ name: 'cost_price_usd', label: 'Çıplak USD', field: row => formatMoney(row.cost_price_usd, 'USD'), align: 'right', sortable: true },
|
||||
{ name: 'expected_profit_base_usd', label: 'Brüt K/Z', field: row => formatMoney(row.expected_profit_base_usd, 'USD'), align: 'right', sortable: true },
|
||||
{ name: 'expected_profit_cost_usd', label: 'Yakl. Net K/Z', field: 'expected_profit_cost_usd', align: 'right', sortable: true },
|
||||
{ name: 'expected_margin_base', label: 'Brüt Marj', field: row => formatPercent(row.expected_margin_base), align: 'right', sortable: true },
|
||||
{ name: 'expected_margin_cost', label: 'Yakl. Net Marj', field: row => formatPercent(row.expected_margin_cost), align: 'right', sortable: true },
|
||||
{ name: 'order_count', label: 'Sipariş', field: row => formatNumber(row.order_count, 0), align: 'right', sortable: true },
|
||||
{ name: 'line_count', label: 'Satır', field: row => formatNumber(row.line_count, 0), align: 'right', sortable: true },
|
||||
{ name: 'overdue_qty', label: 'Geciken Adet', field: row => formatNumber(row.overdue_qty, 0), align: 'right', sortable: true },
|
||||
{ name: 'performance_bucket', label: 'Durum', field: 'performance_bucket', align: 'left', sortable: true },
|
||||
{ name: 'recommendation', label: 'Yorum', field: 'recommendation', align: 'left' }
|
||||
]
|
||||
|
||||
const orderMarketDetailColumns = [
|
||||
{ name: 'market_key', label: 'Piyasa', field: 'market_key', align: 'left', sortable: true },
|
||||
{ name: 'customer_code', label: 'Müşteri Kodu', field: 'customer_code', align: 'left', sortable: true },
|
||||
{ name: 'customer_name', label: 'Müşteri', field: 'customer_name', align: 'left', sortable: true },
|
||||
{ name: 'order_number', label: 'Sipariş No', field: 'order_number', align: 'left', sortable: true },
|
||||
{ name: 'order_date', label: 'Sipariş Tarihi', field: 'order_date', align: 'left', sortable: true },
|
||||
{ name: 'due_date', label: 'Termin', field: 'due_date', align: 'left', sortable: true },
|
||||
{ name: 'product_code', label: 'Ürün', field: 'product_code', align: 'left', sortable: true },
|
||||
{ name: 'color_code', label: 'Renk', field: 'color_code', align: 'left', sortable: true },
|
||||
{ name: 'yaka_kodu', label: 'Yaka', field: 'yaka_kodu', align: 'left', sortable: true },
|
||||
{ name: 'item_description', label: 'Açıklama', field: 'item_description', align: 'left', sortable: true },
|
||||
{ name: 'order_qty', label: 'Adet', field: row => formatNumber(row.order_qty, 0), align: 'right', sortable: true },
|
||||
{ name: 'order_usd', label: 'USD', field: row => formatMoney(row.order_usd, 'USD'), align: 'right', sortable: true },
|
||||
{ name: 'avg_order_price_usd', label: 'Ort. USD', field: row => formatMoney(row.avg_order_price_usd, 'USD'), align: 'right', sortable: true },
|
||||
{ name: 'stock_qty', label: 'Stok', field: row => formatNumber(row.stock_qty, 0), align: 'right', sortable: true },
|
||||
{ name: 'net_stock_after_order', label: 'Stok - Sipariş', field: 'net_stock_after_order', align: 'right', sortable: true },
|
||||
{ name: 'expected_profit_base_usd', label: 'Brüt K/Z', field: row => formatMoney(row.expected_profit_base_usd, 'USD'), align: 'right', sortable: true },
|
||||
{ name: 'expected_profit_cost_usd', label: 'Yakl. Net K/Z', field: 'expected_profit_cost_usd', align: 'right', sortable: true },
|
||||
{ name: 'expected_margin_base', label: 'Brüt Marj', field: row => formatPercent(row.expected_margin_base), align: 'right', sortable: true },
|
||||
{ name: 'expected_margin_cost', label: 'Yakl. Net Marj', field: row => formatPercent(row.expected_margin_cost), align: 'right', sortable: true },
|
||||
{ name: 'is_overdue', label: 'Gecikti', field: row => row.is_overdue ? 'Evet' : 'Hayır', align: 'left', sortable: true },
|
||||
{ name: 'performance_bucket', label: 'Durum', field: 'performance_bucket', align: 'left', sortable: true },
|
||||
{ name: 'recommendation', label: 'Yorum', field: 'recommendation', align: 'left' }
|
||||
]
|
||||
|
||||
const idleColumns = [
|
||||
{ name: 'image', label: 'Foto', field: 'image', align: 'center' },
|
||||
{ name: 'product_code', label: 'Ürün', field: 'product_code', align: 'left', sortable: true },
|
||||
@@ -612,6 +861,17 @@ const idleRows = computed(() => rows.value
|
||||
))
|
||||
.sort((a, b) => Number(b.idle_cost_usd || 0) - Number(a.idle_cost_usd || 0)))
|
||||
|
||||
const visibleOrderAnalysisRows = computed(() => {
|
||||
if (orderAnalysisMode.value === 'market') return orderMarketRows.value
|
||||
if (orderAnalysisMode.value === 'customer') return orderCustomerRows.value
|
||||
return orderAnalysisRows.value
|
||||
})
|
||||
|
||||
const visibleOrderAnalysisColumns = computed(() => {
|
||||
if (orderAnalysisMode.value === 'market' || orderAnalysisMode.value === 'customer') return orderGroupColumns
|
||||
return orderAnalysisColumns
|
||||
})
|
||||
|
||||
const performanceCards = computed(() => {
|
||||
const row = performanceDialogRow.value || {}
|
||||
return [
|
||||
@@ -642,8 +902,30 @@ const filterableColumnNames = new Set([
|
||||
'urun_ana_grubu',
|
||||
'urun_alt_grubu',
|
||||
'market_key',
|
||||
'performance_bucket',
|
||||
'recommendation'
|
||||
'stock_qty',
|
||||
'sales_qty_90d',
|
||||
'sales_qty_180d',
|
||||
'sales_usd_90d',
|
||||
'sales_usd_180d',
|
||||
'stock_days_90d',
|
||||
'stock_days_180d',
|
||||
'avg_price_usd_90d',
|
||||
'avg_price_usd_180d',
|
||||
'base_price_usd',
|
||||
'cost_price_usd',
|
||||
'unit_profit_base_90d',
|
||||
'unit_profit_cost_90d',
|
||||
'unit_profit_base_180d',
|
||||
'unit_profit_cost_180d',
|
||||
'gross_profit_usd_90d',
|
||||
'gross_profit_usd_180d',
|
||||
'gross_margin_90d',
|
||||
'gross_margin_180d',
|
||||
'market_count_90d',
|
||||
'customer_count_90d',
|
||||
'sales_index_90d',
|
||||
'performance_score',
|
||||
'performance_bucket'
|
||||
])
|
||||
|
||||
const groupLevels = [
|
||||
@@ -659,12 +941,33 @@ const groupLevels = [
|
||||
const filteredProductRows = computed(() => rows.value.filter(row => {
|
||||
return columns.every(col => {
|
||||
if (!isColumnFilterable(col.name)) return true
|
||||
const needle = String(columnFilters[col.name] || '').trim().toLocaleLowerCase('tr-TR')
|
||||
if (!needle) return true
|
||||
return String(rawProductCellValue(row, col.name) || '').toLocaleLowerCase('tr-TR').includes(needle)
|
||||
const selected = Array.isArray(columnFilters[col.name]) ? columnFilters[col.name] : []
|
||||
if (!selected.length) return true
|
||||
return selected.includes(columnFilterValue(row, col.name))
|
||||
})
|
||||
}))
|
||||
|
||||
const columnFilterOptionMap = computed(() => {
|
||||
const out = {}
|
||||
for (const col of columns) {
|
||||
if (!isColumnFilterable(col.name)) continue
|
||||
const seen = new Map()
|
||||
for (const row of rows.value) {
|
||||
const value = columnFilterValue(row, col.name)
|
||||
if (!value) continue
|
||||
if (!seen.has(value)) {
|
||||
seen.set(value, {
|
||||
label: columnFilterLabel(row, col.name),
|
||||
value
|
||||
})
|
||||
}
|
||||
}
|
||||
out[col.name] = Array.from(seen.values())
|
||||
.sort((a, b) => String(a.label).localeCompare(String(b.label), 'tr', { numeric: true }))
|
||||
}
|
||||
return out
|
||||
})
|
||||
|
||||
const productTableRows = computed(() => {
|
||||
const out = []
|
||||
appendGroupRows(out, filteredProductRows.value, 0, [])
|
||||
@@ -709,13 +1012,16 @@ function makeGroupRow (key, level, label, groupRows) {
|
||||
sales_qty_180d: sumRows(groupRows, 'sales_qty_180d'),
|
||||
sales_usd_90d: sumRows(groupRows, 'sales_usd_90d'),
|
||||
sales_usd_180d: sumRows(groupRows, 'sales_usd_180d'),
|
||||
gross_profit_usd_90d: sumRows(groupRows, 'gross_profit_usd_90d'),
|
||||
gross_profit_usd_180d: sumRows(groupRows, 'gross_profit_usd_180d'),
|
||||
market_count_90d: distinctCount(groupRows, 'market_key'),
|
||||
customer_count_90d: sumRows(groupRows, 'customer_count_90d'),
|
||||
avg_price_usd_90d: weightedAverage(groupRows, 'sales_usd_90d', 'sales_qty_90d'),
|
||||
avg_price_usd_180d: weightedAverage(groupRows, 'sales_usd_180d', 'sales_qty_180d'),
|
||||
avg_base_price_usd: averageRows(groupRows, 'base_price_usd'),
|
||||
avg_cost_price_usd: averageRows(groupRows, 'cost_price_usd'),
|
||||
avg_gross_margin_90d: averageRows(groupRows, 'gross_margin_90d')
|
||||
gross_margin_90d: ratio(sumRows(groupRows, 'gross_profit_usd_90d'), sumRows(groupRows, 'sales_usd_90d')),
|
||||
gross_margin_180d: ratio(sumRows(groupRows, 'gross_profit_usd_180d'), sumRows(groupRows, 'sales_usd_180d'))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -732,6 +1038,12 @@ function distinctCount (sourceRows, field) {
|
||||
return values.size
|
||||
}
|
||||
|
||||
function ratio (amount, base) {
|
||||
const divisor = Number(base || 0)
|
||||
if (!divisor) return 0
|
||||
return Number(amount || 0) / divisor
|
||||
}
|
||||
|
||||
function averageRows (sourceRows, field) {
|
||||
const values = sourceRows.map(row => Number(row[field] || 0)).filter(value => Number.isFinite(value) && value !== 0)
|
||||
if (!values.length) return 0
|
||||
@@ -764,6 +1076,18 @@ function isColumnFilterable (name) {
|
||||
return filterableColumnNames.has(name)
|
||||
}
|
||||
|
||||
function columnFilterOptions (name) {
|
||||
return columnFilterOptionMap.value[name] || []
|
||||
}
|
||||
|
||||
function columnFilterValue (row, name) {
|
||||
return String(rawProductCellValue(row, name) ?? '').trim()
|
||||
}
|
||||
|
||||
function columnFilterLabel (row, name) {
|
||||
return formatProductCell(row, name) || columnFilterValue(row, name)
|
||||
}
|
||||
|
||||
function rawProductCellValue (row, name) {
|
||||
switch (name) {
|
||||
case 'urun_ilk_grubu': return row.urun_ilk_grubu || row.yas_grubu || ''
|
||||
@@ -796,6 +1120,8 @@ function formatProductCell (row, name) {
|
||||
case 'unit_profit_cost_90d':
|
||||
case 'unit_profit_base_180d':
|
||||
case 'unit_profit_cost_180d':
|
||||
case 'gross_profit_usd_90d':
|
||||
case 'gross_profit_usd_180d':
|
||||
return formatMoney(row[name], 'USD')
|
||||
case 'gross_margin_90d':
|
||||
case 'gross_margin_180d':
|
||||
@@ -820,6 +1146,63 @@ function normalizeRow (row) {
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeGeneralRow (row) {
|
||||
const product = String(row?.product_code || '').trim()
|
||||
const color = String(row?.color_code || '').trim()
|
||||
const yaka = String(row?.yaka_kodu || '').trim()
|
||||
const market = String(row?.market_key || '').trim()
|
||||
return {
|
||||
...row,
|
||||
row_key: `general|${product}|${color}|${yaka}|${market}`
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeOrderAnalysisRow (row) {
|
||||
const product = String(row?.product_code || '').trim()
|
||||
const color = String(row?.color_code || '').trim()
|
||||
const yaka = String(row?.yaka_kodu || '').trim()
|
||||
const market = String(row?.market_key || '').trim()
|
||||
return {
|
||||
...row,
|
||||
row_key: `orders|${product}|${color}|${yaka}|${market}`
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeOrderGroupRow (row) {
|
||||
const key = String(row?.group_key || '').trim()
|
||||
const mode = String(row?.breakdown || '').trim()
|
||||
return {
|
||||
...row,
|
||||
row_key: `order-group|${mode}|${key}|${row?.market_key || ''}|${row?.customer_code || ''}`
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeOrderProductCustomerRow (row) {
|
||||
const product = String(row?.product_code || '').trim()
|
||||
const color = String(row?.color_code || '').trim()
|
||||
const yaka = String(row?.yaka_kodu || '').trim()
|
||||
const market = String(row?.market_key || '').trim()
|
||||
const customer = String(row?.customer_code || '').trim()
|
||||
return {
|
||||
...row,
|
||||
row_key: `order-product-customer|${product}|${color}|${yaka}|${market}|${customer}`
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeOrderMarketDetailRow (row) {
|
||||
return {
|
||||
...row,
|
||||
row_key: `order-market-detail|${row?.market_key || ''}|${row?.customer_code || ''}|${row?.order_number || ''}|${row?.product_code || ''}|${row?.color_code || ''}|${row?.yaka_kodu || ''}`
|
||||
}
|
||||
}
|
||||
|
||||
function orderGroupLabel (row) {
|
||||
if (row?.breakdown === 'customer') {
|
||||
return [row.customer_code, row.customer_name].filter(Boolean).join(' - ') || '-'
|
||||
}
|
||||
return row?.group_key || row?.market_key || '-'
|
||||
}
|
||||
|
||||
function productImageKey (row) {
|
||||
const product = String(row?.product_code || '').trim()
|
||||
const color = String(row?.color_code || '').trim()
|
||||
@@ -961,22 +1344,18 @@ async function openPerformanceDialog (row) {
|
||||
async function reload () {
|
||||
loading.value = true
|
||||
try {
|
||||
const [summaryResp, listResp, marketResp, countryResp, customerResp] = await Promise.all([
|
||||
const [summaryResp, listResp, generalResp, marketResp, countryResp, customerResp] = await Promise.all([
|
||||
api.get('/pricing/product-performance/summary', { timeout: 60000 }),
|
||||
api.get('/pricing/product-performance', {
|
||||
params: {
|
||||
page: pagination.value.page,
|
||||
limit: pagination.value.rowsPerPage,
|
||||
q: filters.q || undefined,
|
||||
product_code: filters.product_code || undefined,
|
||||
kategori: filters.kategori || undefined,
|
||||
seri: filters.seri || undefined,
|
||||
bucket: filters.bucket || undefined,
|
||||
sort_by: pagination.value.sortBy || undefined,
|
||||
descending: pagination.value.descending ? 'true' : 'false'
|
||||
},
|
||||
timeout: 60000
|
||||
}),
|
||||
api.get('/pricing/product-performance/general', { params: { limit: 500 }, timeout: 60000 }),
|
||||
api.get('/pricing/product-performance/markets', { params: { limit: 200 }, timeout: 60000 }),
|
||||
api.get('/pricing/product-performance/countries', { params: { limit: 200 }, timeout: 60000 }),
|
||||
api.get('/pricing/product-performance/customers', {
|
||||
@@ -986,6 +1365,7 @@ async function reload () {
|
||||
])
|
||||
summary.value = summaryResp?.data || {}
|
||||
rows.value = (Array.isArray(listResp?.data?.rows) ? listResp.data.rows : []).map(normalizeRow)
|
||||
generalRows.value = (Array.isArray(generalResp?.data) ? generalResp.data : []).map(normalizeGeneralRow)
|
||||
marketRows.value = Array.isArray(marketResp?.data) ? marketResp.data : []
|
||||
countryRows.value = (Array.isArray(countryResp?.data) ? countryResp.data : []).map(row => ({
|
||||
...row,
|
||||
@@ -996,7 +1376,13 @@ async function reload () {
|
||||
customer_key: `${row.breakdown || '-'}|${row.market_key || '-'}|${row.country || '-'}|${row.customer_segment || '-'}|${row.customer_code || '-'}`
|
||||
}))
|
||||
pagination.value.rowsNumber = Number(listResp?.data?.total_count || 0)
|
||||
void primeProductImages(rows.value)
|
||||
if (activeTab.value === 'order_product_customers') {
|
||||
await loadOrderProductCustomers(false)
|
||||
}
|
||||
if (activeTab.value === 'order_market_details') {
|
||||
await loadOrderMarketDetails(false)
|
||||
}
|
||||
void primeProductImages([...rows.value, ...generalRows.value])
|
||||
} catch (err) {
|
||||
Notify.create({ type: 'negative', message: err?.response?.data || err?.message || 'Ürün performans verisi alınamadı' })
|
||||
} finally {
|
||||
@@ -1004,6 +1390,53 @@ async function reload () {
|
||||
}
|
||||
}
|
||||
|
||||
async function loadOrderProductCustomers (showLoading = true) {
|
||||
if (showLoading) loading.value = true
|
||||
try {
|
||||
const resp = await api.get('/pricing/product-performance/orders/product-customers', { params: { limit: 500 }, timeout: 90000 })
|
||||
orderProductCustomerRows.value = (Array.isArray(resp?.data) ? resp.data : []).map(normalizeOrderProductCustomerRow)
|
||||
orderProductCustomersLoaded.value = true
|
||||
void primeProductImages(orderProductCustomerRows.value)
|
||||
} catch (err) {
|
||||
Notify.create({ type: 'negative', message: err?.response?.data || err?.message || 'Ürün müşteri sipariş analizi alınamadı' })
|
||||
} finally {
|
||||
if (showLoading) loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function loadOrderMarketDetails (showLoading = true) {
|
||||
if (showLoading) loading.value = true
|
||||
try {
|
||||
const resp = await api.get('/pricing/product-performance/orders/market-details', { params: { limit: 800 }, timeout: 90000 })
|
||||
orderMarketDetailRows.value = (Array.isArray(resp?.data) ? resp.data : []).map(normalizeOrderMarketDetailRow)
|
||||
orderMarketDetailsLoaded.value = true
|
||||
} catch (err) {
|
||||
Notify.create({ type: 'negative', message: err?.response?.data || err?.message || 'Piyasa sipariş detay analizi alınamadı' })
|
||||
} finally {
|
||||
if (showLoading) loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function loadOrderAnalysis (showLoading = true) {
|
||||
if (showLoading) loading.value = true
|
||||
try {
|
||||
const [productResp, marketResp, customerResp] = await Promise.all([
|
||||
api.get('/pricing/product-performance/orders', { params: { limit: 500 }, timeout: 90000 }),
|
||||
api.get('/pricing/product-performance/orders/groups', { params: { limit: 500, breakdown: 'market' }, timeout: 90000 }),
|
||||
api.get('/pricing/product-performance/orders/groups', { params: { limit: 500, breakdown: 'customer' }, timeout: 90000 })
|
||||
])
|
||||
orderAnalysisRows.value = (Array.isArray(productResp?.data) ? productResp.data : []).map(normalizeOrderAnalysisRow)
|
||||
orderMarketRows.value = (Array.isArray(marketResp?.data) ? marketResp.data : []).map(normalizeOrderGroupRow)
|
||||
orderCustomerRows.value = (Array.isArray(customerResp?.data) ? customerResp.data : []).map(normalizeOrderGroupRow)
|
||||
orderAnalysisLoaded.value = true
|
||||
void primeProductImages(orderAnalysisRows.value)
|
||||
} catch (err) {
|
||||
Notify.create({ type: 'negative', message: err?.response?.data || err?.message || 'Sipariş analiz verisi alınamadı' })
|
||||
} finally {
|
||||
if (showLoading) loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function runDelta () {
|
||||
refreshing.value = true
|
||||
try {
|
||||
@@ -1040,6 +1473,15 @@ function bucketColor (value) {
|
||||
}
|
||||
}
|
||||
|
||||
watch(activeTab, tab => {
|
||||
if (tab === 'order_product_customers' && !orderProductCustomersLoaded.value) {
|
||||
void loadOrderProductCustomers(true)
|
||||
}
|
||||
if (tab === 'order_market_details' && !orderMarketDetailsLoaded.value) {
|
||||
void loadOrderMarketDetails(true)
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(reload)
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user