Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add partial approve related logic in funding detail frame #1403

Merged
merged 2 commits into from
Jan 31, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 107 additions & 0 deletions packages/web/src/common/components/ApproveReasonToast.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import React from "react";

import styled from "styled-components";

import colors from "@sparcs-clubs/web/styles/themes/colors";
import { formatDotDetailDate } from "@sparcs-clubs/web/utils/Date/formatDate";

import FlexWrapper from "./FlexWrapper";
import Icon from "./Icon";

import Typography from "./Typography";

interface Reason {
datetime: Date;
reason: React.ReactNode;
status?: string;
}

interface ApproveReasonToastProps {
title: string;
reasons: Reason[];
}

const ForceBorderRadius = styled.div`
position: sticky;
top: 0px;
border-radius: 8px;
width: 100%;
overflow: hidden;
border: 1px solid ${({ theme }) => theme.colors.GREEN[600]};
background: ${({ theme }) => theme.colors.GREEN[100]};
z-index: ${({ theme }) => theme.zIndices.toast};
`;

const ApproveReasonToastInner = styled.div`
color: ${({ theme }) => theme.colors.BLACK};
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 8px;
width: 100%;
max-height: 300px;
overflow-y: auto;

.ApproveReasonToast-title {
padding: 12px 16px 0 16px;

position: sticky;
top: 0;
display: flex;
align-items: center;
gap: 8px;
background: ${({ theme }) => theme.colors.GREEN[100]};
z-index: ${({ theme }) => theme.zIndices.toast + 1};
}

.ApproveReasonToast-reasons {
display: flex;
width: 100%;
padding: 0 16px 12px 44px;
flex-direction: column;
gap: 8px;
flex: 1 0 0;
}

.ApproveReasonToast-sticky-title {
position: sticky;
top: 0;
background: ${({ theme }) => theme.colors.GREEN[100]};
z-index: ${({ theme }) => theme.zIndices.toast + 1};
}
`;

const ApproveReasonToast: React.FC<ApproveReasonToastProps> = ({
title,
reasons,
}) => (
<ForceBorderRadius>
<ApproveReasonToastInner>
<div className="ApproveReasonToast-title">
<Icon type="error" size={20} color={colors.GREEN[600]} />
<Typography fs={16} lh={24} fw="MEDIUM">
{title}
</Typography>
</div>
<div className="ApproveReasonToast-reasons">
{reasons.map(reason => (
<FlexWrapper
direction="column"
gap={4}
key={formatDotDetailDate(reason.datetime)}
>
<Typography fs={14} lh={16} fw="REGULAR" color="GRAY.600">
{formatDotDetailDate(reason.datetime)}{" "}
{reason.status && `• ${reason.status}`}
</Typography>
<Typography fs={16} lh={24} fw="REGULAR">
{reason.reason}
</Typography>
</FlexWrapper>
))}
</div>
</ApproveReasonToastInner>
</ForceBorderRadius>
);

export default ApproveReasonToast;
4 changes: 3 additions & 1 deletion packages/web/src/common/components/RejectReasonToast.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import Typography from "./Typography";
interface Reason {
datetime: Date;
reason: React.ReactNode;
status?: string;
}

