From bca895ed480e91dab1ffd918c6300772dffdc37a Mon Sep 17 00:00:00 2001 From: bicochan Date: Wed, 4 Jun 2025 17:16:49 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=82=AC=EC=9A=A9=EC=9E=90=20=EC=84=A4?= =?UTF-8?q?=EB=AC=B8=20=ED=8F=BC=20=EC=9E=91=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cs/src/pages/survey/api/survey-service.ts | 5 + .../pages/survey/model/survey-interface.ts | 20 + .../pages/survey/ui/user-survey-article.tsx | 444 ++++++++++++------ 3 files changed, 331 insertions(+), 138 deletions(-) diff --git a/apps/cs/src/pages/survey/api/survey-service.ts b/apps/cs/src/pages/survey/api/survey-service.ts index e91b2202a..3a43d1d03 100644 --- a/apps/cs/src/pages/survey/api/survey-service.ts +++ b/apps/cs/src/pages/survey/api/survey-service.ts @@ -14,6 +14,7 @@ import type { ArticleList, ArticleParams, UserSurvey, + UserSurveyFormData, } from "../model/survey-interface"; export class SurveyApiService implements SurveyService { @@ -97,6 +98,10 @@ export class SurveyApiService implements SurveyService { ); return response.data; } + + public async saveUserSurvey(data: UserSurveyFormData): Promise { + await client.post(`${ENDPOINT.CMS_SERVICE.SURVEY}/user`, data); + } } export const SurveyApi = new SurveyApiService(); diff --git a/apps/cs/src/pages/survey/model/survey-interface.ts b/apps/cs/src/pages/survey/model/survey-interface.ts index c60c3523f..3f0a5dd0f 100644 --- a/apps/cs/src/pages/survey/model/survey-interface.ts +++ b/apps/cs/src/pages/survey/model/survey-interface.ts @@ -89,6 +89,24 @@ export const articleSchema = z.object({ mode: z.string(), }); +export const userSurveyFormSchema = z.object({ + srvySn: z.number(), + rspdnts: z.object({ + rspdntNm: z.string(), + gndrCd: z.string(), + crTypeCd: z.string(), + brdt: z.string(), + }), + rspnsRslts: z.array( + z.object({ + srvyQitemNo: z.number(), + qstnTypeCd: z.string(), + ansCn: z.string(), + etcAnsCn: z.string(), + }), + ), +}); + export type SurveyList = PageableList; export type SurveyFormData = z.infer; export type QuestionList = PageableList; @@ -100,6 +118,7 @@ export type UserSurvey = SurveyDetail & { articles: Article[]; })[]; }; +export type UserSurveyFormData = z.infer; export interface SurveyService { getSurveyList(params: SurveyParams): Promise; @@ -114,4 +133,5 @@ export interface SurveyService { getArticleList(params: ArticleParams): Promise; saveArticleList(data: Article[]): Promise; getUserSurvey(srvySn: number): Promise; + saveUserSurvey(data: UserSurveyFormData): Promise; } diff --git a/apps/cs/src/pages/survey/ui/user-survey-article.tsx b/apps/cs/src/pages/survey/ui/user-survey-article.tsx index 2b3e8ad8a..156d01aee 100644 --- a/apps/cs/src/pages/survey/ui/user-survey-article.tsx +++ b/apps/cs/src/pages/survey/ui/user-survey-article.tsx @@ -1,209 +1,377 @@ "use client"; -import { useQuery } from "@tanstack/react-query"; +import { useForm, type Control } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useMutation, useQuery } from "@tanstack/react-query"; import { Button, Checkbox, + DatePicker, Input, Label, RadioGroup, RadioGroupItem, Textarea, } from "@common/components"; -import { ENDPOINT } from "@/shared/config"; import { CodeSelect } from "@/entities/code"; -import { SurveyApi } from "../api/survey-service"; -import type { Article } from "../model/survey-interface"; -import { useState } from "react"; import { useAlertStore } from "@/shared/lib"; +import { ENDPOINT } from "@/shared/config"; +import { SurveyApi } from "../api/survey-service"; +import { + userSurveyFormSchema, + type Article, + type UserSurveyFormData, +} from "../model/survey-interface"; +import dayjs from "dayjs"; +import { Form, FormControl, FormField, FormItem } from "@/shared/ui/form"; function UserSurveyArticle({ srvySn }: { srvySn: string }) { + const alert = useAlertStore((state) => state.setMessage); + const { data: userSurvey } = useQuery({ queryKey: [ENDPOINT.CMS_SERVICE.SURVEY, srvySn], queryFn: () => SurveyApi.getUserSurvey(parseInt(srvySn)), }); + const { mutate: saveUserSurvey } = useMutation({ + mutationFn: (data: UserSurveyFormData) => SurveyApi.saveUserSurvey(data), + onSuccess: () => { + alert("설문이 제출되었습니다.", window.close); + }, + }); + + const form = useForm({ + resolver: zodResolver(userSurveyFormSchema), + values: { + srvySn: parseInt(srvySn), + rspdnts: { + rspdntNm: "", + gndrCd: "", + crTypeCd: "", + brdt: "", + }, + rspnsRslts: + userSurvey?.qitems.map((item) => ({ + srvyQitemNo: item.srvyQitemNo, + qstnTypeCd: item.qstnTypeCd, + ansCn: "", + etcAnsCn: "", + })) || [], + }, + }); + + const onSubmit = (data: UserSurveyFormData) => { + saveUserSurvey(data); + }; + return (
-
-

- {userSurvey?.srvyTtl} -

-
- - -
-
-
-
-

- 설문 참여자 정보 -

-
-
- 응답자명 - -
-
- 직업 - -
-
- 생년월일 - -
-
- 성별 - -
- - -
-
- - -
-
+
+ +
+

+ {userSurvey?.srvyTtl} +

+
+ +
-
-
-
- {userSurvey?.srvyWrtGdCn} -
-
- {userSurvey?.qitems.map((item) => ( -
- - {item.qstnSeq} - - {item.qstnTypeCd === "RADIO" && ( - - )} - {item.qstnTypeCd === "CHECKBOX" && ( - - )} - {item.qstnTypeCd === "INPUT" && ( - - )} +
+
+

+ 설문 참여자 정보 +

+
+
+ 응답자명 + +
+
+ 직업 + ( + + )} + /> +
+
+ 생년월일 + ( + + + { + field.onChange(dayjs(date).format("YYYYMMDD")); + }} + {...field} + /> + + + )} + /> +
+
+ 성별 + ( + + + +
+ + +
+
+ + +
+
+
+
+ )} + /> +
+
- ))} -
-
+
+
+ {userSurvey?.srvyWrtGdCn} +
+
+ {userSurvey?.qitems.map((item, qIndex) => ( +
+ + {item.qstnSeq} + + {item.qstnTypeCd === "RADIO" && ( + + )} + {item.qstnTypeCd === "CHECKBOX" && ( + + )} + {item.qstnTypeCd === "INPUT" && ( + + )} +
+ ))} +
+
+ +
); } function RadioArticle({ - name, + control, qstnCn, + index: i, articles, }: { - name: string; + control: Control; qstnCn: string; + index: number; articles: Article[]; }) { return (

{qstnCn}

- -
- {articles.map((article, index) => ( -
- - -
- ))} -
-
+ ( + + + +
+ {articles.map((article, j) => ( +
+ + +
+ ))} +
+
+
+
+ )} + />
); } function CheckboxArticle({ - name, + control, qstnCn, + index: i, maxChcCnt, articles, }: { - name: string; + control: Control; + index: number; qstnCn: string; maxChcCnt: number; articles: Article[]; }) { - const [selectedValues, setSelectedValues] = useState([]); const alert = useAlertStore((state) => state.setMessage); - const handleCheckboxChange = (value: number) => (checked: boolean) => { - if (checked && selectedValues.length >= maxChcCnt) { - alert("최대 선택 개수를 초과했습니다."); - return; - } - if (selectedValues.includes(value)) { - setSelectedValues((prev) => prev.filter((item) => item !== value)); - return; - } - setSelectedValues((prev) => [...prev, value]); - }; - return (

{qstnCn} (최대 {maxChcCnt}개 선택)

-
- {articles.map((article, index) => ( -
- - -
- ))} -
+ { + const selectedValues = field.value?.split(",") || []; + const handleCheckboxChange = + (value: string) => (checked: boolean) => { + const values = [...selectedValues]; + + if (checked) { + if (values.length >= maxChcCnt) { + alert("최대 선택 개수를 초과했습니다."); + return; + } + values.push(value); + } else { + const valueIndex = values.indexOf(value); + if (valueIndex !== -1) { + values.splice(valueIndex, 1); + } + } + + field.onChange(values.filter((v) => v.trim() !== "").join(",")); + }; + + return ( + + +
+ {articles.map((article, j) => { + const value = article.srvyArtclNo?.toString() ?? ""; + return ( +
+ + +
+ ); + })} +
+
+
+ ); + }} + />
); } -function TextareaArticle({ qstnCn }: { qstnCn: string; article?: Article }) { +function TextareaArticle({ + control, + qstnCn, + index: i, +}: { + control: Control; + qstnCn: string; + index: number; +}) { return (

{qstnCn}

-