1831 lines
54 KiB
Vue
1831 lines
54 KiB
Vue
<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 cursor-pointer" @click.stop="openProductCard(grp1, grp2)">
|
||
<q-card-section class="q-pa-xs product-image-wrap">
|
||
<q-img
|
||
v-if="getProductImageUrl(grp1.productCode, grp2.colorCode, grp2.photoDim3 || grp2.secondColor, grp2.photoDim1ID || '', grp2.photoDim3ID || '')"
|
||
:src="getProductImageUrl(grp1.productCode, grp2.colorCode, grp2.photoDim3 || grp2.secondColor, grp2.photoDim1ID || '', grp2.photoDim3ID || '')"
|
||
fit="contain"
|
||
class="product-image"
|
||
loading="lazy"
|
||
@error="onProductImageError(grp1.productCode, grp2.colorCode, grp2.photoDim3 || grp2.secondColor, grp2.photoDim1ID || '', grp2.photoDim3ID || '')"
|
||
/>
|
||
<div v-else class="product-image-placeholder">
|
||
<q-icon name="image_not_supported" size="22px" color="grey-6" />
|
||
</div>
|
||
</q-card-section>
|
||
</q-card>
|
||
<q-btn
|
||
dense
|
||
flat
|
||
color="primary"
|
||
label="Urun Detayi Gor"
|
||
class="detail-open-btn"
|
||
@click.stop="openProductCard(grp1, grp2)"
|
||
/>
|
||
</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-dialog v-model="productCardDialog" maximized>
|
||
<q-card class="product-card-dialog">
|
||
<q-card-section class="row items-center q-pb-sm">
|
||
<div class="text-h6">Urun Karti</div>
|
||
<q-space />
|
||
<q-btn icon="close" flat round dense v-close-popup />
|
||
</q-card-section>
|
||
|
||
<q-separator />
|
||
|
||
<q-card-section class="q-pt-md">
|
||
<div class="product-card-stock">
|
||
<div class="text-subtitle1 text-weight-bold">
|
||
{{ productCardData.productCode || '-' }} / {{ productCardData.colorCode || '-' }}{{ productCardData.secondColor ? '-' + productCardData.secondColor : '' }}
|
||
</div>
|
||
<div class="text-caption">Toplam Stok: {{ formatNumber(productCardData.totalQty || 0) }}</div>
|
||
<div class="stock-size-grid q-mt-sm">
|
||
<div v-for="sz in sizeLabels" :key="'dlg-sz-' + sz" class="stock-size-chip">
|
||
<span class="label">{{ sz }}</span>
|
||
<span class="value">{{ Number(productCardData.sizeTotals?.[sz] || 0) > 0 ? formatNumber(productCardData.sizeTotals[sz]) : '-' }}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<q-separator class="q-my-md" />
|
||
|
||
<div class="product-card-content">
|
||
<div class="product-card-images">
|
||
<q-carousel
|
||
v-if="productCardImages.length"
|
||
v-model="productCardSlide"
|
||
animated
|
||
swipeable
|
||
navigation
|
||
arrows
|
||
height="560px"
|
||
class="product-card-carousel rounded-borders bg-grey-2"
|
||
>
|
||
<q-carousel-slide
|
||
v-for="(img, idx) in productCardImages"
|
||
:key="'img-' + idx"
|
||
:name="idx"
|
||
class="column no-wrap flex-center"
|
||
>
|
||
<div class="dialog-image-stage cursor-pointer" @click="openProductImageFullscreen(img)">
|
||
<q-img :src="img" fit="contain" class="dialog-image" />
|
||
</div>
|
||
</q-carousel-slide>
|
||
</q-carousel>
|
||
<div v-else class="dialog-image-empty">
|
||
<q-icon name="image_not_supported" size="36px" color="grey-6" />
|
||
</div>
|
||
</div>
|
||
|
||
<div class="product-card-fields">
|
||
<div class="field-row"><span class="k">Urun Kodu</span><span class="v">{{ productCardData.productCode || '-' }}</span></div>
|
||
<div class="field-row"><span class="k">Urun Renk</span><span class="v">{{ productCardData.colorCode || '-' }}</span></div>
|
||
<div class="field-row"><span class="k">Urun 2.Renk</span><span class="v">{{ productCardData.secondColor || '-' }}</span></div>
|
||
<div class="field-row"><span class="k">Kategori</span><span class="v">{{ productCardData.kategori || '-' }}</span></div>
|
||
<div class="field-row"><span class="k">Urun Ana Grubu</span><span class="v">{{ productCardData.urunAnaGrubu || '-' }}</span></div>
|
||
<div class="field-row"><span class="k">Urun Alt Grubu</span><span class="v">{{ productCardData.urunAltGrubu || '-' }}</span></div>
|
||
<div class="field-row"><span class="k">Urun Icerigi</span><span class="v">{{ productCardData.urunIcerigi || '-' }}</span></div>
|
||
<div class="field-row"><span class="k">Fit</span><span class="v">{{ productCardData.fit || '-' }}</span></div>
|
||
<div class="field-row"><span class="k">Drop</span><span class="v">{{ productCardData.drop || '-' }}</span></div>
|
||
<div class="field-row"><span class="k">Kumas</span><span class="v">{{ productCardData.kumas || '-' }}</span></div>
|
||
<div class="field-row"><span class="k">Karisim</span><span class="v">{{ productCardData.karisim || '-' }}</span></div>
|
||
</div>
|
||
</div>
|
||
</q-card-section>
|
||
</q-card>
|
||
</q-dialog>
|
||
|
||
<q-dialog v-model="productImageFullscreenDialog" maximized>
|
||
<q-card class="image-fullscreen-dialog">
|
||
<q-card-section class="row items-center q-pb-sm">
|
||
<div class="text-h6">Urun Fotografi</div>
|
||
<q-space />
|
||
<q-btn icon="close" flat round dense v-close-popup />
|
||
</q-card-section>
|
||
<q-separator />
|
||
<q-card-section class="image-fullscreen-body">
|
||
<div class="image-fullscreen-stage cursor-pointer" @click="toggleFullscreenImageZoom">
|
||
<q-img
|
||
:src="productImageFullscreenSrc"
|
||
fit="contain"
|
||
class="image-fullscreen-img"
|
||
:style="fullscreenImageStyle"
|
||
/>
|
||
</div>
|
||
</q-card-section>
|
||
</q-card>
|
||
</q-dialog>
|
||
</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 { normalizeSearchText } from 'src/utils/searchText'
|
||
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 productCardDialog = ref(false)
|
||
const productCardData = ref({})
|
||
const productCardImages = ref([])
|
||
const productCardSlide = ref(0)
|
||
const productImageFullscreenDialog = ref(false)
|
||
const productImageFullscreenSrc = ref('')
|
||
const productImageFullscreenZoom = ref(1)
|
||
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'
|
||
)
|
||
const fullscreenImageStyle = computed(() => ({
|
||
transform: `scale(${productImageFullscreenZoom.value})`,
|
||
transformOrigin: 'center center',
|
||
transition: 'transform 0.15s ease-out'
|
||
}))
|
||
|
||
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 sortByColorCodeAsc(a, b) {
|
||
const compareCodeLike = (va, vb) => {
|
||
const sa = String(va || '').trim()
|
||
const sb = String(vb || '').trim()
|
||
const pa = sa.match(/^(\d+)(?:_(\d+))?$/)
|
||
const pb = sb.match(/^(\d+)(?:_(\d+))?$/)
|
||
if (pa && pb) {
|
||
const a1 = Number.parseInt(pa[1], 10)
|
||
const b1 = Number.parseInt(pb[1], 10)
|
||
if (a1 !== b1) return a1 - b1
|
||
const a2 = Number.parseInt(pa[2] || '0', 10)
|
||
const b2 = Number.parseInt(pb[2] || '0', 10)
|
||
if (a2 !== b2) return a2 - b2
|
||
}
|
||
return sa.localeCompare(sb, 'tr', { sensitivity: 'base' })
|
||
}
|
||
|
||
const ca = String(a?.colorCode || '').trim()
|
||
const cb = String(b?.colorCode || '').trim()
|
||
const na = Number.parseInt(ca, 10)
|
||
const nb = Number.parseInt(cb, 10)
|
||
const aNum = Number.isFinite(na)
|
||
const bNum = Number.isFinite(nb)
|
||
|
||
if (aNum && bNum && na !== nb) return na - nb
|
||
if (aNum !== bNum) return aNum ? -1 : 1
|
||
|
||
const cmp = compareCodeLike(ca, cb)
|
||
if (cmp !== 0) return cmp
|
||
|
||
return compareCodeLike(a?.secondColor, b?.secondColor)
|
||
}
|
||
|
||
function buildImageKey(code, color, secondColor = '', dim1Id = '', dim3Id = '') {
|
||
return `${String(code || '').trim().toUpperCase()}::${String(color || '').trim().toUpperCase()}::${String(secondColor || '').trim().toUpperCase()}::${String(dim1Id || '').trim().toUpperCase()}::${String(dim3Id || '').trim().toUpperCase()}`
|
||
}
|
||
|
||
function normalizeImageDim3(value) {
|
||
const s = String(value || '').trim().toUpperCase()
|
||
if (!s) return ''
|
||
if (/^\d{3}(?:_\d+)?$/.test(s)) return s
|
||
return ''
|
||
}
|
||
|
||
function resolvePhotoDim3(item, secondColorDisplay = '') {
|
||
return (
|
||
normalizeImageDim3(item?.Renk2) ||
|
||
normalizeImageDim3(item?.ItemDim3Code) ||
|
||
normalizeImageDim3(secondColorDisplay) ||
|
||
''
|
||
)
|
||
}
|
||
|
||
function resolvePhotoDim1ID(item) {
|
||
const candidates = [item?.ItemDim1Code, item?.itemDim1Code, item?.ITEMDIM1CODE, item?.Beden]
|
||
for (const value of candidates) {
|
||
const s = String(value || '').trim()
|
||
if (/^\d+$/.test(s)) return s
|
||
}
|
||
return ''
|
||
}
|
||
|
||
function resolvePhotoDim3ID(item) {
|
||
const candidates = [item?.ItemDim3Code, item?.itemDim3Code, item?.ITEMDIM3CODE, item?.Renk2]
|
||
for (const value of candidates) {
|
||
const s = String(value || '').trim()
|
||
if (/^\d+$/.test(s)) return s
|
||
}
|
||
return ''
|
||
}
|
||
|
||
function buildDim3Candidates(secondColor) {
|
||
const secondTrim = normalizeImageDim3(secondColor)
|
||
if (!secondTrim) return ['']
|
||
const set = new Set([secondTrim])
|
||
if (/^\d{3}$/.test(secondTrim)) set.add(`${secondTrim}_1`)
|
||
return Array.from(set)
|
||
}
|
||
|
||
async function fetchProductImageList(codeTrim, colorTrim, secondTrim, dim1IdTrim = '', dim3IdTrim = '') {
|
||
const dim3Candidates = buildDim3Candidates(secondTrim)
|
||
for (const dim3Candidate of dim3Candidates) {
|
||
const params = { code: codeTrim, dim1: colorTrim }
|
||
if (String(dim1IdTrim || '').trim()) params.dim1_id = String(dim1IdTrim || '').trim()
|
||
if (String(dim3IdTrim || '').trim()) params.dim3_id = String(dim3IdTrim || '').trim()
|
||
if (dim3Candidate) params.dim3 = dim3Candidate
|
||
const res = await api.get('/product-images', { params })
|
||
const list = Array.isArray(res?.data) ? res.data : []
|
||
if (list.length) return list
|
||
}
|
||
return []
|
||
}
|
||
|
||
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: '', thumbUrl: '', fullUrl: '' }
|
||
}
|
||
|
||
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 = ''
|
||
if (uploadsPath) {
|
||
publicUrl = uploadsPath
|
||
} else {
|
||
const fileName = String(item.file_name || item.FileName || '').trim()
|
||
if (fileName) publicUrl = `/uploads/image/${fileName}`
|
||
}
|
||
|
||
const thumbUrl = String(item.thumb_url || item.thumbUrl || '').trim()
|
||
const fullUrl = String(item.full_url || item.fullUrl || '').trim()
|
||
|
||
return { contentUrl, publicUrl, thumbUrl, fullUrl }
|
||
}
|
||
|
||
async function resolveProductImageUrlForCarousel(item) {
|
||
const resolved = resolveProductImageUrl(item)
|
||
const fullUrl = String(resolved.fullUrl || '').trim()
|
||
if (fullUrl) return fullUrl
|
||
const contentUrl = String(resolved.contentUrl || '').trim()
|
||
if (contentUrl) {
|
||
try {
|
||
const blobRes = await api.get(contentUrl, { baseURL: '', responseType: 'blob' })
|
||
const blob = blobRes?.data
|
||
if (blob instanceof Blob) {
|
||
const objectUrl = URL.createObjectURL(blob)
|
||
productImageBlobUrls.value.push(objectUrl)
|
||
return objectUrl
|
||
}
|
||
} catch {
|
||
// fall through to public url
|
||
}
|
||
}
|
||
const publicUrl = String(resolved.publicUrl || '').trim()
|
||
return String(publicUrl || fullUrl || contentUrl || '').trim()
|
||
}
|
||
|
||
function getProductImageUrl(code, color, secondColor = '', dim1Id = '', dim3Id = '') {
|
||
const key = buildImageKey(code, color, secondColor, dim1Id, dim3Id)
|
||
const existing = productImageCache.value[key]
|
||
if (existing !== undefined) return existing || ''
|
||
void ensureProductImage(code, color, secondColor, dim1Id, dim3Id)
|
||
return ''
|
||
}
|
||
|
||
async function onProductImageError(code, color, secondColor = '', dim1Id = '', dim3Id = '') {
|
||
const key = buildImageKey(code, color, secondColor, dim1Id, dim3Id)
|
||
productImageCache.value[key] = ''
|
||
}
|
||
|
||
async function ensureProductImage(code, color, secondColor = '', dim1Id = '', dim3Id = '') {
|
||
const key = buildImageKey(code, color, secondColor, dim1Id, dim3Id)
|
||
const codeTrim = String(code || '').trim().toUpperCase()
|
||
const colorTrim = String(color || '').trim().toUpperCase()
|
||
const secondTrim = String(secondColor || '').trim().toUpperCase()
|
||
const dim1IDTrim = String(dim1Id || '').trim().toUpperCase()
|
||
const dim3IDTrim = String(dim3Id || '').trim().toUpperCase()
|
||
const listKey = buildImageKey(codeTrim, colorTrim, secondTrim, dim1IDTrim, dim3IDTrim)
|
||
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[listKey]) {
|
||
if (!productImageListLoading.value[listKey]) {
|
||
productImageListLoading.value[listKey] = true
|
||
try {
|
||
if (imageListActiveRequests >= IMAGE_LIST_CONCURRENCY) {
|
||
await new Promise((resolve) => imageListWaitQueue.push(resolve))
|
||
}
|
||
imageListActiveRequests++
|
||
productImageListByCode.value[listKey] = await fetchProductImageList(codeTrim, colorTrim, secondTrim, dim1IDTrim, dim3IDTrim)
|
||
} catch (err) {
|
||
productImageListByCode.value[listKey] = []
|
||
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, color: colorTrim, secondColor: secondTrim, err })
|
||
} finally {
|
||
imageListActiveRequests = Math.max(0, imageListActiveRequests - 1)
|
||
const nextInQueue = imageListWaitQueue.shift()
|
||
if (nextInQueue) nextInQueue()
|
||
delete productImageListLoading.value[listKey]
|
||
}
|
||
} else {
|
||
while (productImageListLoading.value[listKey]) {
|
||
await new Promise((resolve) => setTimeout(resolve, 25))
|
||
}
|
||
}
|
||
}
|
||
|
||
const list = productImageListByCode.value[listKey] || []
|
||
const first = list[0] || null
|
||
|
||
const resolved = resolveProductImageUrl(first)
|
||
productImageCache.value[key] = resolved.thumbUrl || resolved.fullUrl || resolved.contentUrl || resolved.publicUrl || ''
|
||
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 photoDim3 = resolvePhotoDim3(item, secondColor)
|
||
const photoDim1ID = resolvePhotoDim1ID(item)
|
||
const photoDim3ID = resolvePhotoDim3ID(item)
|
||
const depoKodu = String(item.Depo_Kodu || '').trim()
|
||
const depoAdi = String(item.Depo_Adi || '').trim()
|
||
const kategori = String(item.YETISKIN_GARSON || '').trim()
|
||
const urunAnaGrubu = String(item.URUN_ANA_GRUBU || '').trim()
|
||
const urunAltGrubu = String(item.URUN_ALT_GRUBU || '').trim()
|
||
const urunIcerigi = String(item.URUN_ICERIGI || item.KISA_KAR || '').trim()
|
||
const fit = String(item.BIRINCI_PARCA_FIT || '').trim()
|
||
const drop = String(item.DR || '').trim()
|
||
const kumas = String(item.BIRINCI_PARCA_KUMAS || '').trim()
|
||
const karisim = String(item.BIRINCI_PARCA_KARISIM || '').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}|${photoDim3}|${photoDim1ID}|${photoDim3ID}`
|
||
if (!l1.childrenMap.has(l2Key)) {
|
||
l1.childrenMap.set(l2Key, {
|
||
key: `L2|${productCode}|${l2Key}`,
|
||
colorCode,
|
||
colorDesc,
|
||
secondColor,
|
||
photoDim3,
|
||
photoDim1ID,
|
||
photoDim3ID,
|
||
kategori,
|
||
urunAnaGrubu,
|
||
urunAltGrubu,
|
||
urunIcerigi,
|
||
fit,
|
||
drop,
|
||
kumas,
|
||
karisim,
|
||
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())
|
||
}))
|
||
.sort(sortByColorCodeAsc)
|
||
}))
|
||
})
|
||
|
||
function filterProducts(val, update) {
|
||
if (!val) {
|
||
update(() => {
|
||
filteredProductOptions.value = [...productOptions.value]
|
||
})
|
||
return
|
||
}
|
||
const needle = normalizeSearchText(val)
|
||
update(() => {
|
||
filteredProductOptions.value = productOptions.value.filter(opt =>
|
||
normalizeSearchText(opt.label).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, grp2.photoDim3 || grp2.secondColor, grp2.photoDim1ID || '', grp2.photoDim3ID || '')
|
||
}
|
||
}
|
||
|
||
async function openProductCard(grp1, grp2) {
|
||
const productCode = String(grp1?.productCode || '').trim()
|
||
const colorCode = String(grp2?.colorCode || '').trim()
|
||
const secondColor = String(grp2?.secondColor || '').trim()
|
||
const photoDim3 = String(grp2?.photoDim3 || secondColor).trim()
|
||
const photoDim1ID = String(grp2?.photoDim1ID || '').trim()
|
||
const photoDim3ID = String(grp2?.photoDim3ID || '').trim()
|
||
const listKey = buildImageKey(productCode, colorCode, photoDim3, photoDim1ID, photoDim3ID)
|
||
const codeTrim = String(productCode || '').trim().toUpperCase()
|
||
const colorTrim = String(colorCode || '').trim().toUpperCase()
|
||
const secondTrim = String(photoDim3 || '').trim().toUpperCase()
|
||
const dim1IDTrim = String(photoDim1ID || '').trim().toUpperCase()
|
||
const dim3IDTrim = String(photoDim3ID || '').trim().toUpperCase()
|
||
|
||
await ensureProductImage(productCode, colorCode, photoDim3, photoDim1ID, photoDim3ID)
|
||
let list = Array.isArray(productImageListByCode.value[listKey]) ? productImageListByCode.value[listKey] : []
|
||
console.info('[ProductStockQuery][openProductCard] request', {
|
||
productCode,
|
||
colorCode,
|
||
secondColor,
|
||
dim1ID: dim1IDTrim,
|
||
dim3ID: dim3IDTrim,
|
||
listKey,
|
||
cachedListCount: list.length
|
||
})
|
||
if (!list.length && codeTrim) {
|
||
try {
|
||
list = await fetchProductImageList(codeTrim, colorTrim, secondTrim, dim1IDTrim, dim3IDTrim)
|
||
productImageListByCode.value[listKey] = list
|
||
console.info('[ProductStockQuery][openProductCard] refetch', {
|
||
productCode: codeTrim,
|
||
dim1: colorTrim,
|
||
dim1ID: dim1IDTrim,
|
||
dim3: secondTrim,
|
||
dim3ID: dim3IDTrim,
|
||
fetchedCount: list.length,
|
||
fileNames: list.map((x) => String(x?.file_name || x?.FileName || '').trim()).filter(Boolean)
|
||
})
|
||
} catch (err) {
|
||
console.warn('[ProductStockQuery] product card image list refetch failed', {
|
||
code: codeTrim,
|
||
color: colorTrim,
|
||
secondColor: secondTrim,
|
||
err
|
||
})
|
||
}
|
||
}
|
||
|
||
const imageCandidates = await Promise.all(
|
||
list.map((item) => resolveProductImageUrlForCarousel(item))
|
||
)
|
||
const images = imageCandidates.filter((x) => String(x || '').trim() !== '')
|
||
console.info('[ProductStockQuery][openProductCard] render', {
|
||
productCode,
|
||
colorCode,
|
||
secondColor,
|
||
candidateCount: imageCandidates.length,
|
||
imageCount: images.length,
|
||
firstImages: images.slice(0, 3)
|
||
})
|
||
const uniqueImages = Array.from(new Set(images))
|
||
|
||
if (!uniqueImages.length) {
|
||
const single = getProductImageUrl(productCode, colorCode, photoDim3, photoDim1ID, photoDim3ID)
|
||
if (single) uniqueImages.push(single)
|
||
}
|
||
|
||
productCardImages.value = uniqueImages
|
||
productCardSlide.value = 0
|
||
productCardData.value = {
|
||
productCode,
|
||
colorCode,
|
||
secondColor,
|
||
kategori: String(grp2?.kategori || '').trim(),
|
||
urunAnaGrubu: String(grp2?.urunAnaGrubu || '').trim(),
|
||
urunAltGrubu: String(grp2?.urunAltGrubu || '').trim(),
|
||
urunIcerigi: String(grp2?.urunIcerigi || '').trim(),
|
||
fit: String(grp2?.fit || '').trim(),
|
||
drop: String(grp2?.drop || '').trim(),
|
||
kumas: String(grp2?.kumas || '').trim(),
|
||
karisim: String(grp2?.karisim || '').trim(),
|
||
sizeTotals: grp2?.sizeTotals || {},
|
||
totalQty: Number(grp2?.totalQty || 0)
|
||
}
|
||
productCardDialog.value = true
|
||
}
|
||
|
||
function openProductImageFullscreen(src) {
|
||
const value = String(src || '').trim()
|
||
if (!value) return
|
||
productImageFullscreenSrc.value = value
|
||
productImageFullscreenZoom.value = 1
|
||
productImageFullscreenDialog.value = true
|
||
}
|
||
|
||
function toggleFullscreenImageZoom() {
|
||
const current = Number(productImageFullscreenZoom.value || 1)
|
||
if (current < 1.5) productImageFullscreenZoom.value = 1.8
|
||
else if (current < 2.3) productImageFullscreenZoom.value = 2.6
|
||
else productImageFullscreenZoom.value = 1
|
||
}
|
||
|
||
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
|
||
productCardDialog.value = false
|
||
productCardData.value = {}
|
||
productCardImages.value = []
|
||
productCardSlide.value = 0
|
||
productImageFullscreenDialog.value = false
|
||
productImageFullscreenSrc.value = ''
|
||
productImageFullscreenZoom.value = 1
|
||
}
|
||
|
||
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: 190px;
|
||
--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: 252px !important;
|
||
height: 252px !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;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 8px;
|
||
padding: 8px 10px;
|
||
border-left: 1px solid #d4c79f;
|
||
}
|
||
|
||
.product-image-card {
|
||
width: 162px;
|
||
height: 216px;
|
||
border-radius: 8px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.product-image-wrap {
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
|
||
.product-image {
|
||
width: 100%;
|
||
height: 100%;
|
||
background: #fff;
|
||
}
|
||
|
||
.product-image-placeholder {
|
||
width: 100%;
|
||
height: 100%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: #f5f6f7;
|
||
}
|
||
|
||
.detail-open-btn {
|
||
margin-top: 4px;
|
||
font-size: 11px;
|
||
}
|
||
|
||
.product-card-dialog {
|
||
background: #fffef9;
|
||
}
|
||
|
||
.product-card-stock {
|
||
background: #f8f5e7;
|
||
border: 1px solid #e2d9b6;
|
||
border-radius: 10px;
|
||
padding: 12px;
|
||
}
|
||
|
||
.stock-size-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(84px, 1fr));
|
||
gap: 8px;
|
||
}
|
||
|
||
.stock-size-chip {
|
||
border: 1px solid #d8cca6;
|
||
border-radius: 8px;
|
||
background: #fff;
|
||
padding: 6px 8px;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.stock-size-chip .label {
|
||
font-weight: 700;
|
||
}
|
||
|
||
.product-card-content {
|
||
display: grid;
|
||
grid-template-columns: minmax(360px, 1fr) 420px;
|
||
gap: 12px;
|
||
align-items: stretch;
|
||
justify-content: start;
|
||
}
|
||
|
||
.product-card-images {
|
||
grid-column: 2;
|
||
grid-row: 1;
|
||
min-height: 560px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: flex-start;
|
||
justify-content: flex-start;
|
||
}
|
||
|
||
.product-card-carousel {
|
||
width: 420px;
|
||
max-width: 100%;
|
||
}
|
||
|
||
.dialog-image {
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
|
||
.dialog-image-stage {
|
||
width: 420px;
|
||
max-width: 100%;
|
||
height: 560px;
|
||
overflow: hidden;
|
||
border-radius: 8px;
|
||
background: #f7f4e9;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.dialog-image-empty {
|
||
width: 420px;
|
||
max-width: 100%;
|
||
height: 560px;
|
||
border: 1px dashed #cabf9a;
|
||
border-radius: 8px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: #faf7ee;
|
||
}
|
||
|
||
.image-fullscreen-dialog {
|
||
background: #f4f0e2;
|
||
}
|
||
|
||
.image-fullscreen-body {
|
||
height: calc(100vh - 72px);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.image-fullscreen-stage {
|
||
width: min(96vw, 1400px);
|
||
height: calc(100vh - 120px);
|
||
border-radius: 10px;
|
||
background: #efe7cc;
|
||
overflow: hidden;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.image-fullscreen-img {
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
|
||
.product-card-fields {
|
||
grid-column: 1;
|
||
grid-row: 1;
|
||
border: 1px solid #e2d9b6;
|
||
border-radius: 10px;
|
||
background: #fff;
|
||
padding: 10px;
|
||
height: 560px;
|
||
overflow: auto;
|
||
}
|
||
|
||
.field-row {
|
||
display: grid;
|
||
grid-template-columns: 150px 1fr;
|
||
gap: 8px;
|
||
padding: 7px 0;
|
||
border-bottom: 1px solid #f0ead7;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.field-row:last-child {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.field-row .k {
|
||
color: #5a4f2c;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.field-row .v {
|
||
color: #1f1f1f;
|
||
word-break: break-word;
|
||
}
|
||
|
||
.q-btn,
|
||
.q-icon,
|
||
.product-image-card,
|
||
.cursor-pointer {
|
||
cursor: pointer !important;
|
||
}
|
||
|
||
@media (max-width: 1024px) {
|
||
.product-card-content {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.product-card-images,
|
||
.product-card-fields {
|
||
grid-column: auto;
|
||
grid-row: auto;
|
||
}
|
||
|
||
.product-card-fields {
|
||
height: auto;
|
||
}
|
||
}
|
||
|
||
.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>
|
||
|
||
|
||
|
||
|
||
|
||
|