WIP: 설문 결과 페이지 작업중

This commit is contained in:
2025-06-05 15:51:51 +09:00
parent a72ff34c19
commit 44bb96bfa6
10 changed files with 217 additions and 11 deletions
@@ -0,0 +1,10 @@
import { UserSurveyResult } from "@/pages/survey";
export default async function UserSurveyResultPage({
params,
}: {
params: Promise<{ srvySn: string }>;
}) {
const { srvySn } = await params;
return <UserSurveyResult srvySn={srvySn} />;
}
-2
View File
@@ -1,4 +1,2 @@
export { SiteApi } from "../api/site-service";
export type { Site } from "../model/site-interface";
export { SiteSelect } from "../ui/site-select";
+1 -2
View File
@@ -5,5 +5,4 @@ export {
type Site,
type SiteFormData,
} from "./model/site-interface";
export { SiteSelect } from "./ui/site-select";
export { SiteSelect } from "./ui/site-select";
+10 -4
View File
@@ -10,23 +10,29 @@ import { ENDPOINT } from "@/shared/config";
import { SiteApi } from "../api/site-service";
type SiteSelectProps = {
siteId: string;
placeholder?: string;
enabledAllSite?: boolean;
} & React.ComponentPropsWithoutRef<typeof Select>;
function SiteSelect({
defaultValue,
placeholder = "선택",
enabledAllSite,
...props
}: SiteSelectProps) {
const { data: sites } = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.SITES],
queryFn: () => SiteApi.getSiteList(),
enabled: true,
select: (data) => {
if (enabledAllSite) {
return [{ siteId: "ALL", siteNm: "전체" }, ...data.content];
}
return data.content;
},
});
const defaultValueText =
sites?.content.find((item) => item.siteId === defaultValue)?.siteNm ?? placeholder;
sites?.find((item) => item.siteId === defaultValue)?.siteNm ?? placeholder;
return (
<Select {...props}>
@@ -34,7 +40,7 @@ function SiteSelect({
<SelectValue placeholder={defaultValueText} />
</SelectTrigger>
<SelectContent>
{sites?.content.map((item) => (
{sites?.map((item) => (
<SelectItem key={item.siteId} value={item.siteId}>
{item.siteNm}
</SelectItem>
@@ -212,7 +212,6 @@ const UpCodeManagementDialog = memo(
<FormItem className="flex flex-col">
<FormControl>
<SiteSelect
siteId={field.value || ""}
value={field.value || ""}
onValueChange={field.onChange}
/>
@@ -15,6 +15,8 @@ import type {
ArticleParams,
UserSurvey,
UserSurveyFormData,
UserSurveyResultParams,
UserSurveyResult,
} from "../model/survey-interface";
export class SurveyApiService implements SurveyService {
@@ -102,6 +104,17 @@ export class SurveyApiService implements SurveyService {
public async saveUserSurvey(data: UserSurveyFormData): Promise<void> {
await client.post(`${ENDPOINT.CMS_SERVICE.SURVEY}/user`, data);
}
public async getUserSurveyResult(
srvySn: number,
params: UserSurveyResultParams,
): Promise<UserSurveyResult> {
const response = await client.get<UserSurveyResult>(
`${ENDPOINT.CMS_SERVICE.SURVEY}/rslt/stat/${srvySn}`,
{ params },
);
return response.data;
}
}
export const SurveyApi = new SurveyApiService();
+1
View File
@@ -3,3 +3,4 @@ export { CreatePage } from "./ui/create-page";
export { DetailPage } from "./ui/detail-page";
export { ModifyPage } from "./ui/modify-page";
export { UserSurveyArticle } from "./ui/user-survey-article";
export { UserSurveyResult } from "./ui/user-survey-result";
@@ -38,6 +38,12 @@ export interface ArticleParams extends PageableParams {
delYn?: string;
}
export interface UserSurveyResultParams {
keywordType?: string;
keyword?: string;
siteId?: string;
}
export interface Survey {
srvySn: number;
srvyTtl: string;
@@ -112,6 +118,48 @@ export const userSurveyFormSchema = z.object({
),
});
const inputResultSchema = z.object({
rspdntNm: z.string(),
ansCn: z.string(),
ansDt: z.string(),
});
const articleWithResultSchema = articleSchema.omit({ mode: true }).extend({
inputRslts: z.array(inputResultSchema),
rspnsCount: z.number(),
rspnsRate: z.number(),
});
const questionWithArticlesSchema = questionFormSchema.extend({
srvyQitemNo: z.number(),
articles: z.array(articleWithResultSchema),
});
const respondentStateSchema = z.object({
ageN20대: z.number(),
ageN30대: z.number(),
ageN40대: z.number(),
ageN50대: z.number(),
ageN60대이상: z.number(),
job학생: z.number(),
job대학생: z.number(),
job회사원: z.number(),
job공단직원: z.number(),
job공무원: z.number(),
job기타: z.number(),
mf남성: z.number(),
mf여성: z.number(),
구분: z.string(),
});
export const userSurveyResultSchema = surveyFormSchema.extend({
srvySn: z.number(),
srvyPeriodFrTo: z.string(),
qitems: z.array(questionWithArticlesSchema),
srvyRspntStatList: z.array(respondentStateSchema),
totalRspdnt: z.number(),
});
export type SurveyList = PageableList<Survey>;
export type SurveyFormData = z.infer<typeof surveyFormSchema>;
export type QuestionList = PageableList<Question>;
@@ -124,6 +172,7 @@ export type UserSurvey = SurveyDetail & {
})[];
};
export type UserSurveyFormData = z.infer<typeof userSurveyFormSchema>;
export type UserSurveyResult = z.infer<typeof userSurveyResultSchema>;
export interface SurveyService {
getSurveyList(params: SurveyParams): Promise<SurveyList>;
@@ -139,4 +188,8 @@ export interface SurveyService {
saveArticleList(data: Article[]): Promise<void>;
getUserSurvey(srvySn: number): Promise<UserSurvey>;
saveUserSurvey(data: UserSurveyFormData): Promise<void>;
getUserSurveyResult(
srvySn: number,
params: UserSurveyResultParams,
): Promise<UserSurveyResult>;
}
@@ -81,8 +81,24 @@ export const columns: ColumnDef<Survey>[] = [
{
id: "result",
header: "설문 결과",
cell: () => (
<Button size="sm" color="gray">
cell: ({ row }) => (
<Button
size="sm"
color="gray"
onClick={(e) => {
e.stopPropagation();
const width = 767;
const height = 900;
const left = window.screen.width / 2 - width / 2 + window.screenX;
const top = window.screen.height / 2 - height / 2 + window.screenY;
window.open(
`/user-survey-result/${row.original.srvySn}`,
"",
`width=${width},height=${height},left=${left},top=${top}`,
);
}}
>
설문 결과
</Button>
),
@@ -0,0 +1,111 @@
"use client";
import { useState } from "react";
import { keepPreviousData, useQuery } from "@tanstack/react-query";
import {
Button,
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@common/components";
import { ENDPOINT } from "@/shared/config";
import { SurveyApi } from "../api/survey-service";
import type { UserSurveyResultParams } from "../model/survey-interface";
import { SiteSelect } from "@/entities/site";
function UserSurveyResult({ srvySn }: { srvySn: string }) {
const [params, setParams] = useState<UserSurveyResultParams>({
siteId: "ALL",
});
const { data } = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.SURVEY, srvySn, params.siteId],
queryFn: () => SurveyApi.getUserSurveyResult(parseInt(srvySn), params),
placeholderData: keepPreviousData,
});
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">
{data?.srvyTtl}
</h1>
<div className="flex gap-2">
<SiteSelect
defaultValue="ALL"
value={params.siteId}
onValueChange={(siteId) => setParams({ siteId })}
enabledAllSite
/>
<Button>인쇄</Button>
<Button color="white" onClick={() => window.close()}>
닫기
</Button>
</div>
</div>
<div className="card">
<h2 className="mb-2 flex items-center justify-between gap-2 text-[1.3rem] font-bold text-foreground">
응답자 통계
<div className="text-[1.2rem] font-medium tracking-tighter text-label">
<p>
( 총 응답자 수: <span>{data?.totalRspdnt}</span>명 / 설문 기간:{" "}
<span>{data?.srvyPeriodFrTo}</span> )
</p>
</div>
</h2>
<Table variant="secondary" className="text-center">
<TableHeader>
<TableRow>
<TableHead rowSpan={2}>구분</TableHead>
<TableHead colSpan={2}>성별</TableHead>
<TableHead colSpan={6}>직업</TableHead>
<TableHead colSpan={5}>나이</TableHead>
</TableRow>
<TableRow>
<TableHead>남성</TableHead>
<TableHead>여성</TableHead>
<TableHead>학생</TableHead>
<TableHead>대학생</TableHead>
<TableHead>회사원</TableHead>
<TableHead>공단직원</TableHead>
<TableHead>공무원</TableHead>
<TableHead>기타</TableHead>
<TableHead>20대</TableHead>
<TableHead>30대</TableHead>
<TableHead>40대</TableHead>
<TableHead>50대</TableHead>
<TableHead>60대 이상</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{data?.srvyRspntStatList.map((item) => (
<TableRow key={item.구분}>
<TableHead>{item.구분}</TableHead>
<TableCell>{item.mf남성}</TableCell>
<TableCell>{item.mf여성}</TableCell>
<TableCell>{item.job학생}</TableCell>
<TableCell>{item.job대학생}</TableCell>
<TableCell>{item.job회사원}</TableCell>
<TableCell>{item.job공단직원}</TableCell>
<TableCell>{item.job공무원}</TableCell>
<TableCell>{item.job기타}</TableCell>
<TableCell>{item.ageN20대}</TableCell>
<TableCell>{item.ageN30대}</TableCell>
<TableCell>{item.ageN40대}</TableCell>
<TableCell>{item.ageN50대}</TableCell>
<TableCell>{item.ageN60대이상}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</div>
</div>
);
}
export { UserSurveyResult };