feat: 이미지 목록 페이지 작업
This commit is contained in:
@@ -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") : "";
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -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
-1
@@ -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;
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user