WIP: 설문 보기 작업중

This commit is contained in:
2025-06-02 16:09:07 +09:00
parent e91818e130
commit 4e0c4e9b28
10 changed files with 241 additions and 154 deletions
@@ -1,2 +0,0 @@
import { SurveyArticle } from "@/pages/survey";
export default SurveyArticle;
@@ -0,0 +1,10 @@
import { UserSurveyArticle } from "@/pages/survey";
export default async function UserSurveyArticlePage({
params,
}: {
params: Promise<{ srvySn: string }>;
}) {
const { srvySn } = await params;
return <UserSurveyArticle srvySn={srvySn} />;
}
@@ -13,6 +13,7 @@ import type {
Article,
ArticleList,
ArticleParams,
UserSurvey,
} from "../model/survey-interface";
export class SurveyApiService implements SurveyService {
@@ -89,6 +90,13 @@ export class SurveyApiService implements SurveyService {
public async saveArticleList(data: Article[]): Promise<void> {
await client.post(ENDPOINT.CMS_SERVICE.SURVEY_ARTICLE, data);
}
public async getUserSurvey(srvySn: number): Promise<UserSurvey> {
const response = await client.get<UserSurvey>(
`${ENDPOINT.CMS_SERVICE.SURVEY}/user/${srvySn}`,
);
return response.data;
}
}
export const SurveyApi = new SurveyApiService();
+1 -1
View File
@@ -2,4 +2,4 @@ export { SurveyPage } from "./ui/survey-page";
export { CreatePage } from "./ui/create-page";
export { DetailPage } from "./ui/detail-page";
export { ModifyPage } from "./ui/modify-page";
export { SurveyArticle } from "./ui/survey-article";
export { UserSurveyArticle } from "./ui/user-survey-article";
@@ -12,7 +12,7 @@ import { ArticleManagementDialog } from "../ui/article-management-dialog";
const QUESTION_TYPE = {
RADIO: "객관식",
CHECK: "다중응답",
CHECKBOX: "다중응답",
INPUT: "주관식",
} as const;
@@ -3,7 +3,7 @@ import type { PageableList, PageableParams } from "@/shared/model";
export const QUESTION_TYPE_CODE = {
RADIO: "RADIO",
CHECK: "CHECK",
CHECKBOX: "CHECKBOX",
INPUT: "INPUT",
} as const;
@@ -95,6 +95,11 @@ export type QuestionList = PageableList<Question>;
export type QuestionFormData = z.infer<typeof questionFormSchema>;
export type Article = z.infer<typeof articleSchema>;
export type ArticleList = PageableList<Article>;
export type UserSurvey = SurveyDetail & {
qitems: (Question & {
articles: Article[];
})[];
};
export interface SurveyService {
getSurveyList(params: SurveyParams): Promise<SurveyList>;
@@ -108,4 +113,5 @@ export interface SurveyService {
deleteQuestion(srvyQitemNo: number): Promise<void>;
getArticleList(params: ArticleParams): Promise<ArticleList>;
saveArticleList(data: Article[]): Promise<void>;
getUserSurvey(srvySn: number): Promise<UserSurvey>;
}
@@ -65,7 +65,7 @@ export const columns: ColumnDef<Survey>[] = [
const left = window.screen.width / 2 - width / 2 + window.screenX;
const top = window.screen.height / 2 - height / 2 + window.screenY;
window.open(
`/survey-article/${row.original.srvySn}`,
`/user-survey-article/${row.original.srvySn}`,
"",
`width=${width},height=${height},left=${left},top=${top}`,
);
@@ -107,7 +107,7 @@ function QuestionForm({ form, actionSlot, handleSave }: QuestionFormProps) {
/>
</TableCell>
</TableRow>
{qstnTypeCd === "CHECK" && (
{qstnTypeCd === "CHECKBOX" && (
<TableRow>
<TableHead>최대선택갯수</TableHead>
<TableCell className="border">
@@ -134,7 +134,7 @@ function QuestionForm({ form, actionSlot, handleSave }: QuestionFormProps) {
</TableCell>
</TableRow>
)}
{(qstnTypeCd === "RADIO" || qstnTypeCd === "CHECK") && (
{(qstnTypeCd === "RADIO" || qstnTypeCd === "CHECKBOX") && (
<TableRow>
<TableHead>기타답변여부</TableHead>
<TableCell className="border">
@@ -1,146 +0,0 @@
"use client";
import { CodeSelect } from "@/entities/code";
import {
Button,
Checkbox,
Input,
Label,
RadioGroup,
RadioGroupItem,
Textarea,
} from "@common/components";
function SurveyArticle() {
return (
<div className="h-screen bg-[#f1f6f9]">
<div className="card card-border">
<div className="card-header">
<h1 className="text-2xl font-bold text-foreground">설문 제목</h1>
<div className="flex gap-2">
<Button>제출</Button>
<Button color="white">닫기</Button>
</div>
</div>
<div className="card">
<div>
<h2 className="text-[1.3rem] font-bold text-foreground">
설문 참여자 정보
</h2>
<div className="my-4 flex gap-4">
<div className="flex flex-1 flex-col gap-2">
<span>응답자명</span>
<Input
className="!text-[1.3rem]"
placeholder="이름을 입력해주세요"
/>
</div>
<div className="flex flex-1 flex-col gap-2">
<span>직업</span>
<CodeSelect upCd="" className="w-full" />
</div>
<div className="flex flex-1 flex-col gap-2">
<span>생년월일</span>
<Input type="date" className="!text-[1.3rem]" />
</div>
<div className="flex flex-1 flex-col gap-2">
<span>성별</span>
<RadioGroup name="gndrCd" className="h-[3.2rem]">
<div className="flex items-center space-x-2">
<RadioGroupItem value="M" id="M" />
<Label htmlFor="M">남자</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="F" id="F" />
<Label htmlFor="F">여자</Label>
</div>
</RadioGroup>
</div>
</div>
</div>
<hr className="my-8" />
<div className="h-[8rem] overflow-auto rounded-sm bg-accent p-4">
답변 설명
</div>
<div className="mt-10 flex flex-col gap-10">
<div className="flex items-start gap-4">
<span className="flex size-8 items-center justify-center rounded-full bg-primary text-[1.3rem] font-medium text-primary-foreground">
1
</span>
<RadioArticle />
</div>
<div className="flex items-start gap-4">
<span className="flex size-8 items-center justify-center rounded-full bg-primary text-[1.3rem] font-medium text-primary-foreground">
2
</span>
<CheckboxArticle />
</div>
<div className="flex items-start gap-4">
<span className="flex size-8 items-center justify-center rounded-full bg-primary text-[1.3rem] font-medium text-primary-foreground">
3
</span>
<TextareaArticle />
</div>
</div>
</div>
</div>
</div>
);
}
function RadioArticle() {
return (
<div>
<p className="mb-4 text-2xl font-bold text-foreground">질문 1</p>
<RadioGroup name="name">
<div className="flex flex-col gap-3">
<div className="flex items-center space-x-2">
<RadioGroupItem value="1" id="r1" />
<Label htmlFor="r1" className="!text-[1.4rem]">
답변1
</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="2" id="r2" />
<Label htmlFor="r2" className="!text-[1.4rem]">
답변2
</Label>
</div>
</div>
</RadioGroup>
</div>
);
}
function CheckboxArticle() {
return (
<div>
<p className="mb-4 text-2xl font-bold text-foreground">질문 2</p>
<div className="flex flex-col gap-3">
<div className="flex h-[1.8rem] items-center gap-2">
<Checkbox id="chk1" />
<Label htmlFor="chk1" className="!text-[1.4rem]">
답변1
</Label>
</div>
<div className="flex h-[1.8rem] items-center gap-2">
<Checkbox id="chk2" />
<Label htmlFor="chk2" className="!text-[1.4rem]">
답변2
</Label>
</div>
</div>
</div>
);
}
function TextareaArticle() {
return (
<div className="w-full">
<p className="mb-4 text-2xl font-bold text-foreground">질문 3</p>
<Textarea className="!text-[1.3rem]" placeholder="답변을 입력해주세요" />
</div>
);
}
export { SurveyArticle };
@@ -0,0 +1,211 @@
"use client";
import { useQuery } from "@tanstack/react-query";
import {
Button,
Checkbox,
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";
function UserSurveyArticle({ srvySn }: { srvySn: string }) {
const { data: userSurvey } = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.SURVEY, srvySn],
queryFn: () => SurveyApi.getUserSurvey(parseInt(srvySn)),
});
return (
<div className="h-screen bg-[#f1f6f9] p-4">
<div className="card card-border !m-0 max-h-full overflow-auto">
<div className="card-header sticky -top-2 z-10 bg-background">
<h1 className="text-2xl font-bold text-foreground">
{userSurvey?.srvyTtl}
</h1>
<div className="flex gap-2">
<Button>제출</Button>
<Button color="white" onClick={() => window.close()}>
닫기
</Button>
</div>
</div>
<div className="card">
<div>
<h2 className="text-[1.3rem] font-bold text-foreground">
설문 참여자 정보
</h2>
<div className="my-4 flex gap-4">
<div className="flex flex-1 flex-col gap-2">
<span>응답자명</span>
<Input
className="!text-[1.3rem]"
placeholder="이름을 입력해주세요"
/>
</div>
<div className="flex flex-1 flex-col gap-2">
<span>직업</span>
<CodeSelect upCd="" className="w-full" />
</div>
<div className="flex flex-1 flex-col gap-2">
<span>생년월일</span>
<Input type="date" className="!text-[1.3rem]" />
</div>
<div className="flex flex-1 flex-col gap-2">
<span>성별</span>
<RadioGroup name="gndrCd" className="h-[3.2rem]">
<div className="flex items-center space-x-2">
<RadioGroupItem value="M" id="M" />
<Label htmlFor="M">남자</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="F" id="F" />
<Label htmlFor="F">여자</Label>
</div>
</RadioGroup>
</div>
</div>
</div>
<hr className="my-8" />
<div className="h-[8rem] overflow-auto rounded-sm bg-accent p-4">
{userSurvey?.srvyWrtGdCn}
</div>
<div className="mt-10 flex flex-col gap-10">
{userSurvey?.qitems.map((item) => (
<div className="flex items-start gap-4" key={item.srvyQitemNo}>
<span className="flex size-8 items-center justify-center rounded-full bg-primary text-[1.3rem] font-medium text-primary-foreground">
{item.qstnSeq}
</span>
{item.qstnTypeCd === "RADIO" && (
<RadioArticle
name={`r-${item.srvyQitemNo}`}
qstnCn={item.qstnCn}
articles={item.articles}
/>
)}
{item.qstnTypeCd === "CHECKBOX" && (
<CheckboxArticle
name={`chk-${item.srvyQitemNo}`}
qstnCn={item.qstnCn}
maxChcCnt={item.maxChcCnt}
articles={item.articles}
/>
)}
{item.qstnTypeCd === "INPUT" && (
<TextareaArticle
qstnCn={item.qstnCn}
article={item.articles[0]}
/>
)}
</div>
))}
</div>
</div>
</div>
</div>
);
}
function RadioArticle({
name,
qstnCn,
articles,
}: {
name: string;
qstnCn: string;
articles: Article[];
}) {
return (
<div>
<p className="mb-4 text-2xl font-bold text-foreground">{qstnCn}</p>
<RadioGroup name={name}>
<div className="flex flex-col gap-3">
{articles.map((article, index) => (
<div
className="flex items-center space-x-2"
key={article.srvyArtclNo}
>
<RadioGroupItem
value={article.srvyArtclNo?.toString() ?? ""}
id={`${name}-${index}`}
/>
<Label htmlFor={`${name}-${index}`} className="!text-[1.4rem]">
{article.artclCn}
</Label>
</div>
))}
</div>
</RadioGroup>
</div>
);
}
function CheckboxArticle({
name,
qstnCn,
maxChcCnt,
articles,
}: {
name: string;
qstnCn: string;
maxChcCnt: number;
articles: Article[];
}) {
const [selectedValues, setSelectedValues] = useState<number[]>([]);
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 (
<div>
<p className="mb-4 text-2xl font-bold text-foreground">
{qstnCn} (최대 {maxChcCnt}개 선택)
</p>
<div className="flex flex-col gap-3">
{articles.map((article, index) => (
<div
className="flex h-[1.8rem] items-center gap-2"
key={article.srvyArtclNo}
>
<Checkbox
id={`${name}-${index}`}
checked={selectedValues.includes(article.srvyArtclNo ?? 0)}
onCheckedChange={handleCheckboxChange(article.srvyArtclNo ?? 0)}
/>
<Label htmlFor={`${name}-${index}`} className="!text-[1.4rem]">
{article.artclCn}
</Label>
</div>
))}
</div>
</div>
);
}
function TextareaArticle({ qstnCn }: { qstnCn: string; article?: Article }) {
return (
<div className="w-full">
<p className="mb-4 text-2xl font-bold text-foreground">{qstnCn}</p>
<Textarea className="!text-[1.3rem]" placeholder="답변을 입력해주세요" />
</div>
);
}
export { UserSurveyArticle };