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

#377 Refactor: 기수 관련 UI 수정 및 추가 api 연결 #389

Open
wants to merge 16 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
347b3f8
style:#377 기수 추가 모달 ui 수정
dalzzy Feb 20, 2025
eb19ac2
refactor:#377 기수 추가 모달 ui 수정 및 유효성검사 추가
dalzzy Feb 21, 2025
77c0296
Merge branch 'develop' of https://github.com/Leets-Official/Weeth-FE …
dalzzy Feb 21, 2025
2ef759f
feat:#377 기수 클릭 시 해당 기수에 맞는 멤버만 필터링되도록 추가
dalzzy Feb 21, 2025
d3bb124
feat:#377 기수 수정 api 추가
dalzzy Feb 21, 2025
908f6a9
fix: #377 선택한 기수로 상태가 변경되지 않는 문제 해결
dalzzy Feb 21, 2025
b11ad61
fix:#377 중복 모달 생성 문제 해결
dalzzy Feb 21, 2025
73d6327
feat:#377 직접 입력 클릭시 모달 추가
dalzzy Feb 22, 2025
41edcdd
refactor:#377 기수 추가 시 현재 활동 중인지 값을 추가하여 요청하도록 수정
dalzzy Feb 25, 2025
ecfa656
fix:#377 멤버의 최신 기수를 불러오지 못하는 오류 수정
dalzzy Feb 26, 2025
3e9cdd5
refactor:#377 memberContext에서 기수를 비교하여 membershipType을 반환하도록 수정
dalzzy Feb 26, 2025
de0dd14
refactor:#377 기수 정보가 완전히 입력되지 않은 기수 컴포넌트의 ui 수정
dalzzy Feb 26, 2025
79bce07
refactor:#377 기수 수정 api 연결
dalzzy Feb 26, 2025
9a65830
refactor:#377 불필요한 값 제거
dalzzy Feb 26, 2025
c1c0dfa
refactor:#377 멤버 상세관리 모달 내의 기수 변경 모달 위치 수정
dalzzy Feb 27, 2025
b42b810
refactor:#377 페널티 페이지 기수 필터링 추가
dalzzy Feb 27, 2025
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
32 changes: 30 additions & 2 deletions src/api/admin/cardinal/postCardinal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,19 @@ import axios from 'axios';
const BASE_URL = import.meta.env.VITE_API_URL;
const PATH = '/api/v1/admin/cardinals';

