-
Notifications
You must be signed in to change notification settings - Fork 2
[FE] 260224 #241 기능추가출석 출석하기 페이지 수정 #273
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
The head ref may contain hidden characters: "260224_#241-\uAE30\uB2A5\uCD94\uAC00\uCD9C\uC11D-\uCD9C\uC11D\uD558\uAE30-\uD398\uC774\uC9C0-\uC218\uC815"
Changes from all commits
7b7bbbd
e379ab0
343a0bc
f397a4f
d8f80be
e0d22c8
4ef0bd3
64e0182
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -126,8 +126,6 @@ | |
| } | ||
|
|
||
| .fileName { | ||
| min-width: 0; | ||
| flex: 1; | ||
| font-size: 13px; | ||
| color: #111827; | ||
| overflow: hidden; | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,4 +1,4 @@ | ||||||
| import { useEffect, useMemo, useRef, useState } from 'react'; | ||||||
| import { useEffect, useMemo, useState } from 'react'; | ||||||
| import { Search, Star } from 'lucide-react'; | ||||||
| import styles from './AdminMemberManage.module.css'; | ||||||
| import { | ||||||
|
|
@@ -55,55 +55,39 @@ const AdminMemberManage = () => { | |||||
|
|
||||||
| // 회원 목록 데이터 상태 | ||||||
| const [members, setMembers] = useState([]); | ||||||
| const [isPromotingById, setIsPromotingById] = useState({}); | ||||||
| const [isDeletingById, setIsDeletingById] = useState({}); | ||||||
| const [isChangingById, setIsChangingById] = useState({}); | ||||||
| const [changeDialog, setChangeDialog] = useState({ | ||||||
| open: false, | ||||||
| type: 'role', | ||||||
| member: null, | ||||||
| value: '', | ||||||
| }); | ||||||
| const latestRequestIdRef = useRef(0); | ||||||
|
|
||||||
| // 회원 목록 조회 (필요한 필터만 백엔드로 전달) | ||||||
| const loadMembers = async ({ keyword, role, status, requestId } = {}) => { | ||||||
| const loadMembers = async ({ keyword, role, status } = {}) => { | ||||||
| try { | ||||||
| const data = await getAdminMembersData({ keyword, role, status }); | ||||||
|
|
||||||
| if (requestId != null && requestId !== latestRequestIdRef.current) { | ||||||
| return; | ||||||
| } | ||||||
|
|
||||||
| const nextMembers = data.members || []; | ||||||
| setMembers(nextMembers); | ||||||
| console.log('회원 목록 로드 성공:', nextMembers); | ||||||
| } catch (error) { | ||||||
| if (requestId != null && requestId !== latestRequestIdRef.current) { | ||||||
| return; | ||||||
| } | ||||||
|
|
||||||
| window.alert(error?.message || '회원 목록을 불러오지 못했습니다.'); | ||||||
| setMembers([]); | ||||||
| } | ||||||
| }; | ||||||
|
|
||||||
| useEffect(() => { | ||||||
| loadMembers(); | ||||||
| }, []); | ||||||
|
|
||||||
| // 필터/검색 조건 변경 시 목록 재조회 | ||||||
| useEffect(() => { | ||||||
| const requestId = ++latestRequestIdRef.current; | ||||||
| const backendRole = roleFilter === 'all' ? undefined : roleFilter; | ||||||
| const backendStatus = statusFilter === 'all' ? undefined : statusFilter; | ||||||
| loadMembers({ | ||||||
| keyword: searchQuery.trim() || undefined, | ||||||
| role: backendRole, | ||||||
| status: backendStatus, | ||||||
| requestId, | ||||||
| }); | ||||||
|
|
||||||
| return () => { | ||||||
| if (latestRequestIdRef.current === requestId) { | ||||||
| latestRequestIdRef.current += 1; | ||||||
| } | ||||||
| }; | ||||||
| }, [roleFilter, searchQuery, statusFilter]); | ||||||
|
|
||||||
| const filteredMembers = useMemo(() => { | ||||||
|
|
@@ -140,15 +124,6 @@ const AdminMemberManage = () => { | |||||
| const { type, member, value } = changeDialog; | ||||||
| if (!member) return; | ||||||
|
|
||||||
| if (isChangingById[member.id]) { | ||||||
| return; | ||||||
| } | ||||||
|
|
||||||
| setIsChangingById((prev) => ({ | ||||||
| ...prev, | ||||||
| [member.id]: true, | ||||||
| })); | ||||||
|
|
||||||
| try { | ||||||
| if (type === 'role') { | ||||||
| if (!ROLE_OPTIONS.includes(value)) { | ||||||
|
|
@@ -175,29 +150,15 @@ const AdminMemberManage = () => { | |||||
| error?.message || | ||||||
| (type === 'role' ? '권한 변경에 실패했습니다.' : '상태 변경에 실패했습니다.') | ||||||
| ); | ||||||
| } finally { | ||||||
| setIsChangingById((prev) => ({ | ||||||
| ...prev, | ||||||
| [member.id]: false, | ||||||
| })); | ||||||
| } | ||||||
| }; | ||||||
|
|
||||||
| // 단일 회원 선배 전환 | ||||||
| const handlePromoteSenior = async (member) => { | ||||||
| if (isPromotingById[member.id]) { | ||||||
| return; | ||||||
| } | ||||||
|
|
||||||
| if (!window.confirm(`${member.name}님을 선배(SENIOR)로 전환하시겠습니까?`)) { | ||||||
| return; | ||||||
| } | ||||||
|
|
||||||
| setIsPromotingById((prev) => ({ | ||||||
| ...prev, | ||||||
| [member.id]: true, | ||||||
| })); | ||||||
|
|
||||||
| try { | ||||||
| await promoteAdminMemberSenior({ userId: member.id }); | ||||||
| await loadMembers({ | ||||||
|
|
@@ -207,29 +168,15 @@ const AdminMemberManage = () => { | |||||
| }); | ||||||
| } catch (error) { | ||||||
| window.alert(error?.message || '선배 전환에 실패했습니다.'); | ||||||
| } finally { | ||||||
| setIsPromotingById((prev) => ({ | ||||||
| ...prev, | ||||||
| [member.id]: false, | ||||||
| })); | ||||||
| } | ||||||
| }; | ||||||
|
|
||||||
| // 단일 회원 삭제 | ||||||
| const handleDelete = async (member) => { | ||||||
| if (isDeletingById[member.id]) { | ||||||
| return; | ||||||
| } | ||||||
|
|
||||||
| if (!window.confirm(`${member.name}님을 강제 탈퇴 처리하시겠습니까?`)) { | ||||||
| return; | ||||||
| } | ||||||
|
|
||||||
| setIsDeletingById((prev) => ({ | ||||||
| ...prev, | ||||||
| [member.id]: true, | ||||||
| })); | ||||||
|
|
||||||
| try { | ||||||
| await deleteAdminMember({ userId: member.id }); | ||||||
| await loadMembers({ | ||||||
|
|
@@ -239,11 +186,6 @@ const AdminMemberManage = () => { | |||||
| }); | ||||||
| } catch (error) { | ||||||
| window.alert(error?.message || '회원 삭제에 실패했습니다.'); | ||||||
| } finally { | ||||||
| setIsDeletingById((prev) => ({ | ||||||
| ...prev, | ||||||
| [member.id]: false, | ||||||
| })); | ||||||
| } | ||||||
| }; | ||||||
|
|
||||||
|
|
@@ -295,7 +237,7 @@ const AdminMemberManage = () => { | |||||
| <th>권한</th> | ||||||
| <th>포인트</th> | ||||||
| <th>상태</th> | ||||||
| <th>기수</th> | ||||||
| <th>가입일</th> | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 컬럼 라벨과 실제 값이 불일치합니다. 헤더는 Also applies to: 278-278 🤖 Prompt for AI Agents |
||||||
| <th className={styles.rightAlign}>작업</th> | ||||||
| </tr> | ||||||
| </thead> | ||||||
|
|
@@ -304,7 +246,7 @@ const AdminMemberManage = () => { | |||||
| <tr key={member.id}> | ||||||
| <td> | ||||||
| <div className={styles.memberInfo}> | ||||||
| <div className={styles.avatar}>{member.name?.[0] ?? '?'}</div> | ||||||
| <div className={styles.avatar}>{member.name[0]}</div> | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 아바타 이니셜 접근에서 런타임 에러 가능성이 있습니다.
🔧 제안 수정안- <div className={styles.avatar}>{member.name[0]}</div>
+ <div className={styles.avatar}>{member.name?.[0] ?? '?'}</div>📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
| <div> | ||||||
| <p className={styles.memberName}>{member.name}</p> | ||||||
| <p className={styles.memberEmail}>{member.email}</p> | ||||||
|
|
@@ -354,17 +296,15 @@ const AdminMemberManage = () => { | |||||
| type="button" | ||||||
| className={styles.actionButton} | ||||||
| onClick={() => handlePromoteSenior(member)} | ||||||
| disabled={Boolean(isPromotingById[member.id])} | ||||||
| > | ||||||
| {isPromotingById[member.id] ? '처리 중...' : '선배 전환'} | ||||||
| 선배 전환 | ||||||
| </button> | ||||||
| <button | ||||||
| type="button" | ||||||
| className={styles.actionButton} | ||||||
| onClick={() => handleDelete(member)} | ||||||
| disabled={Boolean(isDeletingById[member.id])} | ||||||
| > | ||||||
| {isDeletingById[member.id] ? '처리 중...' : '회원 삭제'} | ||||||
| 회원 삭제 | ||||||
| </button> | ||||||
| </div> | ||||||
| </td> | ||||||
|
|
@@ -428,9 +368,8 @@ const AdminMemberManage = () => { | |||||
| type="button" | ||||||
| className={styles.actionButton} | ||||||
| onClick={confirmChangeDialog} | ||||||
| disabled={Boolean(isChangingById[changeDialog.member.id])} | ||||||
| > | ||||||
| {isChangingById[changeDialog.member.id] ? '처리 중...' : '변경'} | ||||||
| 변경 | ||||||
| </button> | ||||||
| </div> | ||||||
| </div> | ||||||
|
|
||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
민감한 회원 정보를 브라우저 콘솔에 출력하지 마세요.
회원 목록 객체에는 이메일/학번 등 식별자가 포함될 수 있어, 운영 환경 콘솔 로그는 개인정보 노출 경로가 됩니다.
🔧 제안 수정안
- console.log('회원 목록 로드 성공:', nextMembers);📝 Committable suggestion
🤖 Prompt for AI Agents