Files
frontend/apps/cs/src/widgets/post/model/grid-view-table-columns.tsx
T
jcchoi 0b502cbc99
Deploy Frontend / deploy (push) Successful in 7m28s
접근성 체크 박스 (적절한 링크 텍스트)
2026-10-11 17:09:37 +09:00

191 lines
5.8 KiB
TypeScript

import dayjs from "dayjs";
import Image from "next/image";
import { useQuery } from "@tanstack/react-query";
import type { ColumnDef } from "@tanstack/react-table";
import { Badge, Checkbox, Skeleton, cn } from "@common/components";
import { AttachmentApi } from "@/entities/attachment";
import type { Post } from "@/entities/post";
import type { BbsType } from "@/shared/store";
import { ENDPOINT } from "@/shared/config";
declare module "@tanstack/react-table" {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
interface TableMeta<TData> {
bbsTypeCd?: BbsType;
}
}
export const createColumns = (
thumbRefs: React.MutableRefObject<Map<number, string>>,
): ColumnDef<Post>[] => [
{
id: "select",
header: ({ table }) => (
<Checkbox
aria-label="전체 선택"
title="전체 선택"
checked={table.getIsAllPageRowsSelected()}
onClick={(e) => e.stopPropagation()}
onCheckedChange={(checked) =>
table.toggleAllPageRowsSelected(!!checked)
}
className="h-4 w-4 rounded border-gray-300"
/>
),
cell: ({ row, table }) => {
const { atflCd, pstNo, pstTtl, pstCn, inqNocs, rgtrNm, regDt } =
row.original;
const bbsTypeCd = table.options.meta?.bbsTypeCd;
if (bbsTypeCd === "BBSIMAGE") {
return (
<div className="flex flex-col gap-4">
<div className="flex items-center gap-2">
<Checkbox
aria-label="행 선택"
title="행 선택"
checked={row.getIsSelected()}
onClick={(e) => e.stopPropagation()}
onCheckedChange={(checked) => row.toggleSelected(!!checked)}
className="h-4 w-4 rounded border-gray-300"
/>
{inqNocs >= 100 && <Badge color="red">HOT</Badge>}
{dayjs().diff(dayjs(row.original.regDt), "hour") < 24 && (
<Badge color="blue">NEW</Badge>
)}
<span className="text-[1.6rem] font-bold text-label">
{pstTtl}
</span>
</div>
<ThumbImage
atflCd={atflCd}
pstNo={pstNo}
thumbRefs={thumbRefs}
bbsTypeCd={bbsTypeCd}
/>
<div className="flex flex-col">
<span className="text-[1.4rem] font-bold text-label">
{rgtrNm}
</span>
<span className="text-[1.2rem] text-label">
{dayjs(regDt).format("YYYY-MM-DD")}
</span>
</div>
</div>
);
}
if (bbsTypeCd === "BBSMOVIE") {
return (
<div className="-m-4">
<ThumbImage
atflCd={atflCd}
pstNo={pstNo}
thumbRefs={thumbRefs}
bbsTypeCd={bbsTypeCd}
classes={{
image: "object-cover",
}}
/>
<div className="p-4">
<div className="flex flex-col gap-4">
<div className="flex items-center gap-2">
<Checkbox
aria-label="행 선택"
title="행 선택"
checked={row.getIsSelected()}
onClick={(e) => e.stopPropagation()}
onCheckedChange={(checked) => row.toggleSelected(!!checked)}
className="h-4 w-4 rounded border-gray-300"
/>
{inqNocs >= 100 && <Badge color="red">HOT</Badge>}
{dayjs().diff(dayjs(regDt), "hour") < 24 && (
<Badge color="blue">NEW</Badge>
)}
<span className="text-[1.6rem] font-bold text-label">
{pstTtl}
</span>
</div>
<p className="line-clamp-3 overflow-hidden text-[1.2rem] text-label">
{pstCn}
</p>
<div className="flex flex-col">
<span className="text-[1.4rem] font-bold text-label">
{rgtrNm}
</span>
<span className="text-[1.2rem] text-label">
{dayjs(regDt).format("YYYY-MM-DD")}
</span>
</div>
</div>
</div>
</div>
);
}
},
},
];
type ThumbImageProps = {
atflCd: string;
pstNo: number;
thumbRefs: React.MutableRefObject<Map<number, string>>;
bbsTypeCd?: string;
classes?: {
image?: string;
};
};
const ThumbImage = ({
atflCd,
pstNo,
thumbRefs,
bbsTypeCd,
classes,
}: ThumbImageProps) => {
const prevImage = thumbRefs.current.get(pstNo);
const { data, isLoading } = useQuery({
queryKey: [ENDPOINT.ATTACH_SERVICE.IMAGES, atflCd],
queryFn: () => {
if (bbsTypeCd === "BBSIMAGE") {
return AttachmentApi.getImageUrlByAtflCd(atflCd);
} else if (bbsTypeCd === "BBSMOVIE") {
return AttachmentApi.getMovieThumbnailUrlByAtflCd(atflCd);
}
},
select: (url) => {
if (url) thumbRefs.current.set(pstNo, url);
return url;
},
enabled: !prevImage && !!atflCd,
});
const imageUrl = prevImage ?? data;
if (isLoading)
return (
<div className="h-[16rem] w-full">
<Skeleton className="h-full w-full" />
</div>
);
if (imageUrl)
return (
<div className="relative flex h-[16rem] w-full items-center justify-center">
<Image
src={imageUrl}
className={cn("object-contain", classes?.image)}
fill
alt="썸네일 이미지"
/>
</div>
);
return (
<div className="flex h-[16rem] w-full items-center justify-center rounded-sm border border-border bg-background">
<i className="diveicon di-image text-3xl" />
</div>
);
};