interface RejectReasonToastProps {
Expand Down Expand Up @@ -90,7 +91,8 @@ const RejectReasonToast: React.FC<RejectReasonToastProps> = ({
key={formatDotDetailDate(reason.datetime)}
>
<Typography fs={14} lh={16} fw="REGULAR" color="GRAY.600">
{formatDotDetailDate(reason.datetime)}
{formatDotDetailDate(reason.datetime)}{" "}
{reason.status && `• ${reason.status} 사유`}
</Typography>
<Typography fs={16} lh={24} fw="REGULAR">
{reason.reason}
Expand Down
2 changes: 1 addition & 1 deletion packages/web/src/constants/tableTagList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ const FundingTagList: {
[FundingStatusEnum.Committee]: { text: "운위", color: "YELLOW" },
[FundingStatusEnum.Approved]: { text: "승인", color: "GREEN" },
[FundingStatusEnum.Rejected]: { text: "반려", color: "RED" },
[FundingStatusEnum.Partial]: { text: "부분 승인", color: "PURPLE" },
[FundingStatusEnum.Partial]: { text: "부분", color: "PURPLE" },
};

// TODO: interface enum 사용
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ const getFundingProgress = (
},
],
};
// TODO. 부분 승인 케이스 수정 필요
case FundingStatusEnum.Partial:
return {
labels: ["신청 완료", "동아리 연합회 부분 승인"],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import React, { useMemo } from "react";

import { IFundingCommentResponse } from "@sparcs-clubs/interface/api/funding/type/funding.type";
import { FundingStatusEnum } from "@sparcs-clubs/interface/common/enum/funding.enum";

import ApproveReasonToast from "@sparcs-clubs/web/common/components/ApproveReasonToast";
import ProgressStatus from "@sparcs-clubs/web/common/components/ProgressStatus";

import RejectReasonToast from "@sparcs-clubs/web/common/components/RejectReasonToast";

import { FundingTagList } from "@sparcs-clubs/web/constants/tableTagList";
import { getFundingProgress } from "@sparcs-clubs/web/features/manage-club/funding/constants/fundingProgressStatus";
import { getTagDetail } from "@sparcs-clubs/web/utils/getTagDetail";

interface FundingStatusSectionProps {
status: FundingStatusEnum;
editedAt: Date;
commentedAt?: Date;
comments: IFundingCommentResponse[];
}

const FundingStatusSection: React.FC<FundingStatusSectionProps> = ({
status,
editedAt,
commentedAt = undefined,
comments,
}) => {
const progressStatus = getFundingProgress(status, editedAt, commentedAt);

const ToastSection = useMemo(() => {
if (status === FundingStatusEnum.Rejected) {
return (
<RejectReasonToast
title="코멘트"
reasons={comments.map(comment => ({
datetime: comment.createdAt,
reason: comment.content,
status: getTagDetail(comment.fundingStatusEnum, FundingTagList)
.text,
}))}
/>
);
}

return (
<ApproveReasonToast
title="코멘트"
reasons={comments.map(comment => ({
datetime: comment.createdAt,
reason: comment.content,
status:
comment.fundingStatusEnum === FundingStatusEnum.Partial
? `${getTagDetail(comment.fundingStatusEnum, FundingTagList).text}승인`
: getTagDetail(comment.fundingStatusEnum, FundingTagList).text,
}))}
/>
);
}, [comments, status]);
Comment on lines +45 to +58
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

가장 최신 커멘트의 상태에 따라 ApproveReasonToast, RejectReasonToast, (+ 운위나 부분 승인도?) 으로 감싸면 어떨까?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

그 가장 최신 커멘트의 상태가 funding.fundingStatusEnum 아냐?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

지금 funding 상세조회 api 에서 보내주는 fundingStatusEnum(comment 안에 statusEnum 말고) 기준으로 rejected이면 RejectReasonToast(빨간색), 그 외의 나머지 케이스에는 ApproveReasonToast(초록색) 로 했는데

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

으악.. 코드를 반만 읽었나.. 그렇네요!
운위도 따로 만들면 좋을 것 같긴 한데, 일단 그건 집행부 쪽 할때 같이 하는걸로!


return (
<ProgressStatus
labels={progressStatus.labels}
progress={progressStatus.progress}
optional={comments && comments.length > 0 && ToastSection}
/>
);
};

export default FundingStatusSection;
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,7 @@ import Card from "@sparcs-clubs/web/common/components/Card";
import FlexWrapper from "@sparcs-clubs/web/common/components/FlexWrapper";
import Modal from "@sparcs-clubs/web/common/components/Modal";
import CancellableModalContent from "@sparcs-clubs/web/common/components/Modal/CancellableModalContent";
import ProgressStatus from "@sparcs-clubs/web/common/components/ProgressStatus";
import RejectReasonToast from "@sparcs-clubs/web/common/components/RejectReasonToast";

import { getFundingProgress } from "@sparcs-clubs/web/features/manage-club/funding/constants/fundingProgressStatus";
import { useDeleteFunding } from "@sparcs-clubs/web/features/manage-club/funding/services/useDeleteFunding";
import { useGetFunding } from "@sparcs-clubs/web/features/manage-club/funding/services/useGetFunding";
import useGetFundingDeadline from "@sparcs-clubs/web/features/manage-club/funding/services/useGetFundingDeadline";
Expand All @@ -26,6 +23,7 @@ import { isActivityReportUnverifiable } from "@sparcs-clubs/web/features/manage-
import BasicEvidenceList from "../components/BasicEvidenceList";
import FixtureEvidenceList from "../components/FixtureEvidenceList";
import FundingInfoList from "../components/FundingInfoList";
import FundingStatusSection from "../components/FundingStatusSection";
import NonCorpEvidenceList from "../components/NonCorpEvidenceList";
import OtherEvidenceList from "../components/OtherEvidenceList";
import TransportationEvidenceList from "../components/TransportationEvidenceList";
Expand Down Expand Up @@ -135,34 +133,11 @@ const FundingDetailFrame: React.FC<FundingDetailFrameProps> = ({ clubId }) => {
<FlexWrapper direction="column" gap={40}>
<Card outline>
{!isPastFunding && (
// TODO. 부분 승인 케이스 추가
<ProgressStatus
labels={
getFundingProgress(
funding.fundingStatusEnum,
funding.editedAt,
funding.commentedAt,
).labels
}
progress={
getFundingProgress(
funding.fundingStatusEnum,
funding.editedAt,
funding.commentedAt,
).progress
}
optional={
funding.comments &&
funding.comments.length > 0 && (
<RejectReasonToast
title="반려 사유"
reasons={funding.comments.map(comment => ({
datetime: comment.createdAt,
reason: comment.content,
}))}
/>
)
}
<FundingStatusSection
status={funding.fundingStatusEnum}
editedAt={funding.editedAt}
commentedAt={funding.commentedAt}
comments={funding.comments.toReversed()}
/>
)}
<AsyncBoundary isLoading={isLoading} isError={isError}>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import apiFnd002, {
ApiFnd002ResponseOk,
} from "@sparcs-clubs/interface/api/funding/endpoint/apiFnd002";
import { FundingStatusEnum } from "@sparcs-clubs/interface/common/enum/funding.enum";
import { useQuery } from "@tanstack/react-query";

import {
Expand All @@ -26,5 +27,114 @@ export const useGetFunding = (fundingId: number) =>
});

defineAxiosMock(mock => {
mock.onGet(apiFnd002.url(1)).reply(() => [200, {}]);
const fundingStatus = FundingStatusEnum.Approved;
const mockFundingDetail = {
id: 1,
club: {
id: 112,
},
activityD: {
id: 5,
},
fundingStatusEnum: fundingStatus,
purposeActivity: {
id: 3335,
activityStatusEnum: 2,
activityTypeEnum: 1,
club: {
id: 112,
name: "술박스",
},
name: "ffsgdgfd",
commentedAt: null,
editedAt: "2025-01-22T23:07:21.000Z",
updatedAt: "2025-01-25T04:35:48.000Z",
},
name: "ㅁㄹㅁㄴㅇㄹㅁㄴㅇㄹ",
expenditureDate: "2024-08-07T09:00:00.000Z",
expenditureAmount: 234234,
approvedAmount: 0,
tradeEvidenceFiles: [
{
id: "b5884fb8-672d-45cf-a4de-5d1ce34a35f4",
name: "websiteplanet-dummy-540X400.png",
extension: "png",
size: 8141,
userId: 8608,
url: "https://ar-002-clubs-v2-dev.s3.ap-northeast-2.amazonaws.com/file/8608.1737884920000.websiteplanet-dummy-540X400.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAZWM3SVKMLN74ARWD%2F20250130%2Fap-northeast-2%2Fs3%2Faws4_request&X-Amz-Date=20250130T133654Z&X-Amz-Expires=600&X-Amz-Signature=210bbac945b25b631498baca8ac0efa1df5cf06768c9287aa4f1d79a04541e9a&X-Amz-SignedHeaders=host&x-id=GetObject",
},
],
tradeDetailFiles: [
{
id: "c0c3fe89-66bd-441f-89e1-069ec53cdfbc",
name: "websiteplanet-dummy-540X400 (1).png",
extension: "png",
size: 8110,
userId: 8608,
url: "https://ar-002-clubs-v2-dev.s3.ap-northeast-2.amazonaws.com/file/8608.1737884926000.websiteplanet-dummy-540X400%20%281%29.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAZWM3SVKMLN74ARWD%2F20250130%2Fap-northeast-2%2Fs3%2Faws4_request&X-Amz-Date=20250130T133654Z&X-Amz-Expires=600&X-Amz-Signature=2cb8499190a6de332edc28793c08f7702e84a596ba033a0bbf1044eab8ffc2e8&X-Amz-SignedHeaders=host&x-id=GetObject",
},
],
tradeDetailExplanation: "ㅇㅎㅇㅎㄹㅇㅎ",
isFixture: false,
isTransportation: false,
isNonCorporateTransaction: false,
isFoodExpense: false,
isLaborContract: true,
laborContract: {
explanation: "ㅇㄴㄹㅇ",
files: [
{
id: "4c991eb5-a5d9-4df9-a5cc-9dbf87d1c67a",
name: "websiteplanet-dummy-540X400 (1).png",
extension: "png",
size: 8110,
userId: 8608,
url: "https://ar-002-clubs-v2-dev.s3.ap-northeast-2.amazonaws.com/file/8608.1737884935000.websiteplanet-dummy-540X400%20%281%29.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAZWM3SVKMLN74ARWD%2F20250130%2Fap-northeast-2%2Fs3%2Faws4_request&X-Amz-Date=20250130T133654Z&X-Amz-Expires=600&X-Amz-Signature=0d291445afba29cc48a30136ca4d9b52ee89a4f4102df8418e4a6002e95ad7c4&X-Amz-SignedHeaders=host&x-id=GetObject",
},
],
},
isExternalEventParticipationFee: false,
isPublication: false,
isProfitMakingActivity: false,
isJointExpense: false,
isEtcExpense: false,
editedAt: "2025-01-26T18:48:59.000Z",
commentedAt: new Date(),
createdAt: "2025-01-26T18:48:59.000Z",
updatedAt: "2025-01-29T22:41:08.000Z",
comments: [
{
id: 1,
funding: { id: 1 },
chargedExecutive: { id: 123 },
content: "대충 어떤 반려 사유",
fundingStatusEnum: FundingStatusEnum.Rejected,
approvedAmount: 1230,
createdAt: new Date(2024, 11, 10),
},
{
id: 1,
funding: { id: 1 },
chargedExecutive: { id: 123 },
content: "대충 어떤 부분 승인 사유",
fundingStatusEnum: FundingStatusEnum.Partial,
approvedAmount: 1230,
createdAt: new Date(2024, 12, 20),
},
{
id: 1,
funding: { id: 1 },
chargedExecutive: { id: 123 },
content: "대충 어떤 승인 사유",
fundingStatusEnum: FundingStatusEnum.Approved,
approvedAmount: 1230,
createdAt: new Date(),
},
],
};

const baseUrl = `/student/fundings/funding/`;
mock
.onGet(new RegExp(`^${baseUrl}\\d+$`))
.reply(() => [200, mockFundingDetail]);
});
Loading