Skip to content

Commit

Permalink
Merge pull request #385 from depromeet/feat/339/qaSpaceDetailView
Browse files Browse the repository at this point in the history
스페이스 상세뷰 - 분석 뷰까지의 로직 생성
  • Loading branch information
sean2337 authored Sep 13, 2024
2 parents 28fe151 + fc08588 commit db84f14
Show file tree
Hide file tree
Showing 18 changed files with 676 additions and 191 deletions.
20 changes: 11 additions & 9 deletions apps/web/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import Cookies from "js-cookie";
/** API 사용 전, ENV 파일을 통해 서버 연동 설정을 해주세요 */
const API_URL = import.meta.env.VITE_API_URL as string;

export type ErrorResponse = { name: string; message: string };

const baseApi = axios.create({
baseURL: API_URL,
timeout: 5000,
Expand All @@ -21,8 +23,8 @@ const logOnDev = (message: string) => {
};

/** API 요청이 실패한 경우 호출되는 함수 */
const onError = (status: number, message: string) => {
const error = { status, message };
const onError = (status: number, message: string, data?: ErrorResponse) => {
const error = { status, message, data };
throw error;
};

Expand Down Expand Up @@ -64,32 +66,32 @@ const onErrorResponse = (error: AxiosError | Error) => {
if (axios.isAxiosError(error)) {
const { message } = error;
const { method, url } = error?.config as AxiosRequestConfig;
const { status, statusText } = error?.response as AxiosResponse;
const { status, statusText, data } = error?.response as AxiosResponse<ErrorResponse>;

logOnDev(`[API ERROR_RESPONSE ${status} | ${statusText} | ${message}] ${method?.toUpperCase()} ${url}`);

switch (status) {
case 400:
onError(status, "잘못된 요청을 했어요");
onError(status, "잘못된 요청을 했어요", data);
break;
case 401: {
onError(status, "인증을 실패했어요");
onError(status, "인증을 실패했어요", data);
break;
}
case 403: {
onError(status, "권한이 없는 상태로 접근했어요");
onError(status, "권한이 없는 상태로 접근했어요", data);
break;
}
case 404: {
onError(status, "찾을 수 없는 페이지를 요청했어요");
onError(status, "찾을 수 없는 페이지를 요청했어요", data);
break;
}
case 500: {
onError(status, "서버 오류가 발생했어요");
onError(status, "서버 오류가 발생했어요", data);
break;
}
default: {
onError(status, `기타 에러가 발생했어요 : ${error?.message}`);
onError(status, `기타 에러가 발생했어요 : ${error?.message}`, data);
}
}
} else if (error instanceof Error && error?.name === "TimoutError") {
Expand Down
14 changes: 13 additions & 1 deletion apps/web/src/app/info/PrivacyPolicyPage.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,23 @@
import { css } from "@emotion/react";

import { Typography } from "@/component/common/typography";
import { info } from "@/config/info";
import { DefaultLayout } from "@/layout/DefaultLayout";

export function PrivacyPolicyPage() {
return (
<DefaultLayout title="개인정보 처리방침">
<Typography variant="body16Medium">{info.privacyPolicy}</Typography>
<Typography variant="body16Medium">
<pre
css={css`
white-space: pre-wrap;
word-wrap: break-word;
overflow-wrap: break-word;
`}
>
{info.privacyPolicy}
</pre>
</Typography>
</DefaultLayout>
);
}
14 changes: 13 additions & 1 deletion apps/web/src/app/info/TermsOfServicePage.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,23 @@
import { css } from "@emotion/react";

import { Typography } from "@/component/common/typography";
import { info } from "@/config/info";
import { DefaultLayout } from "@/layout/DefaultLayout";

export function TermsOfServicePage() {
return (
<DefaultLayout title="이용약관">
<Typography variant="body16Medium">{info.termsOfService}</Typography>
<Typography variant="body16Medium">
<pre
css={css`
white-space: pre-wrap;
word-wrap: break-word;
overflow-wrap: break-word;
`}
>
{info.termsOfService}
</pre>
</Typography>
</DefaultLayout>
);
}
20 changes: 14 additions & 6 deletions apps/web/src/app/retrospect/analysis/RetrospectAnalysisPage.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Fragment } from "react";
import { Fragment, useEffect } from "react";
import { useLocation } from "react-router-dom";

import { LoadingModal } from "@/component/common/Modal/LoadingModal.tsx";
Expand All @@ -10,9 +10,10 @@ import { QuestionForm } from "@/component/retrospect/analysis/QuestionForm.tsx";
import { useGetAnalysisAnswer } from "@/hooks/api/retrospect/analysis/useGetAnalysisAnswer.ts";
import { useTabs } from "@/hooks/useTabs";
import { DualToneLayout } from "@/layout/DualToneLayout";
import { EmptyList } from "@/component/common/empty";

export const RetrospectAnalysisPage = () => {
const { title } = useLocation().state as { title: string };
const { title, defaultTab } = useLocation().state as { title: string; defaultTab: "질문" | "개별" | "분석" };

const tabMappings = {
질문: "QUESTIONS",
Expand All @@ -27,6 +28,11 @@ export const RetrospectAnalysisPage = () => {
const spaceId = queryParams.get("spaceId");
const retrospectId = queryParams.get("retrospectId");
const { data, isLoading } = useGetAnalysisAnswer({ spaceId: spaceId!, retrospectId: retrospectId! });
useEffect(() => {
if (defaultTab) {
selectTab(defaultTab);
}
}, []);
return (
<DualToneLayout
bottomTheme="gray"
Expand All @@ -38,13 +44,15 @@ export const RetrospectAnalysisPage = () => {
}
>
{isLoading && <LoadingModal />}
{
{!data || data.individuals.length === 0 ? (
<EmptyList icon={"ic_clock"} message={"제출된 회고가 없어요"} />
) : (
{
QUESTIONS: <QuestionForm data={data!} />,
INDIVIDUAL_ANALYSIS: <PersonalForm data={data!} />,
QUESTIONS: <QuestionForm data={data} />,
INDIVIDUAL_ANALYSIS: <PersonalForm data={data} />,
ANALYSIS: <AnalysisContainer spaceId={spaceId!} retrospectId={retrospectId!} hasAIAnalyzed={data?.hasAIAnalyzed} />,
}[selectedTab]
}
)}
</DualToneLayout>
);
};
60 changes: 32 additions & 28 deletions apps/web/src/app/space/SpaceViewPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { css } from "@emotion/react";
import { PATHS } from "@layer/shared";
import { useQueries } from "@tanstack/react-query";
import Cookies from "js-cookie";
import { Fragment, useEffect, useState } from "react";
import { Fragment, useEffect, useRef, useState } from "react";
import { useNavigate } from "react-router-dom";

import { BottomSheet } from "@/component/BottomSheet";
Expand All @@ -22,12 +22,16 @@ import { useApiOptionsGetSpaceInfo } from "@/hooks/api/space/useApiOptionsGetSpa
import { useBottomSheet } from "@/hooks/useBottomSheet";
import { useModal } from "@/hooks/useModal";
import { useRequiredParams } from "@/hooks/useRequiredParams";
import { DualToneLayout } from "@/layout/DualToneLayout";
import { DefaultLayout } from "@/layout/DefaultLayout";
import { useTestNatigate } from "@/lib/test-natigate";
import { DESIGN_TOKEN_COLOR } from "@/style/designTokens";
import { Retrospect } from "@/types/retrospect";
import { useCollisionDetection } from "@/hooks/useCollisionDetection";

export function SpaceViewPage() {
const appbarRef = useRef<HTMLDivElement>(null);
const contentRef = useRef<HTMLDivElement>(null);
const isColliding = useCollisionDetection(appbarRef, contentRef);
const navigate = useNavigate();
const appNavigate = useTestNatigate();
const memberId = Cookies.get("memberId");
Expand Down Expand Up @@ -99,32 +103,20 @@ export function SpaceViewPage() {
}

return (
<DualToneLayout
topTheme="dark"
<DefaultLayout
theme={isColliding ? "default" : "dark"}
LeftComp={
<Icon
size={2.4}
icon="ic_arrow_left"
css={css`
cursor: pointer;
`}
onClick={() => appNavigate(PATHS.home())}
color={DESIGN_TOKEN_COLOR.gray00}
/>
}
TopComp={
<>
<ActionItemListView
restrospectArr={restrospectArr ? restrospectArr : []}
isPossibleMake={doneRetrospects.length === 0}
spaceId={spaceInfo?.id}
teamActionList={teamActionList?.teamActionItemList || []}
leaderId={spaceInfo?.leader.id}
<div ref={appbarRef}>
<Icon
size={2.4}
icon="ic_arrow_left"
css={css`
cursor: pointer;
`}
onClick={() => appNavigate(PATHS.home())}
color={DESIGN_TOKEN_COLOR.gray00}
/>
<Spacing size={1.1} />
<SpaceCountView mainTemplate={spaceInfo?.formTag} memberCount={spaceInfo?.memberCount} isLeader={isLeader} />
<Spacing size={2.4} />
</>
</div>
}
title={spaceInfo?.name}
RightComp={
Expand Down Expand Up @@ -154,6 +146,18 @@ export function SpaceViewPage() {
/>
}
>
<div ref={contentRef}>
<ActionItemListView
restrospectArr={restrospectArr ? restrospectArr : []}
isPossibleMake={doneRetrospects.length === 0}
spaceId={spaceInfo?.id}
teamActionList={teamActionList?.teamActionItemList || []}
leaderId={spaceInfo?.leader.id}
/>
<Spacing size={1.1} />
<SpaceCountView mainTemplate={spaceInfo?.formTag} memberCount={spaceInfo?.memberCount} isLeader={isLeader} />
<Spacing size={2.4} />
</div>
<div
css={css`
width: calc(100% + 4rem);
Expand Down Expand Up @@ -210,7 +214,7 @@ export function SpaceViewPage() {
gap: 0.6rem;
`}
>
<Typography variant="title18Bold">완료된 회고</Typography>
<Typography variant="title18Bold">마감된 회고</Typography>
<Typography variant="title16Bold" color="gray600">
{doneRetrospects?.length}
</Typography>
Expand Down Expand Up @@ -245,6 +249,6 @@ export function SpaceViewPage() {
handler={true}
/>
<Toast />
</DualToneLayout>
</DefaultLayout>
);
}
6 changes: 5 additions & 1 deletion apps/web/src/component/common/empty/EmptyList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ import { IconType } from "@/component/common/Icon/Icon";
import { Typography } from "@/component/common/typography";

type EmptyListProps = {
title?: React.ReactNode;
message: React.ReactNode;
icon: IconType;
iconSize?: number;
} & React.HTMLAttributes<HTMLDivElement>;

export function EmptyList({ message, icon, iconSize = 7.2, children, ...props }: PropsWithChildren<EmptyListProps>) {
export function EmptyList({ title, message, icon, iconSize = 7.2, children, ...props }: PropsWithChildren<EmptyListProps>) {
return (
<div
css={css`
Expand All @@ -25,6 +26,9 @@ export function EmptyList({ message, icon, iconSize = 7.2, children, ...props }:
{...props}
>
<Icon icon={icon} size={iconSize} />
<Typography variant="title18Bold" color={"gray900"}>
{title}
</Typography>
<div
css={css`
text-align: center;
Expand Down
59 changes: 41 additions & 18 deletions apps/web/src/component/retrospect/analysis/Analysis.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,22 @@ type AnalysisContainerProps = {
};

export function AnalysisContainer({ spaceId, retrospectId, hasAIAnalyzed }: AnalysisContainerProps) {
const { data, isLoading } = useApiGetAnalysis({ spaceId, retrospectId });
const { data, isError, error, isLoading } = useApiGetAnalysis({ spaceId, retrospectId });

if (isError) {
console.log(error);
}
const [selectedTab, setSelectedTab] = useState<"personal" | "team">("personal");
if (isLoading) {
return <LoadingModal />;
}

{
/**분석이 진행중일 때**/
}
if (hasAIAnalyzed == false) {
return <AnalysisingComp />;
}
{
/**분석이 결과가 아무것도 없을 때**/
}

return (
<div
Expand Down Expand Up @@ -116,22 +118,43 @@ export function AnalysisContainer({ spaceId, retrospectId, hasAIAnalyzed }: Anal
]}
/>
<GoalCompletionRateChart goalCompletionRate={data.teamAnalyze.goalCompletionRate} />

<InsightsBoxSection type="goodPoints" insightArr={data.teamAnalyze.goodPoints} isTeam={true} />
<InsightsBoxSection type="badPoints" insightArr={data.teamAnalyze.badPoints} isTeam={true} />
<InsightsBoxSection type="improvementPoints" insightArr={data.teamAnalyze.badPoints} isTeam={true} />
{data.teamAnalyze.goodPoints && <InsightsBoxSection type="goodPoints" insightArr={data.teamAnalyze.goodPoints} isTeam={true} />}
{data.teamAnalyze.badPoints && <InsightsBoxSection type="badPoints" insightArr={data.teamAnalyze.badPoints} isTeam={true} />}
{data.teamAnalyze.improvementPoints && (
<InsightsBoxSection type="improvementPoints" insightArr={data.teamAnalyze.improvementPoints} isTeam={true} />
)}
</>
)}
{selectedTab === "personal" && (
<>
{data?.individualAnalyze.badPoints == null &&
data?.individualAnalyze.goodPoints == null &&
data?.individualAnalyze.improvementPoints == null ? (
<EmptyList
message={
<>
회고를 작성하지 않으셨거나 <br />
너무 적은 내용을 입력하셨어요
</>
}
icon={"ic_empty_list"}
iconSize={12}
/>
) : (
<>
{data?.individualAnalyze.goodPoints && (
<InsightsBoxSection type="goodPoints" insightArr={data.individualAnalyze.goodPoints} isTeam={false} />
)}
{data?.individualAnalyze.badPoints && (
<InsightsBoxSection type="badPoints" insightArr={data.individualAnalyze.badPoints} isTeam={false} />
)}
{data?.individualAnalyze.improvementPoints && (
<InsightsBoxSection type="improvementPoints" insightArr={data.individualAnalyze.improvementPoints} isTeam={false} />
)}
</>
)}
</>
)}
{selectedTab === "personal" &&
(data?.individualAnalyze ? (
<>
<InsightsBoxSection type="goodPoints" insightArr={data.individualAnalyze.goodPoints} isTeam={false} />
<InsightsBoxSection type="badPoints" insightArr={data.individualAnalyze.badPoints} isTeam={false} />
<InsightsBoxSection type="improvementPoints" insightArr={data.individualAnalyze.badPoints} isTeam={false} />
</>
) : (
<EmptyList message={<>회고를 작성해야 확인할 수 있어요</>} icon={"ic_empty_list"} iconSize={12} />
))}
</div>
);
}
Expand Down
Loading

0 comments on commit db84f14

Please sign in to comment.