Skip to content

Commit 4d4cb9b

Browse files
authored
[Feature]: 학생 api 연결 (#205)
2 parents 9e07366 + 42a93c8 commit 4d4cb9b

50 files changed

Lines changed: 1306 additions & 461 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/graduate/src/pages/admin/all/constants/allManagementColumns.tsx

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,31 @@
11
import type { Column } from '~/shared/components/DataTable/DataTable';
22

3+
import {
4+
HEADER_NAME,
5+
HEADER_NO,
6+
HEADER_STATUS,
7+
HEADER_STUDENT_ID,
8+
HEADER_TYPE,
9+
} from './allManagementTexts';
310
import type { AllManagementRow } from '../types/allManagement';
411

512
import { vars } from '~/vars.css';
613

14+
715
export const allManagementColumns = (
816
onNameClick: (row: AllManagementRow) => void,
917
): ReadonlyArray<Column<AllManagementRow>> =>
1018
[
11-
{ key: 'no', header: '번호', width: 56, cell: r => r.no },
12-
{ key: 'studentId', header: '학번', width: 100, cell: r => r.studentId },
19+
{ key: 'no', header: HEADER_NO, width: 56, cell: r => r.no },
20+
{
21+
key: 'studentId',
22+
header: HEADER_STUDENT_ID,
23+
width: 100,
24+
cell: r => r.studentId,
25+
},
1326
{
1427
key: 'name',
15-
header: '이름',
28+
header: HEADER_NAME,
1629
width: 120,
1730
cell: r => (
1831
<button
@@ -32,6 +45,6 @@ export const allManagementColumns = (
3245
</button>
3346
),
3447
},
35-
{ key: 'type', header: '졸업 유형', width: 120, cell: r => r.type },
36-
{ key: 'status', header: '상태', width: 140, cell: r => r.status },
48+
{ key: 'type', header: HEADER_TYPE, width: 120, cell: r => r.type },
49+
{ key: 'status', header: HEADER_STATUS, width: 140, cell: r => r.status },
3750
] as const;
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import type { GraduationType } from '~/shared/api/student/fetchGraduationUsers';
2+
3+
import type { AllManagementRow } from '../types/allManagement';
4+
5+
export const STATUS_UNKNOWN = '미정';
6+
export const STATUS_NOT_SUBMITTED = '미제출';
7+
export const STATUS_SUBMITTED = '제출';
8+
export const STATUS_SUBMITTED_APPROVED = '제출-승인';
9+
export const STATUS_MID_NOT_SUBMITTED = '중간보고서-미제출';
10+
export const STATUS_FINAL_NOT_SUBMITTED = '최종보고서-미제출';
11+
12+
export const HEADER_NO = '번호';
13+
export const HEADER_STUDENT_ID = '학번';
14+
export const HEADER_NAME = '이름';
15+
export const HEADER_TYPE = '졸업 유형';
16+
export const HEADER_STATUS = '상태';
17+
export const HEADER_STAGE = '단계';
18+
export const HEADER_PERIOD = '기간';
19+
20+
export const TYPE_THESIS = '논문';
21+
export const TYPE_CERTIFICATE = '자격증';
22+
export const TYPE_UNKNOWN = '미정';
23+
24+
export const TITLE_ALL_MANAGEMENT = '졸업 대상 전체 관리';
25+
export const LOADING_TEXT = '불러오는 중...';
26+
27+
export const TYPE_LABEL: Record<GraduationType, AllManagementRow['type']> = {
28+
미정: TYPE_UNKNOWN,
29+
논문: TYPE_THESIS,
30+
자격증: TYPE_CERTIFICATE,
31+
};

apps/graduate/src/pages/admin/all/constants/stageColumns.tsx

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,20 @@
1+
2+
import {
3+
HEADER_PERIOD,
4+
HEADER_STAGE,
5+
HEADER_STATUS,
6+
STATUS_NOT_SUBMITTED,
7+
STATUS_SUBMITTED,
8+
} from './allManagementTexts';
19
import type { Mode, StageData } from '../types/allManagement';
210
import { STAGE_NAME_TO_MODE } from '../types/allManagement';
311

412
import { vars } from '~/vars.css';
513

14+
615
export const getStageColumns = (setMode: (mode: Mode) => void) => [
716
{
8-
title: '단계',
17+
title: HEADER_STAGE,
918
dataIndex: 'stage',
1019
key: 'stage',
1120
render: (_: string, record: StageData) => {
@@ -33,20 +42,22 @@ export const getStageColumns = (setMode: (mode: Mode) => void) => [
3342
},
3443
},
3544
{
36-
title: '일정',
45+
title: HEADER_PERIOD,
3746
dataIndex: 'period',
3847
key: 'period',
3948
},
4049
{
41-
title: '상태',
50+
title: HEADER_STATUS,
4251
dataIndex: 'state',
4352
key: 'state',
4453
render: (_: string, record: StageData) => {
4554
return (
4655
<>
4756
<span>{record.date}</span>
4857
&nbsp;&nbsp;&nbsp;
49-
<span>({record.isSubmit ? '제출' : '미제출'})</span>
58+
<span>
59+
({record.isSubmit ? STATUS_SUBMITTED : STATUS_NOT_SUBMITTED})
60+
</span>
5061
</>
5162
);
5263
},

apps/graduate/src/pages/admin/all/mock/allManagement.ts

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,17 @@ import type {
44
UserDetail,
55
} from '../types/allManagement';
66

7+
const NAMES = [
8+
'홍길동'
9+
];
10+
711
export const MOCK_ROWS: AllManagementRow[] = Array.from({ length: 88 }).map(
812
(_, i) => ({
913
id: i + 1,
1014
no: i + 1,
1115
studentId: `${20190000 + (i % 30)}`,
12-
name: [
13-
'서진규',
14-
'이은신',
15-
'이여웅',
16-
'아이',
17-
'이도',
18-
'한태',
19-
'김현수',
20-
'최용환',
21-
'곽수',
22-
'최압',
23-
][i % 10],
24-
type: i % 2 === 0 ? '자격증' : '졸업 논문',
16+
name: NAMES[i % NAMES.length],
17+
type: i % 2 === 0 ? '자격증' : '논문',
2518
status: [
2619
'제출',
2720
'미제출',
@@ -38,7 +31,7 @@ export const MOCK_ROWS: AllManagementRow[] = Array.from({ length: 88 }).map(
3831
export const userDetailData: UserDetail = {
3932
studentId: '202211461',
4033
period: '2028-02',
41-
name: '서진규',
34+
name: '서지국',
4235
professor: '김교수',
4336
department: '컴퓨터공학과',
4437
delay: 0,

apps/graduate/src/pages/admin/all/types/allManagement.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ export type AllManagementRow = {
33
no: number;
44
studentId: string;
55
name: string;
6-
type: '자격증' | '졸업 논문';
6+
type: '자격증' | '논문' | '미정';
77
status: string;
88
};
99

apps/graduate/src/pages/admin/all/ui/AllManagementPage.tsx

Lines changed: 144 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,185 @@
11
import { useState } from 'react';
22

3-
import { Toolbar, Header, Pagination, DataTable } from '~/shared/components';
4-
import { useTableState } from '~/shared/hooks';
3+
import type { GraduationUserStatus } from '~/shared/api/student/fetchGraduationUsers';
4+
import { DataTable, Header, Pagination, Toolbar } from '~/shared/components';
5+
import {
6+
DELETE_ALERT,
7+
DELETE_CONFIRM_TITLE,
8+
} from '~/shared/components/Toolbar/toolbarTexts';
9+
import {
10+
useFetchGraduationUsers,
11+
useRemoveGraduationUsers,
12+
useToast,
13+
} from '~/shared/hooks';
14+
import { downloadGraduationUsersExcel } from '~/shared/utils';
515

6-
import { allManagementColumns } from '../constants/allManagementColumns.tsx';
7-
import { MOCK_ROWS } from '../mock/allManagement';
16+
import { allManagementColumns } from '../constants/allManagementColumns';
17+
import {
18+
LOADING_TEXT,
19+
STATUS_FINAL_NOT_SUBMITTED,
20+
STATUS_MID_NOT_SUBMITTED,
21+
STATUS_NOT_SUBMITTED,
22+
STATUS_SUBMITTED,
23+
STATUS_SUBMITTED_APPROVED,
24+
STATUS_UNKNOWN,
25+
TITLE_ALL_MANAGEMENT,
26+
TYPE_LABEL,
27+
TYPE_UNKNOWN,
28+
} from '../constants/allManagementTexts';
829
import * as style from '../styles/AllManagementPage.css.ts';
930
import type { AllManagementRow } from '../types/allManagement';
1031
import UserDetailModal from './UserDetailModal/UserDetailModal';
1132

12-
import { handleDownload } from '~/pages/admin/all/utils';
33+
function formatStatus(status?: GraduationUserStatus | null) {
34+
if (!status) return STATUS_UNKNOWN;
35+
if (status.type === 'CERTIFICATE') {
36+
if (!status.submitted) return STATUS_NOT_SUBMITTED;
37+
return status.approval ? STATUS_SUBMITTED_APPROVED : STATUS_SUBMITTED;
38+
}
39+
40+
if (!status.finalThesis.submitted) return STATUS_FINAL_NOT_SUBMITTED;
41+
if (!status.midThesis.submitted) return STATUS_MID_NOT_SUBMITTED;
42+
if (status.finalThesis.approval && status.midThesis.approval) {
43+
return STATUS_SUBMITTED_APPROVED;
44+
}
45+
return STATUS_SUBMITTED;
46+
}
1347

1448
export default function AllManagementPage() {
1549
const [isModalOpen, setIsModalOpen] = useState(false);
50+
const [page, setPage] = useState(1);
51+
const [pageSize, setPageSize] = useState(10);
52+
const [query, setQuery] = useState('');
53+
const [selectedIds, setSelectedIds] = useState<number[]>([]);
54+
const { toast, confirm } = useToast();
1655

17-
// const [selectedId, setSelectedId] = useState<string>('');
18-
const onNameClick = () => {
19-
setIsModalOpen(true);
56+
const resetSelection = () => {
57+
setSelectedIds([]);
58+
setPage(1);
59+
};
60+
61+
const { removeGraduationUsers } = useRemoveGraduationUsers({
62+
onSuccess: resetSelection,
63+
});
64+
65+
const { data, isLoading } = useFetchGraduationUsers({
66+
page: page - 1,
67+
size: pageSize,
68+
name: query || undefined,
69+
});
70+
71+
const rows: AllManagementRow[] = data
72+
? data.contents.map((user, idx) => {
73+
const type = TYPE_LABEL[user.graduationType] ?? TYPE_UNKNOWN;
74+
75+
return {
76+
id: user.id,
77+
no: (page - 1) * pageSize + idx + 1,
78+
studentId: user.studentId,
79+
name: user.name,
80+
type,
81+
status: formatStatus(user.status),
82+
};
83+
})
84+
: [];
85+
86+
const handleDeleteSelected = () => {
87+
if (selectedIds.length === 0) return;
88+
confirm({
89+
title: DELETE_CONFIRM_TITLE,
90+
content: DELETE_ALERT,
91+
okText: '삭제',
92+
cancelText: '취소',
93+
onOk: async () => {
94+
try {
95+
await removeGraduationUsers(selectedIds);
96+
toast.success('선택한 학생을 삭제했습니다.');
97+
} catch (error) {
98+
toast.error(
99+
error instanceof Error ? error.message : '삭제에 실패했습니다.',
100+
);
101+
}
102+
},
103+
});
20104
};
21105

22-
const columns = allManagementColumns(onNameClick);
106+
const handleDownloadExcel = async () => {
107+
try {
108+
await downloadGraduationUsersExcel();
109+
} catch (error) {
110+
toast.error(
111+
error instanceof Error ? error.message : '다운로드에 실패했습니다.',
112+
);
113+
}
114+
};
23115

24-
const st = useTableState<AllManagementRow>(MOCK_ROWS, r => r.id, {
25-
pageSize: 10,
26-
keys: ['studentId', 'name', 'type', 'status'],
116+
const columns = allManagementColumns(() => {
117+
setIsModalOpen(true);
27118
});
119+
const totalItems = data?.pageable.totalElements ?? 0;
120+
121+
const toggleAll = () => {
122+
const pageIds = rows.map(r => r.id);
123+
const allChecked =
124+
pageIds.length > 0 && pageIds.every(id => selectedIds.includes(id));
125+
setSelectedIds(prev =>
126+
allChecked
127+
? prev.filter(id => !pageIds.includes(id))
128+
: Array.from(new Set([...prev, ...pageIds])),
129+
);
130+
};
131+
132+
const toggleOne = (id: string | number) => {
133+
const numericId = Number(id);
134+
setSelectedIds(prev =>
135+
prev.includes(numericId)
136+
? prev.filter(x => x !== numericId)
137+
: [...prev, numericId],
138+
);
139+
};
28140

29141
return (
30142
<div className={style.root}>
31143
<div className={style.container}>
32-
<Header title='대상자 전체 관리' />
144+
<Header title={TITLE_ALL_MANAGEMENT} />
33145

34146
<Toolbar
35-
selectedCount={st.selected.length}
36-
query={st.query}
147+
selectedCount={selectedIds.length}
148+
query={query}
37149
onQueryChange={v => {
38-
st.setQuery(v);
39-
st.resetToFirstPage();
150+
setQuery(v);
151+
setPage(1);
40152
}}
41153
onApprove={() => {}}
42-
onDownload={() => handleDownload(st.selected, st.filtered)}
43-
onAddStudent={() => {}}
154+
onDeleteSelected={handleDeleteSelected}
155+
onDownload={handleDownloadExcel}
44156
disabledApprove={true}
45157
/>
46158

47159
<div className={style.card}>
48160
<DataTable<AllManagementRow>
49-
rows={st.pageRows}
161+
rows={rows}
50162
getRowId={r => r.id}
51163
columns={columns}
52-
allChecked={st.allChecked}
53-
onToggleAll={st.toggleAll}
54-
selectedIds={st.selected}
55-
onToggleOne={id => st.toggleOne(id as number)}
164+
onToggleAll={toggleAll}
165+
selectedIds={selectedIds}
166+
onToggleOne={toggleOne}
167+
emptyText={isLoading ? LOADING_TEXT : undefined}
56168
/>
57169
</div>
58170
<UserDetailModal
59171
isModalOpen={isModalOpen}
60172
setIsModalOpen={setIsModalOpen}
61173
/>
62174
<Pagination
63-
page={st.page}
64-
pageSize={st.pageSize}
65-
totalItems={st.filtered.length}
66-
onGoto={st.setPage}
67-
onPageSizeChange={size => st.setPageSize(size)}
175+
page={page}
176+
pageSize={pageSize}
177+
totalItems={totalItems}
178+
onGoto={setPage}
179+
onPageSizeChange={size => {
180+
setPageSize(size);
181+
setPage(1);
182+
}}
68183
/>
69184
</div>
70185
</div>

apps/graduate/src/pages/admin/all/utils/index.ts

Lines changed: 0 additions & 1 deletion
This file was deleted.

0 commit comments

Comments
 (0)