Merge remote-tracking branch 'origin/master'
This commit is contained in:
87
scripts/sql/product_filter_tr_cache_refresh.sql
Normal file
87
scripts/sql/product_filter_tr_cache_refresh.sql
Normal file
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
Product filter cache refresh for Product Stock By Attributes endpoints.
|
||||
This cache is used by backend queries when dbo.ProductFilterTRCache exists.
|
||||
*/
|
||||
|
||||
USE BAGGI_V3;
|
||||
GO
|
||||
|
||||
IF OBJECT_ID('dbo.ProductFilterTRCache','U') IS NULL
|
||||
BEGIN
|
||||
CREATE TABLE dbo.ProductFilterTRCache
|
||||
(
|
||||
ProductCode NVARCHAR(50) NOT NULL PRIMARY KEY,
|
||||
ProductDescription NVARCHAR(255) NULL,
|
||||
ProductAtt01Desc NVARCHAR(255) NULL,
|
||||
ProductAtt02Desc NVARCHAR(255) NULL,
|
||||
ProductAtt11Desc NVARCHAR(255) NULL,
|
||||
ProductAtt38Desc NVARCHAR(255) NULL,
|
||||
ProductAtt41Desc NVARCHAR(255) NULL,
|
||||
ProductAtt44Desc NVARCHAR(255) NULL
|
||||
);
|
||||
END
|
||||
GO
|
||||
|
||||
TRUNCATE TABLE dbo.ProductFilterTRCache;
|
||||
GO
|
||||
|
||||
INSERT INTO dbo.ProductFilterTRCache
|
||||
(
|
||||
ProductCode,
|
||||
ProductDescription,
|
||||
ProductAtt01Desc,
|
||||
ProductAtt02Desc,
|
||||
ProductAtt11Desc,
|
||||
ProductAtt38Desc,
|
||||
ProductAtt41Desc,
|
||||
ProductAtt44Desc
|
||||
)
|
||||
SELECT
|
||||
ProductCode,
|
||||
ProductDescription,
|
||||
ProductAtt01Desc,
|
||||
ProductAtt02Desc,
|
||||
ProductAtt11Desc,
|
||||
ProductAtt38Desc,
|
||||
ProductAtt41Desc,
|
||||
ProductAtt44Desc
|
||||
FROM ProductFilterWithDescription('TR')
|
||||
WHERE LEN(ProductCode) = 13;
|
||||
GO
|
||||
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM sys.indexes
|
||||
WHERE name = 'IX_ProductFilterTRCache_Filter'
|
||||
AND object_id = OBJECT_ID('dbo.ProductFilterTRCache')
|
||||
)
|
||||
BEGIN
|
||||
DROP INDEX IX_ProductFilterTRCache_Filter ON dbo.ProductFilterTRCache;
|
||||
END
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM sys.indexes
|
||||
WHERE name = 'IX_ProductFilterTRCache_KatAna'
|
||||
AND object_id = OBJECT_ID('dbo.ProductFilterTRCache')
|
||||
)
|
||||
BEGIN
|
||||
CREATE NONCLUSTERED INDEX IX_ProductFilterTRCache_KatAna
|
||||
ON dbo.ProductFilterTRCache (ProductAtt44Desc, ProductAtt01Desc, ProductCode)
|
||||
INCLUDE (ProductDescription, ProductAtt02Desc, ProductAtt41Desc, ProductAtt38Desc, ProductAtt11Desc);
|
||||
END
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM sys.indexes
|
||||
WHERE name = 'IX_ProductFilterTRCache_KatAnaAlt'
|
||||
AND object_id = OBJECT_ID('dbo.ProductFilterTRCache')
|
||||
)
|
||||
BEGIN
|
||||
CREATE NONCLUSTERED INDEX IX_ProductFilterTRCache_KatAnaAlt
|
||||
ON dbo.ProductFilterTRCache (ProductAtt44Desc, ProductAtt01Desc, ProductAtt02Desc, ProductCode)
|
||||
INCLUDE (ProductDescription, ProductAtt41Desc, ProductAtt38Desc, ProductAtt11Desc);
|
||||
END
|
||||
GO
|
||||
|
||||
UPDATE STATISTICS dbo.ProductFilterTRCache WITH FULLSCAN;
|
||||
GO
|
||||
74
scripts/sql/product_stock_by_attributes_indexes.sql
Normal file
74
scripts/sql/product_stock_by_attributes_indexes.sql
Normal file
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
Performance indexes for Product Stock By Attributes queries.
|
||||
Target: SQL Server
|
||||
*/
|
||||
|
||||
/* trStock (inventory aggregation) */
|
||||
IF NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM sys.indexes
|
||||
WHERE name = 'IX_trStock_Item_Warehouse_Dims'
|
||||
AND object_id = OBJECT_ID('dbo.trStock')
|
||||
)
|
||||
BEGIN
|
||||
CREATE NONCLUSTERED INDEX IX_trStock_Item_Warehouse_Dims
|
||||
ON dbo.trStock (ItemTypeCode, ItemCode, WarehouseCode, ColorCode, ItemDim1Code, ItemDim2Code, ItemDim3Code)
|
||||
INCLUDE (In_Qty1, Out_Qty1, CompanyCode, OfficeCode, StoreTypeCode, StoreCode);
|
||||
END;
|
||||
GO
|
||||
|
||||
/* PickingStates */
|
||||
IF NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM sys.indexes
|
||||
WHERE name = 'IX_PickingStates_Item_Warehouse_Dims'
|
||||
AND object_id = OBJECT_ID('dbo.PickingStates')
|
||||
)
|
||||
BEGIN
|
||||
CREATE NONCLUSTERED INDEX IX_PickingStates_Item_Warehouse_Dims
|
||||
ON dbo.PickingStates (ItemTypeCode, ItemCode, WarehouseCode, ColorCode, ItemDim1Code, ItemDim2Code, ItemDim3Code)
|
||||
INCLUDE (Qty1, CompanyCode, OfficeCode, StoreTypeCode, StoreCode);
|
||||
END;
|
||||
GO
|
||||
|
||||
/* ReserveStates */
|
||||
IF NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM sys.indexes
|
||||
WHERE name = 'IX_ReserveStates_Item_Warehouse_Dims'
|
||||
AND object_id = OBJECT_ID('dbo.ReserveStates')
|
||||
)
|
||||
BEGIN
|
||||
CREATE NONCLUSTERED INDEX IX_ReserveStates_Item_Warehouse_Dims
|
||||
ON dbo.ReserveStates (ItemTypeCode, ItemCode, WarehouseCode, ColorCode, ItemDim1Code, ItemDim2Code, ItemDim3Code)
|
||||
INCLUDE (Qty1, CompanyCode, OfficeCode, StoreTypeCode, StoreCode);
|
||||
END;
|
||||
GO
|
||||
|
||||
/* DispOrderStates */
|
||||
IF NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM sys.indexes
|
||||
WHERE name = 'IX_DispOrderStates_Item_Warehouse_Dims'
|
||||
AND object_id = OBJECT_ID('dbo.DispOrderStates')
|
||||
)
|
||||
BEGIN
|
||||
CREATE NONCLUSTERED INDEX IX_DispOrderStates_Item_Warehouse_Dims
|
||||
ON dbo.DispOrderStates (ItemTypeCode, ItemCode, WarehouseCode, ColorCode, ItemDim1Code, ItemDim2Code, ItemDim3Code)
|
||||
INCLUDE (Qty1, CompanyCode, OfficeCode, StoreTypeCode, StoreCode);
|
||||
END;
|
||||
GO
|
||||
|
||||
/* Latest price lookup */
|
||||
IF NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM sys.indexes
|
||||
WHERE name = 'IX_prItemBasePrice_ItemType_ItemCode_PriceDate'
|
||||
AND object_id = OBJECT_ID('dbo.prItemBasePrice')
|
||||
)
|
||||
BEGIN
|
||||
CREATE NONCLUSTERED INDEX IX_prItemBasePrice_ItemType_ItemCode_PriceDate
|
||||
ON dbo.prItemBasePrice (ItemTypeCode, ItemCode, PriceDate DESC)
|
||||
INCLUDE (Price);
|
||||
END;
|
||||
GO
|
||||
@@ -132,6 +132,7 @@ SELECT
|
||||
P.ProductAtt38Desc AS BIRINCI_PARCA_FIT,
|
||||
P.ProductAtt39Desc AS IKINCI_PARCA_FIT,
|
||||
P.ProductAtt40Desc AS BOS2,
|
||||
P.ProductAtt41Desc AS URUN_ICERIGI,
|
||||
P.ProductAtt41Desc AS KISA_KAR,
|
||||
P.ProductAtt42Desc AS SERI_FASON,
|
||||
P.ProductAtt43Desc AS STOK_GIRIS_YONTEMI,
|
||||
|
||||
@@ -13,7 +13,7 @@ DECLARE @Fit NVARCHAR(100) = NULLIF(LTRIM(RTRIM(@p7)), '');
|
||||
DECLARE @Drop NVARCHAR(100) = NULLIF(LTRIM(RTRIM(@p8)), '');
|
||||
DECLARE @Beden NVARCHAR(100) = NULLIF(LTRIM(RTRIM(@p9)), '');
|
||||
|
||||
DECLARE @AttrBase TABLE
|
||||
CREATE TABLE #AttrBase
|
||||
(
|
||||
ProductCode NVARCHAR(50) NOT NULL,
|
||||
Kategori NVARCHAR(100) NOT NULL,
|
||||
@@ -24,7 +24,29 @@ DECLARE @AttrBase TABLE
|
||||
DropVal NVARCHAR(100) NOT NULL
|
||||
);
|
||||
|
||||
INSERT INTO @AttrBase (ProductCode, Kategori, UrunAnaGrubu, UrunAltGrubu, UrunIcerigi, Fit, DropVal)
|
||||
IF OBJECT_ID('dbo.ProductFilterTRCache','U') IS NOT NULL
|
||||
BEGIN
|
||||
INSERT INTO #AttrBase (ProductCode, Kategori, UrunAnaGrubu, UrunAltGrubu, UrunIcerigi, Fit, DropVal)
|
||||
SELECT
|
||||
ProductCode,
|
||||
Kategori = LTRIM(RTRIM(ProductAtt44Desc)),
|
||||
UrunAnaGrubu = LTRIM(RTRIM(ProductAtt01Desc)),
|
||||
UrunAltGrubu = LTRIM(RTRIM(ProductAtt02Desc)),
|
||||
UrunIcerigi = LTRIM(RTRIM(ProductAtt41Desc)),
|
||||
Fit = LTRIM(RTRIM(ProductAtt38Desc)),
|
||||
DropVal = LTRIM(RTRIM(ProductAtt11Desc))
|
||||
FROM dbo.ProductFilterTRCache
|
||||
WHERE LEN(ProductCode) = 13
|
||||
AND (@Kategori IS NULL OR ProductAtt44Desc = @Kategori)
|
||||
AND (@UrunAnaGrubu IS NULL OR ProductAtt01Desc = @UrunAnaGrubu)
|
||||
AND (@UrunAltGrubu IS NULL OR ProductAtt02Desc = @UrunAltGrubu)
|
||||
AND (@UrunIcerigi IS NULL OR ProductAtt41Desc = @UrunIcerigi)
|
||||
AND (@Fit IS NULL OR ProductAtt38Desc = @Fit)
|
||||
AND (@Drop IS NULL OR ProductAtt11Desc = @Drop);
|
||||
END
|
||||
ELSE
|
||||
BEGIN
|
||||
INSERT INTO #AttrBase (ProductCode, Kategori, UrunAnaGrubu, UrunAltGrubu, UrunIcerigi, Fit, DropVal)
|
||||
SELECT
|
||||
ProductCode,
|
||||
Kategori = LTRIM(RTRIM(ProductAtt44Desc)),
|
||||
@@ -41,25 +63,30 @@ WHERE LEN(ProductCode) = 13
|
||||
AND (@UrunIcerigi IS NULL OR ProductAtt41Desc = @UrunIcerigi)
|
||||
AND (@Fit IS NULL OR ProductAtt38Desc = @Fit)
|
||||
AND (@Drop IS NULL OR ProductAtt11Desc = @Drop);
|
||||
END;
|
||||
|
||||
IF @Kategori IS NULL OR @UrunAnaGrubu IS NULL
|
||||
BEGIN
|
||||
CREATE CLUSTERED INDEX IX_AttrBase_ProductCode ON #AttrBase(ProductCode);
|
||||
SELECT 'kategori' AS FieldName, X.FieldValue
|
||||
FROM (
|
||||
SELECT DISTINCT FieldValue = AB.Kategori
|
||||
FROM @AttrBase AB
|
||||
FROM #AttrBase AB
|
||||
WHERE AB.Kategori <> ''
|
||||
) X
|
||||
UNION ALL
|
||||
SELECT 'urun_ana_grubu', X.FieldValue
|
||||
FROM (
|
||||
SELECT DISTINCT FieldValue = AB.UrunAnaGrubu
|
||||
FROM @AttrBase AB
|
||||
FROM #AttrBase AB
|
||||
WHERE AB.UrunAnaGrubu <> ''
|
||||
) X;
|
||||
RETURN;
|
||||
END;
|
||||
|
||||
CREATE CLUSTERED INDEX IX_AttrBase_ProductCode ON #AttrBase(ProductCode);
|
||||
CREATE NONCLUSTERED INDEX IX_AttrBase_Filter ON #AttrBase(Kategori, UrunAnaGrubu, UrunAltGrubu, UrunIcerigi, Fit, DropVal);
|
||||
|
||||
;WITH INV AS
|
||||
(
|
||||
SELECT
|
||||
@@ -77,7 +104,7 @@ END;
|
||||
P.ItemCode, P.ColorCode, P.ItemDim1Code, P.ItemDim2Code,
|
||||
P.Qty1 AS PickingQty1, 0 AS ReserveQty1, 0 AS DispOrderQty1, 0 AS InventoryQty1
|
||||
FROM PickingStates P
|
||||
INNER JOIN @AttrBase AB ON AB.ProductCode = P.ItemCode
|
||||
INNER JOIN #AttrBase AB ON AB.ProductCode = P.ItemCode
|
||||
WHERE P.ItemTypeCode = 1
|
||||
AND LEN(P.ItemCode) = 13
|
||||
|
||||
@@ -86,7 +113,7 @@ END;
|
||||
R.ItemCode, R.ColorCode, R.ItemDim1Code, R.ItemDim2Code,
|
||||
0, R.Qty1, 0, 0
|
||||
FROM ReserveStates R
|
||||
INNER JOIN @AttrBase AB ON AB.ProductCode = R.ItemCode
|
||||
INNER JOIN #AttrBase AB ON AB.ProductCode = R.ItemCode
|
||||
WHERE R.ItemTypeCode = 1
|
||||
AND LEN(R.ItemCode) = 13
|
||||
|
||||
@@ -95,7 +122,7 @@ END;
|
||||
D.ItemCode, D.ColorCode, D.ItemDim1Code, D.ItemDim2Code,
|
||||
0, 0, D.Qty1, 0
|
||||
FROM DispOrderStates D
|
||||
INNER JOIN @AttrBase AB ON AB.ProductCode = D.ItemCode
|
||||
INNER JOIN #AttrBase AB ON AB.ProductCode = D.ItemCode
|
||||
WHERE D.ItemTypeCode = 1
|
||||
AND LEN(D.ItemCode) = 13
|
||||
|
||||
@@ -104,7 +131,7 @@ END;
|
||||
T.ItemCode, T.ColorCode, T.ItemDim1Code, T.ItemDim2Code,
|
||||
0, 0, 0, SUM(T.In_Qty1 - T.Out_Qty1)
|
||||
FROM trStock T WITH (NOLOCK)
|
||||
INNER JOIN @AttrBase AB ON AB.ProductCode = T.ItemCode
|
||||
INNER JOIN #AttrBase AB ON AB.ProductCode = T.ItemCode
|
||||
WHERE T.ItemTypeCode = 1
|
||||
AND LEN(T.ItemCode) = 13
|
||||
GROUP BY T.ItemCode, T.ColorCode, T.ItemDim1Code, T.ItemDim2Code
|
||||
@@ -129,21 +156,21 @@ Avail AS
|
||||
SELECT 'kategori' AS FieldName, X.FieldValue
|
||||
FROM (
|
||||
SELECT DISTINCT FieldValue = AB.Kategori
|
||||
FROM @AttrBase AB
|
||||
FROM #AttrBase AB
|
||||
WHERE AB.Kategori <> ''
|
||||
) X
|
||||
UNION ALL
|
||||
SELECT 'urun_ana_grubu', X.FieldValue
|
||||
FROM (
|
||||
SELECT DISTINCT FieldValue = AB.UrunAnaGrubu
|
||||
FROM @AttrBase AB
|
||||
FROM #AttrBase AB
|
||||
WHERE AB.UrunAnaGrubu <> ''
|
||||
) X
|
||||
UNION ALL
|
||||
SELECT 'urun_alt_grubu', X.FieldValue
|
||||
FROM (
|
||||
SELECT DISTINCT FieldValue = AB.UrunAltGrubu
|
||||
FROM @AttrBase AB
|
||||
FROM #AttrBase AB
|
||||
WHERE AB.UrunAltGrubu <> ''
|
||||
AND (@Kategori IS NULL OR AB.Kategori = @Kategori)
|
||||
AND (@UrunAnaGrubu IS NULL OR AB.UrunAnaGrubu = @UrunAnaGrubu)
|
||||
@@ -153,7 +180,7 @@ SELECT 'renk', X.FieldValue
|
||||
FROM (
|
||||
SELECT DISTINCT FieldValue = CASE WHEN A.RenkAciklama <> '' THEN A.RenkAciklama ELSE A.Renk END
|
||||
FROM Avail A
|
||||
INNER JOIN @AttrBase AB ON AB.ProductCode = A.ItemCode
|
||||
INNER JOIN #AttrBase AB ON AB.ProductCode = A.ItemCode
|
||||
WHERE (CASE WHEN A.RenkAciklama <> '' THEN A.RenkAciklama ELSE A.Renk END) <> ''
|
||||
AND (@Kategori IS NULL OR AB.Kategori = @Kategori)
|
||||
AND (@UrunAnaGrubu IS NULL OR AB.UrunAnaGrubu = @UrunAnaGrubu)
|
||||
@@ -169,7 +196,7 @@ SELECT 'renk2', X.FieldValue
|
||||
FROM (
|
||||
SELECT DISTINCT FieldValue = A.Renk2
|
||||
FROM Avail A
|
||||
INNER JOIN @AttrBase AB ON AB.ProductCode = A.ItemCode
|
||||
INNER JOIN #AttrBase AB ON AB.ProductCode = A.ItemCode
|
||||
WHERE A.Renk2 <> ''
|
||||
AND (@Kategori IS NULL OR AB.Kategori = @Kategori)
|
||||
AND (@UrunAnaGrubu IS NULL OR AB.UrunAnaGrubu = @UrunAnaGrubu)
|
||||
@@ -184,7 +211,7 @@ UNION ALL
|
||||
SELECT 'urun_icerigi', X.FieldValue
|
||||
FROM (
|
||||
SELECT DISTINCT FieldValue = AB.UrunIcerigi
|
||||
FROM @AttrBase AB
|
||||
FROM #AttrBase AB
|
||||
WHERE AB.UrunIcerigi <> ''
|
||||
AND (@Kategori IS NULL OR AB.Kategori = @Kategori)
|
||||
AND (@UrunAnaGrubu IS NULL OR AB.UrunAnaGrubu = @UrunAnaGrubu)
|
||||
@@ -193,7 +220,7 @@ UNION ALL
|
||||
SELECT 'fit', X.FieldValue
|
||||
FROM (
|
||||
SELECT DISTINCT FieldValue = AB.Fit
|
||||
FROM @AttrBase AB
|
||||
FROM #AttrBase AB
|
||||
WHERE AB.Fit <> ''
|
||||
AND (@Kategori IS NULL OR AB.Kategori = @Kategori)
|
||||
AND (@UrunAnaGrubu IS NULL OR AB.UrunAnaGrubu = @UrunAnaGrubu)
|
||||
@@ -202,7 +229,7 @@ UNION ALL
|
||||
SELECT 'drop', X.FieldValue
|
||||
FROM (
|
||||
SELECT DISTINCT FieldValue = AB.DropVal
|
||||
FROM @AttrBase AB
|
||||
FROM #AttrBase AB
|
||||
WHERE AB.DropVal <> ''
|
||||
AND (@Kategori IS NULL OR AB.Kategori = @Kategori)
|
||||
AND (@UrunAnaGrubu IS NULL OR AB.UrunAnaGrubu = @UrunAnaGrubu)
|
||||
@@ -212,7 +239,7 @@ SELECT 'beden', X.FieldValue
|
||||
FROM (
|
||||
SELECT DISTINCT FieldValue = A.Beden
|
||||
FROM Avail A
|
||||
INNER JOIN @AttrBase AB ON AB.ProductCode = A.ItemCode
|
||||
INNER JOIN #AttrBase AB ON AB.ProductCode = A.ItemCode
|
||||
WHERE A.Beden <> ''
|
||||
AND (@Kategori IS NULL OR AB.Kategori = @Kategori)
|
||||
AND (@UrunAnaGrubu IS NULL OR AB.UrunAnaGrubu = @UrunAnaGrubu)
|
||||
@@ -222,7 +249,8 @@ FROM (
|
||||
AND (@Drop IS NULL OR AB.DropVal = @Drop)
|
||||
AND (@Renk IS NULL OR (CASE WHEN A.RenkAciklama <> '' THEN A.RenkAciklama ELSE A.Renk END) = @Renk)
|
||||
AND (@Renk2 IS NULL OR A.Renk2 = @Renk2)
|
||||
) X;
|
||||
) X
|
||||
OPTION (RECOMPILE);
|
||||
`
|
||||
|
||||
// GetProductStockQueryByAttributes:
|
||||
@@ -238,8 +266,158 @@ DECLARE @Fit NVARCHAR(100) = NULLIF(LTRIM(RTRIM(@p7)), '');
|
||||
DECLARE @Drop NVARCHAR(100) = NULLIF(LTRIM(RTRIM(@p8)), '');
|
||||
DECLARE @Beden NVARCHAR(100) = NULLIF(LTRIM(RTRIM(@p9)), '');
|
||||
|
||||
;WITH AttrFiltered AS
|
||||
CREATE TABLE #AttrFiltered
|
||||
(
|
||||
ProductCode NVARCHAR(50) NOT NULL,
|
||||
ProductDescription NVARCHAR(255) NULL,
|
||||
ProductAtt01Desc NVARCHAR(255) NULL,
|
||||
ProductAtt02Desc NVARCHAR(255) NULL,
|
||||
ProductAtt10Desc NVARCHAR(255) NULL,
|
||||
ProductAtt11Desc NVARCHAR(255) NULL,
|
||||
ProductAtt21Desc NVARCHAR(255) NULL,
|
||||
ProductAtt22Desc NVARCHAR(255) NULL,
|
||||
ProductAtt23Desc NVARCHAR(255) NULL,
|
||||
ProductAtt24Desc NVARCHAR(255) NULL,
|
||||
ProductAtt25Desc NVARCHAR(255) NULL,
|
||||
ProductAtt26Desc NVARCHAR(255) NULL,
|
||||
ProductAtt27Desc NVARCHAR(255) NULL,
|
||||
ProductAtt28Desc NVARCHAR(255) NULL,
|
||||
ProductAtt29Desc NVARCHAR(255) NULL,
|
||||
ProductAtt30Desc NVARCHAR(255) NULL,
|
||||
ProductAtt31Desc NVARCHAR(255) NULL,
|
||||
ProductAtt32Desc NVARCHAR(255) NULL,
|
||||
ProductAtt33Desc NVARCHAR(255) NULL,
|
||||
ProductAtt34Desc NVARCHAR(255) NULL,
|
||||
ProductAtt35Desc NVARCHAR(255) NULL,
|
||||
ProductAtt36Desc NVARCHAR(255) NULL,
|
||||
ProductAtt37Desc NVARCHAR(255) NULL,
|
||||
ProductAtt38Desc NVARCHAR(255) NULL,
|
||||
ProductAtt39Desc NVARCHAR(255) NULL,
|
||||
ProductAtt40Desc NVARCHAR(255) NULL,
|
||||
ProductAtt41Desc NVARCHAR(255) NULL,
|
||||
ProductAtt42Desc NVARCHAR(255) NULL,
|
||||
ProductAtt43Desc NVARCHAR(255) NULL,
|
||||
ProductAtt44Desc NVARCHAR(255) NULL,
|
||||
ProductAtt45Desc NVARCHAR(255) NULL,
|
||||
ProductAtt46Desc NVARCHAR(255) NULL
|
||||
);
|
||||
|
||||
IF OBJECT_ID('dbo.ProductFilterTRCache','U') IS NOT NULL
|
||||
BEGIN
|
||||
INSERT INTO #AttrFiltered
|
||||
(
|
||||
ProductCode,
|
||||
ProductDescription,
|
||||
ProductAtt01Desc,
|
||||
ProductAtt02Desc,
|
||||
ProductAtt10Desc,
|
||||
ProductAtt11Desc,
|
||||
ProductAtt21Desc,
|
||||
ProductAtt22Desc,
|
||||
ProductAtt23Desc,
|
||||
ProductAtt24Desc,
|
||||
ProductAtt25Desc,
|
||||
ProductAtt26Desc,
|
||||
ProductAtt27Desc,
|
||||
ProductAtt28Desc,
|
||||
ProductAtt29Desc,
|
||||
ProductAtt30Desc,
|
||||
ProductAtt31Desc,
|
||||
ProductAtt32Desc,
|
||||
ProductAtt33Desc,
|
||||
ProductAtt34Desc,
|
||||
ProductAtt35Desc,
|
||||
ProductAtt36Desc,
|
||||
ProductAtt37Desc,
|
||||
ProductAtt38Desc,
|
||||
ProductAtt39Desc,
|
||||
ProductAtt40Desc,
|
||||
ProductAtt41Desc,
|
||||
ProductAtt42Desc,
|
||||
ProductAtt43Desc,
|
||||
ProductAtt44Desc,
|
||||
ProductAtt45Desc,
|
||||
ProductAtt46Desc
|
||||
)
|
||||
SELECT
|
||||
C.ProductCode,
|
||||
C.ProductDescription,
|
||||
C.ProductAtt01Desc,
|
||||
C.ProductAtt02Desc,
|
||||
'' AS ProductAtt10Desc,
|
||||
C.ProductAtt11Desc,
|
||||
'' AS ProductAtt21Desc,
|
||||
'' AS ProductAtt22Desc,
|
||||
'' AS ProductAtt23Desc,
|
||||
'' AS ProductAtt24Desc,
|
||||
'' AS ProductAtt25Desc,
|
||||
'' AS ProductAtt26Desc,
|
||||
'' AS ProductAtt27Desc,
|
||||
'' AS ProductAtt28Desc,
|
||||
'' AS ProductAtt29Desc,
|
||||
'' AS ProductAtt30Desc,
|
||||
'' AS ProductAtt31Desc,
|
||||
'' AS ProductAtt32Desc,
|
||||
'' AS ProductAtt33Desc,
|
||||
'' AS ProductAtt34Desc,
|
||||
'' AS ProductAtt35Desc,
|
||||
'' AS ProductAtt36Desc,
|
||||
'' AS ProductAtt37Desc,
|
||||
C.ProductAtt38Desc,
|
||||
'' AS ProductAtt39Desc,
|
||||
'' AS ProductAtt40Desc,
|
||||
C.ProductAtt41Desc,
|
||||
'' AS ProductAtt42Desc,
|
||||
'' AS ProductAtt43Desc,
|
||||
C.ProductAtt44Desc,
|
||||
'' AS ProductAtt45Desc,
|
||||
'' AS ProductAtt46Desc
|
||||
FROM dbo.ProductFilterTRCache C
|
||||
WHERE LEN(C.ProductCode) = 13
|
||||
AND (@Kategori IS NULL OR C.ProductAtt44Desc = @Kategori)
|
||||
AND (@UrunAnaGrubu IS NULL OR C.ProductAtt01Desc = @UrunAnaGrubu)
|
||||
AND (@UrunAltGrubu IS NULL OR C.ProductAtt02Desc = @UrunAltGrubu)
|
||||
AND (@UrunIcerigi IS NULL OR C.ProductAtt41Desc = @UrunIcerigi)
|
||||
AND (@Fit IS NULL OR C.ProductAtt38Desc = @Fit)
|
||||
AND (@Drop IS NULL OR C.ProductAtt11Desc = @Drop);
|
||||
END
|
||||
ELSE
|
||||
BEGIN
|
||||
INSERT INTO #AttrFiltered
|
||||
(
|
||||
ProductCode,
|
||||
ProductDescription,
|
||||
ProductAtt01Desc,
|
||||
ProductAtt02Desc,
|
||||
ProductAtt10Desc,
|
||||
ProductAtt11Desc,
|
||||
ProductAtt21Desc,
|
||||
ProductAtt22Desc,
|
||||
ProductAtt23Desc,
|
||||
ProductAtt24Desc,
|
||||
ProductAtt25Desc,
|
||||
ProductAtt26Desc,
|
||||
ProductAtt27Desc,
|
||||
ProductAtt28Desc,
|
||||
ProductAtt29Desc,
|
||||
ProductAtt30Desc,
|
||||
ProductAtt31Desc,
|
||||
ProductAtt32Desc,
|
||||
ProductAtt33Desc,
|
||||
ProductAtt34Desc,
|
||||
ProductAtt35Desc,
|
||||
ProductAtt36Desc,
|
||||
ProductAtt37Desc,
|
||||
ProductAtt38Desc,
|
||||
ProductAtt39Desc,
|
||||
ProductAtt40Desc,
|
||||
ProductAtt41Desc,
|
||||
ProductAtt42Desc,
|
||||
ProductAtt43Desc,
|
||||
ProductAtt44Desc,
|
||||
ProductAtt45Desc,
|
||||
ProductAtt46Desc
|
||||
)
|
||||
SELECT
|
||||
ProductCode,
|
||||
ProductDescription,
|
||||
@@ -280,8 +458,12 @@ DECLARE @Beden NVARCHAR(100) = NULLIF(LTRIM(RTRIM(@p9)), '');
|
||||
AND (@UrunAltGrubu IS NULL OR ProductAtt02Desc = @UrunAltGrubu)
|
||||
AND (@UrunIcerigi IS NULL OR ProductAtt41Desc = @UrunIcerigi)
|
||||
AND (@Fit IS NULL OR ProductAtt38Desc = @Fit)
|
||||
AND (@Drop IS NULL OR ProductAtt11Desc = @Drop)
|
||||
),
|
||||
AND (@Drop IS NULL OR ProductAtt11Desc = @Drop);
|
||||
END;
|
||||
|
||||
CREATE CLUSTERED INDEX IX_AttrFiltered_ProductCode ON #AttrFiltered(ProductCode);
|
||||
|
||||
;WITH
|
||||
INV AS
|
||||
(
|
||||
SELECT
|
||||
@@ -307,9 +489,15 @@ INV AS
|
||||
P.ItemTypeCode, P.ItemCode, P.ColorCode, P.ItemDim1Code, P.ItemDim2Code, P.ItemDim3Code,
|
||||
P.Qty1 AS PickingQty1, 0 AS ReserveQty1, 0 AS DispOrderQty1, 0 AS InventoryQty1
|
||||
FROM PickingStates P
|
||||
INNER JOIN AttrFiltered AF ON AF.ProductCode = P.ItemCode
|
||||
INNER JOIN #AttrFiltered AF ON AF.ProductCode = P.ItemCode
|
||||
WHERE P.ItemTypeCode = 1
|
||||
AND LEN(P.ItemCode) = 13
|
||||
AND P.WarehouseCode IN
|
||||
(
|
||||
'1-0-14','1-0-10','1-0-8','1-2-5','1-2-4','1-0-12','100','1-0-28',
|
||||
'1-0-24','1-2-6','1-1-14','1-0-2','1-0-52','1-1-2','1-0-21','1-1-3',
|
||||
'1-0-33','101','1-014','1-0-49','1-0-36'
|
||||
)
|
||||
|
||||
UNION ALL
|
||||
SELECT
|
||||
@@ -317,9 +505,15 @@ INV AS
|
||||
R.ItemTypeCode, R.ItemCode, R.ColorCode, R.ItemDim1Code, R.ItemDim2Code, R.ItemDim3Code,
|
||||
0, R.Qty1, 0, 0
|
||||
FROM ReserveStates R
|
||||
INNER JOIN AttrFiltered AF ON AF.ProductCode = R.ItemCode
|
||||
INNER JOIN #AttrFiltered AF ON AF.ProductCode = R.ItemCode
|
||||
WHERE R.ItemTypeCode = 1
|
||||
AND LEN(R.ItemCode) = 13
|
||||
AND R.WarehouseCode IN
|
||||
(
|
||||
'1-0-14','1-0-10','1-0-8','1-2-5','1-2-4','1-0-12','100','1-0-28',
|
||||
'1-0-24','1-2-6','1-1-14','1-0-2','1-0-52','1-1-2','1-0-21','1-1-3',
|
||||
'1-0-33','101','1-014','1-0-49','1-0-36'
|
||||
)
|
||||
|
||||
UNION ALL
|
||||
SELECT
|
||||
@@ -327,9 +521,15 @@ INV AS
|
||||
D.ItemTypeCode, D.ItemCode, D.ColorCode, D.ItemDim1Code, D.ItemDim2Code, D.ItemDim3Code,
|
||||
0, 0, D.Qty1, 0
|
||||
FROM DispOrderStates D
|
||||
INNER JOIN AttrFiltered AF ON AF.ProductCode = D.ItemCode
|
||||
INNER JOIN #AttrFiltered AF ON AF.ProductCode = D.ItemCode
|
||||
WHERE D.ItemTypeCode = 1
|
||||
AND LEN(D.ItemCode) = 13
|
||||
AND D.WarehouseCode IN
|
||||
(
|
||||
'1-0-14','1-0-10','1-0-8','1-2-5','1-2-4','1-0-12','100','1-0-28',
|
||||
'1-0-24','1-2-6','1-1-14','1-0-2','1-0-52','1-1-2','1-0-21','1-1-3',
|
||||
'1-0-33','101','1-014','1-0-49','1-0-36'
|
||||
)
|
||||
|
||||
UNION ALL
|
||||
SELECT
|
||||
@@ -337,9 +537,15 @@ INV AS
|
||||
T.ItemTypeCode, T.ItemCode, T.ColorCode, T.ItemDim1Code, T.ItemDim2Code, T.ItemDim3Code,
|
||||
0, 0, 0, SUM(T.In_Qty1 - T.Out_Qty1)
|
||||
FROM trStock T WITH (NOLOCK)
|
||||
INNER JOIN AttrFiltered AF ON AF.ProductCode = T.ItemCode
|
||||
INNER JOIN #AttrFiltered AF ON AF.ProductCode = T.ItemCode
|
||||
WHERE T.ItemTypeCode = 1
|
||||
AND LEN(T.ItemCode) = 13
|
||||
AND T.WarehouseCode IN
|
||||
(
|
||||
'1-0-14','1-0-10','1-0-8','1-2-5','1-2-4','1-0-12','100','1-0-28',
|
||||
'1-0-24','1-2-6','1-1-14','1-0-2','1-0-52','1-1-2','1-0-21','1-1-3',
|
||||
'1-0-33','101','1-014','1-0-49','1-0-36'
|
||||
)
|
||||
GROUP BY
|
||||
T.CompanyCode, T.OfficeCode, T.StoreTypeCode, T.StoreCode, T.WarehouseCode,
|
||||
T.ItemTypeCode, T.ItemCode, T.ColorCode, T.ItemDim1Code, T.ItemDim2Code, T.ItemDim3Code
|
||||
@@ -391,7 +597,7 @@ Grouped AS
|
||||
A.ColorCode,
|
||||
A.ItemDim2Code
|
||||
FROM Avail A
|
||||
INNER JOIN AttrFiltered AF ON AF.ProductCode = A.ItemCode
|
||||
INNER JOIN #AttrFiltered AF ON AF.ProductCode = A.ItemCode
|
||||
WHERE (@Renk IS NULL OR (CASE WHEN A.RenkAciklama <> '' THEN A.RenkAciklama ELSE A.ColorCode END) = @Renk)
|
||||
AND (@Renk2 IS NULL OR A.ItemDim2Code = @Renk2)
|
||||
AND (
|
||||
@@ -454,7 +660,7 @@ INNER JOIN Grouped G
|
||||
ON G.ItemCode = A.ItemCode
|
||||
AND G.ColorCode = A.ColorCode
|
||||
AND ISNULL(G.ItemDim2Code, '') = ISNULL(A.ItemDim2Code, '')
|
||||
INNER JOIN AttrFiltered AF
|
||||
INNER JOIN #AttrFiltered AF
|
||||
ON AF.ProductCode = A.ItemCode
|
||||
LEFT JOIN cdWarehouseDesc W WITH (NOLOCK)
|
||||
ON W.WarehouseCode = A.WarehouseCode
|
||||
@@ -469,5 +675,6 @@ OUTER APPLY (
|
||||
AND PB.ItemCode = A.ItemCode
|
||||
AND LEN(PB.ItemCode) = 13
|
||||
ORDER BY PB.PriceDate DESC
|
||||
) P;
|
||||
) P
|
||||
OPTION (RECOMPILE);
|
||||
`
|
||||
|
||||
@@ -23,11 +23,53 @@ type ProductImageItem struct {
|
||||
ContentURL string `json:"content_url"`
|
||||
}
|
||||
|
||||
func tokenizeImageFileName(fileName string) []string {
|
||||
up := strings.ToUpper(strings.TrimSpace(fileName))
|
||||
if up == "" {
|
||||
return nil
|
||||
}
|
||||
return strings.FieldsFunc(up, func(r rune) bool {
|
||||
isUpper := r >= 'A' && r <= 'Z'
|
||||
isDigit := r >= '0' && r <= '9'
|
||||
if isUpper || isDigit || r == '_' {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func imageFileMatches(fileName, color, secondColor string) bool {
|
||||
color = strings.ToUpper(strings.TrimSpace(color))
|
||||
secondColor = strings.ToUpper(strings.TrimSpace(secondColor))
|
||||
if color == "" && secondColor == "" {
|
||||
return true
|
||||
}
|
||||
|
||||
tokens := tokenizeImageFileName(fileName)
|
||||
if len(tokens) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
hasToken := func(target string) bool {
|
||||
if target == "" {
|
||||
return true
|
||||
}
|
||||
for _, t := range tokens {
|
||||
if t == target {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
return hasToken(color) && hasToken(secondColor)
|
||||
}
|
||||
|
||||
//
|
||||
// LIST PRODUCT IMAGES
|
||||
//
|
||||
|
||||
// GET /api/product-images?code=...&color=...
|
||||
// GET /api/product-images?code=...&color=...&yaka=...
|
||||
func GetProductImagesHandler(pg *sql.DB) http.HandlerFunc {
|
||||
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -41,6 +83,10 @@ func GetProductImagesHandler(pg *sql.DB) http.HandlerFunc {
|
||||
|
||||
code := strings.TrimSpace(r.URL.Query().Get("code"))
|
||||
color := strings.TrimSpace(r.URL.Query().Get("color"))
|
||||
secondColor := strings.TrimSpace(r.URL.Query().Get("yaka"))
|
||||
if secondColor == "" {
|
||||
secondColor = strings.TrimSpace(r.URL.Query().Get("renk2"))
|
||||
}
|
||||
|
||||
if code == "" {
|
||||
|
||||
@@ -67,18 +113,13 @@ JOIN mmitem i
|
||||
WHERE b.typ = 'img'
|
||||
AND b.src_table = 'mmitem'
|
||||
AND UPPER(i.code) = UPPER($1)
|
||||
AND (
|
||||
$2 = ''
|
||||
OR b.file_name ILIKE '%' || '-' || $2 || '-%'
|
||||
OR b.file_name ILIKE '%' || '-' || $2 || '_%'
|
||||
)
|
||||
ORDER BY
|
||||
COALESCE(b.sort_order,999999),
|
||||
b.zlins_dttm DESC,
|
||||
b.id DESC
|
||||
`
|
||||
|
||||
rows, err := pg.Query(query, code, color)
|
||||
rows, err := pg.Query(query, code)
|
||||
|
||||
if err != nil {
|
||||
|
||||
@@ -86,6 +127,7 @@ ORDER BY
|
||||
"req_id", reqID,
|
||||
"code", code,
|
||||
"color", color,
|
||||
"second_color", secondColor,
|
||||
"err", err.Error(),
|
||||
)
|
||||
|
||||
@@ -109,6 +151,9 @@ ORDER BY
|
||||
); err != nil {
|
||||
continue
|
||||
}
|
||||
if !imageFileMatches(it.FileName, color, secondColor) {
|
||||
continue
|
||||
}
|
||||
|
||||
it.ContentURL = fmt.Sprintf("/api/product-images/%d/content", it.ID)
|
||||
|
||||
@@ -119,6 +164,7 @@ ORDER BY
|
||||
"req_id", reqID,
|
||||
"code", code,
|
||||
"color", color,
|
||||
"second_color", secondColor,
|
||||
"count", len(items),
|
||||
)
|
||||
|
||||
|
||||
@@ -181,21 +181,29 @@
|
||||
</div>
|
||||
|
||||
<div class="sub-image level2-image">
|
||||
<q-card flat bordered class="product-image-card">
|
||||
<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)"
|
||||
:src="getProductImageUrl(grp1.productCode, grp2.colorCode)"
|
||||
fit="cover"
|
||||
v-if="getProductImageUrl(grp1.productCode, grp2.colorCode, grp2.secondColor)"
|
||||
:src="getProductImageUrl(grp1.productCode, grp2.colorCode, grp2.secondColor)"
|
||||
fit="contain"
|
||||
class="product-image"
|
||||
loading="lazy"
|
||||
@error="onProductImageError(grp1.productCode, grp2.colorCode)"
|
||||
@error="onProductImageError(grp1.productCode, grp2.colorCode, grp2.secondColor)"
|
||||
/>
|
||||
<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>
|
||||
|
||||
@@ -231,6 +239,99 @@
|
||||
</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">
|
||||
@@ -284,6 +385,7 @@ const filters = ref({
|
||||
})
|
||||
const optionLists = ref({})
|
||||
const filteredOptionLists = ref({})
|
||||
const filterOptionsCache = ref({})
|
||||
const rawRows = ref([])
|
||||
const productImageCache = ref({})
|
||||
const productImageLoading = ref({})
|
||||
@@ -293,8 +395,19 @@ 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
|
||||
const FILTER_OPTIONS_CACHE_TTL_MS = 60 * 1000
|
||||
const FILTER_OPTIONS_DEBOUNCE_MS = 250
|
||||
let imageListActiveRequests = 0
|
||||
let filterOptionsDebounceTimer = null
|
||||
let filterOptionsRequestSeq = 0
|
||||
const imageListWaitQueue = []
|
||||
const activeSchema = ref(storeSchemaByKey.tak)
|
||||
const activeGrpKey = ref('tak')
|
||||
@@ -324,6 +437,11 @@ const allDetailsExpanded = computed(() => {
|
||||
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 = {}
|
||||
@@ -347,8 +465,20 @@ function sortByTotalQtyDesc(a, b) {
|
||||
return String(a?.key || '').localeCompare(String(b?.key || ''), 'tr', { sensitivity: 'base' })
|
||||
}
|
||||
|
||||
function buildImageKey(code, color) {
|
||||
return `${String(code || '').trim().toUpperCase()}::${String(color || '').trim().toUpperCase()}`
|
||||
function buildImageKey(code, color, secondColor = '') {
|
||||
return `${String(code || '').trim().toUpperCase()}::${String(color || '').trim().toUpperCase()}::${String(secondColor || '').trim().toUpperCase()}`
|
||||
}
|
||||
|
||||
function imageNameMatches(fileName, color, secondColor) {
|
||||
const text = String(fileName || '').toUpperCase()
|
||||
if (!text) return false
|
||||
const tokens = text.replace(/[^A-Z0-9_]+/g, ' ').trim().split(/\s+/).filter(Boolean)
|
||||
if (!tokens.length) return false
|
||||
const colorTrim = String(color || '').trim().toUpperCase()
|
||||
const secondTrim = String(secondColor || '').trim().toUpperCase()
|
||||
if (colorTrim && !tokens.includes(colorTrim)) return false
|
||||
if (secondTrim && !tokens.includes(secondTrim)) return false
|
||||
return true
|
||||
}
|
||||
|
||||
function normalizeUploadsPath(storagePath) {
|
||||
@@ -403,17 +533,17 @@ function resolveProductImageUrl(item) {
|
||||
return { contentUrl, publicUrl }
|
||||
}
|
||||
|
||||
function getProductImageUrl(code, color) {
|
||||
const key = buildImageKey(code, color)
|
||||
function getProductImageUrl(code, color, secondColor = '') {
|
||||
const key = buildImageKey(code, color, secondColor)
|
||||
const existing = productImageCache.value[key]
|
||||
if (existing !== undefined) return existing || ''
|
||||
|
||||
void ensureProductImage(code, color)
|
||||
void ensureProductImage(code, color, secondColor)
|
||||
return ''
|
||||
}
|
||||
|
||||
async function onProductImageError(code, color) {
|
||||
const key = buildImageKey(code, color)
|
||||
async function onProductImageError(code, color, secondColor = '') {
|
||||
const key = buildImageKey(code, color, secondColor)
|
||||
const fallback = String(productImageFallbackByKey.value[key] || '')
|
||||
if (fallback && !productImageContentLoading.value[key]) {
|
||||
productImageContentLoading.value[key] = true
|
||||
@@ -438,10 +568,12 @@ async function onProductImageError(code, color) {
|
||||
productImageCache.value[key] = ''
|
||||
}
|
||||
|
||||
async function ensureProductImage(code, color) {
|
||||
const key = buildImageKey(code, color)
|
||||
async function ensureProductImage(code, color, secondColor = '') {
|
||||
const key = buildImageKey(code, color, secondColor)
|
||||
const codeTrim = String(code || '').trim().toUpperCase()
|
||||
const colorTrim = String(color || '').trim().toUpperCase()
|
||||
const secondTrim = String(secondColor || '').trim().toUpperCase()
|
||||
const listKey = buildImageKey(codeTrim, colorTrim, secondTrim)
|
||||
if (!codeTrim) {
|
||||
productImageCache.value[key] = ''
|
||||
return ''
|
||||
@@ -455,44 +587,44 @@ async function ensureProductImage(code, color) {
|
||||
|
||||
productImageLoading.value[key] = true
|
||||
try {
|
||||
if (!productImageListByCode.value[codeTrim]) {
|
||||
if (!productImageListLoading.value[codeTrim]) {
|
||||
productImageListLoading.value[codeTrim] = true
|
||||
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++
|
||||
const res = await api.get('/product-images', { params: { code: codeTrim } })
|
||||
productImageListByCode.value[codeTrim] = Array.isArray(res?.data) ? res.data : []
|
||||
const params = { code: codeTrim, dim1: colorTrim, dim3: secondTrim }
|
||||
const res = await api.get('/product-images', { params })
|
||||
productImageListByCode.value[listKey] = Array.isArray(res?.data) ? res.data : []
|
||||
} catch (err) {
|
||||
productImageListByCode.value[codeTrim] = []
|
||||
productImageListByCode.value[listKey] = []
|
||||
const status = Number(err?.response?.status || 0)
|
||||
if (status >= 500 || status === 403 || status === 0) {
|
||||
// Backend dengesizken istek firtinasini kisaca kes.
|
||||
productImageListBlockedUntil.value = Date.now() + 30 * 1000
|
||||
}
|
||||
console.warn('[ProductStockByAttributes] product image list fetch failed', { code: codeTrim, err })
|
||||
console.warn('[ProductStockByAttributes] 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[codeTrim]
|
||||
delete productImageListLoading.value[listKey]
|
||||
}
|
||||
} else {
|
||||
// Ayni code icin baska bir istek zaten calisiyorsa tamamlanmasini bekle.
|
||||
while (productImageListLoading.value[codeTrim]) {
|
||||
while (productImageListLoading.value[listKey]) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 25))
|
||||
}
|
||||
}
|
||||
}
|
||||
const list = productImageListByCode.value[codeTrim] || []
|
||||
const list = productImageListByCode.value[listKey] || []
|
||||
|
||||
let first = null
|
||||
if (colorTrim) {
|
||||
const needle = `-${colorTrim.toLowerCase()}-`
|
||||
if (colorTrim || secondTrim) {
|
||||
first = list.find((item) =>
|
||||
String(item?.file_name || item?.FileName || '').toLowerCase().includes(needle)
|
||||
imageNameMatches(String(item?.file_name || item?.FileName || ''), colorTrim, secondTrim)
|
||||
) || null
|
||||
}
|
||||
if (!first) first = list[0] || null
|
||||
@@ -625,6 +757,11 @@ const level1Groups = computed(() => {
|
||||
const depoAdi = String(item.Depo_Adi || '').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)
|
||||
@@ -654,6 +791,11 @@ const level1Groups = computed(() => {
|
||||
secondColor,
|
||||
urunAnaGrubu,
|
||||
urunAltGrubu,
|
||||
urunIcerigi,
|
||||
fit,
|
||||
drop,
|
||||
kumas,
|
||||
karisim,
|
||||
aciklama,
|
||||
sizeTotals: emptySizeTotals(),
|
||||
totalQty: 0,
|
||||
@@ -714,6 +856,11 @@ function buildFilterParams() {
|
||||
return out
|
||||
}
|
||||
|
||||
function buildFilterCacheKey(params) {
|
||||
const keys = Object.keys(params || {}).sort()
|
||||
return keys.map((k) => `${k}=${String(params[k] || '').trim()}`).join('&')
|
||||
}
|
||||
|
||||
function isFilterDisabled(key) {
|
||||
if (key === 'kategori') return false
|
||||
if (key === 'urun_ana_grubu') {
|
||||
@@ -747,7 +894,12 @@ function onFilterValueChange(changedKey) {
|
||||
filters.value.beden = ''
|
||||
}
|
||||
|
||||
if (filterOptionsDebounceTimer) {
|
||||
clearTimeout(filterOptionsDebounceTimer)
|
||||
}
|
||||
filterOptionsDebounceTimer = setTimeout(() => {
|
||||
void loadFilterOptions()
|
||||
}, FILTER_OPTIONS_DEBOUNCE_MS)
|
||||
}
|
||||
|
||||
function filterOptions(field, val, update) {
|
||||
@@ -768,12 +920,26 @@ function filterOptions(field, val, update) {
|
||||
})
|
||||
}
|
||||
|
||||
async function loadFilterOptions() {
|
||||
async function loadFilterOptions(force = false) {
|
||||
const params = buildFilterParams()
|
||||
const cacheKey = buildFilterCacheKey(params)
|
||||
const now = Date.now()
|
||||
if (!force) {
|
||||
const cached = filterOptionsCache.value[cacheKey]
|
||||
if (cached && Number(cached.expiresAt || 0) > now && cached.payload) {
|
||||
optionLists.value = cached.payload.optionLists
|
||||
filteredOptionLists.value = cached.payload.filteredOptionLists
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const reqSeq = ++filterOptionsRequestSeq
|
||||
loadingFilterOptions.value = true
|
||||
try {
|
||||
const res = await api.get('/product-stock-attribute-options', {
|
||||
params: buildFilterParams()
|
||||
params
|
||||
})
|
||||
if (reqSeq !== filterOptionsRequestSeq) return
|
||||
const payload = res?.data && typeof res.data === 'object' ? res.data : {}
|
||||
const next = {}
|
||||
const nextFiltered = {}
|
||||
@@ -795,13 +961,23 @@ async function loadFilterOptions() {
|
||||
|
||||
optionLists.value = next
|
||||
filteredOptionLists.value = nextFiltered
|
||||
filterOptionsCache.value[cacheKey] = {
|
||||
expiresAt: now + FILTER_OPTIONS_CACHE_TTL_MS,
|
||||
payload: {
|
||||
optionLists: next,
|
||||
filteredOptionLists: nextFiltered
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
if (reqSeq !== filterOptionsRequestSeq) return
|
||||
errorMessage.value = 'Urun ozellik secenekleri alinamadi.'
|
||||
console.error('loadFilterOptions error:', err)
|
||||
} finally {
|
||||
if (reqSeq === filterOptionsRequestSeq) {
|
||||
loadingFilterOptions.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchStockByAttributes() {
|
||||
if (!canQuery.value) {
|
||||
@@ -871,10 +1047,65 @@ async function fetchStockByAttributes() {
|
||||
function onLevel2Click(productCode, grp2) {
|
||||
toggleOpen(grp2.key)
|
||||
if (isOpen(grp2.key)) {
|
||||
void ensureProductImage(productCode, grp2.colorCode)
|
||||
void ensureProductImage(productCode, grp2.colorCode, grp2.secondColor)
|
||||
}
|
||||
}
|
||||
|
||||
async function openProductCard(grp1, grp2) {
|
||||
const productCode = String(grp1?.productCode || '').trim()
|
||||
const colorCode = String(grp2?.colorCode || '').trim()
|
||||
const secondColor = String(grp2?.secondColor || '').trim()
|
||||
const listKey = buildImageKey(productCode, colorCode, secondColor)
|
||||
|
||||
await ensureProductImage(productCode, colorCode, secondColor)
|
||||
const list = Array.isArray(productImageListByCode.value[listKey]) ? productImageListByCode.value[listKey] : []
|
||||
const images = list
|
||||
.map((item) => {
|
||||
const resolved = resolveProductImageUrl(item)
|
||||
return resolved.contentUrl || resolved.publicUrl || ''
|
||||
})
|
||||
.filter((x) => String(x || '').trim() !== '')
|
||||
|
||||
if (!images.length) {
|
||||
const single = getProductImageUrl(productCode, colorCode, secondColor)
|
||||
if (single) images.push(single)
|
||||
}
|
||||
|
||||
productCardImages.value = images
|
||||
productCardSlide.value = 0
|
||||
productCardData.value = {
|
||||
productCode,
|
||||
colorCode,
|
||||
secondColor,
|
||||
kategori: String(filters.value?.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() {
|
||||
filters.value = {
|
||||
kategori: '',
|
||||
@@ -898,14 +1129,26 @@ function resetForm() {
|
||||
productImageFallbackByKey.value = {}
|
||||
productImageContentLoading.value = {}
|
||||
productImageListBlockedUntil.value = 0
|
||||
void loadFilterOptions()
|
||||
productCardDialog.value = false
|
||||
productCardData.value = {}
|
||||
productCardImages.value = []
|
||||
productCardSlide.value = 0
|
||||
productImageFullscreenDialog.value = false
|
||||
productImageFullscreenSrc.value = ''
|
||||
productImageFullscreenZoom.value = 1
|
||||
filterOptionsCache.value = {}
|
||||
void loadFilterOptions(true)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadFilterOptions()
|
||||
void loadFilterOptions(true)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (filterOptionsDebounceTimer) {
|
||||
clearTimeout(filterOptionsDebounceTimer)
|
||||
filterOptionsDebounceTimer = null
|
||||
}
|
||||
for (const url of productImageBlobUrls.value) {
|
||||
try { URL.revokeObjectURL(url) } catch {}
|
||||
}
|
||||
@@ -920,7 +1163,7 @@ onUnmounted(() => {
|
||||
--grp-title-w: 44px;
|
||||
--psq-header-h: 56px;
|
||||
--psq-col-adet: calc(var(--col-adet) + var(--beden-w));
|
||||
--psq-col-img: 126px;
|
||||
--psq-col-img: 190px;
|
||||
--psq-l1-lift: 42px;
|
||||
}
|
||||
|
||||
@@ -1003,8 +1246,8 @@ onUnmounted(() => {
|
||||
}
|
||||
|
||||
.order-sub-header.level-2 {
|
||||
min-height: 82px !important;
|
||||
height: 82px !important;
|
||||
min-height: 252px !important;
|
||||
height: 252px !important;
|
||||
background: #fff9c4 !important;
|
||||
border-top: 1px solid #d4c79f !important;
|
||||
border-bottom: 1px solid #d4c79f !important;
|
||||
@@ -1250,16 +1493,18 @@ onUnmounted(() => {
|
||||
.order-sub-header.level-2 .sub-image.level2-image {
|
||||
grid-column: 8 / 9;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-left: 1px solid #d4c79f;
|
||||
padding: 0 6px;
|
||||
gap: 8px;
|
||||
padding: 8px 10px;
|
||||
background: #fffef7;
|
||||
}
|
||||
|
||||
.product-image-card {
|
||||
width: 108px;
|
||||
height: 66px;
|
||||
width: 162px;
|
||||
height: 216px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
@@ -1272,6 +1517,7 @@ onUnmounted(() => {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 6px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.product-image-placeholder {
|
||||
@@ -1284,6 +1530,178 @@ onUnmounted(() => {
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.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;
|
||||
|
||||
@@ -179,21 +179,29 @@
|
||||
</div>
|
||||
|
||||
<div class="sub-image level2-image">
|
||||
<q-card flat bordered class="product-image-card">
|
||||
<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)"
|
||||
:src="getProductImageUrl(grp1.productCode, grp2.colorCode)"
|
||||
fit="cover"
|
||||
v-if="getProductImageUrl(grp1.productCode, grp2.colorCode, grp2.secondColor)"
|
||||
:src="getProductImageUrl(grp1.productCode, grp2.colorCode, grp2.secondColor)"
|
||||
fit="contain"
|
||||
class="product-image"
|
||||
loading="lazy"
|
||||
@error="onProductImageError(grp1.productCode, grp2.colorCode)"
|
||||
@error="onProductImageError(grp1.productCode, grp2.colorCode, grp2.secondColor)"
|
||||
/>
|
||||
<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>
|
||||
|
||||
@@ -229,6 +237,99 @@
|
||||
</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">
|
||||
@@ -269,6 +370,13 @@ 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 = []
|
||||
@@ -296,6 +404,11 @@ const allDetailsExpanded = computed(() => {
|
||||
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 = {}
|
||||
@@ -312,8 +425,53 @@ function parseNumber(value) {
|
||||
return Number.isFinite(n) ? n : 0
|
||||
}
|
||||
|
||||
function buildImageKey(code, color) {
|
||||
return `${String(code || '').trim().toUpperCase()}::${String(color || '').trim().toUpperCase()}`
|
||||
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 = '') {
|
||||
return `${String(code || '').trim().toUpperCase()}::${String(color || '').trim().toUpperCase()}::${String(secondColor || '').trim().toUpperCase()}`
|
||||
}
|
||||
|
||||
function imageNameMatches(fileName, color, secondColor) {
|
||||
const text = String(fileName || '').toUpperCase()
|
||||
if (!text) return false
|
||||
const tokens = text.replace(/[^A-Z0-9_]+/g, ' ').trim().split(/\s+/).filter(Boolean)
|
||||
if (!tokens.length) return false
|
||||
const colorTrim = String(color || '').trim().toUpperCase()
|
||||
const secondTrim = String(secondColor || '').trim().toUpperCase()
|
||||
if (colorTrim && !tokens.includes(colorTrim)) return false
|
||||
if (secondTrim && !tokens.includes(secondTrim)) return false
|
||||
return true
|
||||
}
|
||||
|
||||
function normalizeUploadsPath(storagePath) {
|
||||
@@ -358,16 +516,16 @@ function resolveProductImageUrl(item) {
|
||||
return { contentUrl, publicUrl }
|
||||
}
|
||||
|
||||
function getProductImageUrl(code, color) {
|
||||
const key = buildImageKey(code, color)
|
||||
function getProductImageUrl(code, color, secondColor = '') {
|
||||
const key = buildImageKey(code, color, secondColor)
|
||||
const existing = productImageCache.value[key]
|
||||
if (existing !== undefined) return existing || ''
|
||||
void ensureProductImage(code, color)
|
||||
void ensureProductImage(code, color, secondColor)
|
||||
return ''
|
||||
}
|
||||
|
||||
async function onProductImageError(code, color) {
|
||||
const key = buildImageKey(code, color)
|
||||
async function onProductImageError(code, color, secondColor = '') {
|
||||
const key = buildImageKey(code, color, secondColor)
|
||||
const fallback = String(productImageFallbackByKey.value[key] || '')
|
||||
if (fallback && !productImageContentLoading.value[key]) {
|
||||
productImageContentLoading.value[key] = true
|
||||
@@ -389,10 +547,12 @@ async function onProductImageError(code, color) {
|
||||
productImageCache.value[key] = ''
|
||||
}
|
||||
|
||||
async function ensureProductImage(code, color) {
|
||||
const key = buildImageKey(code, color)
|
||||
async function ensureProductImage(code, color, secondColor = '') {
|
||||
const key = buildImageKey(code, color, secondColor)
|
||||
const codeTrim = String(code || '').trim().toUpperCase()
|
||||
const colorTrim = String(color || '').trim().toUpperCase()
|
||||
const secondTrim = String(secondColor || '').trim().toUpperCase()
|
||||
const listKey = buildImageKey(codeTrim, colorTrim, secondTrim)
|
||||
if (!codeTrim) {
|
||||
productImageCache.value[key] = ''
|
||||
return ''
|
||||
@@ -406,42 +566,42 @@ async function ensureProductImage(code, color) {
|
||||
|
||||
productImageLoading.value[key] = true
|
||||
try {
|
||||
if (!productImageListByCode.value[codeTrim]) {
|
||||
if (!productImageListLoading.value[codeTrim]) {
|
||||
productImageListLoading.value[codeTrim] = true
|
||||
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++
|
||||
const res = await api.get('/product-images', { params: { code: codeTrim } })
|
||||
productImageListByCode.value[codeTrim] = Array.isArray(res?.data) ? res.data : []
|
||||
const params = { code: codeTrim, dim1: colorTrim, dim3: secondTrim }
|
||||
const res = await api.get('/product-images', { params })
|
||||
productImageListByCode.value[listKey] = Array.isArray(res?.data) ? res.data : []
|
||||
} catch (err) {
|
||||
productImageListByCode.value[codeTrim] = []
|
||||
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, err })
|
||||
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[codeTrim]
|
||||
delete productImageListLoading.value[listKey]
|
||||
}
|
||||
} else {
|
||||
while (productImageListLoading.value[codeTrim]) {
|
||||
while (productImageListLoading.value[listKey]) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 25))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const list = productImageListByCode.value[codeTrim] || []
|
||||
const list = productImageListByCode.value[listKey] || []
|
||||
let first = null
|
||||
if (colorTrim) {
|
||||
const needle = `-${colorTrim.toLowerCase()}-`
|
||||
if (colorTrim || secondTrim) {
|
||||
first = list.find((item) =>
|
||||
String(item?.file_name || item?.FileName || '').toLowerCase().includes(needle)
|
||||
imageNameMatches(String(item?.file_name || item?.FileName || ''), colorTrim, secondTrim)
|
||||
) || null
|
||||
}
|
||||
if (!first) first = list[0] || null
|
||||
@@ -571,8 +731,14 @@ const level1Groups = computed(() => {
|
||||
const secondColor = String(item.Yaka || '').trim()
|
||||
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)
|
||||
@@ -600,8 +766,14 @@ const level1Groups = computed(() => {
|
||||
colorCode,
|
||||
colorDesc,
|
||||
secondColor,
|
||||
kategori,
|
||||
urunAnaGrubu,
|
||||
urunAltGrubu,
|
||||
urunIcerigi,
|
||||
fit,
|
||||
drop,
|
||||
kumas,
|
||||
karisim,
|
||||
aciklama,
|
||||
sizeTotals: emptySizeTotals(),
|
||||
totalQty: 0,
|
||||
@@ -638,10 +810,12 @@ const level1Groups = computed(() => {
|
||||
|
||||
return Array.from(l1Map.values()).map((l1) => ({
|
||||
...l1,
|
||||
children: Array.from(l1.childrenMap.values()).map((l2) => ({
|
||||
children: Array.from(l1.childrenMap.values())
|
||||
.map((l2) => ({
|
||||
...l2,
|
||||
children: Array.from(l2.childrenMap.values())
|
||||
}))
|
||||
.sort(sortByColorCodeAsc)
|
||||
}))
|
||||
})
|
||||
|
||||
@@ -740,10 +914,65 @@ async function fetchStockByCode() {
|
||||
function onLevel2Click(productCode, grp2) {
|
||||
toggleOpen(grp2.key)
|
||||
if (isOpen(grp2.key)) {
|
||||
void ensureProductImage(productCode, grp2.colorCode)
|
||||
void ensureProductImage(productCode, grp2.colorCode, grp2.secondColor)
|
||||
}
|
||||
}
|
||||
|
||||
async function openProductCard(grp1, grp2) {
|
||||
const productCode = String(grp1?.productCode || '').trim()
|
||||
const colorCode = String(grp2?.colorCode || '').trim()
|
||||
const secondColor = String(grp2?.secondColor || '').trim()
|
||||
const listKey = buildImageKey(productCode, colorCode, secondColor)
|
||||
|
||||
await ensureProductImage(productCode, colorCode, secondColor)
|
||||
const list = Array.isArray(productImageListByCode.value[listKey]) ? productImageListByCode.value[listKey] : []
|
||||
const images = list
|
||||
.map((item) => {
|
||||
const resolved = resolveProductImageUrl(item)
|
||||
return resolved.contentUrl || resolved.publicUrl || ''
|
||||
})
|
||||
.filter((x) => String(x || '').trim() !== '')
|
||||
|
||||
if (!images.length) {
|
||||
const single = getProductImageUrl(productCode, colorCode, secondColor)
|
||||
if (single) images.push(single)
|
||||
}
|
||||
|
||||
productCardImages.value = images
|
||||
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 = []
|
||||
@@ -757,6 +986,13 @@ function resetForm() {
|
||||
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(() => {
|
||||
@@ -777,7 +1013,7 @@ onUnmounted(() => {
|
||||
--grp-title-w: 44px;
|
||||
--psq-header-h: 56px;
|
||||
--psq-col-adet: calc(var(--col-adet) + var(--beden-w));
|
||||
--psq-col-img: 126px;
|
||||
--psq-col-img: 190px;
|
||||
--psq-l1-lift: 42px;
|
||||
}
|
||||
|
||||
@@ -860,8 +1096,8 @@ onUnmounted(() => {
|
||||
}
|
||||
|
||||
.order-sub-header.level-2 {
|
||||
min-height: 82px !important;
|
||||
height: 82px !important;
|
||||
min-height: 252px !important;
|
||||
height: 252px !important;
|
||||
background: #fff9c4 !important;
|
||||
border-top: 1px solid #d4c79f !important;
|
||||
border-bottom: 1px solid #d4c79f !important;
|
||||
@@ -1107,15 +1343,17 @@ onUnmounted(() => {
|
||||
.order-sub-header.level-2 .sub-image.level2-image {
|
||||
grid-column: 8 / 9;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 6px;
|
||||
gap: 8px;
|
||||
padding: 8px 10px;
|
||||
border-left: 1px solid #d4c79f;
|
||||
}
|
||||
|
||||
.product-image-card {
|
||||
width: 110px;
|
||||
height: 68px;
|
||||
width: 162px;
|
||||
height: 216px;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
@@ -1128,6 +1366,7 @@ onUnmounted(() => {
|
||||
.product-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.product-image-placeholder {
|
||||
@@ -1139,6 +1378,178 @@ onUnmounted(() => {
|
||||
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;
|
||||
|
||||
Reference in New Issue
Block a user