feat: 배너 관리 페이지 작업

This commit is contained in:
2025-04-07 17:05:55 +09:00
parent a1dd0dc931
commit 9a2bdfc0ca
16 changed files with 950 additions and 2 deletions
@@ -0,0 +1,2 @@
import { BannerPage } from "@/pages/banner";
export default BannerPage;
@@ -0,0 +1,10 @@
import { ModifyPage as VModifyPage } from "@/pages/banner";
export default async function ModifyPage({
params,
}: {
params: Promise<{ serial: string }>;
}) {
const { serial } = await params;
return <VModifyPage serial={serial} />;
}
@@ -0,0 +1,2 @@
import { CreatePage } from "@/pages/banner";
export default CreatePage;
@@ -0,0 +1,51 @@
import { client } from "@/shared/api";
import { ENDPOINT } from "@/shared/config";
import type {
BannerFormData,
BannerInfo,
BannerList,
BannerParams,
BannerService,
} from "../model/banner-interface";
export class BannerApiService implements BannerService {
public async getBannerList(params: BannerParams): Promise<BannerList> {
const response = await client.get<BannerList>(
`${ENDPOINT.CMS_SERVICE.BANNERS}`,
{ params },
);
return response.data;
}
public async getBannerInfo(bnrSn: string): Promise<BannerInfo> {
const response = await client.get<BannerInfo>(
`${ENDPOINT.CMS_SERVICE.BANNERS}/${bnrSn}`,
);
return response.data;
}
public async createBanner(data: BannerFormData): Promise<void> {
const response = await client.post<void>(
ENDPOINT.CMS_SERVICE.BANNERS,
data,
);
return response.data;
}
public async updateBanner(
bnrSn: string,
data: BannerFormData,
): Promise<void> {
const response = await client.put<void>(
`${ENDPOINT.CMS_SERVICE.BANNERS}/${bnrSn}`,
data,
);
return response.data;
}
public async deleteBanner(bnrSn: string): Promise<void> {
await client.delete(`${ENDPOINT.CMS_SERVICE.BANNERS}/${bnrSn}`);
}
}
export const BannerApi = new BannerApiService();
+3
View File
@@ -0,0 +1,3 @@
export { CreatePage } from "./ui/create-page";
export { ModifyPage } from "./ui/modify-page";
export { BannerPage } from "./ui/banner-page";
+61
View File
@@ -0,0 +1,61 @@
import { Upload, type HttpResponse } from "tus-js-client";
import { getCookie, setCookie } from "@/shared/lib";
import { AUTH_USER_ID, CLAIM_NAME, ENDPOINT } from "@/shared/config";
type UploadOptions = {
handleUploadStart?: (upload: Upload) => void;
handleProgress?: (percentage: string) => void;
handleSuccess?: (upload: Upload) => void;
handleResponse?: (res: HttpResponse) => void;
};
export const handleUpload = (files: FileList, options?: UploadOptions) => {
let metadata = {};
const file = files.item(0);
if (!file) return;
metadata = {
filename: file.name,
filetype: file.type,
};
const atflCd = getCookie("atfl-cd-key");
if (atflCd) {
metadata = {
...metadata,
atflCd,
};
}
const upload = new Upload(file, {
endpoint: `${ENDPOINT.ATTACH_SERVICE.TUS_UPLOAD}`,
chunkSize: 10 * 1024 * 1024,
headers: {
[CLAIM_NAME]: getCookie(CLAIM_NAME) ?? "",
[AUTH_USER_ID]: getCookie(AUTH_USER_ID) ?? "",
},
metadata,
onError(error) {
console.error(`Failed because: ${error}`);
},
onProgress(bytesUploaded, bytesTotal) {
const percentage = ((bytesUploaded / bytesTotal) * 100).toFixed(2);
options?.handleProgress?.(percentage);
},
onAfterResponse(req, res) {
const fileInfo = res.getHeader("X-File-Info");
if (fileInfo === undefined || fileInfo === null) return;
setCookie("atfl-cd-key", JSON.parse(fileInfo)?.atflCd ?? "");
options?.handleResponse?.(res);
},
onSuccess() {
options?.handleSuccess?.(upload);
},
});
upload.start();
options?.handleUploadStart?.(upload);
console.log("upload start >>>>>", upload);
};
@@ -0,0 +1,56 @@
import { z } from "zod";
import type { PageableList, PageableParams } from "@/shared/model";
export const BANNER_TYPE = {
BNRMAIN: "BNRMAIN",
BNRFTER: "BNRFTER",
} as const;
export type BannerType = (typeof BANNER_TYPE)[keyof typeof BANNER_TYPE];
export interface BannerParams extends PageableParams {
bnrTypeCd?: BannerType;
bnrTtl?: string;
useYn?: string;
}
export interface Banner {
bnrSn: number;
bnrTypeCd: BannerType;
bnrTypeNm: string;
bnrTtl: string;
lnkgUrlAddr: string;
useYn: string;
mdfcnDt: string;
mdfr: string;
mdfrNm: string;
}
export interface BannerInfo extends Omit<Banner, "mdfr"> {
bnrExpln: string;
newWndwYn: string;
atflCd: string;
regDt: string;
rgTrNm: string;
}
export const bannerFormSchema = z.object({
bnrTypeCd: z.string(),
bnrTtl: z.string(),
bnrExpln: z.string().nullish(),
lnkgUrlAddr: z.string().nullish(),
newWndwYn: z.string(),
atflCd: z.string().nullish(),
useYn: z.string(),
});
export type BannerFormData = z.infer<typeof bannerFormSchema>;
export type BannerList = PageableList<Banner>;
export interface BannerService {
getBannerList: (params: BannerParams) => Promise<BannerList>;
getBannerInfo: (bnrSn: string) => Promise<BannerInfo>;
createBanner: (data: BannerFormData) => Promise<void>;
updateBanner: (bnrSn: string, data: BannerFormData) => Promise<void>;
deleteBanner: (bnrSn: string) => Promise<void>;
}
@@ -0,0 +1,55 @@
"use client";
import dayjs from "dayjs";
import { type ColumnDef } from "@tanstack/react-table";
import { Checkbox } from "@common/components/ui";
import { type Banner } from "./banner-interface";
export const columns: ColumnDef<Banner>[] = [
{
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"
/>
),
},
// TODO: 배너 이미지 셀 추가
{
accessorKey: "bnrTtl",
header: "배너명",
},
{
accessorKey: "bnrTypeNm",
header: "배너구분",
},
{
accessorKey: "useYn",
header: "사용여부",
},
{
accessorKey: "mdfrNm",
header: "최종수정자",
},
{
accessorKey: "mdfcnDt",
header: "최종수정일",
cell: ({ row }) => {
const date = row.getValue("mdfcnDt") as string;
return date ? dayjs(date).format("YYYY-MM-DD") : "";
},
},
];
@@ -0,0 +1,44 @@
"use client";
import dayjs from "dayjs";
import type { ColumnDef } from "@tanstack/react-table";
import { Checkbox } from "@common/components/ui";
import type { Post } from "@/entities/posting";
export const columns: ColumnDef<Post>[] = [
{
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: "siteNm",
header: "사이트",
},
{
accessorKey: "pstgPrdSeNm",
header: "게시기간",
cell: ({ row }) => {
const pstgPrdSeNm = row.original.pstgPrdSeNm;
const pstgBgngDt = row.original.pstgBgngDt;
const pstgEndDt = row.original.pstgEndDt;
return `${pstgPrdSeNm} (${dayjs(pstgBgngDt).format("YYYY-MM-DD")} ~ ${dayjs(pstgEndDt).format("YYYY-MM-DD")})`;
},
},
];
+337
View File
@@ -0,0 +1,337 @@
"use client";
import Image from "next/image";
import { useEffect, useState } from "react";
import { useRouter } from "next/navigation";
import { useQuery } from "@tanstack/react-query";
import type { UseFormReturn } from "react-hook-form";
import type { HttpResponse, Upload } from "tus-js-client";
import { useConfirm } from "@frontend-opensource/use-react-hooks";
import {
Button,
FileUploader,
Input,
Label,
Progress,
RadioGroup,
RadioGroupItem,
Table,
TableBody,
TableCell,
TableHead,
TableRow,
Textarea,
} from "@common/components";
import { CodeSelect } from "@/entities/code";
import { AttachmentApi } from "@/entities/attachment";
import { ENDPOINT } from "@/shared/config";
import { Form, FormControl, FormField, FormItem } from "@/shared/ui/form";
import type { BannerFormData } from "../model/banner-interface";
import { handleUpload } from "../lib/utils";
type BannerFormProps = {
form: UseFormReturn<BannerFormData>;
bnrSn?: string;
handleSave: (data: BannerFormData) => Promise<void>;
handleDelete?: () => Promise<void>;
};
function BannerForm({
form,
bnrSn,
handleSave,
handleDelete,
}: BannerFormProps) {
const isModifyMode = !!bnrSn;
const atflCd = form.watch("atflCd");
const router = useRouter();
const { confirm } = useConfirm();
const [attachmentFileId, setAttachmentFileId] = useState("");
const [upload, setUpload] = useState<Upload | null>(null);
const [progress, setProgress] = useState(0);
const { data: atflId } = useQuery({
queryKey: [ENDPOINT.ATTACH_SERVICE.ATTACHMENTS, atflCd],
queryFn: () => AttachmentApi.getAttachments(atflCd!),
select: (data) => data?.[data.length - 1]?.atflId,
enabled: !!atflCd,
});
const onSubmit = async (data: BannerFormData) => {
const confirmMessage = isModifyMode
? "수정하시겠습니까?"
: "저장하시겠습니까?";
if (await confirm(confirmMessage)) {
await handleSave(data);
}
};
const onDelete = async () => {
if (await confirm("삭제하시겠습니까?")) {
await handleDelete?.();
}
};
const handleProgress = (percentage: string) => {
setProgress(Number(percentage));
};
const handleUploadResponse = async (res: HttpResponse) => {
const fileInfo = res.getHeader("X-File-Info");
if (fileInfo === undefined || fileInfo === null) return;
const { atflCd, atflId } = JSON.parse(fileInfo) ?? {};
if (atflCd) form.setValue("atflCd", atflCd);
if (atflId) setAttachmentFileId(atflId);
};
useEffect(() => {
if (atflId) setAttachmentFileId(atflId);
}, [atflId]);
return (
<>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<div className="card">
<Table variant="secondary">
<colgroup>
<col width="20%" />
<col width="80%" />
</colgroup>
<TableBody>
<TableRow>
<TableHead>배너명</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="bnrTtl"
render={({ field }) => (
<FormItem>
<FormControl>
<Input
{...field}
value={field.value ?? ""}
className="!text-[1.3rem]"
placeholder="배너명을 입력해주세요"
/>
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>배너구분</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="bnrTypeCd"
render={({ field }) => (
<FormItem>
<FormControl>
<CodeSelect
{...field}
value={field.value ?? ""}
upCd="BNRTYPE"
onValueChange={field.onChange}
/>
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>배너 이미지</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="atflCd"
render={() => (
<FormItem className="flex flex-col">
<FormControl>
<FileUploader
id="atflCd"
className="!h-auto !max-w-none flex-col !items-start"
accept=".jpeg,.jpg,.png"
onUpload={(e) => {
const files = e.target.files;
if (!files) return;
handleUpload(files, {
handleUploadStart: setUpload,
handleProgress,
handleSuccess: setUpload,
handleResponse: handleUploadResponse,
});
}}
customPreview={
<>
<Label htmlFor="atflCd">
<div className="relative h-[27rem] w-[48rem] overflow-hidden rounded-sm border border-border bg-accent">
<p className="absolute bottom-3 left-0 p-2 text-base leading-none">
{(upload?.file as File)?.name}
</p>
<div className="relative h-full w-full">
{attachmentFileId && (
<Image
src={`${ENDPOINT.ATTACH_SERVICE.IMAGES}/${attachmentFileId}`}
alt="업로드된 이미지"
fill
objectFit="contain"
/>
)}
</div>
<Progress
value={progress}
className="absolute bottom-0 left-0"
/>
</div>
</Label>
<div className="mb-2 text-lg font-medium text-label">
<ul className="ml-10 list-disc">
<li>
이미지 파일(jpg, jpeg, png, gif)만
업로드 가능합니다.
</li>
</ul>
</div>
</>
}
/>
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>링크 URL</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="lnkgUrlAddr"
render={({ field }) => (
<FormItem>
<FormControl>
<Input
{...field}
value={field.value ?? ""}
className="!text-[1.3rem]"
placeholder="배너에 연결할 링크를 입력해주세요"
/>
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>링크 열기</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="newWndwYn"
render={({ field }) => (
<FormItem>
<FormControl>
<RadioGroup
{...field}
name="newWndwYn"
onValueChange={field.onChange}
>
<div className="flex items-center space-x-2">
<RadioGroupItem value="N" id="newWndwN" />
<Label htmlFor="newWndwN">현재창</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="Y" id="newWndwY" />
<Label htmlFor="newWndwY">새창</Label>
</div>
</RadioGroup>
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>배너 설명</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="bnrExpln"
render={({ field }) => (
<FormItem>
<FormControl>
<Textarea
{...field}
value={field.value ?? ""}
className="!text-[1.3rem] !leading-6"
placeholder="배너에 대한 설명을 입력해주세요"
/>
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>사용여부</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="useYn"
render={({ field }) => (
<FormItem>
<FormControl>
<RadioGroup
{...field}
name="useYn"
onValueChange={field.onChange}
>
<div className="flex items-center space-x-2">
<RadioGroupItem value="Y" id="useY" />
<Label htmlFor="useY">사용</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="N" id="useN" />
<Label htmlFor="useN">미사용</Label>
</div>
</RadioGroup>
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
</TableBody>
</Table>
</div>
<div className="flex justify-center gap-4 pb-12 pt-10">
<Button
size="lg"
color="white"
type="button"
onClick={() => router.back()}
>
목록
</Button>
<Button size="lg" type="submit">
저장
</Button>
{isModifyMode && (
<Button size="lg" color="red" type="button" onClick={onDelete}>
삭제
</Button>
)}
</div>
</form>
</Form>
</>
);
}
export { BannerForm };
+136
View File
@@ -0,0 +1,136 @@
"use client";
import { useEffect, useState } from "react";
import { useRouter } from "next/navigation";
import { useQuery } from "@tanstack/react-query";
import {
getCoreRowModel,
useReactTable,
type PaginationState,
type RowSelectionState,
} from "@tanstack/react-table";
import { Button, DataTable, Input, Label } from "@common/components";
import { POSTING_TYPE, PostingDialog } from "@/entities/posting";
import { ENDPOINT } from "@/shared/config";
import { BannerApi } from "../api/banner-service";
import { columns } from "../model/banner-table-columns";
import { type Banner, type BannerParams } from "../model/banner-interface";
type PostingTarget = {
pstgTrgtNm: string;
pstgTrgtNo: number;
};
function BannerPage() {
const router = useRouter();
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
const [postingTargets, setPostingTargets] = useState<PostingTarget[]>([]);
const [bannerQuery, setBannerQuery] = useState<BannerParams>({ bnrTtl: "" });
const [pagination, setPagination] = useState<PaginationState>({
pageIndex: 0,
pageSize: 5,
});
const { data: bannerList, refetch } = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.BANNERS],
queryFn: () =>
BannerApi.getBannerList({
...bannerQuery,
page: pagination.pageIndex,
size: pagination.pageSize,
}),
enabled: false,
});
const table = useReactTable({
data: bannerList?.content ?? [],
columns,
state: {
pagination,
rowSelection,
},
rowCount: bannerList?.totalElements ?? 0,
manualPagination: true,
enableMultiRowSelection: true,
getCoreRowModel: getCoreRowModel(),
onPaginationChange: setPagination,
onRowSelectionChange: setRowSelection,
});
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
refetch();
};
const handleRowClick = (row: Banner) => {
router.push(`/banner/register/${row.bnrSn}`);
};
useEffect(() => {
const postingTargets = table
.getFilteredSelectedRowModel()
.rows.map((row) => ({
pstgTrgtNm: row.original.bnrTtl,
pstgTrgtNo: row.original.bnrSn,
}));
if (!postingTargets.length) {
setPostingTargets([]);
return;
}
setPostingTargets(postingTargets);
}, [rowSelection]);
useEffect(() => {
refetch();
}, []);
useEffect(() => {
refetch();
}, [pagination]);
return (
<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={bannerQuery.bnrTtl}
onChange={(e) =>
setBannerQuery({ ...bannerQuery, bnrTtl: e.target.value })
}
/>
<Button className="h-[3.2rem]" type="submit">
조회
</Button>
</form>
</div>
<div className="card card-border">
<div className="card-header">
<div className="flex w-full items-center justify-between">
<h3 className="text-2xl font-medium text-label">배너 목록</h3>
<div className="flex gap-2">
<PostingDialog
postingType={POSTING_TYPE.BANNER}
postingTargets={postingTargets}
/>
<Button onClick={() => router.push("/banner/register")}>
등록
</Button>
</div>
</div>
</div>
<div className="card !m-[1.2rem] h-[30.4rem] overflow-auto !p-0">
<DataTable table={table} onRowClick={handleRowClick} viewPagination />
</div>
</div>
</div>
);
}
export { BannerPage };
@@ -0,0 +1,46 @@
"use client";
import { useRouter } from "next/navigation";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { useMutation } from "@tanstack/react-query";
import { useAlertStore } from "@/shared/lib/use-alert-store";
import {
bannerFormSchema,
type BannerFormData,
} from "../model/banner-interface";
import { BannerApi } from "../api/banner-service";
import { BannerForm } from "./banner-form";
function CreatePage() {
const router = useRouter();
const { setMessage: alert } = useAlertStore((state) => state);
const { mutateAsync: createBanner } = useMutation({
mutationFn: (data: BannerFormData) => BannerApi.createBanner(data),
onSuccess: () => {
alert("등록되었습니다.", router.back);
},
});
const form = useForm<BannerFormData>({
resolver: zodResolver(bannerFormSchema),
defaultValues: {
newWndwYn: "N",
useYn: "Y",
},
});
console.log(form.formState.errors);
return (
<div className="card card-border !mt-6">
<div className="card-header">
<h3 className="text-2xl font-medium text-label">배너 등록</h3>
</div>
<BannerForm form={form} handleSave={createBanner} />
</div>
);
}
export { CreatePage };
+140
View File
@@ -0,0 +1,140 @@
"use client";
import { useState } from "react";
import { useRouter } from "next/navigation";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import {
getCoreRowModel,
useReactTable,
type RowSelectionState,
} from "@tanstack/react-table";
import { useConfirm } from "@frontend-opensource/use-react-hooks";
import { Button, DataTable } from "@common/components/ui";
import { PostingApi, type WithModePost } from "@/entities/posting";
import { useAlertStore } from "@/shared/lib/use-alert-store";
import { ConfirmDialog } from "@/shared/ui/confirm-dialog";
import { ENDPOINT } from "@/shared/config";
import {
bannerFormSchema,
type BannerFormData,
} from "../model/banner-interface";
import { columns } from "../model/posting-table-columns";
import { BannerApi } from "../api/banner-service";
import { BannerForm } from "./banner-form";
function ModifyPage({ serial }: { serial: string }) {
const router = useRouter();
const { confirm } = useConfirm();
const queryClient = useQueryClient();
const { setMessage: alert } = useAlertStore((state) => state);
const [onConfirm, setOnConfirm] = useState(false);
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
const { data: bannerDetail } = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.BANNERS, serial],
queryFn: () => BannerApi.getBannerInfo(serial),
});
const { data: postHistory } = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.POSTINGS, serial],
queryFn: () => PostingApi.getPostingHistory({ pstgTrgtNo: Number(serial) }),
});
const { mutateAsync: updateBanner } = useMutation({
mutationFn: (data: BannerFormData) => BannerApi.updateBanner(serial, data),
onSuccess: () => {
alert("수정되었습니다.");
},
});
const { mutateAsync: deleteBanner } = useMutation({
mutationFn: () => BannerApi.deleteBanner(serial),
onSuccess: () => {
alert("삭제되었습니다.", router.back);
},
});
const { mutateAsync: requestPosting } = useMutation({
mutationFn: (data: WithModePost[]) => PostingApi.requestPosting(data),
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: [ENDPOINT.CMS_SERVICE.POSTINGS, serial],
});
},
});
const form = useForm<BannerFormData>({
resolver: zodResolver(bannerFormSchema),
values: bannerDetail,
});
const table = useReactTable({
data: postHistory?.content ?? [],
columns,
state: {
rowSelection,
},
enableMultiRowSelection: true,
getCoreRowModel: getCoreRowModel(),
onRowSelectionChange: setRowSelection,
});
const handleDeletePostingHistory = async () => {
const data = table.getFilteredSelectedRowModel().rows.map((row) => ({
...row.original,
mode: "D",
}));
setOnConfirm(true);
if (await confirm("삭제하시겠습니까?")) {
setOnConfirm(false);
await requestPosting(data);
alert("삭제되었습니다.");
}
setOnConfirm(false);
};
return (
<>
<div className="mt-6 flex flex-col">
<div className="card card-border">
<div className="card-header">
<h3 className="text-2xl font-medium text-label">배너 수정</h3>
</div>
<BannerForm
form={form}
bnrSn={serial}
handleSave={updateBanner}
handleDelete={deleteBanner}
/>
</div>
<div className="card card-border">
<div className="card-header">
<h3 className="text-2xl font-medium text-label">
사이트 게시 이력
</h3>
</div>
<div className="card">
<div className="mb-4 flex justify-end">
<Button
size="sm"
color="red"
disabled={!table.getFilteredSelectedRowModel().rows.length}
onClick={handleDeletePostingHistory}
>
이력삭제
</Button>
</div>
<DataTable table={table} />
</div>
</div>
</div>
<ConfirmDialog open={onConfirm} />
</>
);
}
export { ModifyPage };
@@ -57,7 +57,6 @@ export const columns: ColumnDef<Image>[] = [
{
accessorKey: "useYn",
header: "사용여부",
cell: ({ row }) => (row.getValue("useYn") === "Y" ? "사용" : "미사용"),
},
{
accessorKey: "mdfrNm",
+6 -1
View File
@@ -50,6 +50,7 @@ function ImagePage() {
pagination,
rowSelection,
},
rowCount: imageList?.totalElements ?? 0,
manualPagination: true,
enableMultiRowSelection: true,
getCoreRowModel: getCoreRowModel(),
@@ -86,6 +87,10 @@ function ImagePage() {
refetch();
}, []);
useEffect(() => {
refetch();
}, [pagination]);
return (
<div className="card card-border !mt-6">
<div className="card card-border">
@@ -120,7 +125,7 @@ function ImagePage() {
</div>
</div>
</div>
<div className="card !m-[1.2rem] h-[29.2rem] overflow-auto !p-0">
<div className="card !m-[1.2rem] h-[30.4rem] overflow-auto !p-0">
<DataTable table={table} onRowClick={handleRowClick} viewPagination />
</div>
</div>
+1
View File
@@ -24,6 +24,7 @@ export const ENDPOINT = {
CONTENTS: "/cms-service/api/v1/contents",
USER_ORGANIZATION: "/cms-service/api/v1/user-ognz",
IMAGES: "/cms-service/api/v1/images",
BANNERS: "/cms-service/api/v1/bnrs",
},
BOARD_SERVICE: {
BBS: "/board-service/api/v1/bbs",