feat: 가맹몰 썸네일이 없으면 같은 크기의 틀에 "no image"를 띄운다
목록의 대표이미지와 메뉴 표의 이미지가 `-` 한 글자로 떨어져 열이 들쭉날쭉했다. 두 곳이 같은 56×40 틀을 쓰므로 MerchantThumbnail 하나로 모으고 중복된 SCSS를 지운다. Co-Authored-By: Claude Opus 5
@f65aaedd6713e98f6c618fd0f323f6744545757b
--- app/(protected)/(basic)/system/merchant-details/_components/merchant-detail-list.module.scss
+++ app/(protected)/(basic)/system/merchant-details/_components/merchant-detail-list.module.scss
... | ... | @@ -16,13 +16,3 @@ |
| 16 | 16 |
align-items: center; |
| 17 | 17 |
gap: fox.gap(2); |
| 18 | 18 |
} |
| 19 |
- |
|
| 20 |
-// 목록 표의 대표이미지 — 기획 BO-MAL-001의 56×40 썸네일. 토큰 스케일이 96px에서 끝나 |
|
| 21 |
-// 딱 맞는 값이 없어 리터럴로 두고 근거를 남긴다(꾸미기아이템 썸네일과 같은 처리). |
|
| 22 |
-.thumbnail {
|
|
| 23 |
- inline-size: 5.6rem; |
|
| 24 |
- block-size: 4rem; |
|
| 25 |
- border: fox.border(1) solid fox.color(border-neutral-subtler); |
|
| 26 |
- border-radius: fox.radius(2); |
|
| 27 |
- object-fit: cover; |
|
| 28 |
-} |
--- app/(protected)/(basic)/system/merchant-details/_components/merchant-detail-list.tsx
+++ app/(protected)/(basic)/system/merchant-details/_components/merchant-detail-list.tsx
... | ... | @@ -38,6 +38,7 @@ |
| 38 | 38 |
deleteMerchantDetailsAction, |
| 39 | 39 |
} from '../_actions'; |
| 40 | 40 |
import { MerchantDetailRowActions } from './merchant-detail-row-actions';
|
| 41 |
+import { MerchantThumbnail } from './merchant-thumbnail';
|
|
| 41 | 42 |
import styles from './merchant-detail-list.module.scss'; |
| 42 | 43 |
|
| 43 | 44 |
interface MerchantDetailListProps {
|
... | ... | @@ -194,15 +195,9 @@ |
| 194 | 195 |
header: '대표이미지', |
| 195 | 196 |
width: 110, |
| 196 | 197 |
align: 'center', |
| 197 |
- render: (row) => |
|
| 198 |
- row.thumbnailUrl ? ( |
|
| 199 |
- // 백엔드의 공개 이미지 GET이라 브라우저가 직접 부른다. `next/image`는 백엔드 호스트를 |
|
| 200 |
- // next.config에 등록해야 해 여기서는 <img>로 둔다(꾸미기아이템과 같은 판단). |
|
| 201 |
- // eslint-disable-next-line @next/next/no-img-element |
|
| 202 |
- <img className={styles.thumbnail} src={row.thumbnailUrl} alt={`${row.name} 대표이미지`} />
|
|
| 203 |
- ) : ( |
|
| 204 |
- EMPTY_FIELD_PLACEHOLDER |
|
| 205 |
- ), |
|
| 198 |
+ render: (row) => ( |
|
| 199 |
+ <MerchantThumbnail src={row.thumbnailUrl} alt={`${row.name} 대표이미지`} />
|
|
| 200 |
+ ), |
|
| 206 | 201 |
}, |
| 207 | 202 |
{ key: 'name', header: '업체명', width: 220, emphasis: true },
|
| 208 | 203 |
{
|
--- app/(protected)/(basic)/system/merchant-details/_components/merchant-product-table.module.scss
+++ app/(protected)/(basic)/system/merchant-details/_components/merchant-product-table.module.scss
... | ... | @@ -29,15 +29,6 @@ |
| 29 | 29 |
overflow-x: auto; |
| 30 | 30 |
} |
| 31 | 31 |
|
| 32 |
-// 기획 BO-MAL-002 메뉴 표의 56×40 썸네일(목록 대표이미지와 같은 크기·같은 근거). |
|
| 33 |
-.thumbnail {
|
|
| 34 |
- inline-size: 5.6rem; |
|
| 35 |
- block-size: 4rem; |
|
| 36 |
- border: fox.border(1) solid fox.color(border-neutral-subtler); |
|
| 37 |
- border-radius: fox.radius(2); |
|
| 38 |
- object-fit: cover; |
|
| 39 |
-} |
|
| 40 |
- |
|
| 41 | 32 |
.description {
|
| 42 | 33 |
display: -webkit-box; |
| 43 | 34 |
overflow: hidden; |
--- app/(protected)/(basic)/system/merchant-details/_components/merchant-product-table.tsx
+++ app/(protected)/(basic)/system/merchant-details/_components/merchant-product-table.tsx
... | ... | @@ -16,6 +16,7 @@ |
| 16 | 16 |
type MerchantProduct, |
| 17 | 17 |
} from '@/lib/domain/merchant-product'; |
| 18 | 18 |
import { MerchantProductRowActions } from './merchant-product-row-actions';
|
| 19 |
+import { MerchantThumbnail } from './merchant-thumbnail';
|
|
| 19 | 20 |
import styles from './merchant-product-table.module.scss'; |
| 20 | 21 |
|
| 21 | 22 |
interface MerchantProductTableProps {
|
... | ... | @@ -75,13 +76,7 @@ |
| 75 | 76 |
{product.sortOrder ?? index + 1}
|
| 76 | 77 |
</FoxTableCell> |
| 77 | 78 |
<FoxTableCell align="center" label="이미지"> |
| 78 |
- {product.imageUrl ? (
|
|
| 79 |
- // 백엔드의 공개 이미지 GET — 목록 대표이미지와 같은 이유로 <img>를 쓴다. |
|
| 80 |
- // eslint-disable-next-line @next/next/no-img-element |
|
| 81 |
- <img className={styles.thumbnail} src={product.imageUrl} alt={`${product.name} 이미지`} />
|
|
| 82 |
- ) : ( |
|
| 83 |
- EMPTY_FIELD_PLACEHOLDER |
|
| 84 |
- )} |
|
| 79 |
+ <MerchantThumbnail src={product.imageUrl} alt={`${product.name} 이미지`} />
|
|
| 85 | 80 |
</FoxTableCell> |
| 86 | 81 |
<FoxTableCell emphasis label="메뉴명">{product.name}</FoxTableCell>
|
| 87 | 82 |
<FoxTableCell label="설명"> |
+++ app/(protected)/(basic)/system/merchant-details/_components/merchant-thumbnail.module.scss
... | ... | @@ -0,0 +1,28 @@ |
| 1 | +@use "@fox/styles/abstracts" as fox; | |
| 2 | + | |
| 3 | +// 기획 BO-MAL-001·002의 56×40 썸네일 틀. 토큰 스케일이 96px에서 끝나 딱 맞는 값이 없어 | |
| 4 | +// 리터럴로 두고 근거를 남긴다(꾸미기아이템 썸네일과 같은 처리). | |
| 5 | +.image, | |
| 6 | +.placeholder { | |
| 7 | + box-sizing: border-box; | |
| 8 | + inline-size: 5.6rem; | |
| 9 | + block-size: 4rem; | |
| 10 | + border: fox.border(1) solid fox.color(border-neutral-subtler); | |
| 11 | + border-radius: fox.radius(2); | |
| 12 | +} | |
| 13 | + | |
| 14 | +.image { | |
| 15 | + display: block; | |
| 16 | + object-fit: cover; | |
| 17 | +} | |
| 18 | + | |
| 19 | +.placeholder { | |
| 20 | + display: inline-flex; | |
| 21 | + align-items: center; | |
| 22 | + justify-content: center; | |
| 23 | + background: fox.color(surface-neutral-gray); | |
| 24 | + color: fox.color(font-neutral-subtle); | |
| 25 | + font-size: fox.font-size(label-xsm); | |
| 26 | + line-height: 1; | |
| 27 | + text-transform: lowercase; | |
| 28 | +} |
+++ app/(protected)/(basic)/system/merchant-details/_components/merchant-thumbnail.tsx
... | ... | @@ -0,0 +1,24 @@ |
| 1 | +import styles from './merchant-thumbnail.module.scss'; | |
| 2 | + | |
| 3 | +interface MerchantThumbnailProps { | |
| 4 | + src: string | null; | |
| 5 | + alt: string; | |
| 6 | +} | |
| 7 | + | |
| 8 | +/** | |
| 9 | + * 목록·메뉴 표의 56×40 썸네일. 이미지가 없어도 같은 크기의 틀에 "no image"를 띄운다 — | |
| 10 | + * `-` 한 글자면 열이 들쭉날쭉해지고 이미지 자리가 어디인지 보이지 않는다(사용자 확정). | |
| 11 | + */ | |
| 12 | +export function MerchantThumbnail({ src, alt }: MerchantThumbnailProps) { | |
| 13 | + if (!src) { | |
| 14 | + return ( | |
| 15 | + <span className={styles.placeholder} role="img" aria-label={`${alt} 없음`}> | |
| 16 | + no image | |
| 17 | + </span> | |
| 18 | + ); | |
| 19 | + } | |
| 20 | + // 백엔드의 공개 이미지 GET이라 브라우저가 직접 부른다. `next/image`는 백엔드 호스트를 | |
| 21 | + // next.config에 등록해야 해 <img>로 둔다(꾸미기아이템과 같은 판단). | |
| 22 | + // eslint-disable-next-line @next/next/no-img-element | |
| 23 | + return <img className={styles.image} src={src} alt={alt} />; | |
| 24 | +} |
Add a comment
Delete comment
Once you delete this comment, you won't be able to recover it. Are you sure you want to delete this comment?