feat: 이미지 목록 페이지 작업

This commit is contained in:
2025-04-01 15:40:38 +09:00
parent f2954e6ebd
commit a912deeefb
10 changed files with 222 additions and 48 deletions
@@ -29,7 +29,7 @@ export interface SiteDetail extends SiteFormData {
mdfcnDt: string;
mdFr: string;
rgTrNm: string;
mdFrNm: string;
mdfrNm: string;
}
export const siteFormSchema = z.object({
@@ -0,0 +1,21 @@
import { client } from "@/shared/api";
import { ENDPOINT } from "@/shared/config";
import type {
ImageParams,
ImageList,
ImageService,
} from "../model/image-interface";
export class ImageApiService implements ImageService {
public async getImageList(params: ImageParams): Promise<ImageList> {
const response = await client.get<ImageList>(
`${ENDPOINT.CMS_SERVICE.IMAGES}`,
{
params,
},
);
return response.data;
}
}
export const ImageApi = new ImageApiService();
@@ -0,0 +1,36 @@
import type { PageableList, PageableParams } from "@/shared/model";
export const IMAGE_TYPE = {
IMGSUB: "IMGSUB",
IMGMAIN: "IMGMAIN",
IMGLOGIN: "IMGLOGIN",
} as const;
export type ImageType = (typeof IMAGE_TYPE)[keyof typeof IMAGE_TYPE];
export interface ImageParams extends PageableParams {
keyword: string;
imgTypeCd: string;
useYn: string;
}
export interface Image {
imgSn: number;
imgTypeCd: string;
imgTypeNm: string;
imgTtl: string;
lnkgUrlAddr: string;
atflYn: string;
sortSeq: number;
atflCd: string;
useYn: string;
mdfcnDt: string;
mdFr: string;
mdfrNm: string;
}
export type ImageList = PageableList<Image>;
export interface ImageService {
getImageList: (params: ImageParams) => Promise<ImageList>;
}
@@ -0,0 +1,74 @@
"use client";
import dayjs from "dayjs";
import { type ColumnDef } from "@tanstack/react-table";
import { Checkbox } from "@common/components/ui";
import { type Image } from "./image-interface";
import { IMAGE_TYPE } from "./image-interface";
export const columns: ColumnDef<Image>[] = [
{
id: "select",
header: ({ table }) => (
<Checkbox
checked={table.getIsAllPageRowsSelected()}
onClick={(e) => e.stopPropagation()}
onCheckedChange={(checked) =>
table.toggleAllPageRowsSelected(!!checked)
}
className="h-4 w-4 rounded border-gray-300"
/>
),
cell: ({ row }) => (
<Checkbox
checked={row.getIsSelected()}
onClick={(e) => e.stopPropagation()}
onCheckedChange={(checked) => row.toggleSelected(!!checked)}
className="h-4 w-4 rounded border-gray-300"
/>
),
},
{
accessorKey: "imgSn",
header: "NO",
cell: ({ row }) => row.index + 1,
},
{
accessorKey: "imgTypeCd",
header: "이미지구분",
cell: ({ row }) => {
const type = row.getValue("imgTypeCd") as string;
switch (type) {
case IMAGE_TYPE.IMGSUB:
return "서브이미지";
case IMAGE_TYPE.IMGMAIN:
return "메인이미지";
case IMAGE_TYPE.IMGLOGIN:
return "로그인이미지";
default:
return type;
}
},
},
{
accessorKey: "imgTtl",
header: "이미지명",
},
{
accessorKey: "useYn",
header: "사용여부",
cell: ({ row }) => (row.getValue("useYn") === "Y" ? "사용" : "미사용"),
},
{
accessorKey: "mdfrNm",
header: "최종수정자",
},
{
accessorKey: "mdfcnDt",
header: "최종수정일",
cell: ({ row }) => {
const date = row.getValue("mdfcnDt") as string;
return date ? dayjs(date).format("YYYY-MM-DD") : "";
},
},
];
+76 -43
View File
@@ -1,56 +1,89 @@
"use client";
import { Upload } from "tus-js-client";
import { FileUploader } from "@common/components/ui";
import { AUTH_USER_ID, CLAIM_NAME, ENDPOINT } from "@/shared/config";
import { getCookie } from "@/shared/lib";
import { useEffect, useState } from "react";
import { useRouter } from "next/navigation";
import { useQuery } from "@tanstack/react-query";
import {
getCoreRowModel,
useReactTable,
type RowSelectionState,
} from "@tanstack/react-table";
import { Button, DataTable, Input } from "@common/components";
import { ENDPOINT } from "@/shared/config";
import { ImageApi } from "../api/image-service";
import { columns } from "../model/table-columns";
import { type Image, type ImageParams } from "../model/image-interface";
function ImagePage() {
const handleUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.item(0);
if (!file) return;
const router = useRouter();
const upload = new Upload(file, {
endpoint: `${ENDPOINT.ATTACH_SERVICE.TUS_UPLOAD}`,
headers: {
[CLAIM_NAME]: getCookie(CLAIM_NAME) ?? "",
[AUTH_USER_ID]: getCookie(AUTH_USER_ID) ?? "",
},
retryDelays: [0, 3000, 5000, 10000, 20000],
metadata: {
filename: file.name,
filetype: file.type,
},
onError: function (error) {
console.log("Failed because: " + error);
},
onProgress: function (bytesUploaded, bytesTotal) {
const percentage = ((bytesUploaded / bytesTotal) * 100).toFixed(2);
console.log(bytesUploaded, bytesTotal, percentage + "%");
},
onSuccess: function () {
console.log(
"Download %s from %s",
(upload.file as File).name,
upload.url,
);
},
});
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
const [imageQuery, setImageQuery] = useState<ImageParams>({
keyword: "",
imgTypeCd: "",
useYn: "",
page: 0,
size: 100,
});
upload.findPreviousUploads().then(function (previousUploads) {
if (previousUploads.length) {
upload.resumeFromPreviousUpload(previousUploads[0]!);
}
upload.start();
});
const { data: imageList, refetch } = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.IMAGES],
queryFn: () => ImageApi.getImageList(imageQuery),
enabled: false,
});
const table = useReactTable({
data: imageList?.content ?? [],
columns,
state: {
rowSelection,
},
enableMultiRowSelection: true,
getCoreRowModel: getCoreRowModel(),
onRowSelectionChange: setRowSelection,
});
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
refetch();
};
const handleRowClick = (row: Image) => {
router.push(`/image/register/${row.imgSn}`);
};
useEffect(() => {
refetch();
}, []);
return (
<div className="card card-border">
<div className="card-header">
<h3 className="text-2xl font-medium text-label">이미지 목록</h3>
<div className="card card-border !mt-6">
<div className="card card-border">
<form onSubmit={handleSubmit} className="flex items-center gap-2 p-2">
<label htmlFor="" className="mr-2 text-label">
이미지명
</label>
<Input
className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]"
value={imageQuery.keyword}
onChange={(e) =>
setImageQuery({ ...imageQuery, keyword: e.target.value })
}
/>
<Button className="h-[3.2rem]" type="submit">
조회
</Button>
</form>
</div>
<div className="card card-border">
<div className="card-header">
<h3 className="text-2xl font-medium text-label">이미지 목록</h3>
<Button onClick={() => router.push("/image/register")}>등록</Button>
</div>
<div className="card !m-[1.2rem] h-[49rem] overflow-auto !p-0">
<DataTable table={table} onRowClick={handleRowClick} />
</div>
</div>
<FileUploader id="image-uploader" onUpload={handleUpload} />
</div>
);
}
@@ -1,6 +1,6 @@
import { createColumnHelper } from "@tanstack/react-table";
import { Checkbox } from "@common/components/ui";
import type { Content } from "../model/content-interface";
import type { Content } from "./content-interface";
const columnHelper = createColumnHelper<Content>();
@@ -18,7 +18,7 @@ import {
import { ENDPOINT } from "@/shared/config";
import { ContentApi } from "../api/content-service";
import type { Content } from "../model/content-interface";
import { columns } from "../model/table-colums";
import { columns } from "../model/content-table-colums";
type ContentSearchDialogProps = {
handleContentSelect: (content: Content) => void;
+1
View File
@@ -20,6 +20,7 @@ export const ENDPOINT = {
MENU_ROLES: "/cms-service/api/v1/menu-roles",
CONTENTS: "/cms-service/api/v1/contents",
USER_OGNZ: "/cms-service/api/v1/user-ognz",
IMAGES: "/cms-service/api/v1/images",
},
BOARD_SERVICE: {
BBS: "/board-service/api/v1/bbs",
+1 -1
View File
@@ -20,7 +20,7 @@ export interface SiteInfo {
mdfcnDt: string;
mdFr: string;
rgTrNm: string;
mdFrNm: string;
mdfrNm: string;
}
export const getSiteInfo = async (siteId: string) => {
@@ -3,6 +3,7 @@ import {
type RowData,
type Table as TableType,
} from "@tanstack/react-table";
import { cn } from "../../lib";
import { Table, TableCell, TableHead, TableHeader, TableRow } from "../table";
declare module "@tanstack/react-table" {
@@ -55,7 +56,15 @@ function DataTable<T>({
<TableRow
key={row.id}
onClick={() => onRowClick?.(row.original)}
className={onRowClick ? "cursor-pointer hover:bg-accent" : ""}
className={cn(
onRowClick && "cursor-pointer hover:bg-accent",
variant === "default" &&
"[data-theme='black'] hover:!bg-[rgba(84,85,86,0.7)]",
variant === "secondary" &&
"[data-theme='black'] hover:!bg-[rgba(86,91,95,0.7)]",
variant === "line" &&
"[data-theme='black'] hover:!bg-[rgba(255,255,255,0.1)]",
)}
>
{row.getVisibleCells().map((cell) => {
const { meta } = cell.column.columnDef;