diff --git a/apps/admin/src/entities/check-post/api/changeEvidenceState.ts b/apps/admin/src/entities/check-post/api/changeEvidenceState.ts index 9949a3b4..97862339 100644 --- a/apps/admin/src/entities/check-post/api/changeEvidenceState.ts +++ b/apps/admin/src/entities/check-post/api/changeEvidenceState.ts @@ -1,5 +1,5 @@ import instance from "@repo/api/axios"; -import type { postState } from "@repo/types/evidences"; +import type { PostStatus } from "@repo/types/evidences"; interface ChangeEvidenceStateResponse { status: number; @@ -8,11 +8,11 @@ interface ChangeEvidenceStateResponse { export const changeEvidenceState = async ( evidenceId: number, - status: postState, + status: PostStatus ): Promise => { const response = await instance.patch( `/evidence/${evidenceId}/status`, - { status }, + { status } ); return response; }; diff --git a/apps/admin/src/entities/check-post/ui/post.tsx b/apps/admin/src/entities/check-post/ui/post.tsx index 8b09f444..513d590d 100644 --- a/apps/admin/src/entities/check-post/ui/post.tsx +++ b/apps/admin/src/entities/check-post/ui/post.tsx @@ -1,13 +1,13 @@ "use client"; -import type { post } from "@repo/types/evidences"; +import type { PostType } from "@repo/types/evidences"; import { getCategoryName } from "@repo/utils/handleCategory"; import { isActivity, isOthers, isReading } from "@repo/utils/handlePost"; import { handleState, handleStateColor } from "@repo/utils/handleState"; import Image from "next/image"; interface PostProps { - data: post; + data: PostType; onClick?: () => void; } diff --git a/apps/admin/src/entities/score/ui/checkbox.tsx b/apps/admin/src/entities/score/ui/checkbox.tsx index 0467839a..e8dd1c59 100644 --- a/apps/admin/src/entities/score/ui/checkbox.tsx +++ b/apps/admin/src/entities/score/ui/checkbox.tsx @@ -1,8 +1,7 @@ "use client"; -import { useCallback, useState } from "react"; - import Checked from "@shared/asset/svg/checked"; +import { useCallback, useState } from "react"; interface CheckboxProps { check?: boolean; diff --git a/apps/admin/src/views/change-password/index.tsx b/apps/admin/src/views/change-password/index.tsx index 9452e6b9..ceacb2be 100644 --- a/apps/admin/src/views/change-password/index.tsx +++ b/apps/admin/src/views/change-password/index.tsx @@ -1,20 +1,21 @@ "use client"; +import { patchPassword } from "@/shared/api/patchPassword"; +import type { + ChangePasswordForm, + ChangePasswordProps, +} from "@/shared/model/changePWForm"; +import ChangePassword from "@/widgets/changePassword/ui"; import { Button } from "@repo/shared/button"; import { useMutation } from "@tanstack/react-query"; +import { AuthForm } from "@widgets/auth/ui"; import { useRouter } from "next/navigation"; import { useCallback } from "react"; import { useForm } from "react-hook-form"; -import { patchPassword } from "@/shared/api/patchPassword"; import type { ServerResponse } from "@/shared/model/AuthForm"; -import type { - ChangePasswordForm, - ChangePasswordProps, -} from "@/shared/model/changePWForm"; import type { HttpError } from "@/shared/types/error"; -import ChangePassword from "@/widgets/changePassword/ui"; -import { AuthForm } from "@widgets/auth/ui"; + const ChangePasswordView = () => { const router = useRouter(); diff --git a/apps/admin/src/views/check-post/api/getPosts.ts b/apps/admin/src/views/check-post/api/getPosts.ts index b895ef4a..5e831954 100644 --- a/apps/admin/src/views/check-post/api/getPosts.ts +++ b/apps/admin/src/views/check-post/api/getPosts.ts @@ -1,12 +1,8 @@ import instance from "@repo/api/axios"; -import type { EvidenceResponse, postState } from "@repo/types/evidences"; -import type { AxiosResponse } from "axios"; +import type { PostResponse, PostStatus } from "@repo/types/evidences"; -export const getPosts = async ( - email: string, - status: postState | null, -): Promise> => { - return await instance.get( - `/evidence/${decodeURIComponent(email).split("@")[0]?.slice(1)}?status=${status}`, +export const getPosts = async (email: string, status: PostStatus | null) => { + return await instance.get( + `/evidence/${decodeURIComponent(email).split("@")[0]?.slice(1)}?status=${status}` ); }; diff --git a/apps/admin/src/views/check-post/model/useChangeEvidenceState.ts b/apps/admin/src/views/check-post/model/useChangeEvidenceState.ts index 0f6f36de..cbcedc54 100644 --- a/apps/admin/src/views/check-post/model/useChangeEvidenceState.ts +++ b/apps/admin/src/views/check-post/model/useChangeEvidenceState.ts @@ -1,4 +1,4 @@ -import type { postState } from "@repo/types/evidences"; +import type { PostStatus } from "@repo/types/evidences"; import { useQueryClient } from "@tanstack/react-query"; import { useCallback } from "react"; import { toast } from "sonner"; @@ -9,7 +9,7 @@ export function useChangeEvidenceState(postId: number) { const queryClient = useQueryClient(); const updatePostState = useCallback( - async (state: postState) => { + async (state: PostStatus) => { try { if (postId) { const res = await changeEvidenceState(postId, state); @@ -28,15 +28,15 @@ export function useChangeEvidenceState(postId: number) { toast.error("게시글 상태 변경에 실패했습니다."); } }, - [postId, queryClient], + [postId, queryClient] ); const handlePostState = useCallback( - (state: postState) => (e: React.MouseEvent) => { + (state: PostStatus) => (e: React.MouseEvent) => { e.stopPropagation(); void updatePostState(state); }, - [updatePostState], + [updatePostState] ); return { handlePostState }; diff --git a/apps/admin/src/views/check-post/model/useGetPosts.ts b/apps/admin/src/views/check-post/model/useGetPosts.ts index 58971106..cfc846d5 100644 --- a/apps/admin/src/views/check-post/model/useGetPosts.ts +++ b/apps/admin/src/views/check-post/model/useGetPosts.ts @@ -1,13 +1,25 @@ -import type { postState } from "@repo/types/evidences"; +import type { PostStatus, PostType } from "@repo/types/evidences"; import { useQuery } from "@tanstack/react-query"; import { getPosts } from "../api/getPosts"; -export const useGetPosts = (email: string, status: postState | null) => { - return useQuery({ +export const useGetPosts = (email: string, status: PostStatus | null) => { + const query = useQuery({ queryKey: ["posts", email, status], queryFn: () => getPosts(email, status), staleTime: 1000 * 60 * 5, gcTime: 1000 * 60 * 30, }); + + const posts: PostType[] = [ + ...(query.data?.data.majorActivityEvidence ?? []), + ...(query.data?.data.humanitiesActivityEvidence ?? []), + ...(query.data?.data.readingEvidence ?? []), + ...(query.data?.data.otherEvidence ?? []), + ]; + + return { + ...query, + posts, + }; }; diff --git a/apps/admin/src/views/detail/ui/index.tsx b/apps/admin/src/views/detail/ui/index.tsx index 6baeeaa4..76b94bdc 100644 --- a/apps/admin/src/views/detail/ui/index.tsx +++ b/apps/admin/src/views/detail/ui/index.tsx @@ -1,6 +1,6 @@ "use client"; -import type { post, postState } from "@repo/types/evidences"; +import type { PostStatus } from "@repo/types/evidences"; import { getCategoryName } from "@repo/utils/handleCategory"; import { isActivity, isOthers, isReading } from "@repo/utils/handlePost"; import Image from "next/image"; @@ -19,11 +19,12 @@ export default function DetailView() { const { handlePostState } = useChangeEvidenceState(Number(id)); const searchParams = useSearchParams(); const email = searchParams.get("email"); - const status = searchParams.get("status") as postState | null; + const status = searchParams.get("status") as PostStatus | null; + const { data: studentData, isError: isStudentError } = useGetStudent( decodeURIComponent(String(student?.email ?? email)), ); - const { data: studentPost, isError: isPostError } = useGetPosts( + const { posts, isError: isPostError } = useGetPosts( String(student?.email ?? email), status, ); @@ -42,13 +43,6 @@ export default function DetailView() { toast.error("회원 정보를 불러오지 못했습니다."); } - const posts: post[] = [ - ...(studentPost?.data.majorActivityEvidence ?? []), - ...(studentPost?.data.humanitiesActivityEvidence ?? []), - ...(studentPost?.data.readingEvidence ?? []), - ...(studentPost?.data.otherEvidence ?? []), - ]; - const post = posts.find((post) => post.id === Number(id)); let title = "Title"; @@ -86,9 +80,9 @@ export default function DetailView() {
{post && - isActivity(post) && - post.imageUri != null && - post.imageUri !== "" ? ( + isActivity(post) && + post.imageUri != null && + post.imageUri !== "" ? (
{post.title} { const [click, setClick] = useState(null); @@ -104,8 +104,8 @@ const MemberView = () => { Pending={member.hasPendingEvidence} back={Number( String(member.grade) + - String(member.classNumber) + - String(member.number).padStart(2, "0"), + String(member.classNumber) + + String(member.number).padStart(2, "0"), )} className={ click === member.email diff --git a/apps/admin/src/views/score/api/featScore.ts b/apps/admin/src/views/score/api/patchScore.ts similarity index 60% rename from apps/admin/src/views/score/api/featScore.ts rename to apps/admin/src/views/score/api/patchScore.ts index a3808a30..83509670 100644 --- a/apps/admin/src/views/score/api/featScore.ts +++ b/apps/admin/src/views/score/api/patchScore.ts @@ -1,28 +1,23 @@ import instance from "@repo/api/axios"; import { isAxiosError, type AxiosResponse } from "axios"; -interface FeatScoreResponse { +interface PatchScore { categoryName: string; value: number; } -interface FeatScoreRequest { - categoryName: string; - value: number; -} - -export const featScore = async ( +export const patchScore = async ( email: string, category: string, score: number -): Promise> => { - const data: FeatScoreRequest = { +): Promise => { + const data: PatchScore = { categoryName: category, value: Number(score), }; try { const id = email.split("@")[0]?.slice(1); - return await instance.patch(`/score/${id}`, data); + return await instance.patch(`/score/${id}`, data); } catch (error) { if (isAxiosError(error) && error.response) { throw error; diff --git a/apps/admin/src/views/score/model/score.ts b/apps/admin/src/views/score/model/score.ts index 26504bde..f0fdd0b5 100644 --- a/apps/admin/src/views/score/model/score.ts +++ b/apps/admin/src/views/score/model/score.ts @@ -4,9 +4,11 @@ export interface Score { } export interface ScoreFormType { - activity: number | null; - oneSemester: number | null; - twoSemester: number | null; - newrrow: number | null; - checkbox: boolean | undefined; + activity: number; + inAward: number; + outAward: number; + oneSemester: number; + twoSemester: number; + newrrow: number; + checkbox: boolean; } diff --git a/apps/admin/src/views/score/model/score_category.ts b/apps/admin/src/views/score/model/score_category.ts new file mode 100644 index 00000000..9d6b61fc --- /dev/null +++ b/apps/admin/src/views/score/model/score_category.ts @@ -0,0 +1,44 @@ +export const SCORE_CATEGORIES = { + AWARD_IN: { + value: "HUMANITIES-AWARD_CAREER-HUMANITY-IN_SCHOOL", + message: "교내인성영역관련수상", + field: "inAward", + isCheckbox: false, + }, + AWARD_OUT: { + field: "outAward", + value: "HUMANITIES-AWARD_CAREER-HUMANITY-OUT_SCHOOL", + message: "교외인성영역관련수상", + isCheckbox: false, + }, + ACTIVITY: { + value: "HUMANITIES-SERVICE-ACTIVITY", + message: "봉사활동", + field: "activity", + isCheckbox: false, + }, + SEMESTER_1: { + value: "HUMANITIES-SERVICE-CLUB_SEMESTER_1", + message: "1학기 봉사 시간", + field: "oneSemester", + isCheckbox: false, + }, + SEMESTER_2: { + value: "HUMANITIES-SERVICE-CLUB_SEMESTER_2", + message: "2학기 봉사 시간", + field: "twoSemester", + isCheckbox: false, + }, + NEWRROW: { + value: "HUMANITIES-ACTIVITIES-NEWRROW_S", + message: "뉴로우 참여 횟수", + field: "newrrow", + isCheckbox: false, + }, + TOEIC: { + value: "FOREIGN_LANG-ATTENDANCE-TOEIC_ACADEMY_STATUS", + message: "TOEIC 사관 학교 참여 여부", + field: "checkbox", + isCheckbox: true, + }, +} as const; diff --git a/apps/admin/src/views/score/ui/scoreForm.tsx b/apps/admin/src/views/score/ui/scoreForm.tsx index 168f7cde..6dd29379 100644 --- a/apps/admin/src/views/score/ui/scoreForm.tsx +++ b/apps/admin/src/views/score/ui/scoreForm.tsx @@ -3,31 +3,26 @@ import { Button } from "@repo/shared/button"; import { Input } from "@repo/shared/input"; import { InputContainer } from "@repo/shared/inputContainer"; +import { useMutation } from "@tanstack/react-query"; +import { HttpStatusCode } from "axios"; import { useParams, useRouter } from "next/navigation"; import { useCallback } from "react"; -import { Controller, useForm, useWatch } from "react-hook-form"; +import { Controller, useForm } from "react-hook-form"; import { toast } from "sonner"; -import { Checkbox } from "@/entities/score/ui/checkbox"; -import Header from "@/shared/ui/header"; - -import { featScore } from "../api/featScore"; +import { patchScore } from "../api/patchScore"; import type { ScoreFormType } from "../model/score"; +import { SCORE_CATEGORIES } from "../model/score_category"; - -const SCORE_CATEGORIES = { - ACTIVITY: "HUMANITIES-SERVICE-ACTIVITY", - SEMESTER_1: "HUMANITIES-SERVICE-CLUB_SEMESTER_1", - SEMESTER_2: "HUMANITIES-SERVICE-CLUB_SEMESTER_2", - NEWRROW: "HUMANITIES-ACTIVITIES-NEWRROW_S", - TOEIC: "FOREIGN_LANG-ATTENDANCE-TOEIC_ACADEMY_STATUS", -} as const; +import { Checkbox } from "@/entities/score/ui/checkbox"; +import type { HttpError } from "@/shared/types/error"; +import Header from "@/shared/ui/header"; const ScoreForm = () => { const { id } = useParams(); const router = useRouter(); - const { handleSubmit, control } = useForm({ + const { handleSubmit, control, formState: { isValid } } = useForm({ mode: "onChange", }); @@ -35,105 +30,43 @@ const ScoreForm = () => { router.back(); }, [router]); - const renderCheckbox = useCallback( - ({ field }: { - field: { value?: boolean; onChange: (value: boolean | null) => void }; - }) => , - [], - ); - - const { oneSemester, twoSemester, newrrow, checkbox } = useWatch({ control }); - - const isFormValid = Boolean( - (oneSemester !== undefined && oneSemester !== null && oneSemester > 0) || - (twoSemester !== undefined && twoSemester !== null && twoSemester > 0) || - (newrrow !== undefined && newrrow !== null && newrrow > 0) || - checkbox !== undefined, - ); - - const handleScoreSubmit = useCallback( - async (category: string, score: number, successMessage: string) => { - try { - const email = decodeURIComponent(String(id)); - const response = await featScore(email, category, score); - - if (response.status === 204) { - toast.success(successMessage); - return true; - } else { - toast.error(`${successMessage.replace(" 완료", "")} 실패`); - return false; - } - } catch { - toast.error("점수 추가 중 오류가 발생했습니다"); - return false; - } + const renderCheckbox = useCallback(({ field }: { + field: { value?: boolean; onChange: (value: boolean | null) => void }; + }) => , []); + + const { mutate, isPending } = useMutation({ + mutationFn: async (data: ScoreFormType) => { + return Promise.all( + Object.values(SCORE_CATEGORIES).map(async (category) => { + const score = data[category.field as keyof ScoreFormType]; + return patchScore( + decodeURIComponent(String(id)), + category.value, + typeof score === "boolean" ? (score ? 0 : 1) : score + ); + }) + ); }, - [id], - ); - - const onSubmit = useCallback( - async (data: ScoreFormType) => { - let success = true; - - if (data.activity !== null && data.activity > 0) { - success = - (await handleScoreSubmit( - SCORE_CATEGORIES.ACTIVITY, - data.activity, - "봉사활동 점수 추가 완료", - )) && success; - } - - else if (data.oneSemester !== null && data.oneSemester > 0) { - success = - (await handleScoreSubmit( - SCORE_CATEGORIES.SEMESTER_1, - data.oneSemester, - "1학기 봉사 시간 점수 추가 완료", - )) && success; - } - - if (data.twoSemester !== null && data.twoSemester > 0) { - success = - (await handleScoreSubmit( - SCORE_CATEGORIES.SEMESTER_2, - data.twoSemester, - "2학기 봉사 시간 점수 추가 완료", - )) && success; - } - - if (data.newrrow !== null && data.newrrow > 0) { - success = - (await handleScoreSubmit( - SCORE_CATEGORIES.NEWRROW, - data.newrrow, - "뉴로우 참여 횟수 점수 추가 완료", - )) && success; + onSuccess: () => { + toast.success("모든 점수 부여 성공"); + router.push("/"); + }, + onError: (error: HttpError) => { + if (error.httpStatus == HttpStatusCode.NotFound) { + toast.error("해당하는 카테고리가 존재하지 않습니다."); + } else { + toast.error("점수 부여 중 오류가 발생 했습니다.") } + } + }); - if (data.checkbox !== undefined) { - success = - (await handleScoreSubmit( - SCORE_CATEGORIES.TOEIC, - data.checkbox ? 1 : 0, - "TOEIC 참여 여부 점수 추가 완료", - )) && success; - } + const onSubmit = useCallback((data: ScoreFormType) => { + mutate(data); + }, [mutate]); - if (success) { - router.push("/"); - } - }, - [handleScoreSubmit, router], - ); - - const handleFormSubmit = useCallback( - (e) => { - void handleSubmit(onSubmit)(e); - }, - [handleSubmit, onSubmit], - ); + const handleFormSubmit = useCallback((e) => { + void handleSubmit(onSubmit)(e); + }, [handleSubmit, onSubmit]); return (
@@ -155,24 +88,46 @@ const ScoreForm = () => { type="number" /> - - - - - - +
+ + + + + + +
+
+ + + + + + +
{
{modalOpen ? ( diff --git a/apps/client/src/views/edit/index.tsx b/apps/client/src/views/edit/index.tsx index b47dcecb..ab94b9b6 100644 --- a/apps/client/src/views/edit/index.tsx +++ b/apps/client/src/views/edit/index.tsx @@ -1,84 +1,7 @@ -"use client"; - -import type { Draft } from "@repo/types/draft"; -import type { post, Activity } from "@repo/types/evidences"; -import { isActivity, isReading } from "@repo/utils/handlePost"; -import { useParams, useSearchParams } from "next/navigation"; -import { toast } from "sonner"; - -import { useGetDraft } from "@/entities/posts/lib/useGetDraft"; -import { useGetPosts } from "@/entities/posts/lib/useGetPosts"; -import EditForm from "@/widgets/edit/ui/EditForm"; +import EditForm from "@/widgets/edit/ui"; const EditView = () => { - const params = useParams(); - const searchParams = useSearchParams(); - - const { id } = params; - const isDraft = searchParams.get("draft") === "true"; - - const { data: postsData, isError: isPostsError } = useGetPosts(null); - const { data: draftsData, isError: isDraftsError } = useGetDraft(); - - if (isPostsError || isDraftsError) { - toast.error("게시물을 불러오지 못했습니다."); - } - - const posts: post[] = [ - ...(postsData?.data.majorActivityEvidence ?? []), - ...(postsData?.data.humanitiesActivityEvidence ?? []), - ...(postsData?.data.readingEvidence ?? []), - ...(postsData?.data.otherEvidence ?? []), - ]; - - const draftPosts: Draft[] = [ - ...(draftsData?.activityEvidences ?? []), - ...(draftsData?.readingEvidences ?? []), - ]; - - const post: post | Draft | undefined = isDraft - ? draftPosts.find((p) => String(p.draftId) === id) - : posts.find((p) => p.id === Number(id)); - - if (!post) { - return
게시물을 찾을 수 없습니다.
; - } - - let type: "major" | "humanities" | "reading" | "others"; - - if ("draftId" in post) { - if ("author" in post) { - type = "reading"; - } else if ("categoryName" in post && post.categoryName === "MAJOR") { - type = "major"; - } else { - type = "humanities"; - } - } else { - const isMajorActivity = - isActivity(post) && - postsData?.data.majorActivityEvidence.some( - (p: Activity) => p.id === post.id, - ); - - const isHumanitiesActivity = - isActivity(post) && - postsData?.data.humanitiesActivityEvidence.some( - (p: Activity) => p.id === post.id, - ); - - if (isMajorActivity ?? false) { - type = "major"; - } else if (isHumanitiesActivity ?? false) { - type = "humanities"; - } else if (isReading(post)) { - type = "reading"; - } else { - type = "others"; - } - } - - return ; + return ; }; export default EditView; diff --git a/apps/client/src/views/main/ui/index.tsx b/apps/client/src/views/main/ui/index.tsx index a2c51c8b..73f96f8a 100644 --- a/apps/client/src/views/main/ui/index.tsx +++ b/apps/client/src/views/main/ui/index.tsx @@ -9,7 +9,7 @@ import Link from "next/link"; import { useCallback, useEffect, useState } from "react"; import ShowSignin from "@/entities/main/ui/showSignin"; -import { useGetCurrentMember } from "@/shared/model/useGetCurrentMember"; +import { useGetCurrentMember } from "@/shared/lib/useGetCurrentMember"; import { getCertification } from "@entities/main/api/getCertification"; import MainDropdown from "@entities/main/ui/dropdown"; import { ShowInformation } from "@entities/main/ui/showInformation"; @@ -39,21 +39,16 @@ const MainView = () => { const handleHoverDropdown = useCallback( (category: string) => () => { setHoverTab(category); - }, - [], - ); + }, []); const handleLeaveDropdown = useCallback(() => { setHoverTab(null); }, []); - const handleOpenModal = useCallback( - (modalname: ModalType) => () => { - setType(modalname); - setShow(true); - }, - [], - ); + const handleOpenModal = useCallback((modalname: ModalType) => () => { + setType(modalname); + setShow(true); + }, []); const handleCloseModal = useCallback(() => { setShow(false); @@ -156,7 +151,7 @@ const MainView = () => {
- +