ui: update ProductPerformanceProfitability page to enhance table views and tabs management
This commit is contained in:
@@ -27,6 +27,23 @@ type ProductImageItem struct {
|
||||
FullURL string `json:"full_url,omitempty"`
|
||||
}
|
||||
|
||||
type ProductImageBatchItem struct {
|
||||
Key string `json:"key"`
|
||||
Code string `json:"code"`
|
||||
Dim1 string `json:"dim1"`
|
||||
Dim3 string `json:"dim3"`
|
||||
Limit int `json:"limit,omitempty"`
|
||||
}
|
||||
|
||||
type ProductImageBatchRequest struct {
|
||||
Items []ProductImageBatchItem `json:"items"`
|
||||
}
|
||||
|
||||
type ProductImageBatchResponseItem struct {
|
||||
Key string `json:"key"`
|
||||
Images []ProductImageItem `json:"images"`
|
||||
}
|
||||
|
||||
var uuidPattern = regexp.MustCompile(`(?i)[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}`)
|
||||
|
||||
func normalizeDimParam(v string) string {
|
||||
@@ -119,6 +136,138 @@ func extractImageUUID(storagePath, fileName string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// POST /api/product-images/batch
|
||||
func PostProductImagesBatchHandler(pg *sql.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
reqID := strings.TrimSpace(r.Header.Get("X-Request-ID"))
|
||||
if reqID == "" {
|
||||
reqID = uuid.NewString()
|
||||
}
|
||||
w.Header().Set("X-Request-ID", reqID)
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
|
||||
var req ProductImageBatchRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, "Gecersiz gorsel batch istegi: "+err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if len(req.Items) > 300 {
|
||||
req.Items = req.Items[:300]
|
||||
}
|
||||
|
||||
cleanItems := make([]ProductImageBatchItem, 0, len(req.Items))
|
||||
seen := map[string]bool{}
|
||||
for _, item := range req.Items {
|
||||
item.Key = strings.TrimSpace(item.Key)
|
||||
item.Code = strings.TrimSpace(item.Code)
|
||||
item.Dim1 = normalizeDimParam(item.Dim1)
|
||||
item.Dim3 = normalizeDimParam(item.Dim3)
|
||||
if item.Key == "" {
|
||||
item.Key = item.Code + "|" + item.Dim1 + "|" + item.Dim3
|
||||
}
|
||||
if item.Code == "" || seen[item.Key] {
|
||||
continue
|
||||
}
|
||||
seen[item.Key] = true
|
||||
cleanItems = append(cleanItems, item)
|
||||
}
|
||||
if len(cleanItems) == 0 {
|
||||
_ = json.NewEncoder(w).Encode([]ProductImageBatchResponseItem{})
|
||||
return
|
||||
}
|
||||
|
||||
payload, err := json.Marshal(cleanItems)
|
||||
if err != nil {
|
||||
http.Error(w, "Gorsel batch hazirlanamadi: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
rows, err := pg.Query(`
|
||||
WITH req AS (
|
||||
SELECT *
|
||||
FROM jsonb_to_recordset($1::jsonb) AS x(key text, code text, dim1 text, dim3 text)
|
||||
),
|
||||
matched_item AS (
|
||||
SELECT DISTINCT ON (r.key)
|
||||
r.key,
|
||||
r.code,
|
||||
r.dim1,
|
||||
r.dim3,
|
||||
m.id AS mmitem_id
|
||||
FROM req r
|
||||
JOIN mmitem m ON
|
||||
UPPER(REPLACE(COALESCE(m.code,''), ' ', '')) = UPPER(REPLACE(COALESCE(r.code,''), ' ', ''))
|
||||
OR UPPER(REPLACE(REGEXP_REPLACE(COALESCE(m.code,''), '^.*-', ''), ' ', '')) =
|
||||
UPPER(REPLACE(REGEXP_REPLACE(COALESCE(r.code,''), '^.*-', ''), ' ', ''))
|
||||
ORDER BY r.key, m.id
|
||||
),
|
||||
ranked AS (
|
||||
SELECT
|
||||
mi.key,
|
||||
b.id,
|
||||
COALESCE(b.file_name,'') AS file_name,
|
||||
COALESCE(b.file_size,0) AS file_size,
|
||||
COALESCE(b.storage_path,'') AS storage_path,
|
||||
ROW_NUMBER() OVER (
|
||||
PARTITION BY mi.key
|
||||
ORDER BY
|
||||
CASE
|
||||
WHEN mi.dim1 <> '' AND mi.dim3 <> '' AND COALESCE(b.dimval1::text,'') = mi.dim1 AND COALESCE(b.dimval3::text,'') = mi.dim3 THEN 0
|
||||
WHEN mi.dim1 <> '' AND COALESCE(b.dimval1::text,'') = mi.dim1 AND COALESCE(b.dimval3::text,'') = '' THEN 1
|
||||
WHEN COALESCE(b.dimval1::text,'') = '' AND COALESCE(b.dimval3::text,'') = '' THEN 2
|
||||
ELSE 3
|
||||
END,
|
||||
COALESCE(b.sort_order,999999),
|
||||
b.id
|
||||
) AS rn
|
||||
FROM matched_item mi
|
||||
JOIN dfblob b ON b.src_table='mmitem'
|
||||
AND b.typ='img'
|
||||
AND b.src_id=mi.mmitem_id
|
||||
)
|
||||
SELECT key, id, file_name, file_size, storage_path
|
||||
FROM ranked
|
||||
WHERE rn <= 1
|
||||
ORDER BY key, rn
|
||||
`, string(payload))
|
||||
if err != nil {
|
||||
slog.Error("product_images.batch.query_failed", "req_id", reqID, "err", err.Error())
|
||||
http.Error(w, "Gorsel batch sorgu hatasi: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
grouped := map[string][]ProductImageItem{}
|
||||
for rows.Next() {
|
||||
var key string
|
||||
var it ProductImageItem
|
||||
if err := rows.Scan(&key, &it.ID, &it.FileName, &it.FileSize, &it.Storage); err != nil {
|
||||
continue
|
||||
}
|
||||
it.ContentURL = fmt.Sprintf("/api/product-images/%d/content", it.ID)
|
||||
if u := extractImageUUID(it.Storage, it.FileName); u != "" {
|
||||
it.UUID = u
|
||||
it.ThumbURL = "/uploads/image/t300/" + u + ".jpg"
|
||||
it.FullURL = "/uploads/image/" + u + ".jpg"
|
||||
}
|
||||
grouped[key] = append(grouped[key], it)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
http.Error(w, "Gorsel batch okuma hatasi: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
out := make([]ProductImageBatchResponseItem, 0, len(cleanItems))
|
||||
for _, item := range cleanItems {
|
||||
out = append(out, ProductImageBatchResponseItem{
|
||||
Key: item.Key,
|
||||
Images: grouped[item.Key],
|
||||
})
|
||||
}
|
||||
_ = json.NewEncoder(w).Encode(out)
|
||||
}
|
||||
}
|
||||
|
||||
// GET /api/product-images?code=...&dim1=...&dim3=...
|
||||
func GetProductImagesHandler(pg *sql.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
Reference in New Issue
Block a user