Files
bssapp/ui/src/pages/ProductStockQuery.vue
2026-03-05 09:56:44 +03:00

1327 lines
37 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
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>
<q-page
v-if="canReadOrder"
class="order-page q-pa-md"
:style="{ '--grid-header-h': gridHeaderHeight }"
>
<div class="sticky-stack">
<div class="filter-bar row q-col-gutter-md q-mb-sm">
<div class="col-12 col-md-6">
<q-select
v-model="selectedProductCode"
:options="filteredProductOptions"
label="Ürün Kodu"
filled
dense
clearable
use-input
input-debounce="250"
emit-value
map-options
option-value="value"
option-label="label"
:loading="loadingProducts"
@filter="filterProducts"
@keyup.enter="fetchStockByCode"
/>
</div>
<div class="col-auto">
<q-btn
color="primary"
icon="search"
label="Sorgula"
:loading="loadingStock"
:disable="!selectedProductCode"
@click="fetchStockByCode"
/>
</div>
<div class="col-auto">
<q-btn
flat
color="grey-8"
icon="restart_alt"
label="Sıfırla"
@click="resetForm"
/>
</div>
<div class="col-auto">
<q-btn
flat
color="primary"
icon="unfold_more"
:label="allDetailsExpanded ? 'Tüm Depoları Kapat' : 'Tüm Depoları Göster'"
:disable="!level1Groups.length"
@click="toggleAllDetails"
/>
</div>
</div>
<div class="save-toolbar">
<div class="text-subtitle2 text-weight-bold">Ürün Kodundan Stok Sorgula</div>
</div>
<div
v-if="showGridHeader"
class="order-grid-header"
>
<div class="col-fixed model">MODEL</div>
<div class="col-fixed renk">RENK</div>
<div class="col-fixed ana">ÜRÜN ANA GRUBU</div>
<div class="col-fixed alt">ÜRÜN ALT GRUBU</div>
<div class="col-fixed aciklama-col">AÇIKLAMA</div>
<div class="beden-block">
<div class="grp-row">
<div class="grp-title">{{ activeSchema?.title || 'BEDEN' }}</div>
<div class="grp-body">
<div
v-for="v in sizeLabels"
:key="'hdr-' + activeGrpKey + '-' + v"
class="grp-cell hdr"
>
{{ v }}
</div>
</div>
</div>
</div>
<div class="total-row">
<div class="total-cell">ADET</div>
<div class="total-cell">FOTO</div>
</div>
</div>
</div>
<q-banner
v-if="errorMessage"
class="bg-red-1 text-negative q-my-sm rounded-borders"
dense
>
{{ errorMessage }}
</q-banner>
<q-banner
v-else-if="!loadingStock && !level1Groups.length"
class="bg-blue-1 text-primary q-my-sm rounded-borders"
dense
>
Ürün kodu seçip sorgulayın.
</q-banner>
<div class="order-scroll-y">
<div v-if="level1Groups.length" class="order-grid-body">
<template v-for="grp1 in level1Groups" :key="grp1.key">
<div class="summary-group open">
<div class="order-sub-header level-1" @click="toggleOpen(grp1.key)">
<div class="sub-col level1-merged">
<div class="text-weight-bold">{{ grp1.productCode }}</div>
<div class="text-caption">{{ grp1.productDesc || '-' }}</div>
</div>
<div class="sub-center level1-center">
<div class="beden-row values-top">
<div v-for="sz in sizeLabels" :key="`v1-${grp1.key}-${sz}`" class="beden-cell">
{{ Number(grp1.sizeTotals[sz] || 0) > 0 ? formatNumber(grp1.sizeTotals[sz]) : '' }}
</div>
</div>
<div class="beden-row headers">
<div v-for="sz in sizeLabels" :key="`h1-${grp1.key}-${sz}`" class="beden-cell">
{{ sz }}
</div>
</div>
</div>
<div class="sub-right level1-right">
<div class="top-total">{{ formatNumber(grp1.totalQty) }}</div>
<div class="bottom-row">
<div class="bottom-label">ADET</div>
</div>
<div class="icon-row">
<q-icon :name="isOpen(grp1.key) ? 'expand_less' : 'expand_more'" size="18px" />
</div>
</div>
</div>
<template v-if="isOpen(grp1.key)">
<template v-for="grp2 in grp1.children" :key="grp2.key">
<div class="order-sub-header level-2" @click="onLevel2Click(grp1.productCode, grp2)">
<div class="sub-col model">{{ grp1.productCode || '-' }}</div>
<div class="sub-col renk">
<div class="renk-kodu">{{ grp2.colorCode || '-' }}{{ grp2.secondColor ? '-' + grp2.secondColor : '' }}</div>
<div class="renk-aciklama">{{ grp2.colorDesc || '-' }}</div>
</div>
<div class="sub-col ana">{{ grp2.urunAnaGrubu || '-' }}</div>
<div class="sub-col alt">{{ grp2.urunAltGrubu || '-' }}</div>
<div class="sub-col aciklama">{{ grp2.aciklama || '-' }}</div>
<div class="sub-center level2-center">
<div class="beden-row values-top">
<div v-for="sz in sizeLabels" :key="`top2-${grp2.key}-${sz}`" class="beden-cell">
{{ Number(grp2.sizeTotals[sz] || 0) > 0 ? formatNumber(grp2.sizeTotals[sz]) : '' }}
</div>
</div>
<div class="beden-row headers">
<div v-for="sz in sizeLabels" :key="`h2-${grp2.key}-${sz}`" class="beden-cell">
{{ sz }}
</div>
</div>
</div>
<div class="sub-right level2-right">
<div class="top-total">{{ formatNumber(grp2.totalQty) }}</div>
<div class="bottom-row">
<div class="bottom-label">ADET</div>
<q-icon :name="isOpen(grp2.key) ? 'expand_less' : 'expand_more'" size="18px" />
</div>
</div>
<div class="sub-image level2-image">
<q-card flat bordered class="product-image-card">
<q-card-section class="q-pa-xs product-image-wrap">
<q-img
v-if="getProductImageUrl(grp1.productCode, grp2.colorCode)"
:src="getProductImageUrl(grp1.productCode, grp2.colorCode)"
fit="cover"
class="product-image"
loading="lazy"
@error="onProductImageError(grp1.productCode, grp2.colorCode)"
/>
<div v-else class="product-image-placeholder">
<q-icon name="image_not_supported" size="22px" color="grey-6" />
</div>
</q-card-section>
</q-card>
</div>
</div>
<template v-if="isOpen(grp2.key)">
<div class="detail-table-wrap">
<div
v-for="row in buildLevel2Rows(grp2)"
:key="row.rowKey"
class="summary-row"
>
<div class="cell depo-merged">{{ row.depoAdi || '-' }}</div>
<div class="grp-area">
<div class="grp-row">
<div
v-for="v in sizeLabels"
:key="row.rowKey + '-sz-' + v"
class="cell beden"
>
{{ resolveBedenValue(row.bedenMap, row.grpKey, v) }}
</div>
</div>
</div>
<div class="cell adet">{{ formatNumber(row.adet) }}</div>
<div class="cell img-placeholder"></div>
</div>
</div>
</template>
</template>
</template>
</div>
</template>
</div>
</div>
</q-page>
<q-page v-else class="q-pa-md flex flex-center">
<div class="text-negative text-subtitle1">Bu modüle erişim yetkiniz yok.</div>
</q-page>
</template>
<script setup>
import { computed, onMounted, onUnmounted, ref } from 'vue'
import { useQuasar } from 'quasar'
import api from 'src/services/api'
import { usePermission } from 'src/composables/usePermission'
import {
detectBedenGroup,
normalizeBedenLabel,
schemaByKey as storeSchemaByKey,
useOrderEntryStore
} from 'src/stores/orderentryStore'
const $q = useQuasar()
const { canRead } = usePermission()
const canReadOrder = canRead('order')
const orderStore = useOrderEntryStore()
const loadingProducts = ref(false)
const loadingStock = ref(false)
const errorMessage = ref('')
const selectedProductCode = ref('')
const productOptions = ref([])
const filteredProductOptions = ref([])
const rawRows = ref([])
const productImageCache = ref({})
const productImageLoading = ref({})
const productImageListByCode = ref({})
const productImageListLoading = ref({})
const productImageFallbackByKey = ref({})
const productImageContentLoading = ref({})
const productImageBlobUrls = ref([])
const productImageListBlockedUntil = ref(0)
const IMAGE_LIST_CONCURRENCY = 8
let imageListActiveRequests = 0
const imageListWaitQueue = []
const activeSchema = ref(storeSchemaByKey.tak)
const activeGrpKey = ref('tak')
const openState = ref({})
const sizeLabels = computed(() => activeSchema.value?.values || [])
const showGridHeader = computed(() =>
!loadingStock.value && level1Groups.value.length > 0
)
const allDetailsExpanded = computed(() => {
const groups = level1Groups.value || []
if (!groups.length) return false
const detailKeys = []
for (const g1 of groups) {
for (const g2 of g1.children || []) {
detailKeys.push(g2.key)
for (const g3 of g2.children || []) detailKeys.push(g3.key)
}
}
if (!detailKeys.length) return false
return detailKeys.every((k) => openState.value[k] === true)
})
const gridHeaderHeight = computed(() =>
showGridHeader.value ? '56px' : '0px'
)
function emptySizeTotals() {
const map = {}
for (const s of sizeLabels.value) map[s] = 0
return map
}
function parseNumber(value) {
if (typeof value === 'number') return Number.isFinite(value) ? value : 0
const text = String(value ?? '').trim()
if (!text) return 0
const normalized = text.replace(/\./g, '').replace(',', '.')
const n = Number.parseFloat(normalized)
return Number.isFinite(n) ? n : 0
}
function buildImageKey(code, color) {
return `${String(code || '').trim().toUpperCase()}::${String(color || '').trim().toUpperCase()}`
}
function normalizeUploadsPath(storagePath) {
const raw = String(storagePath || '').trim()
if (!raw) return ''
const normalized = raw.replace(/\\/g, '/')
const idx = normalized.toLowerCase().indexOf('/uploads/')
if (idx >= 0) return normalized.slice(idx)
if (normalized.toLowerCase().startsWith('uploads/')) return `/${normalized}`
return ''
}
function resolveProductImageUrl(item) {
if (!item || typeof item !== 'object') {
return { contentUrl: '', publicUrl: '' }
}
let contentUrl = ''
const imageId = Number(item.id || item.ID || 0)
if (Number.isFinite(imageId) && imageId > 0) {
contentUrl = `/api/product-images/${imageId}/content`
} else {
const contentURL = String(item.content_url || item.ContentURL || '').trim()
if (contentURL.startsWith('/api/')) contentUrl = contentURL
else if (contentURL.startsWith('/')) contentUrl = `/api${contentURL}`
}
const uploadsPath = normalizeUploadsPath(item.storage_path || item.storage || '')
let publicUrl = ''
const thumbFolder = (typeof window !== 'undefined' && window.devicePixelRatio > 1.5) ? 't600' : 't300'
if (uploadsPath) {
if (uploadsPath.includes('/uploads/image/') && !uploadsPath.includes('/uploads/image/t300/') && !uploadsPath.includes('/uploads/image/t600/')) {
publicUrl = uploadsPath.replace('/uploads/image/', `/uploads/image/${thumbFolder}/`)
} else {
publicUrl = uploadsPath
}
} else {
const fileName = String(item.file_name || item.FileName || '').trim()
if (fileName) publicUrl = `/uploads/image/${thumbFolder}/${fileName}`
}
return { contentUrl, publicUrl }
}
function getProductImageUrl(code, color) {
const key = buildImageKey(code, color)
const existing = productImageCache.value[key]
if (existing !== undefined) return existing || ''
void ensureProductImage(code, color)
return ''
}
async function onProductImageError(code, color) {
const key = buildImageKey(code, color)
const current = String(productImageCache.value[key] || '')
const fallback = String(productImageFallbackByKey.value[key] || '')
if (fallback && current !== fallback && !productImageContentLoading.value[key]) {
productImageContentLoading.value[key] = true
try {
const blobRes = await api.get(fallback, { baseURL: '', responseType: 'blob' })
const blob = blobRes?.data
if (blob instanceof Blob) {
const objectUrl = URL.createObjectURL(blob)
productImageBlobUrls.value.push(objectUrl)
productImageCache.value[key] = objectUrl
return
}
} catch {
// no-op
} finally {
delete productImageContentLoading.value[key]
}
}
productImageCache.value[key] = ''
}
async function ensureProductImage(code, color) {
const key = buildImageKey(code, color)
const codeTrim = String(code || '').trim().toUpperCase()
const colorTrim = String(color || '').trim().toUpperCase()
if (!codeTrim) {
productImageCache.value[key] = ''
return ''
}
if (Date.now() < Number(productImageListBlockedUntil.value || 0)) {
productImageCache.value[key] = ''
return ''
}
if (productImageCache.value[key] !== undefined) return productImageCache.value[key] || ''
if (productImageLoading.value[key]) return ''
productImageLoading.value[key] = true
try {
if (!productImageListByCode.value[codeTrim]) {
if (!productImageListLoading.value[codeTrim]) {
productImageListLoading.value[codeTrim] = true
try {
if (imageListActiveRequests >= IMAGE_LIST_CONCURRENCY) {
await new Promise((resolve) => imageListWaitQueue.push(resolve))
}
imageListActiveRequests++
const res = await api.get('/product-images', { params: { code: codeTrim } })
productImageListByCode.value[codeTrim] = Array.isArray(res?.data) ? res.data : []
} catch (err) {
productImageListByCode.value[codeTrim] = []
const status = Number(err?.response?.status || 0)
if (status >= 500 || status === 403 || status === 0) {
productImageListBlockedUntil.value = Date.now() + 30 * 1000
}
console.warn('[ProductStockQuery] product image list fetch failed', { code: codeTrim, err })
} finally {
imageListActiveRequests = Math.max(0, imageListActiveRequests - 1)
const nextInQueue = imageListWaitQueue.shift()
if (nextInQueue) nextInQueue()
delete productImageListLoading.value[codeTrim]
}
} else {
while (productImageListLoading.value[codeTrim]) {
await new Promise((resolve) => setTimeout(resolve, 25))
}
}
}
const list = productImageListByCode.value[codeTrim] || []
let first = null
if (colorTrim) {
const needle = `-${colorTrim.toLowerCase()}-`
first = list.find((item) =>
String(item?.file_name || item?.FileName || '').toLowerCase().includes(needle)
) || null
}
if (!first) first = list[0] || null
const resolved = resolveProductImageUrl(first)
productImageCache.value[key] = resolved.publicUrl || resolved.contentUrl || ''
productImageFallbackByKey.value[key] = resolved.contentUrl || ''
} catch (err) {
console.warn('[ProductStockQuery] product image fetch failed', { code, color, err })
productImageCache.value[key] = ''
productImageFallbackByKey.value[key] = ''
} finally {
delete productImageLoading.value[key]
}
return productImageCache.value[key] || ''
}
function formatNumber(v) {
return parseNumber(v).toLocaleString('tr-TR', { minimumFractionDigits: 0, maximumFractionDigits: 2 })
}
function normalizeSize(v) {
return normalizeBedenLabel(v)
}
function resolveBedenValue(bedenMap, grpKey, bedenLabel) {
const map = bedenMap?.[grpKey]
if (!map || typeof map !== 'object') return 0
return Number(map[bedenLabel] || 0)
}
function isOpen(key) {
return openState.value[key] !== false
}
function toggleOpen(key) {
openState.value[key] = !isOpen(key)
}
function initOpenState(groups, expandDetails = false) {
const next = {}
for (const g1 of groups) {
next[g1.key] = true
for (const g2 of g1.children) {
next[g2.key] = expandDetails
for (const g3 of g2.children) {
next[g3.key] = expandDetails
}
}
}
openState.value = next
}
function expandAllDetails() {
initOpenState(level1Groups.value || [], true)
}
function collapseAllDetails() {
initOpenState(level1Groups.value || [], false)
}
function toggleAllDetails() {
if (allDetailsExpanded.value) {
collapseAllDetails()
return
}
expandAllDetails()
}
function buildLevel3Rows(grp3) {
const byKey = new Map()
const gk = activeGrpKey.value || 'tak'
for (const item of grp3.items || []) {
const model = String(item.Urun_Kodu || '').trim()
const renk = String(item.Renk_Kodu || '').trim()
const renk2 = String(item.Yaka || '').trim()
const urunAnaGrubu = String(item.URUN_ANA_GRUBU || '').trim()
const urunAltGrubu = String(item.URUN_ALT_GRUBU || '').trim()
const aciklama = String(item.Madde_Aciklamasi || '').trim()
const beden = normalizeSize(item.Beden || '')
const qty = parseNumber(item.Kullanilabilir_Envanter)
const rowKey = [model, renk, renk2, grp3.depoKodu || '', grp3.depoAdi || ''].join('|')
if (!byKey.has(rowKey)) {
byKey.set(rowKey, {
rowKey,
model,
renk,
renk2,
urunAnaGrubu,
urunAltGrubu,
aciklama,
grpKey: gk,
bedenMap: { [gk]: {} },
adet: 0,
depoAdi: grp3.depoAdi || '-'
})
}
const row = byKey.get(rowKey)
row.bedenMap[gk][beden] = Number(row.bedenMap[gk][beden] || 0) + qty
row.adet += qty
}
return Array.from(byKey.values())
}
function buildLevel2Rows(grp2) {
const merged = []
for (const grp3 of grp2.children || []) {
merged.push(...buildLevel3Rows(grp3))
}
return merged
}
const level1Groups = computed(() => {
const l1Map = new Map()
for (const item of rawRows.value) {
const productCode = String(item.Urun_Kodu || '').trim()
const productDesc = String(item.Madde_Aciklamasi || '').trim()
const colorCode = String(item.Renk_Kodu || '').trim()
const colorDesc = String(item.Renk_Aciklamasi || '').trim()
const secondColor = String(item.Yaka || '').trim()
const depoKodu = String(item.Depo_Kodu || '').trim()
const depoAdi = String(item.Depo_Adi || '').trim()
const urunAnaGrubu = String(item.URUN_ANA_GRUBU || '').trim()
const urunAltGrubu = String(item.URUN_ALT_GRUBU || '').trim()
const aciklama = String(item.Madde_Aciklamasi || '').trim()
const beden = normalizeSize(item.Beden || '')
const qty = parseNumber(item.Kullanilabilir_Envanter)
if (!l1Map.has(productCode)) {
l1Map.set(productCode, {
key: `L1|${productCode}`,
productCode,
productDesc,
sizeTotals: emptySizeTotals(),
totalQty: 0,
childrenMap: new Map()
})
}
const l1 = l1Map.get(productCode)
if (Object.prototype.hasOwnProperty.call(l1.sizeTotals, beden)) {
l1.sizeTotals[beden] += qty
}
l1.totalQty += qty
const l2Key = `${colorCode}|${secondColor}`
if (!l1.childrenMap.has(l2Key)) {
l1.childrenMap.set(l2Key, {
key: `L2|${productCode}|${l2Key}`,
colorCode,
colorDesc,
secondColor,
urunAnaGrubu,
urunAltGrubu,
aciklama,
sizeTotals: emptySizeTotals(),
totalQty: 0,
childrenMap: new Map()
})
}
const l2 = l1.childrenMap.get(l2Key)
if (Object.prototype.hasOwnProperty.call(l2.sizeTotals, beden)) {
l2.sizeTotals[beden] += qty
}
l2.totalQty += qty
const l3Key = `${depoKodu}|${depoAdi}`
if (!l2.childrenMap.has(l3Key)) {
l2.childrenMap.set(l3Key, {
key: `L3|${productCode}|${l2Key}|${l3Key}`,
depoKodu,
depoAdi,
sizeTotals: emptySizeTotals(),
totalQty: 0,
items: []
})
}
const l3 = l2.childrenMap.get(l3Key)
if (Object.prototype.hasOwnProperty.call(l3.sizeTotals, beden)) {
l3.sizeTotals[beden] += qty
}
l3.totalQty += qty
l3.items.push({
...item,
_rowKey: `${productCode}|${colorCode}|${secondColor}|${depoKodu}|${beden}`
})
}
return Array.from(l1Map.values()).map((l1) => ({
...l1,
children: Array.from(l1.childrenMap.values()).map((l2) => ({
...l2,
children: Array.from(l2.childrenMap.values())
}))
}))
})
function filterProducts(val, update) {
if (!val) {
update(() => {
filteredProductOptions.value = [...productOptions.value]
})
return
}
const needle = String(val || '').toLocaleLowerCase('tr-TR')
update(() => {
filteredProductOptions.value = productOptions.value.filter(opt =>
String(opt.label || '').toLocaleLowerCase('tr-TR').includes(needle)
)
})
}
async function loadProductOptions() {
loadingProducts.value = true
try {
const res = await api.get('/products')
const arr = Array.isArray(res?.data) ? res.data : []
productOptions.value = arr
.map((x) => String(x?.ProductCode || '').trim())
.filter((code) => code.length === 13)
.sort((a, b) => a.localeCompare(b, 'tr'))
.map((code) => ({ label: code, value: code }))
filteredProductOptions.value = [...productOptions.value]
} catch (err) {
errorMessage.value = 'Ürün kodları alınamadı.'
console.error('loadProductOptions error:', err)
} finally {
loadingProducts.value = false
}
}
async function fetchStockByCode() {
const code = String(selectedProductCode.value || '').trim()
if (!code) return
loadingStock.value = true
errorMessage.value = ''
try {
if (!orderStore.schemaMap || !Object.keys(orderStore.schemaMap).length) {
orderStore.initSchemaMap()
}
const res = await api.get('/product-stock-query', { params: { code } })
const list = Array.isArray(res?.data) ? res.data : []
if (!list.length) {
rawRows.value = []
openState.value = {}
return
}
const first = list[0] || {}
const grpKey = detectBedenGroup(
list.map((x) => x?.Beden || ''),
first?.URUN_ANA_GRUBU || '',
first?.YETISKIN_GARSON || ''
)
const schemaMap = Object.keys(orderStore.schemaMap || {}).length
? orderStore.schemaMap
: storeSchemaByKey
activeGrpKey.value = grpKey || 'tak'
activeSchema.value = schemaMap?.[grpKey] || storeSchemaByKey.tak
rawRows.value = list
productImageCache.value = {}
productImageLoading.value = {}
productImageListByCode.value = {}
productImageListLoading.value = {}
productImageFallbackByKey.value = {}
productImageContentLoading.value = {}
productImageListBlockedUntil.value = 0
initOpenState(level1Groups.value)
} catch (err) {
console.error('fetchStockByCode error:', err)
rawRows.value = []
openState.value = {}
errorMessage.value = 'Stok sorgulama sırasında hata oluştu.'
$q.notify({
type: 'negative',
position: 'top-right',
message: 'Stok sorgusu başarısız.'
})
} finally {
loadingStock.value = false
}
}
function onLevel2Click(productCode, grp2) {
toggleOpen(grp2.key)
if (isOpen(grp2.key)) {
void ensureProductImage(productCode, grp2.colorCode)
}
}
function resetForm() {
selectedProductCode.value = ''
rawRows.value = []
errorMessage.value = ''
openState.value = {}
activeSchema.value = storeSchemaByKey.tak
productImageCache.value = {}
productImageLoading.value = {}
productImageListByCode.value = {}
productImageListLoading.value = {}
productImageFallbackByKey.value = {}
productImageContentLoading.value = {}
productImageListBlockedUntil.value = 0
}
onMounted(() => {
loadProductOptions()
})
onUnmounted(() => {
for (const url of productImageBlobUrls.value) {
try { URL.revokeObjectURL(url) } catch {}
}
productImageBlobUrls.value = []
})
</script>
<style scoped>
.order-page {
--psq-sticky-offset: 12px;
--grp-title-w: 44px;
--psq-header-h: 56px;
--psq-col-adet: calc(var(--col-adet) + var(--beden-w));
--psq-col-img: 126px;
--psq-l1-lift: 42px;
}
.order-grid-header {
top: calc(var(--header-h) + var(--filter-h) + var(--save-h) + var(--psq-sticky-offset)) !important;
grid-template-columns:
var(--col-model)
var(--col-renk)
var(--col-ana)
var(--col-alt)
var(--col-aciklama)
calc(var(--grp-title-w) + var(--grp-title-gap) + (var(--beden-w)*var(--beden-count)))
var(--psq-col-adet)
var(--psq-col-img) !important;
}
.order-grid-header .col-fixed,
.order-grid-header .total-cell {
writing-mode: horizontal-tb !important;
transform: none !important;
height: var(--psq-header-h) !important;
font-size: 10px !important;
line-height: 1 !important;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
padding: 0 4px !important;
}
.order-grid-header .beden-block {
height: var(--psq-header-h) !important;
}
.order-grid-header .grp-row {
height: var(--psq-header-h) !important;
align-items: center;
}
.order-grid-header .grp-title {
width: var(--grp-title-w) !important;
text-align: center !important;
padding-right: 0 !important;
font-size: 10px !important;
}
.order-grid-header .grp-cell.hdr {
height: var(--psq-header-h) !important;
font-size: 10px !important;
}
.order-grid-header .total-row {
grid-column: 7 / -1 !important;
}
.order-grid-header .total-cell {
width: var(--psq-col-adet) !important;
}
.order-grid-header .total-cell:last-child {
width: var(--psq-col-img) !important;
}
.order-sub-header {
grid-template-columns:
var(--col-model)
var(--col-renk)
var(--col-ana)
var(--col-alt)
var(--col-aciklama)
calc(var(--grp-title-w) + var(--grp-title-gap) + (var(--beden-w)*var(--beden-count)))
var(--psq-col-adet)
var(--psq-col-img) !important;
top: calc(
var(--header-h)
+ var(--filter-h)
+ var(--save-h)
+ var(--grid-header-h)
+ var(--psq-sticky-offset)
) !important;
}
.order-sub-header.level-2 {
min-height: 82px !important;
height: 82px !important;
background: #fff9c4 !important;
border-top: 1px solid #d4c79f !important;
border-bottom: 1px solid #d4c79f !important;
}
.order-sub-header.level-1 {
min-height: 84px !important;
height: 84px !important;
top: calc(
var(--header-h)
+ var(--filter-h)
+ var(--save-h)
+ var(--grid-header-h)
+ var(--psq-sticky-offset)
- var(--psq-l1-lift)
) !important;
background: var(--q-primary, #1976d2) !important;
border-top: 1px solid #145ea8 !important;
border-bottom: 1px solid #145ea8 !important;
color: #fff !important;
}
.order-sub-header.level-1 .sub-col.level1-merged {
grid-column: 1 / 6;
display: flex;
flex-direction: column;
justify-content: center;
gap: 2px;
padding: 0 10px;
border-right: 1px solid rgba(255, 255, 255, 0.45);
min-width: 0;
}
.order-sub-header.level-1 .sub-col.level1-merged .text-caption {
color: #fff !important;
opacity: 0.95;
}
.order-sub-header.level-1 .sub-center.level1-center {
grid-column: 6;
display: grid;
grid-template-rows: 42px 42px;
align-items: stretch;
height: 100%;
overflow: hidden;
padding-left: calc(var(--grp-title-w) + var(--grp-title-gap));
margin-left: 0;
}
.order-sub-header.level-1 .beden-row {
display: grid;
grid-auto-flow: column;
grid-auto-columns: var(--beden-w);
height: 42px;
min-height: 42px;
}
.order-sub-header.level-1 .beden-row .beden-cell {
display: flex;
align-items: center;
justify-content: center;
height: 42px;
min-height: 42px;
background: var(--q-primary, #1976d2) !important;
color: #fff !important;
border-right: 1px solid rgba(255, 255, 255, 0.45);
border-bottom: 1px solid rgba(255, 255, 255, 0.45);
border-top: none;
border-left: none;
font-size: 12px;
font-weight: 600;
line-height: 1;
white-space: nowrap;
overflow: hidden;
}
.order-sub-header.level-1 .beden-row.values-top .beden-cell {
background: #fff9c4 !important;
color: var(--q-primary, #1976d2) !important;
font-weight: 700;
}
.order-sub-header.level-1 .beden-row.headers .beden-cell {
font-weight: 500;
color: #fff !important;
}
.order-sub-header.level-1 .sub-right.level1-right {
grid-column: 7 / 8;
display: grid;
grid-template-columns: var(--psq-col-adet);
grid-template-rows: 30px 30px 24px;
align-items: stretch;
justify-items: stretch;
height: 100%;
overflow: hidden;
padding: 0 8px 0 6px;
border-left: 1px solid rgba(255, 255, 255, 0.45);
color: #fff;
}
.order-sub-header.level-1 .sub-right .top-total {
grid-column: 1;
grid-row: 1;
display: flex;
align-items: center;
justify-content: flex-end;
font-size: 14px;
font-weight: 700;
line-height: 1;
white-space: nowrap;
background: #fff9c4 !important;
color: var(--q-primary, #1976d2) !important;
padding: 0 6px;
border-radius: 4px;
}
.order-sub-header.level-1 .sub-right .bottom-label {
font-size: 12px;
font-weight: 700;
}
.order-sub-header.level-1 .sub-right .bottom-row {
grid-column: 1;
grid-row: 2;
width: 100%;
display: flex;
align-items: center;
justify-content: flex-end;
gap: 4px;
white-space: nowrap;
overflow: hidden;
}
.order-sub-header.level-1 .sub-right .icon-row {
grid-column: 1;
grid-row: 3;
display: flex;
align-items: center;
justify-content: flex-end;
}
.order-sub-header.level-2 .sub-col {
display: flex;
align-items: center;
padding: 0 8px;
font-size: 12px;
font-weight: 500;
color: #111;
min-width: 0;
border-right: 1px solid #d4c79f;
white-space: normal;
overflow: visible;
text-overflow: clip;
line-height: 1.2;
word-break: break-word;
overflow-wrap: anywhere;
}
.order-sub-header.level-2 .sub-col.model { grid-column: 1; }
.order-sub-header.level-2 .sub-col.renk { grid-column: 2; }
.order-sub-header.level-2 .sub-col.ana { grid-column: 3; }
.order-sub-header.level-2 .sub-col.alt { grid-column: 4; }
.order-sub-header.level-2 .sub-col.aciklama { grid-column: 5; }
.order-sub-header.level-2 .sub-col.model,
.order-sub-header.level-2 .sub-col.renk,
.order-sub-header.level-2 .sub-col.ana,
.order-sub-header.level-2 .sub-col.alt {
justify-content: center;
text-align: center;
}
.order-sub-header.level-2 .sub-col.renk {
flex-direction: column;
gap: 2px;
line-height: 1.1;
}
.order-sub-header.level-2 .sub-col.renk .renk-kodu {
font-weight: 700;
}
.order-sub-header.level-2 .sub-col.renk .renk-aciklama {
font-size: 11px;
opacity: 0.9;
}
.order-sub-header.level-2 .sub-col.aciklama {
justify-content: flex-start;
text-align: left;
}
.order-sub-header.level-2 .sub-center.level2-center {
grid-column: 6;
display: grid;
grid-template-rows: 1fr 1fr;
align-items: stretch;
height: 100%;
padding-left: var(--grp-title-w);
margin-left: var(--grp-title-gap);
}
.order-sub-header.level-2 .beden-row {
display: grid;
grid-auto-flow: column;
grid-auto-columns: var(--beden-w);
}
.order-sub-header.level-2 .beden-row.values-top .beden-cell {
border-right: 1px solid #d4c79f;
background: transparent;
font-size: 13px;
font-weight: 600;
color: #1f1f1f;
line-height: 1;
}
.order-sub-header.level-2 .beden-row.headers .beden-cell {
border-top: 1px solid #d4c79f;
border-right: 1px solid #d4c79f;
border-bottom: none;
background: var(--q-primary, #1976d2);
font-size: 12px;
font-weight: 700;
color: #fff;
line-height: 1;
}
.order-sub-header.level-2 .sub-right.level2-right {
grid-column: 7 / 8;
display: grid;
grid-template-columns: var(--psq-col-adet);
grid-template-rows: 1fr 1fr;
align-items: center;
justify-items: start;
padding-left: 8px;
padding-right: 0;
transform: none !important;
border-left: 1px solid #d4c79f;
}
.order-sub-header.level-2 .sub-image.level2-image {
grid-column: 8 / 9;
display: flex;
align-items: center;
justify-content: center;
padding: 6px;
border-left: 1px solid #d4c79f;
}
.product-image-card {
width: 110px;
height: 68px;
border-radius: 8px;
overflow: hidden;
}
.product-image-wrap {
width: 100%;
height: 100%;
}
.product-image {
width: 100%;
height: 100%;
}
.product-image-placeholder {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background: #f5f6f7;
}
.order-sub-header.level-2 .sub-right .top-total {
grid-column: 1 / 2;
grid-row: 1;
font-size: 14px;
font-weight: 700;
line-height: 1;
justify-self: start;
text-align: left;
}
.order-sub-header.level-2 .sub-right .bottom-label {
font-size: 12px;
font-weight: 700;
}
.order-sub-header.level-2 .sub-right .bottom-row {
grid-column: 1;
grid-row: 2;
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
gap: 4px;
}
.order-grid-body .summary-row {
display: grid !important;
grid-template-columns:
var(--col-model)
var(--col-renk)
var(--col-ana)
var(--col-alt)
var(--col-aciklama)
calc(var(--grp-title-w) + var(--grp-title-gap) + (var(--beden-w) * var(--beden-count)))
var(--psq-col-adet)
var(--psq-col-img) !important;
min-height: 56px;
height: 56px;
background: #fff;
border-top: 1px solid #d4c79f;
border-bottom: 1px solid #d4c79f;
align-items: stretch;
}
.order-grid-body .summary-row .cell {
min-height: 56px;
height: 56px;
display: flex;
align-items: center;
justify-content: center;
border-right: 1px solid #d4c79f;
font-size: 12px;
overflow: hidden !important;
}
.order-grid-body .summary-row .cell:last-child {
border-right: none;
}
.order-grid-body .summary-row .grp-area {
display: grid !important;
grid-template-columns: var(--grp-title-w) var(--grp-title-gap) 1fr;
width: 100% !important;
height: 56px;
padding-left: 0 !important;
transform: none !important;
border-right: 1px solid #d4c79f;
}
.order-grid-body .summary-row .grp-row {
grid-column: 3;
width: 100%;
height: 56px;
margin-left: 0 !important;
justify-content: start !important;
display: grid;
grid-auto-flow: column;
grid-auto-columns: var(--beden-w);
}
.order-grid-body .summary-row .grp-row .cell.beden {
width: var(--beden-w);
display: flex;
align-items: center;
justify-content: center;
padding: 0 !important;
margin: 0 !important;
border-right: 1px solid #d4c79f;
border-left: none !important;
border-top: none !important;
border-bottom: none !important;
min-height: 56px;
height: 56px;
box-sizing: border-box;
}
.order-grid-body .summary-row .grp-row .cell.beden:first-child {
border-left: 1px solid #d4c79f !important;
}
.order-grid-body .summary-row .cell.model,
.order-grid-body .summary-row .cell.renk,
.order-grid-body .summary-row .cell.ana,
.order-grid-body .summary-row .cell.alt {
justify-content: center;
text-align: center;
}
.order-grid-body .summary-row .cell.aciklama {
grid-column: 5 / 6 !important;
position: static !important;
width: var(--col-aciklama) !important;
margin-right: 0 !important;
min-height: 56px !important;
z-index: auto !important;
background: #fff !important;
box-sizing: border-box !important;
border-right: 1px solid #d4c79f !important;
justify-content: flex-start !important;
text-align: left !important;
white-space: nowrap !important;
overflow: hidden !important;
text-overflow: ellipsis !important;
line-height: 1 !important;
padding-left: 6px !important;
padding-right: 6px !important;
display: flex !important;
flex-direction: row !important;
align-items: center !important;
}
.order-grid-body .summary-row .cell.depo-merged {
grid-column: 1 / 6 !important;
justify-content: center !important;
text-align: center !important;
font-weight: 600;
white-space: nowrap !important;
overflow: hidden !important;
text-overflow: ellipsis !important;
}
.order-grid-body .summary-row .cell.adet,
.order-grid-body .summary-row .cell.termin {
justify-content: flex-end;
text-align: right;
padding-right: 10px !important;
font-weight: 700;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.order-grid-body .summary-row .cell.img-placeholder {
justify-content: center;
text-align: center;
}
.order-grid-body {
margin-top: 0 !important;
padding-top: 0 !important;
}
.order-grid-body > .summary-group,
.order-grid-body > .summary-group:first-child {
margin-top: 0 !important;
padding-top: 0 !important;
}
.detail-table-wrap {
padding: 8px 0 12px 0;
background: #fff;
}
.detail-table :deep(th),
.detail-table :deep(td) {
white-space: nowrap;
}
</style>