feat: 설문 등록 페이지 작업

This commit is contained in:
2025-05-28 14:15:00 +09:00
parent a8e3ea321b
commit 1e02861573
11 changed files with 450 additions and 0 deletions
@@ -0,0 +1,2 @@
import { SurveyPage } from "@/pages/survey";
export default SurveyPage;
@@ -0,0 +1,2 @@
import { CreatePage } from "@/pages/survey";
export default CreatePage;
@@ -5,6 +5,7 @@ export const POSTING_TYPE = {
POPUP: "POPUP",
BANNER: "BANNER",
IMAGE: "IMAGE",
SURVEY: "SURVEY",
} as const;
export type PostingType = (typeof POSTING_TYPE)[keyof typeof POSTING_TYPE];
@@ -0,0 +1,23 @@
import { client } from "@/shared/api";
import { ENDPOINT } from "@/shared/config";
import type {
SurveyParams,
SurveyList,
SurveyService,
SurveyFormData,
} from "../model/survey-interface";
export class SurveyApiService implements SurveyService {
public async getSurveyList(params: SurveyParams): Promise<SurveyList> {
const response = await client.get<SurveyList>(ENDPOINT.CMS_SERVICE.SURVEY, {
params,
});
return response.data;
}
public async createSurvey(data: SurveyFormData): Promise<void> {
await client.post(ENDPOINT.CMS_SERVICE.SURVEY, data);
}
}
export const SurveyApi = new SurveyApiService();
+2
View File
@@ -0,0 +1,2 @@
export { SurveyPage } from "./ui/survey-page";
export { CreatePage } from "./ui/create-page";
@@ -0,0 +1,38 @@
import { z } from "zod";
import type { PageableList, PageableParams } from "@/shared/model";
export interface SurveyParams extends PageableParams {
keywordType?: string;
keyword?: string;
srvyTtl?: string;
srvyPrps?: string;
srvyTrgt?: string;
srvyWrtGdCn?: string;
delYn?: string;
}
export interface Survey {
srvySn: number;
srvyTtl: string;
srvyPrps: string;
srvyTrgt: string;
srvyTrgtNm: string;
delYn: string;
regDt: string;
rgTrNm: string;
}
export const surveyFormSchema = z.object({
srvyTtl: z.string(),
srvyPrps: z.string(),
srvyTrgt: z.string().nullish(),
srvyWrtGdCn: z.string().nullish(),
});
export type SurveyList = PageableList<Survey>;
export type SurveyFormData = z.infer<typeof surveyFormSchema>;
export interface SurveyService {
getSurveyList(params: SurveyParams): Promise<SurveyList>;
createSurvey(data: SurveyFormData): Promise<void>;
}
@@ -0,0 +1,60 @@
import type { ColumnDef } from "@tanstack/react-table";
import { Checkbox } from "@common/components";
import type { Survey } from "./survey-interface";
export const columns: ColumnDef<Survey>[] = [
{
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"
/>
),
meta: {
width: 50,
},
},
{
id: "no",
header: "NO",
cell: ({ row, table }) => {
const { pageIndex, pageSize } = table.getState().pagination;
const totalRows = table.getRowCount();
return totalRows - (pageIndex * pageSize + row.index);
},
meta: {
width: 50,
},
},
{
accessorKey: "srvyTtl",
header: "설문제목",
},
{
accessorKey: "srvyTrgt",
header: "설문대상",
},
{
id: "preview",
header: "설문 보기",
cell: () => {},
},
{
id: "result",
header: "설문 결과",
cell: () => {},
},
];
@@ -0,0 +1,40 @@
"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 { useAlertStore } from "@/shared/lib";
import {
surveyFormSchema,
type SurveyFormData,
} from "../model/survey-interface";
import { SurveyApi } from "../api/survey-service";
import { SurveyForm } from "./survey-form";
function CreatePage() {
const router = useRouter();
const { setMessage: alert } = useAlertStore((state) => state);
const { mutateAsync: createSurvey } = useMutation({
mutationFn: (data: SurveyFormData) => SurveyApi.createSurvey(data),
onSuccess: () => {
alert("등록되었습니다.", router.back);
},
});
const form = useForm<SurveyFormData>({
resolver: zodResolver(surveyFormSchema),
});
return (
<div className="card card-border !mb-0 !mt-6">
<div className="card-header">
<h3 className="text-2xl font-medium text-label">설문 등록</h3>
</div>
<SurveyForm form={form} handleSave={createSurvey} />
</div>
);
}
export { CreatePage };
+153
View File
@@ -0,0 +1,153 @@
import {
Input,
Label,
RadioGroup,
RadioGroupItem,
Table,
TableBody,
TableCell,
TableHead,
TableRow,
Textarea,
} from "@common/components";
import { FormWrapper } from "@/widgets/form-wrapper";
import { CodeSelect } from "@/entities/code";
import { FormControl, FormField, FormItem } from "@/shared/ui/form";
import type { SurveyFormData } from "../model/survey-interface";
import type { UseFormReturn } from "react-hook-form";
type SurveyFormProps = {
form: UseFormReturn<SurveyFormData>;
handleSave: (data: SurveyFormData) => Promise<void>;
};
function SurveyForm({ form, ...props }: SurveyFormProps) {
return (
<FormWrapper form={form} {...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="srvyTtl"
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="srvyPrps"
render={({ field }) => (
<FormItem>
<FormControl>
<Textarea
{...field}
value={field.value ?? ""}
className="!text-[1.3rem]"
placeholder="설문 목적을 입력하십시오."
/>
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>설문 작성 안내 내용</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="srvyWrtGdCn"
render={({ field }) => (
<FormItem>
<FormControl>
<Textarea
{...field}
value={field.value ?? ""}
className="!text-[1.3rem]"
placeholder="설문 작성 안내 내용을 입력하십시오."
/>
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>설문 대상</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="srvyTrgt"
render={({ field }) => (
<FormItem>
<FormControl>
<CodeSelect
upCd="SRVYTARGET"
onValueChange={field.onChange}
/>
</FormControl>
</FormItem>
)}
/>
</TableCell>
</TableRow>
{/* <TableRow>
<TableHead>사용 여부</TableHead>
<TableCell className="border">
<FormField
control={form.control}
name="useYn"
render={({ field }) => (
<FormItem>
<FormControl>
<RadioGroup
{...field}
name="useYn"
value={field.value ?? ""}
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 { SurveyForm };
+128
View File
@@ -0,0 +1,128 @@
"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 { AuthorityButton } from "@/entities/menu-authority";
import { ENDPOINT } from "@/shared/config";
import { SurveyApi } from "../api/survey-service";
import type { SurveyParams } from "../model/survey-interface";
import { columns } from "../model/table-columns";
type PostingTarget = {
pstgTrgtNm: string;
pstgTrgtNo: number;
};
function SurveyPage() {
const router = useRouter();
const [surveyParams, setSurveyParams] = useState<SurveyParams>({
srvyTtl: "",
});
const [pagination, setPagination] = useState<PaginationState>({
pageIndex: 0,
pageSize: 5,
});
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
const [postingTargets, setPostingTargets] = useState<PostingTarget[]>([]);
const { data: surveyList, refetch } = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.SURVEY, pagination],
queryFn: () =>
SurveyApi.getSurveyList({
...surveyParams,
page: pagination.pageIndex,
size: pagination.pageSize,
}),
});
const table = useReactTable({
data: surveyList?.content ?? [],
columns,
rowCount: surveyList?.totalElements ?? 0,
state: {
pagination,
rowSelection,
},
manualPagination: true,
getCoreRowModel: getCoreRowModel(),
onPaginationChange: setPagination,
onRowSelectionChange: setRowSelection,
});
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
refetch();
};
const handleRowClick = (row: unknown) => {};
useEffect(() => {
const postingTargets = table
.getFilteredSelectedRowModel()
.rows.map((row) => ({
pstgTrgtNm: row.original.srvyTtl,
pstgTrgtNo: row.original.srvySn,
}));
if (!postingTargets.length) {
setPostingTargets([]);
return;
}
setPostingTargets(postingTargets);
}, [rowSelection]);
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={surveyParams.srvyTtl}
onChange={(e) =>
setSurveyParams({ ...surveyParams, srvyTtl: 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>
<div className="flex items-center gap-2">
<PostingDialog
postingType={POSTING_TYPE.SURVEY}
postingTargets={postingTargets}
/>
<AuthorityButton
authority="regAuthrtYn"
onClick={() => router.push("/survey/register")}
>
등록
</AuthorityButton>
</div>
</div>
<div className="card !m-[1.2rem] min-h-[30rem] overflow-auto !p-0">
<DataTable table={table} onRowClick={handleRowClick} viewPagination />
</div>
</div>
</div>
);
}
export { SurveyPage };
+1
View File
@@ -40,6 +40,7 @@ export const ENDPOINT = {
UP_CODE: "/cms-service/api/v1/up-codes",
POPUPS: "/cms-service/api/v1/popups",
SCHEDULES: "/cms-service/api/v1/schdls",
SURVEY: "/cms-service/api/v1/srvy",
},
BOARD_SERVICE: {
BBS: "/board-service/api/v1/bbs",