Deduplicate admin review product images
This commit is contained in:
26
HANDOFF.md
26
HANDOFF.md
@ -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
|
||||
|
||||
|
||||
@ -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 ────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
Reference in New Issue
Block a user