Merge remote-tracking branch 'origin/master'
This commit is contained in:
@@ -672,6 +672,20 @@ func cleanProductPricingFilterValues(values []string) []string {
|
||||
return clean
|
||||
}
|
||||
|
||||
func isProductCodeOnlyPricingFilter(filters ProductPricingFilters) bool {
|
||||
return len(cleanProductPricingFilterValues(filters.ProductCode)) > 0 &&
|
||||
strings.TrimSpace(filters.Search) == "" &&
|
||||
len(cleanProductPricingFilterValues(filters.BrandGroup)) == 0 &&
|
||||
len(cleanProductPricingFilterValues(filters.AskiliYan)) == 0 &&
|
||||
len(cleanProductPricingFilterValues(filters.Kategori)) == 0 &&
|
||||
len(cleanProductPricingFilterValues(filters.UrunIlkGrubu)) == 0 &&
|
||||
len(cleanProductPricingFilterValues(filters.UrunAnaGrubu)) == 0 &&
|
||||
len(cleanProductPricingFilterValues(filters.UrunAltGrubu)) == 0 &&
|
||||
len(cleanProductPricingFilterValues(filters.Icerik)) == 0 &&
|
||||
len(cleanProductPricingFilterValues(filters.Karisim)) == 0 &&
|
||||
len(cleanProductPricingFilterValues(filters.Marka)) == 0
|
||||
}
|
||||
|
||||
func GetProductPricingPage(ctx context.Context, page int, limit int, filters ProductPricingFilters, includeTotal bool, sortBy string, descending bool) (ProductPricingPage, error) {
|
||||
result := ProductPricingPage{
|
||||
Rows: []models.ProductPricing{},
|
||||
@@ -751,8 +765,28 @@ func GetProductPricingPage(ctx context.Context, page int, limit int, filters Pro
|
||||
}, " OR ")+")")
|
||||
}
|
||||
whereSQL := strings.Join(whereParts, " AND ")
|
||||
cleanProductCodes := cleanProductPricingFilterValues(filters.ProductCode)
|
||||
productCodeOnlyFilter := isProductCodeOnlyPricingFilter(filters)
|
||||
|
||||
if includeTotal {
|
||||
if includeTotal && productCodeOnlyFilter {
|
||||
result.TotalCount = len(cleanProductCodes)
|
||||
if result.TotalCount == 0 {
|
||||
result.TotalPages = 0
|
||||
result.Page = 1
|
||||
return result, nil
|
||||
}
|
||||
totalPages := int(math.Ceil(float64(result.TotalCount) / float64(limit)))
|
||||
if totalPages <= 0 {
|
||||
totalPages = 1
|
||||
}
|
||||
if page > totalPages {
|
||||
page = totalPages
|
||||
offset = (page - 1) * limit
|
||||
}
|
||||
result.Page = page
|
||||
result.Limit = limit
|
||||
result.TotalPages = totalPages
|
||||
} else if includeTotal {
|
||||
countQuery := `
|
||||
SELECT COUNT(DISTINCT LTRIM(RTRIM(ProductCode)))
|
||||
FROM ProductFilterWithDescription('TR')
|
||||
@@ -789,7 +823,7 @@ func GetProductPricingPage(ctx context.Context, page int, limit int, filters Pro
|
||||
|
||||
// Stage 1: fetch only paged products first. Exact product-code filters do not
|
||||
// need the stock sort temp table here; detailed metrics are fetched below.
|
||||
productCodeFastPath := len(cleanProductPricingFilterValues(filters.ProductCode)) > 0
|
||||
productCodeFastPath := len(cleanProductCodes) > 0
|
||||
sortBy = strings.TrimSpace(sortBy)
|
||||
orderDir := "DESC"
|
||||
if !descending {
|
||||
@@ -876,7 +910,72 @@ func GetProductPricingPage(ctx context.Context, page int, limit int, filters Pro
|
||||
OFFSET ` + strconv.Itoa(offset) + ` ROWS
|
||||
FETCH NEXT ` + strconv.Itoa(limit) + ` ROWS ONLY;
|
||||
`
|
||||
if productCodeFastPath {
|
||||
if productCodeOnlyFilter {
|
||||
valueRows := make([]string, 0, len(cleanProductCodes))
|
||||
directArgs := make([]any, 0, len(cleanProductCodes))
|
||||
for i, code := range cleanProductCodes {
|
||||
paramName := "@p" + strconv.Itoa(i+1)
|
||||
valueRows = append(valueRows, "("+paramName+")")
|
||||
directArgs = append(directArgs, code)
|
||||
}
|
||||
args = directArgs
|
||||
productQuery = `
|
||||
WITH req_codes AS (
|
||||
SELECT DISTINCT LTRIM(RTRIM(v.ProductCode)) AS ProductCode
|
||||
FROM (VALUES ` + strings.Join(valueRows, ",") + `) v(ProductCode)
|
||||
WHERE LEN(LTRIM(RTRIM(v.ProductCode))) > 0
|
||||
),
|
||||
attr AS (
|
||||
SELECT
|
||||
LTRIM(RTRIM(a.ItemCode)) AS ProductCode,
|
||||
MAX(CASE WHEN a.AttributeTypeCode = 45 THEN COALESCE(NULLIF(LTRIM(RTRIM(d.AttributeDescription)), ''), LTRIM(RTRIM(a.AttributeCode))) ELSE '' END) AS AskiliYan,
|
||||
MAX(CASE WHEN a.AttributeTypeCode = 44 THEN COALESCE(NULLIF(LTRIM(RTRIM(d.AttributeDescription)), ''), LTRIM(RTRIM(a.AttributeCode))) ELSE '' END) AS Kategori,
|
||||
MAX(CASE WHEN a.AttributeTypeCode = 42 THEN LTRIM(RTRIM(a.AttributeCode)) ELSE '' END) AS UrunIlkGrubuCode,
|
||||
MAX(CASE WHEN a.AttributeTypeCode = 42 THEN COALESCE(NULLIF(LTRIM(RTRIM(d.AttributeDescription)), ''), LTRIM(RTRIM(a.AttributeCode))) ELSE '' END) AS UrunIlkGrubu,
|
||||
MAX(CASE WHEN a.AttributeTypeCode = 1 THEN COALESCE(NULLIF(LTRIM(RTRIM(d.AttributeDescription)), ''), LTRIM(RTRIM(a.AttributeCode))) ELSE '' END) AS UrunAnaGrubu,
|
||||
MAX(CASE WHEN a.AttributeTypeCode = 2 THEN COALESCE(NULLIF(LTRIM(RTRIM(d.AttributeDescription)), ''), LTRIM(RTRIM(a.AttributeCode))) ELSE '' END) AS UrunAltGrubu,
|
||||
MAX(CASE WHEN a.AttributeTypeCode = 41 THEN COALESCE(NULLIF(LTRIM(RTRIM(d.AttributeDescription)), ''), LTRIM(RTRIM(a.AttributeCode))) ELSE '' END) AS Icerik,
|
||||
MAX(CASE WHEN a.AttributeTypeCode = 29 THEN COALESCE(NULLIF(LTRIM(RTRIM(d.AttributeDescription)), ''), LTRIM(RTRIM(a.AttributeCode))) ELSE '' END) AS Karisim,
|
||||
MAX(CASE WHEN a.AttributeTypeCode = 10 THEN COALESCE(NULLIF(LTRIM(RTRIM(d.AttributeDescription)), ''), LTRIM(RTRIM(a.AttributeCode))) ELSE '' END) AS Marka,
|
||||
MAX(CASE WHEN a.AttributeTypeCode = 10 THEN LTRIM(RTRIM(a.AttributeCode)) ELSE '' END) AS BrandCode
|
||||
FROM dbo.prItemAttribute a WITH(NOLOCK)
|
||||
INNER JOIN req_codes rc
|
||||
ON rc.ProductCode = LTRIM(RTRIM(a.ItemCode))
|
||||
LEFT JOIN dbo.cdItemAttributeDesc d WITH(NOLOCK)
|
||||
ON d.ItemTypeCode = a.ItemTypeCode
|
||||
AND d.AttributeTypeCode = a.AttributeTypeCode
|
||||
AND d.AttributeCode = a.AttributeCode
|
||||
AND d.LangCode = 'TR'
|
||||
WHERE a.ItemTypeCode = 1
|
||||
AND a.AttributeTypeCode IN (1,2,10,29,41,42,44,45)
|
||||
GROUP BY LTRIM(RTRIM(a.ItemCode))
|
||||
)
|
||||
SELECT
|
||||
rc.ProductCode,
|
||||
CAST('' AS NVARCHAR(100)) AS BrandGroupSec,
|
||||
COALESCE(attr.AskiliYan, '') AS AskiliYan,
|
||||
COALESCE(attr.Kategori, '') AS Kategori,
|
||||
COALESCE(attr.UrunIlkGrubu, '') AS UrunIlkGrubu,
|
||||
COALESCE(attr.UrunAnaGrubu, '') AS UrunAnaGrubu,
|
||||
COALESCE(attr.UrunAltGrubu, '') AS UrunAltGrubu,
|
||||
COALESCE(attr.Icerik, '') AS Icerik,
|
||||
COALESCE(attr.Karisim, '') AS Karisim,
|
||||
COALESCE(attr.Marka, '') AS Marka,
|
||||
COALESCE(attr.BrandCode, '') AS BrandCode
|
||||
FROM req_codes rc
|
||||
INNER JOIN dbo.cdItem ci WITH(NOLOCK)
|
||||
ON ci.ItemTypeCode = 1
|
||||
AND LTRIM(RTRIM(ci.ItemCode)) = rc.ProductCode
|
||||
LEFT JOIN attr
|
||||
ON attr.ProductCode = rc.ProductCode
|
||||
WHERE ISNULL(ci.IsBlocked, 0) = 0
|
||||
AND LEN(LTRIM(RTRIM(ci.ItemCode))) = 13
|
||||
AND COALESCE(attr.UrunIlkGrubuCode, '') IN ('SERI', 'AKSESUAR')
|
||||
ORDER BY rc.ProductCode ASC
|
||||
OFFSET ` + strconv.Itoa(offset) + ` ROWS
|
||||
FETCH NEXT ` + strconv.Itoa(limit) + ` ROWS ONLY;
|
||||
`
|
||||
} else if productCodeFastPath {
|
||||
productQuery = `
|
||||
IF OBJECT_ID('tempdb..#req_codes') IS NOT NULL DROP TABLE #req_codes;
|
||||
|
||||
|
||||
@@ -258,8 +258,9 @@ norm AS (
|
||||
COALESCE(price, 0) AS price
|
||||
FROM input
|
||||
),
|
||||
-- Prefer PG's authoritative variant dimension table (mmitem_dim). Fall back to cache table if needed.
|
||||
dims_mmitem_dim AS (
|
||||
-- Only PG's authoritative variant dimension table may drive delta writes.
|
||||
-- Do not union sdprc/cache dimensions; stale rows there can re-create wrong variant keys.
|
||||
dims AS (
|
||||
SELECT
|
||||
norm.product_code AS product_code,
|
||||
md.val1::bigint AS dim1,
|
||||
@@ -275,41 +276,7 @@ norm AS (
|
||||
AND COALESCE(md.is_active, TRUE) = TRUE
|
||||
WHERE md.val1 IS NOT NULL
|
||||
AND md.val1 > 0
|
||||
GROUP BY norm.product_code, md.val1, md.val2
|
||||
),
|
||||
dims_cache_table AS (
|
||||
SELECT
|
||||
NULLIF(BTRIM(c.product_code), '') AS product_code,
|
||||
c.dim1::bigint AS dim1,
|
||||
c.dim3::bigint AS dim3
|
||||
FROM mk_mmitem_dim_combo c
|
||||
JOIN norm
|
||||
ON norm.product_code = c.product_code
|
||||
WHERE c.dim1 IS NOT NULL
|
||||
),
|
||||
dims_cache AS (
|
||||
SELECT product_code, dim1, dim3 FROM dims_mmitem_dim
|
||||
UNION
|
||||
SELECT product_code, dim1, dim3 FROM dims_cache_table
|
||||
),
|
||||
dims_sdprc AS (
|
||||
SELECT
|
||||
norm.product_code AS product_code,
|
||||
s.dim1 AS dim1,
|
||||
s.dim3 AS dim3
|
||||
FROM norm
|
||||
JOIN mmitem mm
|
||||
ON mm.code = norm.product_code
|
||||
JOIN sdprc s
|
||||
ON s.mmitem_id = mm.id
|
||||
WHERE s.dim1 IS NOT NULL
|
||||
AND s.dim1 > 0
|
||||
GROUP BY norm.product_code, s.dim1, s.dim3
|
||||
),
|
||||
dims AS (
|
||||
SELECT product_code, dim1, dim3 FROM dims_cache
|
||||
UNION
|
||||
SELECT product_code, dim1, dim3 FROM dims_sdprc
|
||||
GROUP BY norm.product_code, md.val1, md.val3
|
||||
),
|
||||
mapped AS (
|
||||
SELECT
|
||||
|
||||
@@ -431,37 +431,6 @@ WHERE is_active = TRUE
|
||||
return out
|
||||
}
|
||||
|
||||
loadDimCombosFromCache := func(productCode string) ([]dimCombo, error) {
|
||||
productCode = strings.TrimSpace(productCode)
|
||||
if productCode == "" {
|
||||
return nil, nil
|
||||
}
|
||||
rows, err := pgTx.QueryContext(ctx, `
|
||||
SELECT dim1, dim3
|
||||
FROM mk_mmitem_dim_combo
|
||||
WHERE product_code = $1
|
||||
ORDER BY dim1, dim3_key
|
||||
`, productCode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
out := make([]dimCombo, 0, 32)
|
||||
for rows.Next() {
|
||||
var d1 int64
|
||||
var d3 sql.NullInt64
|
||||
if err := rows.Scan(&d1, &d3); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if d1 <= 0 {
|
||||
continue
|
||||
}
|
||||
out = append(out, dimCombo{Dim1: d1, Dim3: d3})
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
parseDimID := func(s string) (int64, bool) {
|
||||
s = strings.TrimSpace(s)
|
||||
if s == "" {
|
||||
@@ -812,7 +781,7 @@ filtered AS (
|
||||
AND price > 0
|
||||
),
|
||||
grouped AS (
|
||||
-- Ensure one row per business key to avoid unique violations under strict constraints (e.g. uq_sdprc_3).
|
||||
-- Ensure one row per business key to avoid unique violations under strict constraints.
|
||||
SELECT
|
||||
sdprcgrp_id,
|
||||
currency AS crn,
|
||||
@@ -822,16 +791,35 @@ grouped AS (
|
||||
FROM filtered
|
||||
GROUP BY sdprcgrp_id, currency, dim1, dim3
|
||||
),
|
||||
upserted AS (
|
||||
updated AS (
|
||||
UPDATE sdprc s
|
||||
SET prc = g.prc,
|
||||
zlins_dttm = now()
|
||||
FROM grouped g
|
||||
WHERE s.mmitem_id = $2::bigint
|
||||
AND s.sdprcgrp_id = g.sdprcgrp_id
|
||||
AND s.crn = g.crn
|
||||
AND s.dim1 = g.dim1
|
||||
AND COALESCE(s.dim3, 0) = COALESCE(g.dim3, 0)
|
||||
AND s.prc IS DISTINCT FROM g.prc
|
||||
RETURNING 1
|
||||
),
|
||||
inserted AS (
|
||||
INSERT INTO sdprc (mmitem_id, sdprcgrp_id, crn, dim1, dim3, prc, zlins_dttm)
|
||||
SELECT $2::bigint, g.sdprcgrp_id, g.crn, g.dim1, g.dim3, g.prc, now()
|
||||
FROM grouped g
|
||||
ON CONFLICT ON CONSTRAINT uq_sdprc_3
|
||||
DO UPDATE SET prc = EXCLUDED.prc, zlins_dttm = EXCLUDED.zlins_dttm
|
||||
WHERE sdprc.prc IS DISTINCT FROM EXCLUDED.prc
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM sdprc s
|
||||
WHERE s.mmitem_id = $2::bigint
|
||||
AND s.sdprcgrp_id = g.sdprcgrp_id
|
||||
AND s.crn = g.crn
|
||||
AND s.dim1 = g.dim1
|
||||
AND COALESCE(s.dim3, 0) = COALESCE(g.dim3, 0)
|
||||
)
|
||||
RETURNING 1
|
||||
)
|
||||
SELECT COUNT(*)::int FROM upserted;
|
||||
SELECT ((SELECT COUNT(*) FROM updated) + (SELECT COUNT(*) FROM inserted))::int;
|
||||
`
|
||||
var inserted int
|
||||
if err := pgTx.QueryRowContext(ctx, q, raw, mmItemID).Scan(&inserted); err != nil {
|
||||
@@ -1083,28 +1071,8 @@ VALUES (
|
||||
_ = upsertDimCombosCache(code, dims) // best-effort cache fill
|
||||
}
|
||||
|
||||
// 2) Cache fallback (fast).
|
||||
cacheStarted := time.Now()
|
||||
if len(dims) == 0 {
|
||||
cached, cacheErr := loadDimCombosFromCache(code)
|
||||
if cacheErr == nil && len(cached) > 0 {
|
||||
dims = cached
|
||||
logger.Info("save:pg:dims:cache:hit",
|
||||
"product_code", code,
|
||||
"dims", len(dims),
|
||||
"duration_ms", time.Since(cacheStarted).Milliseconds(),
|
||||
)
|
||||
} else if cacheErr != nil {
|
||||
logger.Error("save:pg:dims:cache-load:error", "product_code", code, "err", cacheErr)
|
||||
} else {
|
||||
logger.Info("save:pg:dims:cache:miss",
|
||||
"product_code", code,
|
||||
"duration_ms", time.Since(cacheStarted).Milliseconds(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 3) Last resort: MSSQL stock tokens (legacy).
|
||||
// 2) Last resort: MSSQL stock tokens, then seed mmitem_dim. Do not use
|
||||
// mk_mmitem_dim_combo as a write source; stale cache rows can create wrong keys.
|
||||
if len(dims) == 0 {
|
||||
d, err := loadDimsFromMssqlStock(code)
|
||||
if err != nil {
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
<q-inner-loading :showing="pageBusy">
|
||||
<q-spinner-gears size="52px" color="primary" />
|
||||
</q-inner-loading>
|
||||
<div
|
||||
v-if="pageBusy"
|
||||
class="page-busy-overlay"
|
||||
@click.stop
|
||||
@mousedown.stop
|
||||
@mouseup.stop
|
||||
@touchstart.stop
|
||||
@wheel.stop
|
||||
>
|
||||
<q-spinner-gears size="56px" color="primary" />
|
||||
<div class="page-busy-label">Yukleniyor...</div>
|
||||
</div>
|
||||
|
||||
<div class="top-bar row items-center justify-between q-mb-xs">
|
||||
<div class="text-subtitle1 text-weight-bold">Urun Fiyatlandirma</div>
|
||||
@@ -69,7 +81,7 @@
|
||||
:disable="pageBusy"
|
||||
@click="leftDetailsExpanded = !leftDetailsExpanded"
|
||||
/>
|
||||
<q-btn-dropdown dense color="secondary" outline icon="view_module" label="Doviz Gorunumu" :auto-close="false">
|
||||
<q-btn-dropdown dense color="secondary" outline icon="view_module" label="Doviz Gorunumu" :auto-close="false" :disable="pageBusy">
|
||||
<q-list dense class="currency-menu-list">
|
||||
<q-item clickable @click="selectAllCurrencies">
|
||||
<q-item-section>Tumunu Sec</q-item-section>
|
||||
@@ -126,7 +138,7 @@
|
||||
</div>
|
||||
|
||||
<div class="toolbar-group">
|
||||
<q-btn-dropdown dense color="primary" outline icon="download" label="Cikti Al" :auto-close="true">
|
||||
<q-btn-dropdown dense color="primary" outline icon="download" label="Cikti Al" :auto-close="true" :disable="pageBusy">
|
||||
<q-list dense style="min-width: 260px;">
|
||||
<q-item clickable :disable="filteredRows.length === 0" @click="exportCurrentView">
|
||||
<q-item-section avatar><q-icon name="grid_on" /></q-item-section>
|
||||
@@ -2503,7 +2515,12 @@ async function reloadData ({ page = 1, useCache = true } = {}) {
|
||||
await bindHorizontalScrollSync()
|
||||
// Let the table render before we re-enable actions (prevents double-submits while the UI is still updating).
|
||||
await nextTick()
|
||||
await new Promise((resolve) => setTimeout(resolve, 0))
|
||||
const remainingBusyMs = Math.max(0, 350 - (Date.now() - startedAt))
|
||||
await new Promise((resolve) => setTimeout(resolve, remainingBusyMs))
|
||||
console.info('[product-pricing][ui] render:done', {
|
||||
duration_ms: Date.now() - startedAt,
|
||||
row_count: Array.isArray(store.rows) ? store.rows.length : 0
|
||||
})
|
||||
isReloading.value = false
|
||||
}
|
||||
}
|
||||
@@ -2574,6 +2591,27 @@ onBeforeUnmount(() => {
|
||||
min-width: 170px;
|
||||
}
|
||||
|
||||
.page-busy-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 9000;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
background: rgba(255, 255, 255, 0.72);
|
||||
backdrop-filter: blur(1px);
|
||||
cursor: wait;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.page-busy-label {
|
||||
color: #1f2937;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.top-actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
<q-inner-loading :showing="pageBusy">
|
||||
<q-spinner-gears size="52px" color="primary" />
|
||||
</q-inner-loading>
|
||||
<div
|
||||
v-if="pageBusy"
|
||||
class="page-busy-overlay"
|
||||
@click.stop
|
||||
@mousedown.stop
|
||||
@mouseup.stop
|
||||
@touchstart.stop
|
||||
@wheel.stop
|
||||
>
|
||||
<q-spinner-gears size="56px" color="primary" />
|
||||
<div class="page-busy-label">Yukleniyor...</div>
|
||||
</div>
|
||||
|
||||
<div class="top-bar row items-center justify-between q-mb-xs">
|
||||
<div class="text-subtitle1 text-weight-bold">Toptan Kampanya Yonetimi</div>
|
||||
@@ -69,7 +81,7 @@
|
||||
:disable="pageBusy"
|
||||
@click="leftDetailsExpanded = !leftDetailsExpanded"
|
||||
/>
|
||||
<q-btn-dropdown dense color="secondary" outline icon="view_module" label="Gosterge Fiyat Sec" :auto-close="false">
|
||||
<q-btn-dropdown dense color="secondary" outline icon="view_module" label="Gosterge Fiyat Sec" :auto-close="false" :disable="pageBusy">
|
||||
<q-list dense class="currency-menu-list">
|
||||
<q-item clickable @click="selectAllPriceOptions">
|
||||
<q-item-section>Tumunu Sec</q-item-section>
|
||||
@@ -130,7 +142,7 @@
|
||||
</div>
|
||||
|
||||
<div class="toolbar-group">
|
||||
<q-btn-dropdown dense color="primary" outline icon="download" label="Cikti Al" :auto-close="true">
|
||||
<q-btn-dropdown dense color="primary" outline icon="download" label="Cikti Al" :auto-close="true" :disable="pageBusy">
|
||||
<q-list dense style="min-width: 260px;">
|
||||
<q-item clickable :disable="filteredRows.length === 0" @click="exportCurrentView">
|
||||
<q-item-section avatar><q-icon name="grid_on" /></q-item-section>
|
||||
@@ -2713,7 +2725,13 @@ async function reloadData ({ page = 1, useCache = true } = {}) {
|
||||
})
|
||||
await bindHorizontalScrollSync()
|
||||
await nextTick()
|
||||
await new Promise((resolve) => setTimeout(resolve, 0))
|
||||
const remainingBusyMs = Math.max(0, 350 - (Date.now() - startedAt))
|
||||
await new Promise((resolve) => setTimeout(resolve, remainingBusyMs))
|
||||
console.info('[product-pricing][ui] render:done', {
|
||||
duration_ms: Date.now() - startedAt,
|
||||
row_count: Array.isArray(store.rows) ? store.rows.length : 0,
|
||||
variant_row_count: Array.isArray(variantRows.value) ? variantRows.value.length : 0
|
||||
})
|
||||
isReloading.value = false
|
||||
}
|
||||
}
|
||||
@@ -2787,6 +2805,27 @@ onBeforeUnmount(() => {
|
||||
min-width: 170px;
|
||||
}
|
||||
|
||||
.page-busy-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 9000;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
background: rgba(255, 255, 255, 0.72);
|
||||
backdrop-filter: blur(1px);
|
||||
cursor: wait;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.page-busy-label {
|
||||
color: #1f2937;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.top-actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
Reference in New Issue
Block a user