Deduplicate admin review product images

This commit is contained in:
2026-06-01 14:00:14 +07:00
parent 4e28ccdbd1
commit 84c81d4a2b
2 changed files with 53 additions and 28 deletions

View File

@ -26,7 +26,31 @@ Latest TypeScript verification after the validation/upload changes:
npx tsc --noEmit
```
## Latest Codex Changes After `2cfff02`
## Latest Codex Changes After `4e28ccd`
### Admin review image dedupe
File:
- `src/app/admin/review/[productId]/page.tsx`
Behavior:
- Admin product review now deduplicates product images before rendering.
- The dedupe applies to both:
- update compare section (`Gambar Produk`)
- normal admin review gallery (`Galeri`)
- Root `imageId` / `image` and entries inside `productImages` are merged and sorted by `sequence`.
- Duplicate images are removed by `imageId`, with URL as a fallback key.
- This prevents the main image from appearing twice when backend sends it both as root `imageId` and inside `productImages`.
Verification:
```bash
npm run build
```
The local production server was restarted on `http://localhost:3000` for manual admin review testing.
## Previous Codex Changes After `2cfff02`
### Product edit image payload preservation

View File

@ -166,6 +166,32 @@ function hasChangesForPaths(rows: CompareRow[], paths: string[]) {
});
}
function getProductImageRefs(product: ReviewProductData | null) {
if (!product) return [];
const refs = [
...(product.imageId || product.image
? [{ id: product.imageId || "", url: imgUrl(product.imageId, product.image) || "" }]
: []),
...(Array.isArray(product.productImages)
? [...product.productImages]
.sort((a, b) => (a.sequence ?? 0) - (b.sequence ?? 0))
.map((item) => ({
id: item.imageId || "",
url: imgUrl(item.imageId, item.image) || "",
}))
: []),
];
const seen = new Set<string>();
return refs.filter((item) => {
const key = item.id || item.url;
if (!key || seen.has(key)) return false;
seen.add(key);
return true;
});
}
function ModelCard({
model,
index,
@ -559,7 +585,6 @@ function ProductColumn({
);
const models = Array.isArray(product.productModels) ? product.productModels : [];
const images = Array.isArray(product.productImages) ? product.productImages : [];
const features = Array.isArray(product.productFeatures) ? product.productFeatures : [];
const keywords = Array.isArray(product.productKeyWords) ? product.productKeyWords : [];
const productInfos = Array.isArray(product.productInformations)
@ -571,21 +596,7 @@ function ProductColumn({
const productFiles = Array.isArray(product.productFiles)
? product.productFiles.filter((item) => item.file || item.fileId || item.id)
: [];
const allImages = [
...(product.imageId || product.image
? [{ id: product.imageId, url: imgUrl(product.imageId, product.image) }]
: []),
...images
.sort(
(a: { sequence?: number }, b: { sequence?: number }) =>
(a.sequence ?? 0) - (b.sequence ?? 0)
)
.map((img: { imageId?: string; image?: string }) => ({
id: img.imageId,
url: imgUrl(img.imageId, img.image),
}))
.filter((img) => Boolean(img.url)),
];
const allImages = getProductImageRefs(product);
const shouldRender = (key: CompareSectionKey) => !section || section === key;
@ -951,17 +962,7 @@ function AdminReviewDetailPageInner() {
const categoryInfos = Array.isArray(product.categoryInformations)
? product.categoryInformations.filter((item) => item.paramName && item.paramValue)
: [];
const allImages = [
...(product.imageId || product.image
? [{ id: product.imageId, url: imgUrl(product.imageId, product.image) }]
: []),
...(Array.isArray(product.productImages)
? product.productImages
.sort((a, b) => (a.sequence ?? 0) - (b.sequence ?? 0))
.map((item) => ({ id: item.imageId || "", url: imgUrl(item.imageId, item.image) || "" }))
.filter((item) => Boolean(item.url))
: []),
];
const allImages = getProductImageRefs(product);
// ── Reject modal ────────────────────────────────────────────────────────