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

150 lines
4.3 KiB
TypeScript

import dayjs from "dayjs";
import type { ColumnDef } from "@tanstack/react-table";
import { Badge, Button, Checkbox } from "@common/components";
import { AttachmentApi } from "@/entities/attachment";
import type { Post } from "@/entities/post";
export const columns: ColumnDef<Post & { isUpndFixPost?: boolean }>[] = [
{
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 }) => (
<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"
/>
),
},
{
accessorKey: "rowNo",
header: "NO",
cell: ({ row, table }) => {
if (row.original.isUpndFixPost) {
return (
<Badge
color="orange"
className="flex size-8 items-center justify-center"
>
<i className="diveicon di-volume-up-fill" />
</Badge>
);
}
const totalRows = table.getRowCount();
const currentPage = table.getState().pagination.pageIndex;
const pageSize = table.getState().pagination.pageSize;
return totalRows - (currentPage * pageSize + row.index);
},
},
{
accessorKey: "pstTtl",
header: "제목",
cell: ({ row }) => {
const { ansPstn = 0, pstTtl, inqNocs, cmntCnt } = row.original;
const indentPadding = ansPstn > 0 ? (ansPstn - 1) * 20 : 0;
return (
<div
className="flex min-w-[20vw] items-center gap-2 text-left"
style={{ paddingInlineStart: `${indentPadding}px` }}
>
{ansPstn > 0 && (
<div className="flex items-center gap-1">
<div className="-rotate-90">
<i className="diveicon di-arrow-90left-down rotate-90" />
</div>
<Badge color="smokyBlue">답변</Badge>
</div>
)}
{inqNocs >= 100 && <Badge color="red">HOT</Badge>}
{dayjs().diff(dayjs(row.original.regDt), "hour") < 24 && (
<Badge color="blue">NEW</Badge>
)}
{pstTtl}
{cmntCnt > 0 && (
<div className="ml-4 flex items-end gap-2">
<i className="diveicon di-chat-box-left-text text-gray-400" />
<span className="text-gray-400">{cmntCnt}</span>
</div>
)}
</div>
);
},
},
{
accessorKey: "inqNocs",
header: "조회수",
},
{
accessorKey: "atflCd",
header: "첨부파일",
cell: ({ row }) => {
const atflCd = row.original.atflCd;
if (!atflCd) return null;
return <AttachmentButton atflCd={atflCd} />;
},
},
{
accessorKey: "rgtrNm",
header: "작성자",
},
{
accessorKey: "regDt",
header: "등록일",
cell: ({ row }) => dayjs(row.original.regDt).format("YYYY-MM-DD"),
},
];
const AttachmentButton = ({ atflCd }: { atflCd: string }) => {
const handleDownload = async (e: React.MouseEvent<HTMLButtonElement>) => {
e.stopPropagation();
try {
let filename = "";
const response = await AttachmentApi.downloadAttachmentByAtflCd(atflCd);
const contentDisposition = response.headers["content-disposition"];
if (contentDisposition) {
const filenameMatch = contentDisposition.match(
/filename\*=UTF-8''([^;]+)/,
);
if (filenameMatch) {
filename = decodeURIComponent(filenameMatch[1]);
}
}
const blob = new Blob([response.data]);
const url = window.URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = filename;
document.body.appendChild(link);
link.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(link);
} catch (error) {
console.error(error);
}
};
return (
<Button size="icon" color="white" onClick={handleDownload}>
<i className="diveicon di-download" />
</Button>
);
};