feat: 팝업 등록 작업
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
import { PopupPage } from "@/pages/popup";
|
||||
export default PopupPage;
|
||||
@@ -0,0 +1,2 @@
|
||||
import { CreatePage } from "@/pages/popup";
|
||||
export default CreatePage;
|
||||
@@ -0,0 +1,25 @@
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import {
|
||||
popupFormSchema,
|
||||
type PopupFormData,
|
||||
type PopupList,
|
||||
type PopupParams,
|
||||
type PopupService,
|
||||
} from "../model/popup-interface";
|
||||
import { client } from "@/shared/api";
|
||||
|
||||
export class PopupApiService implements PopupService {
|
||||
async getPopupList(params: PopupParams): Promise<PopupList> {
|
||||
const response = await client.get<PopupList>(ENDPOINT.CMS_SERVICE.POPUPS, {
|
||||
params,
|
||||
});
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async createPopup(data: PopupFormData): Promise<void> {
|
||||
const validatedData = popupFormSchema.parse(data);
|
||||
await client.post(ENDPOINT.CMS_SERVICE.POPUPS, validatedData);
|
||||
}
|
||||
}
|
||||
|
||||
export const PopupApi = new PopupApiService();
|
||||
@@ -0,0 +1,2 @@
|
||||
export { CreatePage } from "./ui/create-page";
|
||||
export { PopupPage } from "./ui/popup-page";
|
||||
@@ -0,0 +1,49 @@
|
||||
import type { PageableList, PageableParams } from "@/shared/model";
|
||||
import { z } from "zod";
|
||||
|
||||
export interface PopupParams extends PageableParams {
|
||||
keyword?: string;
|
||||
popupTypeCd?: string;
|
||||
contsTypeCd?: string;
|
||||
useYn?: string;
|
||||
}
|
||||
|
||||
export interface Popup {
|
||||
popupSn: number;
|
||||
popupTypeCd: string;
|
||||
popupTypeNm: string;
|
||||
contsTypeCd: string;
|
||||
contsTypeNm: string;
|
||||
popupTtl: string;
|
||||
popupWdth: number;
|
||||
popupHgt: number;
|
||||
popupEndUseYn: string;
|
||||
useYn: string;
|
||||
mdfcnDt: string;
|
||||
mdfr: string;
|
||||
mdfrNm: string;
|
||||
}
|
||||
|
||||
export const popupFormSchema = z.object({
|
||||
popupTypeCd: z.string(),
|
||||
contsTypeCd: z.string(),
|
||||
popupTtl: z.string(),
|
||||
popupCn: z.string().nullish(),
|
||||
popupWdth: z.number(),
|
||||
popupHgt: z.number(),
|
||||
popupVtclPstn: z.number(),
|
||||
popupHrzntltyPstn: z.number(),
|
||||
popupEndUseYn: z.string(),
|
||||
popupLnkgUrl: z.string().nullish(),
|
||||
atflCd: z.string().nullish(),
|
||||
useYn: z.string(),
|
||||
rgtr: z.string(),
|
||||
});
|
||||
|
||||
export type PopupList = PageableList<Popup>;
|
||||
export type PopupFormData = z.infer<typeof popupFormSchema>;
|
||||
|
||||
export interface PopupService {
|
||||
getPopupList(params: PopupParams): Promise<PopupList>;
|
||||
createPopup(data: PopupFormData): Promise<void>;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import dayjs from "dayjs";
|
||||
import { type ColumnDef } from "@tanstack/react-table";
|
||||
import type { Popup } from "./popup-interface";
|
||||
|
||||
export const columns: ColumnDef<Popup>[] = [
|
||||
{
|
||||
id: "no",
|
||||
header: "NO",
|
||||
cell: ({ row, table }) => {
|
||||
const { pageIndex, pageSize } = table.getState().pagination;
|
||||
const totalRows = table.getRowCount();
|
||||
return totalRows - (pageIndex * pageSize + row.index);
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "popupTtl",
|
||||
header: "제목",
|
||||
},
|
||||
{
|
||||
accessorKey: "popupTypeNm",
|
||||
header: "팝업유형",
|
||||
},
|
||||
{
|
||||
accessorKey: "contsTypeNm",
|
||||
header: "콘텐츠유형",
|
||||
},
|
||||
{
|
||||
accessorKey: "useYn",
|
||||
header: "사용여부",
|
||||
},
|
||||
{
|
||||
accessorKey: "mdfcnDt",
|
||||
header: "최종수정일",
|
||||
cell: ({ row }) => {
|
||||
const date = dayjs(row.original.mdfcnDt);
|
||||
return date.format("YYYY-MM-DD");
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "mdfrNm",
|
||||
header: "최종수정자",
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,49 @@
|
||||
"use client";
|
||||
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useUserStore, useAlertStore } from "@/shared/lib";
|
||||
import { popupFormSchema, type PopupFormData } from "../model/popup-interface";
|
||||
import { PopupApi } from "../api/popup-service";
|
||||
import { PopupForm } from "./popup-form";
|
||||
import { useEffect } from "react";
|
||||
|
||||
function CreatePage() {
|
||||
const router = useRouter();
|
||||
const { userInfo } = useUserStore((state) => state);
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
|
||||
const { mutateAsync: createPopup } = useMutation({
|
||||
mutationFn: (data: PopupFormData) => PopupApi.createPopup(data),
|
||||
onSuccess: () => {
|
||||
alert("등록되었습니다.", router.back);
|
||||
},
|
||||
});
|
||||
|
||||
const form = useForm<PopupFormData>({
|
||||
resolver: zodResolver(popupFormSchema),
|
||||
defaultValues: {
|
||||
contsTypeCd: "39",
|
||||
popupEndUseYn: "Y",
|
||||
useYn: "Y",
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!userInfo || !form) return;
|
||||
form.setValue("rgtr", userInfo.userUnqId);
|
||||
}, [userInfo, form]);
|
||||
|
||||
return (
|
||||
<div className="card card-border !mb-0 !mt-6">
|
||||
<div className="card-header">
|
||||
<h3 className="text-2xl font-medium text-label">팝업 등록</h3>
|
||||
</div>
|
||||
<PopupForm form={form} handleSave={createPopup} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export { CreatePage };
|
||||
@@ -0,0 +1,333 @@
|
||||
import dynamic from "next/dynamic";
|
||||
import { type UseFormReturn } from "react-hook-form";
|
||||
import {
|
||||
Input,
|
||||
Label,
|
||||
RadioGroup,
|
||||
RadioGroupItem,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableRow,
|
||||
} from "@common/components";
|
||||
import { FormWrapper } from "@/widgets/form-wrapper";
|
||||
import { CodeSelect } from "@/entities/code";
|
||||
import { FormItem, FormControl, FormField } from "@/shared/ui/form";
|
||||
import type { PopupFormData } from "../model/popup-interface";
|
||||
import { GeneralFileUploader } from "@/widgets/post/ui/general-file-uploader";
|
||||
import { useState } from "react";
|
||||
|
||||
const Editor = dynamic(
|
||||
() => import("@/entities/editor").then((module) => module.Editor),
|
||||
{ ssr: false },
|
||||
);
|
||||
|
||||
type PopupFormProps = {
|
||||
form: UseFormReturn<PopupFormData>;
|
||||
popupSn?: string;
|
||||
handleSave: (data: PopupFormData) => Promise<void>;
|
||||
handleDelete?: () => Promise<void>;
|
||||
};
|
||||
|
||||
function PopupForm({ form, popupSn, ...props }: PopupFormProps) {
|
||||
const [deleteAttachmentIds, setDeleteAttachmentIds] = useState<string[]>([]);
|
||||
|
||||
return (
|
||||
<FormWrapper form={form} isModifyMode={!!popupSn} {...props}>
|
||||
<div className="card">
|
||||
<Table variant="secondary">
|
||||
<colgroup>
|
||||
<col width="15%" />
|
||||
<col width="85%" />
|
||||
</colgroup>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableHead>제목</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="popupTtl"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
value={field.value ?? ""}
|
||||
className="!text-[1.3rem]"
|
||||
placeholder="팝업 제목을 입력하십시오."
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>팝업 크기</TableHead>
|
||||
<TableCell className="border">
|
||||
<div className="flex items-center gap-1">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="popupWdth"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
type="number"
|
||||
value={field.value ?? 0}
|
||||
className="w-[10rem] !text-[1.3rem]"
|
||||
placeholder="너비"
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
if (isNaN(parseInt(value))) return;
|
||||
field.onChange(parseInt(value));
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<span className="w-5 text-center">×</span>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="popupHgt"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
type="number"
|
||||
value={field.value ?? 0}
|
||||
className="w-[10rem] !text-[1.3rem]"
|
||||
placeholder="높이"
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
if (isNaN(parseInt(value))) return;
|
||||
field.onChange(parseInt(value));
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>팝업 위치</TableHead>
|
||||
<TableCell className="border">
|
||||
<div className="flex items-center gap-1">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="popupHrzntltyPstn"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
type="number"
|
||||
value={field.value ?? 0}
|
||||
className="w-[10rem] !text-[1.3rem]"
|
||||
placeholder="Left"
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
if (isNaN(parseInt(value))) return;
|
||||
field.onChange(parseInt(value));
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<span className="w-5 text-center">/</span>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="popupVtclPstn"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
type="number"
|
||||
value={field.value ?? 0}
|
||||
className="w-[10rem] !text-[1.3rem]"
|
||||
placeholder="Top"
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
if (isNaN(parseInt(value))) return;
|
||||
field.onChange(parseInt(value));
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>팝업 유형</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="popupTypeCd"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<CodeSelect
|
||||
upCd="POPUPTYPE"
|
||||
onValueChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>콘텐츠 유형</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="contsTypeCd"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<RadioGroup
|
||||
{...field}
|
||||
name="contsTypeCd"
|
||||
onValueChange={field.onChange}
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="39" id="contsTypeCd39" />
|
||||
<Label htmlFor="contsTypeCd39">콘텐츠</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="38" id="contsTypeCd38" />
|
||||
<Label htmlFor="contsTypeCd38">링크</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>링크 주소</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="popupLnkgUrl"
|
||||
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="popupCn"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Editor
|
||||
value={field.value ?? ""}
|
||||
onEditorChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>첨부 파일</TableHead>
|
||||
<TableCell className="border">
|
||||
<GeneralFileUploader
|
||||
form={form}
|
||||
deleteAttachmentIds={deleteAttachmentIds}
|
||||
setDeleteAttachmentIds={setDeleteAttachmentIds}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>팝업 그만 보기 사용 여부</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="popupEndUseYn"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<RadioGroup
|
||||
{...field}
|
||||
name="popupEndUseYn"
|
||||
onValueChange={field.onChange}
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="Y" id="popupEndUseY" />
|
||||
<Label htmlFor="popupEndUseY">사용</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="N" id="popupEndUseN" />
|
||||
<Label htmlFor="popupEndUseN">사용안함</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</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>
|
||||
</FormWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
export { PopupForm };
|
||||
@@ -0,0 +1,92 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { keepPreviousData, useQuery } from "@tanstack/react-query";
|
||||
import {
|
||||
getCoreRowModel,
|
||||
useReactTable,
|
||||
type PaginationState,
|
||||
} from "@tanstack/react-table";
|
||||
import { Button, DataTable, Input, Label } from "@common/components";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import { PopupApi } from "../api/popup-service";
|
||||
import { columns } from "../model/table-columns";
|
||||
import type { Popup, PopupParams } from "../model/popup-interface";
|
||||
|
||||
function PopupPage() {
|
||||
const router = useRouter();
|
||||
|
||||
const [popupParams, setPopupParams] = useState<PopupParams>({
|
||||
keyword: "",
|
||||
});
|
||||
const [pagination, setPagination] = useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
pageSize: 10,
|
||||
});
|
||||
|
||||
const { data: popupList, refetch } = useQuery({
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.POPUPS],
|
||||
queryFn: () =>
|
||||
PopupApi.getPopupList({
|
||||
...popupParams,
|
||||
page: pagination.pageIndex,
|
||||
size: pagination.pageSize,
|
||||
}),
|
||||
placeholderData: keepPreviousData,
|
||||
});
|
||||
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
refetch();
|
||||
};
|
||||
|
||||
const handleRowClick = (row: Popup) => {
|
||||
router.push(`/popup/register/${row.popupSn}`);
|
||||
};
|
||||
|
||||
const table = useReactTable({
|
||||
data: popupList?.content ?? [],
|
||||
columns,
|
||||
rowCount: popupList?.totalElements ?? 0,
|
||||
state: {
|
||||
pagination,
|
||||
},
|
||||
onPaginationChange: setPagination,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
manualPagination: true,
|
||||
});
|
||||
|
||||
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-[20rem] rounded-sm !text-[1.3rem]"
|
||||
value={popupParams.keyword}
|
||||
onChange={(e) =>
|
||||
setPopupParams({ ...popupParams, 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("/popup/register")}>등록</Button>
|
||||
</div>
|
||||
<div className="card !m-[1.2rem] h-[29.2rem] overflow-auto !p-0">
|
||||
<DataTable table={table} onRowClick={handleRowClick} viewPagination />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export { PopupPage };
|
||||
@@ -37,7 +37,8 @@ export const ENDPOINT = {
|
||||
TEMPLATE: "/cms-service/api/v1/tmplt",
|
||||
TERMS: "/cms-service/api/v1/trms",
|
||||
LWPO_CODE: "/cms-service/api/v1/lwpo-cds",
|
||||
UP_CODE: "/cms-service/api/v1/up-codes",
|
||||
UP_CODE: "/cms-service/api/v1/up-codes",
|
||||
POPUPS: "/cms-service/api/v1/popups",
|
||||
},
|
||||
BOARD_SERVICE: {
|
||||
BBS: "/board-service/api/v1/bbs",
|
||||
|
||||
@@ -4,10 +4,9 @@ import type { HttpResponse, Upload } from "tus-js-client";
|
||||
import { Button, FileUploader } from "@common/components";
|
||||
import { handleTusUpload } from "@/entities/tus";
|
||||
import { type Attachment } from "@/entities/attachment";
|
||||
import type { PostFormData, PostUpdateFormData } from "@/entities/post";
|
||||
import { setCookie, useAlertStore, useMenuStore } from "@/shared/lib";
|
||||
|
||||
function GeneralFileUploader<T extends PostFormData | PostUpdateFormData>({
|
||||
function GeneralFileUploader<T extends { atflCd?: string | null }>({
|
||||
form,
|
||||
attachmentData,
|
||||
deleteAttachmentIds,
|
||||
|
||||
Reference in New Issue
Block a user