// 새로운 기수 등록
const postCardinalApi = async (
cardinalNumber: number,
year: number,
semester: number,
inProgress: boolean,
) => {
const accessToken = localStorage.getItem('accessToken');

try {
const response = await axios.post(
`${BASE_URL}${PATH}`,
{ cardinalNumber, year, semester },
{ cardinalNumber, year, semester, inProgress },
{
headers: {
Authorization: `Bearer ${accessToken}`,
Expand All @@ -27,4 +29,30 @@ const postCardinalApi = async (
}
};

export default postCardinalApi;
// 기수 수정
Copy link
Collaborator

Choose a reason for hiding this comment

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

앞으로 추가해야할 api들은 인스턴스 사용해서 적어줘도 좋을 것 같아요!!

Copy link
Member Author

@dalzzy dalzzy Mar 1, 2025

Choose a reason for hiding this comment

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

네넴 알겠습니다! 어차피 api 연결은 이게 마지막이라... 새 이슈에서 한꺼번에 인스턴스 적용해볼게요!

const patchCardinalApi = async (
id: number,
year: number,
semester: number,
inProgress: boolean,
) => {
const accessToken = localStorage.getItem('accessToken');

try {
const response = await axios.patch(
`${BASE_URL}${PATH}`,
{ id, year, semester, inProgress },
{
headers: {
Authorization: `Bearer ${accessToken}`,
},
},
);

return response.data;
} catch (error: any) {
throw new Error(error.response?.data?.message || '기수 수정 실패');
}
};

export { patchCardinalApi, postCardinalApi };
6 changes: 5 additions & 1 deletion src/api/useGetCardinals.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@ export const getAllCardinals = async () => {

export const useGetAllCardinals = () => {
const [allCardinals, setAllCardinals] = useState<
{ id: number; cardinalNumber: number }[]
{
status: string; // 기수 상태값 추가 ( IN_PROGRESS 이면 현재 기수 )
id: number;
cardinalNumber: number;
}[]
>([]);
const [error, setError] = useState<string | null>(null);

Expand Down
65 changes: 54 additions & 11 deletions src/components/Admin/Box.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,40 +9,70 @@ export interface BoxProps {
lastColor?: string;
minWidth?: string;
isCardinalBox?: boolean;
onClick?: () => void;
isClick?: boolean;
isSelected?: boolean;
isIncomplete?: boolean;
}

export const Wrapper = styled.div<{
color: string;
isCardinalBox: boolean;
isClick?: boolean;
isSelected?: boolean;
isIncomplete?: boolean;
}>`
width: ${({ isCardinalBox }) => (isCardinalBox ? 'none' : '234px')};
min-width: ${({ isCardinalBox }) => (isCardinalBox ? '234px' : 'none')};
height: 164px;
background-color: ${(props) => props.color};
background-color: ${({ isIncomplete, isSelected, color }) => {
if (isIncomplete) return 'transparent';
if (isSelected) return theme.color.gray[18];
return color;
}};
border: ${({ isIncomplete }) =>
isIncomplete ? `1.5px dashed ${theme.color.gray[18]}` : 'none'};
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 16px;
box-sizing: border-box;
cursor: ${({ isClick }) => (isClick ? 'pointer' : 'auto')};

${({ isClick, isSelected, isIncomplete }) =>
isClick &&
!isSelected &&
!isIncomplete &&
`
&:hover {
background-color: ${theme.color.gray[18]};
}
`}
`;

export const Title = styled.div<{ isHidden?: boolean }>`
export const Title = styled.div<{
isHidden?: boolean;
isIncomplete?: boolean;
}>`
font-size: 18px;
font-family: ${theme.font.regular};
color: ${theme.color.gray[100]};
color: ${({ isIncomplete }) =>
isIncomplete ? theme.color.gray[18] : theme.color.gray[100]};
`;

export const Description = styled.div`
export const Description = styled.div<{ isIncomplete?: boolean }>`
font-size: 24px;
font-family: ${theme.font.semiBold};
color: ${theme.color.gray[100]};
color: ${({ isIncomplete }) =>
isIncomplete ? theme.color.gray[18] : theme.color.gray[100]};
margin-top: 20px;
`;

export const Last = styled.div<{ lastColor?: string }>`
export const Last = styled.div<{ lastColor?: string; isIncomplete?: boolean }>`
font-size: 18px;
font-family: ${theme.font.regular};
color: ${({ lastColor }) => lastColor || '#979797'};
color: ${({ isIncomplete, lastColor }) =>
isIncomplete ? '#909393' : lastColor || '#979797'};
`;

const Box: React.FC<BoxProps> = ({
Expand All @@ -52,12 +82,25 @@ const Box: React.FC<BoxProps> = ({
color,
lastColor,
isCardinalBox = false,
onClick,
isClick = false,
isSelected = false,
isIncomplete = false,
}) => {
return (
<Wrapper color={color} isCardinalBox={isCardinalBox}>
{title && <Title>{title}</Title>}
<Description>{description}</Description>
<Last lastColor={lastColor}>{last}</Last>
<Wrapper
onClick={onClick}
color={color}
isCardinalBox={isCardinalBox}
isClick={isClick}
isSelected={isSelected}
isIncomplete={isIncomplete}
>
{title && <Title isIncomplete={isIncomplete}>{title}</Title>}
<Description isIncomplete={isIncomplete}>{description}</Description>
<Last lastColor={lastColor} isIncomplete={isIncomplete}>
{last}
</Last>
</Wrapper>
);
};
Expand Down
5 changes: 2 additions & 3 deletions src/components/Admin/ButtonGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,9 @@ const ButtonGroupContainer = styled.div<{ hasEndGap?: boolean }>`
justify-content: center;
align-items: center;
gap: 12px;
padding-right: 20px;
padding: 10px;
overflow-x: auto;
white-space: nowrap;
padding-left: 10px;
&::-webkit-scrollbar {
height: 3px;
}
Expand All @@ -30,7 +29,7 @@ const ButtonGroupContainer = styled.div<{ hasEndGap?: boolean }>`
hasEndGap &&
`
& > :last-child {
margin-left:150px
margin-left:188px
}

@media (max-width: 900px) {
Expand Down
53 changes: 50 additions & 3 deletions src/components/Admin/CardinalInfo.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,30 @@
import theme from '@/styles/theme';
import Box from '@/components/Admin/Box';
import * as S from '@/styles/admin/cardinal/CardinalInfo.styled';
import AddCardinal from '@/components/Admin/AddCardinal';
import useGetAllCardinals from '@/api/useGetCardinals';
import { useEffect, useState } from 'react';
import { useGetAdminUsers } from '@/api/admin/member/getAdminUser';
import { useMemberContext } from '@/components/Admin/context/MemberContext';
import CardinalModal from '@/components/Admin/Modal/CardinalModal';

const CardinalInfo: React.FC = () => {
const { selectedCardinal, setSelectedCardinal } = useMemberContext();
const { allCardinals } = useGetAllCardinals();
const { allUsers } = useGetAdminUsers();
const [isModalOpen, setIsModalOpen] = useState(false);
const [currentEditingCardinal, setCurrentEditingCardinal] = useState<{
id: number;
cardinalNumber: number;
} | null>(null);
const [cardinalList, setCardinalList] = useState<
{ id: number; year?: number; semester?: number; cardinalNumber: number }[]
{
id: number;
year?: number;
semester?: number;
cardinalNumber: number;
status?: string;
}[]
>([]);

useEffect(() => {
Expand All @@ -27,6 +42,19 @@ const CardinalInfo: React.FC = () => {

const totalMembers = allUsers.length;

const handleCardinalClick = (cardinal: {
id: number;
year?: number;
semester?: number;
cardinalNumber: number;
}) => {
if (!cardinal.year || !cardinal.semester) {
setCurrentEditingCardinal(cardinal);
setIsModalOpen(true);
} else {
setSelectedCardinal(cardinal.cardinalNumber);
}
};
return (
<S.CardinalBoxWrapper>
<S.ScrollContainer>
Expand All @@ -38,6 +66,8 @@ const CardinalInfo: React.FC = () => {
color={theme.color.gray[18]}
lastColor="#D3D3D3"
isCardinalBox
isClick
onClick={() => setSelectedCardinal(null)}
/>
{cardinalList.map((cardinal) => {
const memberCount = getMemberCountByCardinal(cardinal.cardinalNumber);
Expand All @@ -47,17 +77,34 @@ const CardinalInfo: React.FC = () => {
: `노정완 외 ${memberCount}명`;

return (
<S.CardinalBox
<Box
key={cardinal.id}
title={`${cardinal.year}년 ${cardinal.semester}학기`}
title={
cardinal.year && cardinal.semester
? `${cardinal.year}년 ${cardinal.semester}학기 ${
cardinal.status === 'IN_PROGRESS' ? '(현재)' : ''
}`
: '정보를 입력해주세요'
}
description={`${cardinal.cardinalNumber}기`}
last={lastText}
color={theme.color.gray[65]}
lastColor="#D3D3D3"
isCardinalBox
isClick
isIncomplete={!cardinal.year || !cardinal.semester}
isSelected={selectedCardinal === cardinal.cardinalNumber}
onClick={() => handleCardinalClick(cardinal)}
/>
);
})}
{isModalOpen && currentEditingCardinal && (
<CardinalModal
isOpen={isModalOpen}
onClose={() => setIsModalOpen(false)}
initialCardinal={currentEditingCardinal}
/>
)}
</S.ScrollContainer>
</S.CardinalBoxWrapper>
);
Expand Down
13 changes: 6 additions & 7 deletions src/components/Admin/CardinalSearchBar.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { Dispatch, SetStateAction, useState } from 'react';
import React, { Dispatch, SetStateAction } from 'react';
import CardinalDropDown from '@/components/Admin/Cardinal';
import SearchBar, { SearchBarWrapper } from '@/components/Admin/SearchBar';

Expand All @@ -7,17 +7,16 @@ interface CombinedSearchBarProps {
setSelectedCardinal: Dispatch<SetStateAction<number | null>>;
}

const CombinedSearchBar: React.FC<CombinedSearchBarProps> = () => {
const [selectedCardinal, setSelectedCardinal] = useState<null | number>(null);

const CombinedSearchBar: React.FC<CombinedSearchBarProps> = ({
selectedCardinal,
setSelectedCardinal,
}) => {
return (
<SearchBarWrapper>
<div>
<CardinalDropDown
selectedCardinal={selectedCardinal}
setSelectedCardinal={(value) => {
setSelectedCardinal(value);
}}
setSelectedCardinal={setSelectedCardinal}
/>
</div>
<SearchBar isWrapped={false} />
Expand Down
7 changes: 4 additions & 3 deletions src/components/Admin/DirectCardinal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,25 +21,26 @@ const DirectCardinalDropdown: React.FC<DirectCardinalProps> = ({
}) => {
const [isOpen, setIsOpen] = useState(false);
const { allCardinals } = useGetAllCardinals();
const [isCustomInput] = useState(false);
const [isCustomInput, setIsCustomInput] = useState(false);

const sortedCardinals = [...allCardinals].reverse();

const toggleDropdown = () => setIsOpen(!isOpen);

const selectCardinal = (value: number) => {
setSelectedCardinal(value, false);
setIsCustomInput(false);
setIsOpen(false);
};

const handleCustomInput = () => {
setSelectedCardinal(0, true);
setIsCustomInput(true);
setIsOpen(false);
};

const getDisplayText = () => {
if (isCustomInput || selectedCardinal === 0 || selectedCardinal === null)
return '직접 입력';
if (isCustomInput || selectedCardinal === null) return '직접 입력';
return `${selectedCardinal}기`;
};

Expand Down
Loading