diff --git a/apps/cs/src/entities/site/model/site-interface.ts b/apps/cs/src/entities/site/model/site-interface.ts index ef83a53c1..a8fb2796f 100644 --- a/apps/cs/src/entities/site/model/site-interface.ts +++ b/apps/cs/src/entities/site/model/site-interface.ts @@ -29,7 +29,7 @@ export interface SiteDetail extends SiteFormData { mdfcnDt: string; mdFr: string; rgTrNm: string; - mdFrNm: string; + mdfrNm: string; } export const siteFormSchema = z.object({ diff --git a/apps/cs/src/pages/image/api/image-service.ts b/apps/cs/src/pages/image/api/image-service.ts new file mode 100644 index 000000000..63918784a --- /dev/null +++ b/apps/cs/src/pages/image/api/image-service.ts @@ -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 { + const response = await client.get( + `${ENDPOINT.CMS_SERVICE.IMAGES}`, + { + params, + }, + ); + return response.data; + } +} + +export const ImageApi = new ImageApiService(); diff --git a/apps/cs/src/pages/image/model/image-interface.ts b/apps/cs/src/pages/image/model/image-interface.ts new file mode 100644 index 000000000..b24c0eed2 --- /dev/null +++ b/apps/cs/src/pages/image/model/image-interface.ts @@ -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; + +export interface ImageService { + getImageList: (params: ImageParams) => Promise; +} diff --git a/apps/cs/src/pages/image/model/table-columns.tsx b/apps/cs/src/pages/image/model/table-columns.tsx new file mode 100644 index 000000000..8ae770e0e --- /dev/null +++ b/apps/cs/src/pages/image/model/table-columns.tsx @@ -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[] = [ + { + id: "select", + header: ({ table }) => ( + e.stopPropagation()} + onCheckedChange={(checked) => + table.toggleAllPageRowsSelected(!!checked) + } + className="h-4 w-4 rounded border-gray-300" + /> + ), + cell: ({ row }) => ( + 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") : ""; + }, + }, +]; diff --git a/apps/cs/src/pages/image/ui/image-page.tsx b/apps/cs/src/pages/image/ui/image-page.tsx index ca9063b5c..7eb5863bd 100644 --- a/apps/cs/src/pages/image/ui/image-page.tsx +++ b/apps/cs/src/pages/image/ui/image-page.tsx @@ -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) => { - 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({}); + const [imageQuery, setImageQuery] = useState({ + 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) => { + e.preventDefault(); + refetch(); }; + const handleRowClick = (row: Image) => { + router.push(`/image/register/${row.imgSn}`); + }; + + useEffect(() => { + refetch(); + }, []); + return ( -
-
-

이미지 목록

+
+
+
+ + + setImageQuery({ ...imageQuery, keyword: e.target.value }) + } + /> + +
+
+
+
+

이미지 목록

+ +
+
+ +
-
); } diff --git a/apps/cs/src/pages/menu/model/table-colums.tsx b/apps/cs/src/pages/menu/model/content-table-colums.tsx similarity index 92% rename from apps/cs/src/pages/menu/model/table-colums.tsx rename to apps/cs/src/pages/menu/model/content-table-colums.tsx index b24b506fd..b8bb28638 100644 --- a/apps/cs/src/pages/menu/model/table-colums.tsx +++ b/apps/cs/src/pages/menu/model/content-table-colums.tsx @@ -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(); diff --git a/apps/cs/src/pages/menu/ui/content-search-dialog.tsx b/apps/cs/src/pages/menu/ui/content-search-dialog.tsx index 60ee794b8..66d939842 100644 --- a/apps/cs/src/pages/menu/ui/content-search-dialog.tsx +++ b/apps/cs/src/pages/menu/ui/content-search-dialog.tsx @@ -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; diff --git a/apps/cs/src/shared/config/api.ts b/apps/cs/src/shared/config/api.ts index a4f861c1a..4430285d8 100644 --- a/apps/cs/src/shared/config/api.ts +++ b/apps/cs/src/shared/config/api.ts @@ -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", diff --git a/packages/common/components/src/api/site.ts b/packages/common/components/src/api/site.ts index 77ed968e4..2ca35bd26 100644 --- a/packages/common/components/src/api/site.ts +++ b/packages/common/components/src/api/site.ts @@ -20,7 +20,7 @@ export interface SiteInfo { mdfcnDt: string; mdFr: string; rgTrNm: string; - mdFrNm: string; + mdfrNm: string; } export const getSiteInfo = async (siteId: string) => { diff --git a/packages/common/components/src/ui/data-table/index.tsx b/packages/common/components/src/ui/data-table/index.tsx index d635e8554..53312fe83 100644 --- a/packages/common/components/src/ui/data-table/index.tsx +++ b/packages/common/components/src/ui/data-table/index.tsx @@ -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({ 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;