1281 lines
39 KiB
Vue
1281 lines
39 KiB
Vue
<template>
|
|
<q-page class="q-pa-xs pricing-page">
|
|
<div class="top-bar row items-center justify-between q-mb-xs">
|
|
<div class="text-subtitle1 text-weight-bold">Urun Fiyatlandirma</div>
|
|
<div class="row items-center q-gutter-xs">
|
|
<q-btn-dropdown color="secondary" outline icon="view_module" label="Doviz Gorunumu" :auto-close="false">
|
|
<q-list dense class="currency-menu-list">
|
|
<q-item clickable @click="selectAllCurrencies">
|
|
<q-item-section>Tumunu Sec</q-item-section>
|
|
</q-item>
|
|
<q-item clickable @click="clearAllCurrencies">
|
|
<q-item-section>Tumunu Temizle</q-item-section>
|
|
</q-item>
|
|
<q-separator />
|
|
<q-item v-for="option in currencyOptions" :key="option.value" clickable @click="toggleCurrencyRow(option.value)">
|
|
<q-item-section avatar>
|
|
<q-checkbox
|
|
:model-value="isCurrencySelected(option.value)"
|
|
dense
|
|
@update:model-value="(val) => toggleCurrency(option.value, val)"
|
|
@click.stop
|
|
/>
|
|
</q-item-section>
|
|
<q-item-section>{{ option.label }}</q-item-section>
|
|
</q-item>
|
|
</q-list>
|
|
</q-btn-dropdown>
|
|
<q-btn
|
|
flat
|
|
:color="showSelectedOnly ? 'primary' : 'grey-7'"
|
|
:icon="showSelectedOnly ? 'checklist_rtl' : 'list_alt'"
|
|
:label="showSelectedOnly ? `Secililer (${selectedRowCount})` : 'Secili Olanlari Getir'"
|
|
:disable="!showSelectedOnly && selectedRowCount === 0"
|
|
@click="toggleShowSelectedOnly"
|
|
/>
|
|
<q-btn flat color="grey-7" icon="restart_alt" label="Filtreleri Sifirla" @click="resetAll" />
|
|
<q-btn color="primary" icon="refresh" label="Veriyi Yenile" :loading="store.loading" @click="reloadData" />
|
|
</div>
|
|
</div>
|
|
|
|
<div class="table-wrap" :style="{ '--sticky-scroll-comp': `${stickyScrollComp}px` }">
|
|
<q-table
|
|
ref="mainTableRef"
|
|
class="pane-table pricing-table"
|
|
flat
|
|
dense
|
|
row-key="id"
|
|
:rows="filteredRows"
|
|
:columns="visibleColumns"
|
|
:loading="store.loading"
|
|
virtual-scroll
|
|
:virtual-scroll-item-size="rowHeight"
|
|
:virtual-scroll-sticky-size-start="headerHeight"
|
|
:virtual-scroll-slice-size="36"
|
|
:rows-per-page-options="[0]"
|
|
v-model:pagination="tablePagination"
|
|
hide-bottom
|
|
:table-style="tableStyle"
|
|
@virtual-scroll="onTableVirtualScroll"
|
|
>
|
|
<template #header="props">
|
|
<q-tr :props="props" class="header-row-fixed">
|
|
<q-th
|
|
v-for="col in props.cols"
|
|
:key="col.name"
|
|
:props="props"
|
|
:class="[col.headerClasses, { 'sticky-col': isStickyCol(col.name), 'sticky-boundary': isStickyBoundary(col.name) }]"
|
|
:style="getHeaderCellStyle(col)"
|
|
>
|
|
<q-checkbox
|
|
v-if="col.name === 'select'"
|
|
size="sm"
|
|
color="primary"
|
|
:model-value="allSelectedVisible"
|
|
:indeterminate="someSelectedVisible && !allSelectedVisible"
|
|
@update:model-value="toggleSelectAllVisible"
|
|
/>
|
|
<div v-else class="header-with-filter">
|
|
<span>{{ col.label }}</span>
|
|
<q-btn
|
|
v-if="isHeaderFilterField(col.field)"
|
|
dense
|
|
flat
|
|
round
|
|
size="8px"
|
|
icon="filter_alt"
|
|
:color="hasFilter(col.field) ? 'primary' : 'grey-7'"
|
|
class="header-filter-btn"
|
|
>
|
|
<q-badge v-if="hasFilter(col.field)" color="primary" floating rounded>
|
|
{{ getFilterBadgeValue(col.field) }}
|
|
</q-badge>
|
|
<q-menu anchor="bottom right" self="top right" :offset="[0, 4]">
|
|
<div v-if="isMultiSelectFilterField(col.field)" class="excel-filter-menu">
|
|
<q-input
|
|
v-model="columnFilterSearch[col.field]"
|
|
dense
|
|
outlined
|
|
clearable
|
|
use-input
|
|
class="excel-filter-select"
|
|
placeholder="Ara"
|
|
/>
|
|
<div class="excel-filter-actions row items-center justify-between q-pt-xs">
|
|
<q-btn flat dense size="sm" label="Tumunu Sec" @click="selectAllColumnFilterOptions(col.field)" />
|
|
<q-btn flat dense size="sm" label="Temizle" @click="clearColumnFilter(col.field)" />
|
|
</div>
|
|
<q-virtual-scroll
|
|
v-if="getFilterOptionsForField(col.field).length > 0"
|
|
class="excel-filter-options"
|
|
:items="getFilterOptionsForField(col.field)"
|
|
:virtual-scroll-item-size="32"
|
|
separator
|
|
>
|
|
<template #default="{ item: option }">
|
|
<q-item
|
|
:key="`${col.field}-${option.value}`"
|
|
dense
|
|
clickable
|
|
class="excel-filter-option"
|
|
@click="toggleColumnFilterValue(col.field, option.value)"
|
|
>
|
|
<q-item-section avatar>
|
|
<q-checkbox
|
|
dense
|
|
size="sm"
|
|
:model-value="isColumnFilterValueSelected(col.field, option.value)"
|
|
@update:model-value="() => toggleColumnFilterValue(col.field, option.value)"
|
|
@click.stop
|
|
/>
|
|
</q-item-section>
|
|
<q-item-section>
|
|
<q-item-label>{{ option.label }}</q-item-label>
|
|
</q-item-section>
|
|
</q-item>
|
|
</template>
|
|
</q-virtual-scroll>
|
|
<div v-else class="excel-filter-empty">
|
|
Sonuc yok
|
|
</div>
|
|
</div>
|
|
<div v-else-if="isNumberRangeFilterField(col.field)" class="excel-filter-menu">
|
|
<div class="range-filter-grid">
|
|
<q-input
|
|
v-model="numberRangeFilters[col.field].min"
|
|
dense
|
|
outlined
|
|
clearable
|
|
label="Min"
|
|
inputmode="decimal"
|
|
class="range-filter-field"
|
|
/>
|
|
<q-input
|
|
v-model="numberRangeFilters[col.field].max"
|
|
dense
|
|
outlined
|
|
clearable
|
|
label="Max"
|
|
inputmode="decimal"
|
|
class="range-filter-field"
|
|
/>
|
|
</div>
|
|
<div class="row justify-end q-pt-xs">
|
|
<q-btn flat dense size="sm" label="Temizle" @click="clearRangeFilter(col.field)" />
|
|
</div>
|
|
</div>
|
|
<div v-else-if="isDateRangeFilterField(col.field)" class="excel-filter-menu">
|
|
<div class="range-filter-grid">
|
|
<q-input
|
|
v-model="dateRangeFilters[col.field].from"
|
|
dense
|
|
outlined
|
|
clearable
|
|
type="date"
|
|
label="Baslangic"
|
|
class="range-filter-field"
|
|
/>
|
|
<q-input
|
|
v-model="dateRangeFilters[col.field].to"
|
|
dense
|
|
outlined
|
|
clearable
|
|
type="date"
|
|
label="Bitis"
|
|
class="range-filter-field"
|
|
/>
|
|
</div>
|
|
<div class="row justify-end q-pt-xs">
|
|
<q-btn flat dense size="sm" label="Temizle" @click="clearRangeFilter(col.field)" />
|
|
</div>
|
|
</div>
|
|
</q-menu>
|
|
</q-btn>
|
|
<q-btn
|
|
v-else
|
|
dense
|
|
flat
|
|
round
|
|
size="8px"
|
|
icon="filter_alt"
|
|
class="header-filter-btn header-filter-ghost"
|
|
tabindex="-1"
|
|
/>
|
|
</div>
|
|
</q-th>
|
|
</q-tr>
|
|
</template>
|
|
|
|
<template #body-cell-select="props">
|
|
<q-td
|
|
:props="props"
|
|
class="text-center selection-col"
|
|
:class="{ 'sticky-col': isStickyCol(props.col.name), 'sticky-boundary': isStickyBoundary(props.col.name) }"
|
|
:style="getBodyCellStyle(props.col)"
|
|
>
|
|
<q-checkbox
|
|
size="sm"
|
|
color="primary"
|
|
:model-value="!!selectedMap[props.row.id]"
|
|
@update:model-value="(val) => toggleRowSelection(props.row.id, val)"
|
|
/>
|
|
</q-td>
|
|
</template>
|
|
|
|
<template #body-cell-productCode="props">
|
|
<q-td
|
|
:props="props"
|
|
:class="{ 'sticky-col': isStickyCol(props.col.name), 'sticky-boundary': isStickyBoundary(props.col.name) }"
|
|
:style="getBodyCellStyle(props.col)"
|
|
>
|
|
<span class="product-code-text" :title="String(props.value ?? '')">{{ props.value }}</span>
|
|
</q-td>
|
|
</template>
|
|
|
|
<template #body-cell-stockQty="props">
|
|
<q-td
|
|
:props="props"
|
|
:class="{ 'sticky-col': isStickyCol(props.col.name), 'sticky-boundary': isStickyBoundary(props.col.name) }"
|
|
:style="getBodyCellStyle(props.col)"
|
|
>
|
|
<span class="stock-qty-text">{{ formatStock(props.value) }}</span>
|
|
</q-td>
|
|
</template>
|
|
|
|
<template #body-cell-stockEntryDate="props">
|
|
<q-td
|
|
:props="props"
|
|
:class="{ 'sticky-col': isStickyCol(props.col.name), 'sticky-boundary': isStickyBoundary(props.col.name) }"
|
|
:style="getBodyCellStyle(props.col)"
|
|
>
|
|
<span class="date-cell-text">{{ formatDateDisplay(props.value) }}</span>
|
|
</q-td>
|
|
</template>
|
|
|
|
<template #body-cell-lastPricingDate="props">
|
|
<q-td
|
|
:props="props"
|
|
:class="{ 'sticky-col': isStickyCol(props.col.name), 'sticky-boundary': isStickyBoundary(props.col.name) }"
|
|
:style="getBodyCellStyle(props.col)"
|
|
>
|
|
<span :class="['date-cell-text', { 'date-warning': needsRepricing(props.row) }]">
|
|
{{ formatDateDisplay(props.value) }}
|
|
</span>
|
|
</q-td>
|
|
</template>
|
|
|
|
<template #body-cell-brandGroupSelection="props">
|
|
<q-td
|
|
:props="props"
|
|
:class="{ 'sticky-col': isStickyCol(props.col.name), 'sticky-boundary': isStickyBoundary(props.col.name) }"
|
|
:style="getBodyCellStyle(props.col)"
|
|
>
|
|
<select
|
|
class="native-cell-select"
|
|
:value="props.row.brandGroupSelection"
|
|
@change="(e) => onBrandGroupSelectionChange(props.row, e.target.value)"
|
|
>
|
|
<option value="">Seciniz</option>
|
|
<option v-for="opt in brandGroupOptions" :key="opt.value" :value="opt.value">
|
|
{{ opt.label }}
|
|
</option>
|
|
</select>
|
|
</q-td>
|
|
</template>
|
|
|
|
<template #body-cell="props">
|
|
<q-td
|
|
:props="props"
|
|
:class="{ 'sticky-col': isStickyCol(props.col.name), 'sticky-boundary': isStickyBoundary(props.col.name) }"
|
|
:style="getBodyCellStyle(props.col)"
|
|
>
|
|
<input
|
|
v-if="editableColumnSet.has(props.col.name)"
|
|
class="native-cell-input text-right"
|
|
:value="formatPrice(props.row[props.col.field])"
|
|
type="text"
|
|
inputmode="decimal"
|
|
@change="(e) => onEditableCellChange(props.row, props.col.field, e.target.value)"
|
|
/>
|
|
<span v-else class="cell-text" :title="String(props.value ?? '')">{{ props.value }}</span>
|
|
</q-td>
|
|
</template>
|
|
</q-table>
|
|
</div>
|
|
|
|
<q-banner v-if="store.error" class="bg-red text-white q-mt-xs">
|
|
Hata: {{ store.error }}
|
|
</q-banner>
|
|
</q-page>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { computed, onMounted, ref, watch } from 'vue'
|
|
import { useProductPricingStore } from 'src/stores/ProductPricingStore'
|
|
|
|
const store = useProductPricingStore()
|
|
const FETCH_LIMIT = 500
|
|
const nextCursor = ref('')
|
|
const loadingMore = ref(false)
|
|
|
|
const usdToTry = 38.25
|
|
const eurToTry = 41.6
|
|
const multipliers = [1, 1.03, 1.06, 1.09, 1.12, 1.15]
|
|
const rowHeight = 31
|
|
const headerHeight = 72
|
|
|
|
const brandGroupOptions = [
|
|
{ label: 'MARKA GRUBU A', value: 'MARKA GRUBU A' },
|
|
{ label: 'MARKA GRUBU B', value: 'MARKA GRUBU B' },
|
|
{ label: 'MARKA GRUBU C', value: 'MARKA GRUBU C' }
|
|
]
|
|
|
|
const currencyOptions = [
|
|
{ label: 'USD', value: 'USD' },
|
|
{ label: 'EUR', value: 'EUR' },
|
|
{ label: 'TRY', value: 'TRY' }
|
|
]
|
|
|
|
const multiFilterColumns = [
|
|
{ field: 'productCode', label: 'Urun Kodu' },
|
|
{ field: 'askiliYan', label: 'Askili Yan' },
|
|
{ field: 'kategori', label: 'Kategori' },
|
|
{ field: 'urunIlkGrubu', label: 'Urun Ilk Grubu' },
|
|
{ field: 'urunAnaGrubu', label: 'Urun Ana Grubu' },
|
|
{ field: 'urunAltGrubu', label: 'Urun Alt Grubu' },
|
|
{ field: 'icerik', label: 'Icerik' },
|
|
{ field: 'karisim', label: 'Karisim' }
|
|
]
|
|
const numberRangeFilterFields = ['stockQty']
|
|
const dateRangeFilterFields = ['stockEntryDate', 'lastPricingDate']
|
|
const columnFilters = ref({
|
|
productCode: [],
|
|
askiliYan: [],
|
|
kategori: [],
|
|
urunIlkGrubu: [],
|
|
urunAnaGrubu: [],
|
|
urunAltGrubu: [],
|
|
icerik: [],
|
|
karisim: []
|
|
})
|
|
const columnFilterSearch = ref({
|
|
productCode: '',
|
|
askiliYan: '',
|
|
kategori: '',
|
|
urunIlkGrubu: '',
|
|
urunAnaGrubu: '',
|
|
urunAltGrubu: '',
|
|
icerik: '',
|
|
karisim: ''
|
|
})
|
|
const numberRangeFilters = ref({
|
|
stockQty: { min: '', max: '' }
|
|
})
|
|
const dateRangeFilters = ref({
|
|
stockEntryDate: { from: '', to: '' },
|
|
lastPricingDate: { from: '', to: '' }
|
|
})
|
|
const multiSelectFilterFieldSet = new Set(multiFilterColumns.map((x) => x.field))
|
|
const numberRangeFilterFieldSet = new Set(numberRangeFilterFields)
|
|
const dateRangeFilterFieldSet = new Set(dateRangeFilterFields)
|
|
const headerFilterFieldSet = new Set([
|
|
...multiFilterColumns.map((x) => x.field),
|
|
...numberRangeFilterFields,
|
|
...dateRangeFilterFields
|
|
])
|
|
|
|
const mainTableRef = ref(null)
|
|
const tablePagination = ref({
|
|
page: 1,
|
|
rowsPerPage: 0,
|
|
sortBy: 'productCode',
|
|
descending: false
|
|
})
|
|
const selectedMap = ref({})
|
|
const selectedCurrencies = ref(['USD', 'EUR', 'TRY'])
|
|
const showSelectedOnly = ref(false)
|
|
|
|
const editableColumns = [
|
|
'costPrice',
|
|
'expenseForBasePrice',
|
|
'basePriceUsd',
|
|
'basePriceTry',
|
|
'usd1',
|
|
'usd2',
|
|
'usd3',
|
|
'usd4',
|
|
'usd5',
|
|
'usd6',
|
|
'eur1',
|
|
'eur2',
|
|
'eur3',
|
|
'eur4',
|
|
'eur5',
|
|
'eur6',
|
|
'try1',
|
|
'try2',
|
|
'try3',
|
|
'try4',
|
|
'try5',
|
|
'try6'
|
|
]
|
|
const editableColumnSet = new Set(editableColumns)
|
|
|
|
function col (name, label, field, width, extra = {}) {
|
|
return {
|
|
name,
|
|
label,
|
|
field,
|
|
align: extra.align || 'left',
|
|
sortable: !!extra.sortable,
|
|
style: `width:${width}px;min-width:${width}px;max-width:${width}px;`,
|
|
headerStyle: `width:${width}px;min-width:${width}px;max-width:${width}px;`,
|
|
classes: extra.classes || '',
|
|
headerClasses: extra.headerClasses || extra.classes || ''
|
|
}
|
|
}
|
|
|
|
const allColumns = [
|
|
col('select', '', 'select', 40, { align: 'center', classes: 'text-center selection-col' }),
|
|
col('productCode', 'URUN KODU', 'productCode', 108, { sortable: true, classes: 'ps-col product-code-col' }),
|
|
col('stockQty', 'STOK ADET', 'stockQty', 72, { align: 'right', sortable: true, classes: 'ps-col stock-col' }),
|
|
col('stockEntryDate', 'STOK GIRIS TARIHI', 'stockEntryDate', 92, { align: 'center', sortable: true, classes: 'ps-col date-col' }),
|
|
col('lastPricingDate', 'SON FIYATLANDIRMA TARIHI', 'lastPricingDate', 108, { align: 'center', sortable: true, classes: 'ps-col date-col' }),
|
|
col('askiliYan', 'ASKILI YAN', 'askiliYan', 54, { sortable: true, classes: 'ps-col' }),
|
|
col('kategori', 'KATEGORI', 'kategori', 54, { sortable: true, classes: 'ps-col' }),
|
|
col('urunIlkGrubu', 'URUN ILK GRUBU', 'urunIlkGrubu', 66, { sortable: true, classes: 'ps-col' }),
|
|
col('urunAnaGrubu', 'URUN ANA GRUBU', 'urunAnaGrubu', 66, { sortable: true, classes: 'ps-col' }),
|
|
col('urunAltGrubu', 'URUN ALT GRUBU', 'urunAltGrubu', 66, { sortable: true, classes: 'ps-col' }),
|
|
col('icerik', 'ICERIK', 'icerik', 62, { sortable: true, classes: 'ps-col' }),
|
|
col('karisim', 'KARISIM', 'karisim', 62, { sortable: true, classes: 'ps-col' }),
|
|
col('marka', 'MARKA', 'marka', 54, { sortable: true, classes: 'ps-col' }),
|
|
col('brandGroupSelection', 'MARKA GRUBU SECIMI', 'brandGroupSelection', 76),
|
|
col('costPrice', 'MALIYET FIYATI', 'costPrice', 74, { align: 'right', sortable: true, classes: 'usd-col' }),
|
|
col('expenseForBasePrice', 'TABAN FIYAT MASRAF', 'expenseForBasePrice', 86, { align: 'right', classes: 'usd-col' }),
|
|
col('basePriceUsd', 'TABAN USD', 'basePriceUsd', 74, { align: 'right', classes: 'usd-col' }),
|
|
col('basePriceTry', 'TABAN TRY', 'basePriceTry', 74, { align: 'right', classes: 'try-col' }),
|
|
col('usd1', 'USD 1', 'usd1', 62, { align: 'right', classes: 'usd-col' }),
|
|
col('usd2', 'USD 2', 'usd2', 62, { align: 'right', classes: 'usd-col' }),
|
|
col('usd3', 'USD 3', 'usd3', 62, { align: 'right', classes: 'usd-col' }),
|
|
col('usd4', 'USD 4', 'usd4', 62, { align: 'right', classes: 'usd-col' }),
|
|
col('usd5', 'USD 5', 'usd5', 62, { align: 'right', classes: 'usd-col' }),
|
|
col('usd6', 'USD 6', 'usd6', 62, { align: 'right', classes: 'usd-col' }),
|
|
col('eur1', 'EUR 1', 'eur1', 62, { align: 'right', classes: 'eur-col' }),
|
|
col('eur2', 'EUR 2', 'eur2', 62, { align: 'right', classes: 'eur-col' }),
|
|
col('eur3', 'EUR 3', 'eur3', 62, { align: 'right', classes: 'eur-col' }),
|
|
col('eur4', 'EUR 4', 'eur4', 62, { align: 'right', classes: 'eur-col' }),
|
|
col('eur5', 'EUR 5', 'eur5', 62, { align: 'right', classes: 'eur-col' }),
|
|
col('eur6', 'EUR 6', 'eur6', 62, { align: 'right', classes: 'eur-col' }),
|
|
col('try1', 'TRY 1', 'try1', 62, { align: 'right', classes: 'try-col' }),
|
|
col('try2', 'TRY 2', 'try2', 62, { align: 'right', classes: 'try-col' }),
|
|
col('try3', 'TRY 3', 'try3', 62, { align: 'right', classes: 'try-col' }),
|
|
col('try4', 'TRY 4', 'try4', 62, { align: 'right', classes: 'try-col' }),
|
|
col('try5', 'TRY 5', 'try5', 62, { align: 'right', classes: 'try-col' }),
|
|
col('try6', 'TRY 6', 'try6', 62, { align: 'right', classes: 'try-col' })
|
|
]
|
|
|
|
const stickyColumnNames = [
|
|
'select',
|
|
'productCode',
|
|
'stockQty',
|
|
'stockEntryDate',
|
|
'lastPricingDate',
|
|
'askiliYan',
|
|
'kategori',
|
|
'urunIlkGrubu',
|
|
'urunAnaGrubu',
|
|
'urunAltGrubu',
|
|
'icerik',
|
|
'karisim',
|
|
'marka',
|
|
'brandGroupSelection',
|
|
'costPrice',
|
|
'expenseForBasePrice',
|
|
'basePriceUsd',
|
|
'basePriceTry'
|
|
]
|
|
const stickyBoundaryColumnName = 'basePriceTry'
|
|
const stickyColumnNameSet = new Set(stickyColumnNames)
|
|
|
|
const visibleColumns = computed(() => {
|
|
const selected = new Set(selectedCurrencies.value)
|
|
return allColumns.filter((c) => {
|
|
if (c.name.startsWith('usd')) return selected.has('USD')
|
|
if (c.name.startsWith('eur')) return selected.has('EUR')
|
|
if (c.name.startsWith('try')) return selected.has('TRY')
|
|
return true
|
|
})
|
|
})
|
|
|
|
const stickyLeftMap = computed(() => {
|
|
const map = {}
|
|
let left = 0
|
|
for (const colName of stickyColumnNames) {
|
|
const c = allColumns.find((x) => x.name === colName)
|
|
if (!c) continue
|
|
map[colName] = left
|
|
left += extractWidth(c.style)
|
|
}
|
|
return map
|
|
})
|
|
const stickyScrollComp = computed(() => {
|
|
const boundaryCol = allColumns.find((x) => x.name === stickyBoundaryColumnName)
|
|
return (stickyLeftMap.value[stickyBoundaryColumnName] || 0) + extractWidth(boundaryCol?.style)
|
|
})
|
|
|
|
const tableMinWidth = computed(() => visibleColumns.value.reduce((sum, c) => sum + extractWidth(c.style), 0))
|
|
const tableStyle = computed(() => ({
|
|
width: `${tableMinWidth.value}px`,
|
|
minWidth: `${tableMinWidth.value}px`,
|
|
tableLayout: 'fixed'
|
|
}))
|
|
|
|
const rows = computed(() => store.rows || [])
|
|
const multiFilterOptionMap = computed(() => {
|
|
const map = {}
|
|
multiFilterColumns.forEach(({ field }) => {
|
|
const uniq = new Set()
|
|
rows.value.forEach((row) => {
|
|
const val = String(row?.[field] ?? '').trim()
|
|
if (val) uniq.add(val)
|
|
})
|
|
map[field] = Array.from(uniq)
|
|
.sort((a, b) => a.localeCompare(b, 'tr'))
|
|
.map((v) => ({ label: v, value: v }))
|
|
})
|
|
return map
|
|
})
|
|
const filteredFilterOptionMap = computed(() => {
|
|
const map = {}
|
|
multiFilterColumns.forEach(({ field }) => {
|
|
const search = String(columnFilterSearch.value[field] || '').trim().toLocaleLowerCase('tr')
|
|
const options = multiFilterOptionMap.value[field] || []
|
|
map[field] = search
|
|
? options.filter((option) => option.label.toLocaleLowerCase('tr').includes(search))
|
|
: options
|
|
})
|
|
return map
|
|
})
|
|
|
|
const filteredRows = computed(() => {
|
|
return rows.value.filter((row) => {
|
|
if (showSelectedOnly.value && !selectedMap.value[row.id]) return false
|
|
for (const mf of multiFilterColumns) {
|
|
const selected = columnFilters.value[mf.field] || []
|
|
if (selected.length > 0 && !selected.includes(String(row?.[mf.field] ?? '').trim())) return false
|
|
}
|
|
const stockQtyMin = parseNullableNumber(numberRangeFilters.value.stockQty?.min)
|
|
const stockQtyMax = parseNullableNumber(numberRangeFilters.value.stockQty?.max)
|
|
const stockQty = Number(row?.stockQty ?? 0)
|
|
if (stockQtyMin !== null && stockQty < stockQtyMin) return false
|
|
if (stockQtyMax !== null && stockQty > stockQtyMax) return false
|
|
if (!matchesDateRange(String(row?.stockEntryDate || '').trim(), dateRangeFilters.value.stockEntryDate)) return false
|
|
if (!matchesDateRange(String(row?.lastPricingDate || '').trim(), dateRangeFilters.value.lastPricingDate)) return false
|
|
return true
|
|
})
|
|
})
|
|
|
|
const visibleRowIds = computed(() => filteredRows.value.map((row) => row.id))
|
|
const selectedRowCount = computed(() => Object.values(selectedMap.value).filter(Boolean).length)
|
|
const selectedVisibleCount = computed(() => visibleRowIds.value.filter((id) => !!selectedMap.value[id]).length)
|
|
const allSelectedVisible = computed(() => visibleRowIds.value.length > 0 && selectedVisibleCount.value === visibleRowIds.value.length)
|
|
const someSelectedVisible = computed(() => selectedVisibleCount.value > 0)
|
|
const hasMoreRows = computed(() => Boolean(store.hasMore))
|
|
|
|
function isHeaderFilterField (field) {
|
|
return headerFilterFieldSet.has(field)
|
|
}
|
|
|
|
function isMultiSelectFilterField (field) {
|
|
return multiSelectFilterFieldSet.has(field)
|
|
}
|
|
|
|
function isNumberRangeFilterField (field) {
|
|
return numberRangeFilterFieldSet.has(field)
|
|
}
|
|
|
|
function isDateRangeFilterField (field) {
|
|
return dateRangeFilterFieldSet.has(field)
|
|
}
|
|
|
|
function hasFilter (field) {
|
|
if (isMultiSelectFilterField(field)) return (columnFilters.value[field] || []).length > 0
|
|
if (isNumberRangeFilterField(field)) {
|
|
const filter = numberRangeFilters.value[field]
|
|
return !!String(filter?.min || '').trim() || !!String(filter?.max || '').trim()
|
|
}
|
|
if (isDateRangeFilterField(field)) {
|
|
const filter = dateRangeFilters.value[field]
|
|
return !!String(filter?.from || '').trim() || !!String(filter?.to || '').trim()
|
|
}
|
|
return false
|
|
}
|
|
|
|
function getFilterBadgeValue (field) {
|
|
if (isMultiSelectFilterField(field)) return (columnFilters.value[field] || []).length
|
|
if (isNumberRangeFilterField(field)) {
|
|
const filter = numberRangeFilters.value[field]
|
|
return [filter?.min, filter?.max].filter((x) => String(x || '').trim()).length
|
|
}
|
|
if (isDateRangeFilterField(field)) {
|
|
const filter = dateRangeFilters.value[field]
|
|
return [filter?.from, filter?.to].filter((x) => String(x || '').trim()).length
|
|
}
|
|
return 0
|
|
}
|
|
|
|
function clearColumnFilter (field) {
|
|
if (!isMultiSelectFilterField(field)) return
|
|
columnFilters.value = {
|
|
...columnFilters.value,
|
|
[field]: []
|
|
}
|
|
}
|
|
|
|
function clearRangeFilter (field) {
|
|
if (isNumberRangeFilterField(field)) {
|
|
numberRangeFilters.value = {
|
|
...numberRangeFilters.value,
|
|
[field]: { min: '', max: '' }
|
|
}
|
|
return
|
|
}
|
|
if (isDateRangeFilterField(field)) {
|
|
dateRangeFilters.value = {
|
|
...dateRangeFilters.value,
|
|
[field]: { from: '', to: '' }
|
|
}
|
|
}
|
|
}
|
|
|
|
function getFilterOptionsForField (field) {
|
|
return filteredFilterOptionMap.value[field] || []
|
|
}
|
|
|
|
function isColumnFilterValueSelected (field, value) {
|
|
return (columnFilters.value[field] || []).includes(value)
|
|
}
|
|
|
|
function toggleColumnFilterValue (field, value) {
|
|
const current = new Set(columnFilters.value[field] || [])
|
|
if (current.has(value)) current.delete(value)
|
|
else current.add(value)
|
|
columnFilters.value = {
|
|
...columnFilters.value,
|
|
[field]: Array.from(current)
|
|
}
|
|
}
|
|
|
|
function selectAllColumnFilterOptions (field) {
|
|
const options = getFilterOptionsForField(field)
|
|
columnFilters.value = {
|
|
...columnFilters.value,
|
|
[field]: options.map((option) => option.value)
|
|
}
|
|
}
|
|
|
|
function extractWidth (style) {
|
|
const m = String(style || '').match(/width:(\d+)px/)
|
|
return m ? Number(m[1]) : 0
|
|
}
|
|
|
|
function isStickyCol (colName) {
|
|
return stickyColumnNameSet.has(colName)
|
|
}
|
|
|
|
function isStickyBoundary (colName) {
|
|
return colName === stickyBoundaryColumnName
|
|
}
|
|
|
|
function getHeaderCellStyle (col) {
|
|
if (!isStickyCol(col.name)) return undefined
|
|
return { left: `${stickyLeftMap.value[col.name] || 0}px`, zIndex: 22 }
|
|
}
|
|
|
|
function getBodyCellStyle (col) {
|
|
if (!isStickyCol(col.name)) return undefined
|
|
return { left: `${stickyLeftMap.value[col.name] || 0}px`, zIndex: 12 }
|
|
}
|
|
|
|
function round2 (value) {
|
|
return Number(Number(value || 0).toFixed(2))
|
|
}
|
|
|
|
function parseNumber (val) {
|
|
if (typeof val === 'number') return Number.isFinite(val) ? val : 0
|
|
const text = String(val ?? '').trim().replace(/\s/g, '')
|
|
if (!text) return 0
|
|
|
|
const lastComma = text.lastIndexOf(',')
|
|
const lastDot = text.lastIndexOf('.')
|
|
let normalized = text
|
|
|
|
if (lastComma >= 0 && lastDot >= 0) {
|
|
if (lastComma > lastDot) {
|
|
normalized = text.replace(/\./g, '').replace(',', '.')
|
|
} else {
|
|
normalized = text.replace(/,/g, '')
|
|
}
|
|
} else if (lastComma >= 0) {
|
|
normalized = text.replace(/\./g, '').replace(',', '.')
|
|
} else {
|
|
normalized = text.replace(/,/g, '')
|
|
}
|
|
|
|
const n = Number(normalized)
|
|
return Number.isFinite(n) ? n : 0
|
|
}
|
|
|
|
function parseNullableNumber (val) {
|
|
const text = String(val ?? '').trim()
|
|
if (!text) return null
|
|
const n = parseNumber(text)
|
|
return Number.isFinite(n) ? n : null
|
|
}
|
|
|
|
function matchesDateRange (value, filter) {
|
|
const from = String(filter?.from || '').trim()
|
|
const to = String(filter?.to || '').trim()
|
|
if (!from && !to) return true
|
|
if (!value) return false
|
|
if (from && value < from) return false
|
|
if (to && value > to) return false
|
|
return true
|
|
}
|
|
|
|
function formatPrice (val) {
|
|
const n = parseNumber(val)
|
|
return n.toLocaleString('tr-TR', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
|
|
}
|
|
|
|
function formatStock (val) {
|
|
const n = Number(val || 0)
|
|
if (!Number.isFinite(n)) return '0'
|
|
const hasFraction = Math.abs(n % 1) > 0.0001
|
|
return n.toLocaleString('tr-TR', {
|
|
minimumFractionDigits: hasFraction ? 2 : 0,
|
|
maximumFractionDigits: hasFraction ? 2 : 0
|
|
})
|
|
}
|
|
|
|
function formatDateDisplay (val) {
|
|
const text = String(val || '').trim()
|
|
if (!text) return '-'
|
|
const [year, month, day] = text.split('-')
|
|
if (!year || !month || !day) return text
|
|
return `${day}.${month}.${year}`
|
|
}
|
|
|
|
function needsRepricing (row) {
|
|
const stockEntryDate = String(row?.stockEntryDate || '').trim()
|
|
const lastPricingDate = String(row?.lastPricingDate || '').trim()
|
|
if (!stockEntryDate) return false
|
|
if (!lastPricingDate) return true
|
|
return lastPricingDate < stockEntryDate
|
|
}
|
|
|
|
function recalcByBasePrice (row) {
|
|
row.basePriceTry = round2((row.basePriceUsd * usdToTry) + row.expenseForBasePrice)
|
|
multipliers.forEach((multiplier, index) => {
|
|
row[`usd${index + 1}`] = round2(row.basePriceUsd * multiplier)
|
|
row[`eur${index + 1}`] = round2((row.basePriceUsd * usdToTry * multiplier) / eurToTry)
|
|
row[`try${index + 1}`] = round2(row.basePriceTry * multiplier)
|
|
})
|
|
}
|
|
|
|
function onEditableCellChange (row, field, val) {
|
|
const parsed = parseNumber(val)
|
|
store.updateCell(row, field, parsed)
|
|
if (field === 'expenseForBasePrice' || field === 'basePriceUsd') recalcByBasePrice(row)
|
|
}
|
|
|
|
function onBrandGroupSelectionChange (row, val) {
|
|
store.updateBrandGroupSelection(row, val)
|
|
}
|
|
|
|
function toggleRowSelection (rowId, val) {
|
|
selectedMap.value = { ...selectedMap.value, [rowId]: !!val }
|
|
}
|
|
|
|
function toggleSelectAllVisible (val) {
|
|
const next = { ...selectedMap.value }
|
|
visibleRowIds.value.forEach((id) => { next[id] = !!val })
|
|
selectedMap.value = next
|
|
}
|
|
|
|
function resetAll () {
|
|
columnFilters.value = {
|
|
productCode: [],
|
|
askiliYan: [],
|
|
kategori: [],
|
|
urunIlkGrubu: [],
|
|
urunAnaGrubu: [],
|
|
urunAltGrubu: [],
|
|
icerik: [],
|
|
karisim: []
|
|
}
|
|
columnFilterSearch.value = {
|
|
productCode: '',
|
|
askiliYan: '',
|
|
kategori: '',
|
|
urunIlkGrubu: '',
|
|
urunAnaGrubu: '',
|
|
urunAltGrubu: '',
|
|
icerik: '',
|
|
karisim: ''
|
|
}
|
|
numberRangeFilters.value = {
|
|
stockQty: { min: '', max: '' }
|
|
}
|
|
dateRangeFilters.value = {
|
|
stockEntryDate: { from: '', to: '' },
|
|
lastPricingDate: { from: '', to: '' }
|
|
}
|
|
showSelectedOnly.value = false
|
|
selectedMap.value = {}
|
|
}
|
|
|
|
function toggleShowSelectedOnly () {
|
|
if (!showSelectedOnly.value && selectedRowCount.value === 0) return
|
|
showSelectedOnly.value = !showSelectedOnly.value
|
|
}
|
|
|
|
function isCurrencySelected (code) {
|
|
return selectedCurrencies.value.includes(code)
|
|
}
|
|
|
|
function toggleCurrency (code, checked) {
|
|
const set = new Set(selectedCurrencies.value)
|
|
if (checked) set.add(code)
|
|
else set.delete(code)
|
|
selectedCurrencies.value = currencyOptions.map((x) => x.value).filter((x) => set.has(x))
|
|
}
|
|
|
|
function toggleCurrencyRow (code) {
|
|
toggleCurrency(code, !isCurrencySelected(code))
|
|
}
|
|
|
|
function selectAllCurrencies () {
|
|
selectedCurrencies.value = currencyOptions.map((x) => x.value)
|
|
}
|
|
|
|
function clearAllCurrencies () {
|
|
selectedCurrencies.value = []
|
|
}
|
|
|
|
async function fetchChunk ({ reset = false } = {}) {
|
|
const afterProductCode = reset ? '' : nextCursor.value
|
|
const result = await store.fetchRows({
|
|
limit: FETCH_LIMIT,
|
|
afterProductCode,
|
|
append: !reset
|
|
})
|
|
const fetched = Number(result?.fetched) || 0
|
|
nextCursor.value = String(result?.nextCursor || '')
|
|
return fetched
|
|
}
|
|
|
|
async function loadMoreRows () {
|
|
if (loadingMore.value || store.loading || !hasMoreRows.value) return
|
|
loadingMore.value = true
|
|
try {
|
|
await fetchChunk({ reset: false })
|
|
} finally {
|
|
loadingMore.value = false
|
|
}
|
|
}
|
|
|
|
function onTableVirtualScroll (details) {
|
|
const to = Number(details?.to || 0)
|
|
if (!Number.isFinite(to)) return
|
|
if (to >= filteredRows.value.length - 25) {
|
|
void loadMoreRows()
|
|
}
|
|
}
|
|
|
|
async function ensureEnoughVisibleRows (minRows = 80, maxBatches = 4) {
|
|
let guard = 0
|
|
while (hasMoreRows.value && filteredRows.value.length < minRows && guard < maxBatches) {
|
|
await loadMoreRows()
|
|
guard++
|
|
}
|
|
}
|
|
|
|
async function reloadData () {
|
|
const startedAt = Date.now()
|
|
console.info('[product-pricing][ui] reload:start', {
|
|
at: new Date(startedAt).toISOString()
|
|
})
|
|
try {
|
|
nextCursor.value = ''
|
|
await fetchChunk({ reset: true })
|
|
await ensureEnoughVisibleRows(120, 6)
|
|
} catch (err) {
|
|
console.error('[product-pricing][ui] reload:error', {
|
|
duration_ms: Date.now() - startedAt,
|
|
message: String(err?.message || err || 'reload failed')
|
|
})
|
|
}
|
|
console.info('[product-pricing][ui] reload:done', {
|
|
duration_ms: Date.now() - startedAt,
|
|
row_count: Array.isArray(store.rows) ? store.rows.length : 0,
|
|
has_error: Boolean(store.error)
|
|
})
|
|
selectedMap.value = {}
|
|
}
|
|
|
|
onMounted(async () => {
|
|
await reloadData()
|
|
})
|
|
|
|
watch(
|
|
[
|
|
columnFilters,
|
|
numberRangeFilters,
|
|
dateRangeFilters,
|
|
showSelectedOnly,
|
|
() => tablePagination.value.sortBy,
|
|
() => tablePagination.value.descending
|
|
],
|
|
() => { void ensureEnoughVisibleRows(80, 4) },
|
|
{ deep: true }
|
|
)
|
|
</script>
|
|
|
|
<style scoped>
|
|
.pricing-page {
|
|
--pricing-row-height: 31px;
|
|
--pricing-header-height: 72px;
|
|
--pricing-table-height: calc(100vh - 210px);
|
|
|
|
height: calc(100vh - 120px);
|
|
display: flex;
|
|
flex-direction: column;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.currency-menu-list {
|
|
min-width: 170px;
|
|
}
|
|
|
|
.table-wrap {
|
|
flex: 1;
|
|
min-height: 0;
|
|
overflow: hidden;
|
|
border: 1px solid rgba(0, 0, 0, 0.12);
|
|
border-radius: 4px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.pane-table {
|
|
height: 100%;
|
|
width: 100%;
|
|
}
|
|
|
|
.pricing-table :deep(.q-table__middle) {
|
|
height: var(--pricing-table-height);
|
|
min-height: var(--pricing-table-height);
|
|
max-height: var(--pricing-table-height);
|
|
overflow: auto !important;
|
|
scrollbar-gutter: stable both-edges;
|
|
overscroll-behavior: contain;
|
|
}
|
|
|
|
.pricing-table :deep(.q-table) {
|
|
width: max-content;
|
|
min-width: 100%;
|
|
table-layout: fixed;
|
|
font-size: 11px;
|
|
border-collapse: separate;
|
|
border-spacing: 0;
|
|
margin-right: var(--sticky-scroll-comp, 0px);
|
|
}
|
|
|
|
.pricing-table :deep(.q-table__container) {
|
|
border: none !important;
|
|
box-shadow: none !important;
|
|
background: transparent !important;
|
|
height: 100% !important;
|
|
}
|
|
|
|
.pricing-table :deep(th),
|
|
.pricing-table :deep(td) {
|
|
box-sizing: border-box;
|
|
padding: 0 4px;
|
|
overflow: hidden;
|
|
vertical-align: middle;
|
|
}
|
|
|
|
.pricing-table :deep(td),
|
|
.pricing-table :deep(.q-table tbody tr) {
|
|
height: var(--pricing-row-height) !important;
|
|
min-height: var(--pricing-row-height) !important;
|
|
max-height: var(--pricing-row-height) !important;
|
|
line-height: var(--pricing-row-height);
|
|
padding: 0 !important;
|
|
border-bottom: 1px solid rgba(0, 0, 0, 0.08) !important;
|
|
}
|
|
|
|
.pricing-table :deep(td > div),
|
|
.pricing-table :deep(td > .q-td) {
|
|
height: 100% !important;
|
|
display: flex !important;
|
|
align-items: center !important;
|
|
padding: 0 4px !important;
|
|
}
|
|
|
|
.pricing-table :deep(th),
|
|
.pricing-table :deep(.q-table thead tr),
|
|
.pricing-table :deep(.q-table thead tr.header-row-fixed),
|
|
.pricing-table :deep(.q-table thead th),
|
|
.pricing-table :deep(.q-table thead tr.header-row-fixed > th) {
|
|
height: var(--pricing-header-height) !important;
|
|
min-height: var(--pricing-header-height) !important;
|
|
max-height: var(--pricing-header-height) !important;
|
|
}
|
|
|
|
.pricing-table :deep(th) {
|
|
padding-top: 0;
|
|
padding-bottom: 0;
|
|
white-space: nowrap;
|
|
word-break: normal;
|
|
text-overflow: ellipsis;
|
|
text-align: center;
|
|
font-size: 10px;
|
|
font-weight: 800;
|
|
line-height: 1.15;
|
|
}
|
|
|
|
.pricing-table :deep(.q-table thead th) {
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 30;
|
|
background: #fff;
|
|
vertical-align: middle !important;
|
|
}
|
|
|
|
.pricing-table :deep(.sticky-col) {
|
|
position: sticky !important;
|
|
background-clip: padding-box;
|
|
}
|
|
|
|
.pricing-table :deep(thead .sticky-col) {
|
|
z-index: 35 !important;
|
|
}
|
|
|
|
.pricing-table :deep(tbody .sticky-col) {
|
|
z-index: 12 !important;
|
|
}
|
|
|
|
.pricing-table :deep(.sticky-boundary) {
|
|
border-right: 2px solid rgba(25, 118, 210, 0.18) !important;
|
|
box-shadow: 8px 0 12px -10px rgba(15, 23, 42, 0.55);
|
|
}
|
|
|
|
.header-with-filter {
|
|
display: grid;
|
|
grid-template-columns: 1fr 20px;
|
|
align-items: center;
|
|
column-gap: 4px;
|
|
height: 100%;
|
|
line-height: 1.25;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.header-with-filter > span {
|
|
min-width: 0;
|
|
width: 100%;
|
|
overflow: hidden;
|
|
text-align: center;
|
|
text-overflow: ellipsis;
|
|
white-space: normal;
|
|
font-weight: 800;
|
|
line-height: 1.15;
|
|
display: -webkit-box;
|
|
-webkit-line-clamp: 2;
|
|
-webkit-box-orient: vertical;
|
|
}
|
|
|
|
.header-filter-btn {
|
|
width: 20px;
|
|
height: 20px;
|
|
min-width: 20px;
|
|
justify-self: end;
|
|
}
|
|
|
|
.header-filter-ghost {
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.excel-filter-menu {
|
|
min-width: 230px;
|
|
padding: 8px;
|
|
}
|
|
|
|
.range-filter-grid {
|
|
display: grid;
|
|
grid-template-columns: 1fr;
|
|
gap: 8px;
|
|
}
|
|
|
|
.range-filter-field {
|
|
min-width: 0;
|
|
}
|
|
|
|
.excel-filter-select :deep(.q-field__control) {
|
|
min-height: 30px;
|
|
}
|
|
|
|
.excel-filter-select :deep(.q-field__native),
|
|
.excel-filter-select :deep(.q-field__input) {
|
|
font-weight: 700;
|
|
}
|
|
|
|
.excel-filter-actions {
|
|
gap: 4px;
|
|
}
|
|
|
|
.excel-filter-options {
|
|
max-height: 220px;
|
|
margin-top: 8px;
|
|
overflow: auto;
|
|
border: 1px solid rgba(0, 0, 0, 0.08);
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.excel-filter-option {
|
|
min-height: 32px;
|
|
}
|
|
|
|
.excel-filter-empty {
|
|
padding: 10px 8px;
|
|
color: #607d8b;
|
|
font-size: 11px;
|
|
}
|
|
|
|
.pricing-table :deep(th.ps-col),
|
|
.pricing-table :deep(td.ps-col) {
|
|
background: #fff;
|
|
color: var(--q-primary);
|
|
font-weight: 700;
|
|
}
|
|
|
|
.pricing-table :deep(td.ps-col .cell-text),
|
|
.pricing-table :deep(td.ps-col .product-code-text),
|
|
.pricing-table :deep(td.ps-col .stock-qty-text) {
|
|
font-size: 11px;
|
|
line-height: 1.1;
|
|
white-space: normal;
|
|
word-break: break-word;
|
|
}
|
|
|
|
.stock-qty-text {
|
|
display: block;
|
|
width: 100%;
|
|
text-align: center;
|
|
font-weight: 700;
|
|
padding: 0 4px;
|
|
}
|
|
|
|
.date-cell-text {
|
|
display: block;
|
|
width: 100%;
|
|
text-align: center;
|
|
font-weight: 700;
|
|
padding: 0 4px;
|
|
}
|
|
|
|
.date-warning {
|
|
color: #c62828;
|
|
}
|
|
|
|
.pricing-table :deep(th.selection-col),
|
|
.pricing-table :deep(td.selection-col) {
|
|
background: #fff;
|
|
color: var(--q-primary);
|
|
padding-left: 0 !important;
|
|
padding-right: 0 !important;
|
|
}
|
|
|
|
.pricing-table :deep(th.selection-col) {
|
|
text-align: center !important;
|
|
}
|
|
|
|
.pricing-table :deep(.selection-col .q-checkbox__inner) {
|
|
color: var(--q-primary);
|
|
font-size: 16px;
|
|
}
|
|
|
|
.pricing-table :deep(th.selection-col .q-checkbox),
|
|
.pricing-table :deep(td.selection-col .q-checkbox) {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.pricing-table :deep(.selection-col .q-checkbox__bg) {
|
|
background: #fff;
|
|
border-color: var(--q-primary);
|
|
}
|
|
|
|
.pricing-table :deep(th.usd-col),
|
|
.pricing-table :deep(td.usd-col) {
|
|
background: #ecf9f0;
|
|
color: #178a3e;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.pricing-table :deep(th.eur-col),
|
|
.pricing-table :deep(td.eur-col) {
|
|
background: #fdeeee;
|
|
color: #c62828;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.pricing-table :deep(th.try-col),
|
|
.pricing-table :deep(td.try-col) {
|
|
background: #edf4ff;
|
|
color: #1e63c6;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.cell-text {
|
|
display: block;
|
|
overflow: hidden;
|
|
white-space: nowrap;
|
|
text-overflow: ellipsis;
|
|
line-height: 1.1;
|
|
padding-top: 0;
|
|
}
|
|
|
|
.product-code-text {
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
display: block;
|
|
font-weight: 700;
|
|
letter-spacing: 0;
|
|
}
|
|
|
|
.native-cell-input,
|
|
.native-cell-select {
|
|
width: 100%;
|
|
height: 22px;
|
|
box-sizing: border-box;
|
|
padding: 1px 3px;
|
|
border: 1px solid #cfd8dc;
|
|
border-radius: 4px;
|
|
background: #fff;
|
|
font-size: 11px;
|
|
margin: 0;
|
|
}
|
|
|
|
.native-cell-input:focus,
|
|
.native-cell-select:focus {
|
|
outline: none;
|
|
border-color: #1976d2;
|
|
}
|
|
</style>
|