From eb46d844628ec2c0f5babaa15866caa236a6012b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A1=B0=EB=AF=BC=ED=98=B8?= <78631876+minh0518@users.noreply.github.com> Date: Wed, 22 May 2024 17:07:10 +0900 Subject: [PATCH] =?UTF-8?q?=ED=94=BC=EB=93=9C=EB=B0=B1=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EC=83=9D=EC=84=B1=20-=20=EB=B0=9C?= =?UTF-8?q?=ED=99=94=20=EC=86=8D=EB=8F=84=20=ED=94=BC=EB=93=9C=EB=B0=B1=20?= =?UTF-8?q?(#56)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 피드백 썸네일 임시 이미지 적용 * feat: 피드백 목록 단일 카드 정보 생성 * feat: 발표 연습 시작 api 적용 * fix: 최근 발표API 임시 주석처리 * feat: 최근 발표 섹션 복구 * feat: 홈 화면에서 바로 연습 시작시, 발표 시작 api적용 * fix: 발표 마지막 페이지 슬라이드 녹음본 저장 로직 추가 * fix: 누락된 api명세 및 UI문구 수정 * feat: 배경 그라데이션 적용 * feat: 발화 속도 피드백 페이지 구현 * fix: Navbar버튼과 모달 버튼 스타일 중첩 제거 --- .../_components/CardInfo.module.scss | 5 +- .../(common_navbar)/_components/CardInfo.tsx | 13 +-- .../_components/CardItem.module.scss | 45 +++------ .../(common_navbar)/_components/CardItem.tsx | 95 +++++++++++-------- .../(common_navbar)/_components/CardList.tsx | 19 ++-- .../FeedbackCardDescription.module.scss | 6 ++ .../_Feedback/FeedbackCardDescription.tsx | 18 ++++ .../_Feedback/FeedbackScoreButton.module.scss | 17 ++++ .../_Feedback/FeedbackScoreButton.tsx | 26 +++++ .../_components/_Home/HomeCardDescription.tsx | 17 ++-- .../_components/_Home/PracticeButton.tsx | 13 ++- .../[id]/_components/CategoryFeedback.tsx | 7 +- .../[id]/_components/MemorizeReview.tsx | 6 +- .../_components/SpeedFeedback.module.scss | 21 ++++ .../[id]/_components/SpeedFeedback.tsx | 24 +++++ .../[id]/_components/TotalScore.module.scss | 10 +- .../feedback/[id]/_components/TotalScore.tsx | 35 +++++-- .../[id]/_components/_charts/SpeedChart.tsx | 79 +++++++++++++++ .../feedback/[id]/_svgs/GoalIcon.tsx | 2 +- .../feedback/[id]/page.module.scss | 2 +- .../(common_navbar)/feedback/[id]/page.tsx | 23 +++-- .../(common_navbar)/feedback/_utils/date.ts | 11 +++ .../(common_navbar)/feedback/list/page.tsx | 3 +- .../home/_hooks/presentationList.ts | 24 ++++- .../(common_navbar)/home/page.tsx | 2 +- .../[id]/_component/UploadMemo.module.scss | 1 + .../[id]/_component/UploadScript.module.scss | 1 + .../[id]/_component/UploadTitle.module.scss | 1 + .../upload/[id]/_hooks/presentation.tsx | 39 ++++++-- src/app/(afterlogin)/_components/NavMenu.tsx | 16 ++-- .../_components/Navbar.module.scss | 2 +- src/app/(afterlogin)/practice/[id]/page.tsx | 4 +- .../setting/[id]/layout.module.scss | 2 - .../_components/_modules/_modal/Confirm.tsx | 4 +- src/app/globals.css | 17 +++- src/services/client/upload.ts | 22 +++++ src/types/service.ts | 8 +- 37 files changed, 486 insertions(+), 154 deletions(-) create mode 100644 src/app/(afterlogin)/(common_navbar)/_components/_Feedback/FeedbackCardDescription.module.scss create mode 100644 src/app/(afterlogin)/(common_navbar)/_components/_Feedback/FeedbackCardDescription.tsx create mode 100644 src/app/(afterlogin)/(common_navbar)/_components/_Feedback/FeedbackScoreButton.module.scss create mode 100644 src/app/(afterlogin)/(common_navbar)/_components/_Feedback/FeedbackScoreButton.tsx create mode 100644 src/app/(afterlogin)/(common_navbar)/feedback/[id]/_components/SpeedFeedback.module.scss create mode 100644 src/app/(afterlogin)/(common_navbar)/feedback/[id]/_components/SpeedFeedback.tsx create mode 100644 src/app/(afterlogin)/(common_navbar)/feedback/[id]/_components/_charts/SpeedChart.tsx create mode 100644 src/app/(afterlogin)/(common_navbar)/feedback/_utils/date.ts diff --git a/src/app/(afterlogin)/(common_navbar)/_components/CardInfo.module.scss b/src/app/(afterlogin)/(common_navbar)/_components/CardInfo.module.scss index 1242c08..14ca0bc 100644 --- a/src/app/(afterlogin)/(common_navbar)/_components/CardInfo.module.scss +++ b/src/app/(afterlogin)/(common_navbar)/_components/CardInfo.module.scss @@ -1,11 +1,14 @@ @import '@/styles/globals'; +@import '@/styles/mixins'; .info { display: flex; flex-direction: column; + gap: 5px; &__title { - margin: 0 0 12px; font-size: $h2; + + @include line(2); } } diff --git a/src/app/(afterlogin)/(common_navbar)/_components/CardInfo.tsx b/src/app/(afterlogin)/(common_navbar)/_components/CardInfo.tsx index 0409bf3..ef92cca 100644 --- a/src/app/(afterlogin)/(common_navbar)/_components/CardInfo.tsx +++ b/src/app/(afterlogin)/(common_navbar)/_components/CardInfo.tsx @@ -5,21 +5,22 @@ import styles from './CardInfo.module.scss'; import HomeCardDescription from './_Home/HomeCardDescription'; import { PresentationListTypeGuard } from '@/types/guards'; +import { usePathname } from 'next/navigation'; +import FeedbackCardDescription from './_Feedback/FeedbackCardDescription'; interface Props { listInfo: CardListType; - usage: 'home' | 'feedback'; } -const CardInfo = ({ listInfo, usage }: Props) => { +const CardInfo = ({ listInfo }: Props) => { + const pathname = usePathname(); + const usage: 'feedback' | 'home' = pathname === `/feedback/list` ? 'feedback' : 'home'; return (
{listInfo.title} {usage === 'home' && PresentationListTypeGuard(listInfo) ? ( - <> - - + ) : ( - <> + )}
); diff --git a/src/app/(afterlogin)/(common_navbar)/_components/CardItem.module.scss b/src/app/(afterlogin)/(common_navbar)/_components/CardItem.module.scss index 35f8682..29ae42e 100644 --- a/src/app/(afterlogin)/(common_navbar)/_components/CardItem.module.scss +++ b/src/app/(afterlogin)/(common_navbar)/_components/CardItem.module.scss @@ -10,11 +10,18 @@ position: relative; width: 100%; height: 250px; - margin-bottom: 20px; + margin-bottom: 12px; border: 1px solid $gray-1; border-radius: 16px; } +.dummyImg { + width: 440px; + height: 250px; + border-radius: 16px; + background-color: black; +} + .menu { @include pure-button; @@ -29,35 +36,9 @@ } } -.info { - width: 280px; - - span { - @include line(1); - } - - em { - background-color: $black; - } - - &__box { - display: flex; - align-items: center; - justify-content: space-between; - } -} - -.action { - @include pure-button; - @include button_theme_inverted; - - width: 100%; - height: 100%; - border-radius: 100px; - font-size: 20px; - - &__box { - width: 120px; - height: 55px; - } +.infoBox { + display: flex; + align-items: center; + justify-content: space-between; + max-width: 440px; } diff --git a/src/app/(afterlogin)/(common_navbar)/_components/CardItem.tsx b/src/app/(afterlogin)/(common_navbar)/_components/CardItem.tsx index 8b19f98..b332bfc 100644 --- a/src/app/(afterlogin)/(common_navbar)/_components/CardItem.tsx +++ b/src/app/(afterlogin)/(common_navbar)/_components/CardItem.tsx @@ -7,25 +7,28 @@ import useToggle from '@/app/_hooks/useToggle'; import Confirm from '@/app/_components/_modules/_modal/Confirm'; import { CardListType } from '@/types/service'; import Image from 'next/image'; -import { useRouter } from 'next/navigation'; +import { usePathname, useRouter } from 'next/navigation'; import DeleteIcon from '../home/_components/_svgs/DeleteIcon'; import ModifyIcon from '../home/_components/_svgs/ModifyIcon'; import MenuIcon from '../home/_components/_svgs/MenuIcon'; import { useDeletePresentation } from '../home/_hooks/presentationList'; import CardInfo from './CardInfo'; import PracticeButton from './_Home/PracticeButton'; +import FeedbackScoreButton from './_Feedback/FeedbackScoreButton'; +import { FeedbackListTypeGuard, PresentationListTypeGuard } from '@/types/guards'; interface Props { listInfo: CardListType; - usage: 'home' | 'feedback'; } -const CardItem = ({ listInfo, usage }: Props) => { +const CardItem = ({ listInfo }: Props) => { const router = useRouter(); const flyout = useToggle(); const modal = useToggle(); + const pathname = usePathname(); + const usage: 'feedback' | 'home' = pathname === `/feedback/list` ? 'feedback' : 'home'; - const { mutate } = useDeletePresentation(listInfo.id); + const { mutate: presentationListMutate } = useDeletePresentation(listInfo.id); const handleModify = () => { router.push(`/upload/${listInfo.id}`); @@ -38,52 +41,62 @@ const CardItem = ({ listInfo, usage }: Props) => { }; const deleteItem = () => { - mutate(); + presentationListMutate(); }; + const thumbnailImage = listInfo.thumbnailPath ? ( + {`${listInfo.id} + ) : ( +
+ ); + return ( <>
- {`${listInfo.id} + {thumbnailImage}
- {/* TODO: 피드백에 맞는 버튼 로직 생성 필요 */} - - - - - - - - - - - - - + {usage === 'home' && ( + + + + + + + + + + + + + + )}
-
-
- -
-
- {usage === 'home' ? ( - router.push(`/setting/${listInfo.id}`)} /> - ) : ( - <> +
+ +
+ {usage === 'home' && PresentationListTypeGuard(listInfo) && ( + + )} + {usage === 'feedback' && FeedbackListTypeGuard(listInfo) && ( + router.push(`/feedback/${listInfo.id}`)} + /> )}
diff --git a/src/app/(afterlogin)/(common_navbar)/_components/CardList.tsx b/src/app/(afterlogin)/(common_navbar)/_components/CardList.tsx index 8e8d9ed..9f14c84 100644 --- a/src/app/(afterlogin)/(common_navbar)/_components/CardList.tsx +++ b/src/app/(afterlogin)/(common_navbar)/_components/CardList.tsx @@ -5,21 +5,16 @@ import { clientHomeApi } from '@/services/client/home'; import { useInView } from 'react-intersection-observer'; import { Fragment, useEffect } from 'react'; import { CardListType, FeedbackListType, PresentationListType } from '@/types/service'; -import { useRouter } from 'next/navigation'; +import { usePathname, useRouter } from 'next/navigation'; import PlusIcon from '../home/_components/_svgs/PlusIcon'; import CardItem from './CardItem'; import { clientFeedbackApi } from '@/services/client/feedback'; -// 패칭 api -// 최근 발표 사용 여부 -// 이미지 아래 설명 컨텐츠 -// 이미지 아래 버튼 -interface Props { - usage: 'home' | 'feedback'; -} -const CardList = ({ usage }: Props) => { - // 여기서 피드백의 경우, 완료가 안 된게 있으면 1초마다 fetch +const CardList = () => { + // 피드백의 경우, 완료가 안 된게 있으면 1초마다 fetch const router = useRouter(); + const pathname = usePathname(); + const usage: 'feedback' | 'home' = pathname === `/feedback/list` ? 'feedback' : 'home'; const { data, fetchNextPage, hasNextPage, isFetching, refetch } = useInfiniteQuery({ queryKey: [usage, 'list'], @@ -33,6 +28,8 @@ const CardList = ({ usage }: Props) => { return await response.json(); } }, + staleTime: 0, + gcTime: 0, initialPageParam: 0, getNextPageParam: (lastPage, pages) => { if (lastPage && pages) { @@ -69,7 +66,7 @@ const CardList = ({ usage }: Props) => { {eachPage.page.content.map((listInfo: CardListType, index) => (
  • - +
  • ))}
    diff --git a/src/app/(afterlogin)/(common_navbar)/_components/_Feedback/FeedbackCardDescription.module.scss b/src/app/(afterlogin)/(common_navbar)/_components/_Feedback/FeedbackCardDescription.module.scss new file mode 100644 index 0000000..47e8f4b --- /dev/null +++ b/src/app/(afterlogin)/(common_navbar)/_components/_Feedback/FeedbackCardDescription.module.scss @@ -0,0 +1,6 @@ +@import '@/styles/globals'; +@import '@/styles/mixins'; + +.generateDate { + color: $gray-6; +} diff --git a/src/app/(afterlogin)/(common_navbar)/_components/_Feedback/FeedbackCardDescription.tsx b/src/app/(afterlogin)/(common_navbar)/_components/_Feedback/FeedbackCardDescription.tsx new file mode 100644 index 0000000..2873204 --- /dev/null +++ b/src/app/(afterlogin)/(common_navbar)/_components/_Feedback/FeedbackCardDescription.tsx @@ -0,0 +1,18 @@ +import { FeedbackListTypeGuard } from '@/types/guards'; +import { CardListType } from '@/types/service'; +import styles from './FeedbackCardDescription.module.scss'; + +interface Props { + listInfo: CardListType; +} + +const FeedbackCardDescription = ({ listInfo }: Props) => { + return ( + listInfo && + FeedbackListTypeGuard(listInfo) && ( +

    {listInfo.practiceDate}

    + ) + ); +}; + +export default FeedbackCardDescription; diff --git a/src/app/(afterlogin)/(common_navbar)/_components/_Feedback/FeedbackScoreButton.module.scss b/src/app/(afterlogin)/(common_navbar)/_components/_Feedback/FeedbackScoreButton.module.scss new file mode 100644 index 0000000..bc9cae5 --- /dev/null +++ b/src/app/(afterlogin)/(common_navbar)/_components/_Feedback/FeedbackScoreButton.module.scss @@ -0,0 +1,17 @@ +@import '@/styles/globals'; +@import '@/styles/mixins'; + +.action { + @include pure-button; + @include button_theme_inverted; + + width: 100%; + height: 100%; + border-radius: 100px; + font-size: 20px; + + &__box { + width: 67px; + height: 36px; + } +} diff --git a/src/app/(afterlogin)/(common_navbar)/_components/_Feedback/FeedbackScoreButton.tsx b/src/app/(afterlogin)/(common_navbar)/_components/_Feedback/FeedbackScoreButton.tsx new file mode 100644 index 0000000..bad60b7 --- /dev/null +++ b/src/app/(afterlogin)/(common_navbar)/_components/_Feedback/FeedbackScoreButton.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import styles from './FeedbackScoreButton.module.scss'; +import { FeedbackListType } from '@/types/service'; + +interface Props { + status: FeedbackListType['page']['content'][0]['status']; + score: number; + onClick: () => void; +} + +const FeedbackScoreButton = ({ status, score, onClick }: Props) => { + return ( +
    + +
    + ); +}; + +export default FeedbackScoreButton; diff --git a/src/app/(afterlogin)/(common_navbar)/_components/_Home/HomeCardDescription.tsx b/src/app/(afterlogin)/(common_navbar)/_components/_Home/HomeCardDescription.tsx index 547a83d..7f32b8a 100644 --- a/src/app/(afterlogin)/(common_navbar)/_components/_Home/HomeCardDescription.tsx +++ b/src/app/(afterlogin)/(common_navbar)/_components/_Home/HomeCardDescription.tsx @@ -8,14 +8,15 @@ interface Props { } const HomeCardDescription = ({ listInfo }: Props) => { - return listInfo && PresentationListTypeGuard(listInfo) ? ( - - D{listInfo.dday < 0 ? `+${Math.abs(listInfo.dday)}` : `-${listInfo.dday}`} - - 발표 시간 {listInfo.timeLimit.hours * 60 + listInfo.timeLimit.minutes}분 - - ) : ( - <> + return ( + listInfo && + PresentationListTypeGuard(listInfo) && ( + + D{listInfo.dday < 0 ? `+${Math.abs(listInfo.dday)}` : `-${listInfo.dday}`} + + 발표 시간 {listInfo.timeLimit.hours * 60 + listInfo.timeLimit.minutes}분 + + ) ); }; diff --git a/src/app/(afterlogin)/(common_navbar)/_components/_Home/PracticeButton.tsx b/src/app/(afterlogin)/(common_navbar)/_components/_Home/PracticeButton.tsx index b14f0a4..a92222b 100644 --- a/src/app/(afterlogin)/(common_navbar)/_components/_Home/PracticeButton.tsx +++ b/src/app/(afterlogin)/(common_navbar)/_components/_Home/PracticeButton.tsx @@ -1,13 +1,18 @@ -import React from 'react'; +'use client'; +import React, { useState } from 'react'; import styles from './PracticeButton.module.scss'; +import { useStartPresentation } from '../../home/_hooks/presentationList'; interface Props { - onClick: () => void; + id: number; } -const PracticeButton = ({ onClick }: Props) => { +const PracticeButton = ({ id }: Props) => { + const [start, setStart] = useState(false); + + useStartPresentation(id, start); return (
    -
    diff --git a/src/app/(afterlogin)/(common_navbar)/feedback/[id]/_components/CategoryFeedback.tsx b/src/app/(afterlogin)/(common_navbar)/feedback/[id]/_components/CategoryFeedback.tsx index fa00adb..e495a45 100644 --- a/src/app/(afterlogin)/(common_navbar)/feedback/[id]/_components/CategoryFeedback.tsx +++ b/src/app/(afterlogin)/(common_navbar)/feedback/[id]/_components/CategoryFeedback.tsx @@ -1,4 +1,9 @@ -const CategoryFeedback = () => { +import { FeedbackInfoType } from '@/types/service'; + +interface Props { + feedbackData: FeedbackInfoType; +} +const CategoryFeedback = ({ feedbackData }: Props) => { return
    ; }; diff --git a/src/app/(afterlogin)/(common_navbar)/feedback/[id]/_components/MemorizeReview.tsx b/src/app/(afterlogin)/(common_navbar)/feedback/[id]/_components/MemorizeReview.tsx index f0f7e2d..71eec6f 100644 --- a/src/app/(afterlogin)/(common_navbar)/feedback/[id]/_components/MemorizeReview.tsx +++ b/src/app/(afterlogin)/(common_navbar)/feedback/[id]/_components/MemorizeReview.tsx @@ -1,4 +1,8 @@ -const MemorizeReview = () => { +interface Props { + id: number; +} + +const MemorizeReview = ({ id }: Props) => { return
    ; }; diff --git a/src/app/(afterlogin)/(common_navbar)/feedback/[id]/_components/SpeedFeedback.module.scss b/src/app/(afterlogin)/(common_navbar)/feedback/[id]/_components/SpeedFeedback.module.scss new file mode 100644 index 0000000..4fffed3 --- /dev/null +++ b/src/app/(afterlogin)/(common_navbar)/feedback/[id]/_components/SpeedFeedback.module.scss @@ -0,0 +1,21 @@ +@import '@/styles/globals'; +@import '@/styles/mixins'; + +.container { + display: flex; + align-items: center; +} + +.description { + display: flex; + flex-direction: column; + gap: 16px; + + h1 { + font-size: $h1; + } + + p { + color: $gray-8; + } +} diff --git a/src/app/(afterlogin)/(common_navbar)/feedback/[id]/_components/SpeedFeedback.tsx b/src/app/(afterlogin)/(common_navbar)/feedback/[id]/_components/SpeedFeedback.tsx new file mode 100644 index 0000000..2a91d0a --- /dev/null +++ b/src/app/(afterlogin)/(common_navbar)/feedback/[id]/_components/SpeedFeedback.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import SpeedChart from './_charts/SpeedChart'; +import BadIcon from '../_svgs/BadIcon'; +import { FeedbackInfoType } from '@/types/service'; +import styles from './SpeedFeedback.module.scss'; +import GoodIcon from '../_svgs/GoodIcon'; + +interface Props { + speedData: FeedbackInfoType['speedFeedback']; +} +const SpeedFeedback = ({ speedData }: Props) => { + return ( +
    + +
    + {speedData.grade === 'Good' ? : } +

    {speedData.title}

    +

    {speedData.description}

    +
    +
    + ); +}; + +export default SpeedFeedback; diff --git a/src/app/(afterlogin)/(common_navbar)/feedback/[id]/_components/TotalScore.module.scss b/src/app/(afterlogin)/(common_navbar)/feedback/[id]/_components/TotalScore.module.scss index 5ad3417..812f276 100644 --- a/src/app/(afterlogin)/(common_navbar)/feedback/[id]/_components/TotalScore.module.scss +++ b/src/app/(afterlogin)/(common_navbar)/feedback/[id]/_components/TotalScore.module.scss @@ -7,7 +7,13 @@ } .title { - font-size: $h2; + display: flex; + align-items: center; + gap: 35px; + + .date { + color: $gray-6; + } } .content { @@ -42,7 +48,6 @@ .total_score { margin-top: 20px; - margin-bottom: 44px; position: relative; width: 906px; height: 24px; @@ -91,6 +96,7 @@ padding: 10px 200px; background-color: $gray-9; border-radius: 12px; + margin-top: 44px; h2 { color: $white; diff --git a/src/app/(afterlogin)/(common_navbar)/feedback/[id]/_components/TotalScore.tsx b/src/app/(afterlogin)/(common_navbar)/feedback/[id]/_components/TotalScore.tsx index 2cd855b..139251b 100644 --- a/src/app/(afterlogin)/(common_navbar)/feedback/[id]/_components/TotalScore.tsx +++ b/src/app/(afterlogin)/(common_navbar)/feedback/[id]/_components/TotalScore.tsx @@ -1,12 +1,18 @@ import styles from './TotalScore.module.scss'; -import GoodIcon from '../_svgs/GoodIcon'; -import GoalIcon from '../_svgs/GoalIcon'; -import ScoreIcon from '../_svgs/ScoreIcon'; import Link from 'next/link'; -const TotalScore = () => { +import { FeedbackInfoType } from '@/types/service'; +import { formatDate } from '../../_utils/date'; +import SpeedFeedback from './SpeedFeedback'; + +interface Props { + feedbackData: FeedbackInfoType; +} + +const TotalScore = ({ feedbackData }: Props) => { + // TODO: 외울 문장 가리기 사용 유무에 따른 분기처리(전체 피드백, 속도 피드백) 필요 return (
    -

    발표 제목 종합 피드백

    + {/*

    발표 제목 종합 피드백

    @@ -22,13 +28,24 @@ const TotalScore = () => {
    - {/*
    +
    -
    */} +
    + +

    발표 연습 시작하기

    + +
    */} + +
    +

    발표 속도 피드백

    +

    평가 일시 : {formatDate(feedbackData.practiceDate)}

    +
    +
    +
    - -

    발표 연습 시작하기

    + +

    발표 연습 다시하기

    diff --git a/src/app/(afterlogin)/(common_navbar)/feedback/[id]/_components/_charts/SpeedChart.tsx b/src/app/(afterlogin)/(common_navbar)/feedback/[id]/_components/_charts/SpeedChart.tsx new file mode 100644 index 0000000..78859d6 --- /dev/null +++ b/src/app/(afterlogin)/(common_navbar)/feedback/[id]/_components/_charts/SpeedChart.tsx @@ -0,0 +1,79 @@ +'use client'; +import { ApexOptions } from 'apexcharts'; +import dynamic from 'next/dynamic'; +const ReactApexChart = dynamic(() => import('react-apexcharts'), { ssr: false }); + +interface Props { + grade: string; +} +const SpeedChart = ({ grade }: Props) => { + let mainColor; + let gradationColor; + if (grade === 'Slow' || grade === 'Fast') { + mainColor = '#fe0062'; + gradationColor = '#ffbad1'; + } + if (grade === 'Good') { + mainColor = '#5a1df8'; + gradationColor = '#d7c4fe'; + } + + const options: ApexOptions = { + chart: { + redrawOnWindowResize: false, + type: 'radialBar', + }, + colors: [mainColor], + + plotOptions: { + radialBar: { + startAngle: -115, + endAngle: 115, + dataLabels: { + name: { + show: false, + }, + value: { + fontSize: '25px', + formatter: function () { + return `${grade}`; + }, + offsetY: 10, + show: true, + }, + }, + hollow: { + margin: 15, + size: '70%', + background: '#fff', + position: 'front', + dropShadow: { + enabled: true, + top: 3, + left: 0, + blur: 4, + opacity: 0.24, + }, + }, + }, + }, + fill: { + type: 'gradient', + gradient: { + shade: 'dark', + type: 'horizontal', + gradientToColors: [gradationColor!], + stops: [1, 20, 80, 100], + }, + }, + stroke: { + lineCap: 'round', + }, + }; + + const series = [100]; + + return ; +}; + +export default SpeedChart; diff --git a/src/app/(afterlogin)/(common_navbar)/feedback/[id]/_svgs/GoalIcon.tsx b/src/app/(afterlogin)/(common_navbar)/feedback/[id]/_svgs/GoalIcon.tsx index 6a17e6d..d5f7882 100644 --- a/src/app/(afterlogin)/(common_navbar)/feedback/[id]/_svgs/GoalIcon.tsx +++ b/src/app/(afterlogin)/(common_navbar)/feedback/[id]/_svgs/GoalIcon.tsx @@ -14,7 +14,7 @@ const GoalIcon = () => { r="16.9583" fill="white" stroke="#DDDDDD" - stroke-width="2.75" + strokeWidth="2.75" /> diff --git a/src/app/(afterlogin)/(common_navbar)/feedback/[id]/page.module.scss b/src/app/(afterlogin)/(common_navbar)/feedback/[id]/page.module.scss index 2e7627b..fa07d43 100644 --- a/src/app/(afterlogin)/(common_navbar)/feedback/[id]/page.module.scss +++ b/src/app/(afterlogin)/(common_navbar)/feedback/[id]/page.module.scss @@ -1,5 +1,5 @@ .container { - background-color: aqua; + min-width: 1320px; display: flex; justify-content: center; } diff --git a/src/app/(afterlogin)/(common_navbar)/feedback/[id]/page.tsx b/src/app/(afterlogin)/(common_navbar)/feedback/[id]/page.tsx index 1849112..3cbe74e 100644 --- a/src/app/(afterlogin)/(common_navbar)/feedback/[id]/page.tsx +++ b/src/app/(afterlogin)/(common_navbar)/feedback/[id]/page.tsx @@ -1,9 +1,10 @@ -import { fetch_ServerAuth } from '@/services/server/fetchServer'; import CategoryFeedback from './_components/CategoryFeedback'; import MemorizeReview from './_components/MemorizeReview'; import TotalScore from './_components/TotalScore'; import styles from './page.module.scss'; import { serverFeedbackApi } from '@/services/server/feedback'; +import { HydrationBoundary, QueryClient, dehydrate } from '@tanstack/react-query'; +import { FeedbackInfoType } from '@/types/service'; interface Props { params: { @@ -12,15 +13,25 @@ interface Props { } const page = async ({ params }: Props) => { const id = Number(params.id); - const res = await serverFeedbackApi.getFeedbackInfo(id); - console.log(await res.json()); + + const queryClient = new QueryClient(); + const feedbackData = await queryClient.fetchQuery({ + queryKey: ['feedback', 'info', id], + queryFn: async () => { + const response = await serverFeedbackApi.getFeedbackInfo(id); + return (await response.json()) as FeedbackInfoType; + }, + }); + const dehydratedState = dehydrate(queryClient); return (
    - - - + + + + +
    ); diff --git a/src/app/(afterlogin)/(common_navbar)/feedback/_utils/date.ts b/src/app/(afterlogin)/(common_navbar)/feedback/_utils/date.ts new file mode 100644 index 0000000..a67e242 --- /dev/null +++ b/src/app/(afterlogin)/(common_navbar)/feedback/_utils/date.ts @@ -0,0 +1,11 @@ +export const formatDate = (dateString: string) => { + const date = new Date(dateString); + // if (!(date instanceof Date) || isNaN(date.getTime())) { + // throw new Error('유효한 Date 객체를 입력해야 합니다.'); + // } + const year = date.getFullYear(); + const month = (date.getMonth() + 1).toString().padStart(2, '0'); + const day = date.getDate().toString().padStart(2, '0'); + + return `${year}.${month}.${day}`; +}; diff --git a/src/app/(afterlogin)/(common_navbar)/feedback/list/page.tsx b/src/app/(afterlogin)/(common_navbar)/feedback/list/page.tsx index e3b00c4..70852fe 100644 --- a/src/app/(afterlogin)/(common_navbar)/feedback/list/page.tsx +++ b/src/app/(afterlogin)/(common_navbar)/feedback/list/page.tsx @@ -14,7 +14,6 @@ export default async function Page() { }, initialPageParam: 0, }); - console.log(listResponse.pages[0].page); const isEmpty = listResponse.pages[0].page.empty; @@ -27,7 +26,7 @@ export default async function Page() { ) : (
    - +
    )} diff --git a/src/app/(afterlogin)/(common_navbar)/home/_hooks/presentationList.ts b/src/app/(afterlogin)/(common_navbar)/home/_hooks/presentationList.ts index e354d3f..f4f3257 100644 --- a/src/app/(afterlogin)/(common_navbar)/home/_hooks/presentationList.ts +++ b/src/app/(afterlogin)/(common_navbar)/home/_hooks/presentationList.ts @@ -1,5 +1,7 @@ import { clientHomeApi } from '@/services/client/home'; +import { clientPptApi } from '@/services/client/upload'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; +import { useRouter } from 'next/navigation'; export const useGetLatestPresentation = () => { const response = useQuery({ @@ -18,7 +20,7 @@ export const useDeletePresentation = (id: number) => { const response = useMutation({ mutationKey: ['delete', id], mutationFn: async () => { - const response = await clientHomeApi.deletePresentationList({ presentationIds: [id] }); + await clientHomeApi.deletePresentationList({ presentationIds: [id] }); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['home', 'list'] }); @@ -29,3 +31,23 @@ export const useDeletePresentation = (id: number) => { }); return response; }; + +export const useStartPresentation = (presentationId: number, start: boolean) => { + const router = useRouter(); + const response = useQuery({ + queryKey: [presentationId, 'start'], + queryFn: async () => { + try { + const response = await clientPptApi.getPracticeStart(presentationId); + router.push(`/setting/${presentationId}`); + return await response.json(); + } catch (e) { + if (e instanceof Error) alert(e.message); + } + }, + staleTime: 0, + gcTime: 0, + enabled: !!start, + }); + return response; +}; diff --git a/src/app/(afterlogin)/(common_navbar)/home/page.tsx b/src/app/(afterlogin)/(common_navbar)/home/page.tsx index b3f3dc1..3a704eb 100644 --- a/src/app/(afterlogin)/(common_navbar)/home/page.tsx +++ b/src/app/(afterlogin)/(common_navbar)/home/page.tsx @@ -37,7 +37,7 @@ export default async function Page() { {latestResponse !== 'empty' && } {/* */} - +
    )} diff --git a/src/app/(afterlogin)/(common_navbar)/upload/[id]/_component/UploadMemo.module.scss b/src/app/(afterlogin)/(common_navbar)/upload/[id]/_component/UploadMemo.module.scss index d212e19..84dbc9f 100644 --- a/src/app/(afterlogin)/(common_navbar)/upload/[id]/_component/UploadMemo.module.scss +++ b/src/app/(afterlogin)/(common_navbar)/upload/[id]/_component/UploadMemo.module.scss @@ -20,6 +20,7 @@ border: 1px solid $gray-3; padding-right: 16px; border-radius: 16px; + background-color: white; ::-webkit-scrollbar { width: 7px; diff --git a/src/app/(afterlogin)/(common_navbar)/upload/[id]/_component/UploadScript.module.scss b/src/app/(afterlogin)/(common_navbar)/upload/[id]/_component/UploadScript.module.scss index 14622f7..afafd2f 100644 --- a/src/app/(afterlogin)/(common_navbar)/upload/[id]/_component/UploadScript.module.scss +++ b/src/app/(afterlogin)/(common_navbar)/upload/[id]/_component/UploadScript.module.scss @@ -23,6 +23,7 @@ border: 1px solid $gray-3; border-radius: 16px; padding-right: 14px; + background-color: white; ::-webkit-scrollbar { width: 7px; diff --git a/src/app/(afterlogin)/(common_navbar)/upload/[id]/_component/UploadTitle.module.scss b/src/app/(afterlogin)/(common_navbar)/upload/[id]/_component/UploadTitle.module.scss index ee76a40..0d56e38 100644 --- a/src/app/(afterlogin)/(common_navbar)/upload/[id]/_component/UploadTitle.module.scss +++ b/src/app/(afterlogin)/(common_navbar)/upload/[id]/_component/UploadTitle.module.scss @@ -22,6 +22,7 @@ height: 72px; border-radius: 16px; border: 1px solid $gray-3; + background-color: white; &.warning { border: 2px solid $red-7; diff --git a/src/app/(afterlogin)/(common_navbar)/upload/[id]/_hooks/presentation.tsx b/src/app/(afterlogin)/(common_navbar)/upload/[id]/_hooks/presentation.tsx index 5ccca94..03cda2a 100644 --- a/src/app/(afterlogin)/(common_navbar)/upload/[id]/_hooks/presentation.tsx +++ b/src/app/(afterlogin)/(common_navbar)/upload/[id]/_hooks/presentation.tsx @@ -21,7 +21,7 @@ export const useGetPresentationData = (slug: number) => { export const usePostPresentationData = (submitAction: 'save' | 'start') => { const router = useRouter(); - + const queryClient = useQueryClient(); const { openToast } = useToastStore(); const openToastWithData = () => @@ -35,14 +35,26 @@ export const usePostPresentationData = (submitAction: 'save' | 'start') => { }, onSuccess: async (response) => { const { presentationId } = await response.json(); - if (submitAction === 'start') router.push(`/setting/${presentationId}`); + if (submitAction === 'start') { + try { + await queryClient.fetchQuery({ + queryKey: [presentationId, 'start'], + queryFn: async () => { + return await clientPptApi.getPracticeStart(presentationId); + }, + }); + router.push(`/setting/${presentationId}`); + } catch (e) { + if (e instanceof Error) alert(e.message); + } + } if (submitAction === 'save') { router.push(`/upload/${presentationId}`); openToastWithData(); } }, - onError: () => { - alert('저장하는 도중 문제가 발생했습니다.'); + onError: (error) => { + alert(error.message); }, }); @@ -52,7 +64,6 @@ export const usePostPresentationData = (submitAction: 'save' | 'start') => { export const usePatchPresentationData = (submitAction: 'save' | 'start', slug: number | 'new') => { const router = useRouter(); const queryClient = useQueryClient(); - const { openToast } = useToastStore(); const openToastWithData = () => @@ -67,7 +78,23 @@ export const usePatchPresentationData = (submitAction: 'save' | 'start', slug: n }, onSuccess: async (response) => { const { presentationId } = await response.json(); - if (submitAction === 'start') router.push(`/setting/${presentationId}`); + + if (submitAction === 'start') { + try { + await queryClient.fetchQuery({ + queryKey: [presentationId, 'start'], + queryFn: async () => { + return await clientPptApi.getPracticeStart(presentationId); + }, + staleTime: 0, + gcTime: 0, + }); + router.push(`/setting/${presentationId}`); + } catch (e) { + if (e instanceof Error) alert(e.message); + } + } + if (submitAction === 'save') openToastWithData(); }, onError: (error) => { diff --git a/src/app/(afterlogin)/_components/NavMenu.tsx b/src/app/(afterlogin)/_components/NavMenu.tsx index 1e072d6..b4a34a6 100644 --- a/src/app/(afterlogin)/_components/NavMenu.tsx +++ b/src/app/(afterlogin)/_components/NavMenu.tsx @@ -1,6 +1,6 @@ 'use client'; -import { EventHandler, MouseEventHandler, useMemo, useState } from 'react'; +import { MouseEventHandler, useMemo } from 'react'; import styles from './Navbar.module.scss'; import classNames from 'classnames'; @@ -11,12 +11,6 @@ import { usePathname, useRouter } from 'next/navigation'; import useToggle from '@/app/_hooks/useToggle'; import Confirm from '@/app/_components/_modules/_modal/Confirm'; -type ClickedList = 'presentationList' | 'report'; - -const isClickedList = (name: string): name is ClickedList => { - return name === 'presentationList' || name === 'report'; -}; - const NavMenu = () => { const pathName = usePathname(); const pageName = useMemo(() => pathName.split('/')[1], [pathName]); @@ -65,10 +59,12 @@ const NavMenu = () => { } if (name === 'home') { + router.refresh(); router.push('/home'); return; } if (name === 'feedback') { + router.refresh(); router.push('/feedback/list'); return; } @@ -77,18 +73,18 @@ const NavMenu = () => { return ( <> { if (isLastSlide) { - router.push(`/feedback/${id}`); + // router.push(`/feedback/${id}`); + savePractice(); + router.push(`/feedback/list`); } if (isActiveModal) { diff --git a/src/app/(afterlogin)/setting/[id]/layout.module.scss b/src/app/(afterlogin)/setting/[id]/layout.module.scss index 3810df5..84f7ae8 100644 --- a/src/app/(afterlogin)/setting/[id]/layout.module.scss +++ b/src/app/(afterlogin)/setting/[id]/layout.module.scss @@ -1,5 +1,3 @@ .container { min-width: 1440px; - background-attachment: fixed; - background-image: url('https://media.discordapp.net/attachments/1180406919098269696/1207958844555141161/Rectangle_20805.jpg?ex=65e18a57&is=65cf1557&hm=2a51b1de1f55361a8e833149009308721f17e9bea0e474ce40b09bb725a52ded&=&format=webp&width=1440&height=945'); } diff --git a/src/app/_components/_modules/_modal/Confirm.tsx b/src/app/_components/_modules/_modal/Confirm.tsx index d4ab30d..acb91e5 100644 --- a/src/app/_components/_modules/_modal/Confirm.tsx +++ b/src/app/_components/_modules/_modal/Confirm.tsx @@ -20,7 +20,7 @@ interface Props { /** 오른쪽 취소 버튼 이벤트 (기본 값 : 창 닫음) */ onCancelClick?: () => void; } - +const cx = classNames.bind(styles); const Confirm = ({ context, title, @@ -30,8 +30,6 @@ const Confirm = ({ onOkayClick, onCancelClick, }: Props) => { - const cx = classNames.bind(styles); - const handleOkay = () => { onOkayClick(); context.onClose(); diff --git a/src/app/globals.css b/src/app/globals.css index 569b981..f0add9e 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -6,11 +6,26 @@ html, body { - width: 100%; height: 100%; margin: 0; + padding: 0; + min-height: 100%; + background: linear-gradient(to bottom, #f5f3ff 70%, #fff 100%); + background-attachment: fixed; } +/* +body::before { + position: fixed; + top: 0; + left: 0; + z-index: -1; + width: 100%; + height: 100%; + background: linear-gradient(to bottom, #f5f3ff 70%, #fff 100%); + content: ''; +} */ + h1 { margin: 0; font-size: 32px; diff --git a/src/services/client/upload.ts b/src/services/client/upload.ts index 804aae3..c3e6013 100644 --- a/src/services/client/upload.ts +++ b/src/services/client/upload.ts @@ -11,6 +11,10 @@ export const clientPptApi = { }, body: JSON.stringify(data), }); + if (!response.ok) { + const errorBody = await response.json(); + throw new Error(errorBody.message || '데이터를 저장하는 도중 문제가 발생 했습니다'); + } return response; }, @@ -21,6 +25,11 @@ export const clientPptApi = { // const delay = (ms: any) => new Promise((resolve) => setTimeout(resolve, ms)); // await delay(3000); + if (!response.ok) { + const errorBody = await response.json(); + throw new Error(errorBody.message || '데이터를 가져오는 도중 문제가 발생 했습니다'); + } + return response; }, @@ -42,6 +51,19 @@ export const clientPptApi = { return response; }, + getPracticeStart: async (presentationId: number) => { + const response = await fetch_ClientAuth(`/api/practices/presentation/${presentationId}/start`, { + method: 'GET', + }); + + if (!response.ok) { + const errorBody = await response.json(); + throw new Error(errorBody.message || '데이터를 저장하는 도중 문제가 발생 했습니다!'); + } + + return response; + }, + // mock // TODO: 백엔드 api로 변경 및 삭제 예정 getMockPresentData: async (id: string) => { diff --git a/src/types/service.ts b/src/types/service.ts index a019c97..b2ce4c0 100644 --- a/src/types/service.ts +++ b/src/types/service.ts @@ -270,7 +270,7 @@ export interface FeedbackListType { content: { id: number; title: string; - practiceDate: Date; + practiceDate: string; totalScore: number; status: 'IN_PROGRESS' | 'DONE'; thumbnailPath: string; // 임시 @@ -285,7 +285,7 @@ export interface FeedbackListType { export interface FeedbackInfoType { id: number; title: string; - practiceDate: Date; + practiceDate: string; practiceTimes: number; isFirstPractice: boolean; totalScore: number; @@ -295,14 +295,18 @@ export interface FeedbackInfoType { description: string; }; memorizationFeedback: { + title: string; score: number; grade: string; description: string; + subGrade: '미흡' | '완벽'; }; speedFeedback: { + title: string; score: string; grade: string; description: string; + subGrade: '미흡' | '완벽'; }; timeFeedback: { targetTime: {