Skip to content

Commit 052fa9d

Browse files
authored
feature(graduation): 파일 미리보기 기능 구현 (#211)
2 parents b581fef + 5ccef4b commit 052fa9d

30 files changed

+1121
-36
lines changed

apps/community/next.config.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,14 @@ const nextConfig: NextConfig = {
4343
'@aics-client/design-system',
4444
'@aics-client/design-system/styles',
4545
],
46+
webpack: (config) => {
47+
// canvas는 jsdom의 서버 전용 네이티브 의존성이므로 클라이언트 번들에서 제외
48+
config.resolve.alias = {
49+
...config.resolve.alias,
50+
canvas: false,
51+
};
52+
return config;
53+
},
4654
};
4755

4856
export default withVanillaExtract(nextConfig);

apps/graduate/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
"format": "prettier --write ."
1414
},
1515
"dependencies": {
16+
"@react-pdf-viewer/core": "^3.12.0",
17+
"@react-pdf-viewer/default-layout": "^3.12.0",
1618
"@tanstack/react-devtools": "^0.7.0",
1719
"@tanstack/react-query": "^5.74.4",
1820
"@tanstack/react-query-devtools": "^5.74.6",
@@ -26,6 +28,7 @@
2628
"dayjs": "^1.11.18",
2729
"lucide-react": "^0.503.0",
2830
"motion": "^12.23.24",
31+
"pdfjs-dist": "3.11.174",
2932
"react": "^19.0.0",
3033
"react-dom": "^19.0.0",
3134
"react-hook-form": "^7.56.1",

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,5 @@ export interface StageData {
2929
createdAt: string | null;
3030
isSubmitted: boolean;
3131
isApproved: boolean;
32+
fileId: number | null;
3233
}

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

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { useState } from 'react';
1+
import { useSearch, useNavigate } from '@tanstack/react-router';
2+
import { useState, useEffect } from 'react';
23

34
import { DataTable, Header, Pagination, Toolbar } from '~/shared/components';
45
import {
@@ -26,6 +27,8 @@ import { extractPeriodData, getStatusLabel } from '../utils';
2627
import UserDetailModal from './UserDetailModal.tsx';
2728

2829
export default function AllManagementPage() {
30+
const navigate = useNavigate();
31+
const searchParams = useSearch({ from: '/_afterLogin/all' });
2932
const [isModalOpen, setIsModalOpen] = useState(false);
3033
const [page, setPage] = useState(1);
3134
const [pageSize, setPageSize] = useState(10);
@@ -34,6 +37,18 @@ export default function AllManagementPage() {
3437
const { toast, confirm } = useToast();
3538
const [selectedStudentId, setSelectedStudentId] = useState<number>();
3639

40+
useEffect(() => {
41+
if (searchParams?.graduationUserId) {
42+
setSelectedStudentId(Number(searchParams.graduationUserId));
43+
setIsModalOpen(true);
44+
navigate({
45+
to: '/all',
46+
search: {},
47+
replace: true,
48+
});
49+
}
50+
}, [searchParams, navigate]);
51+
3752
const { data: schedules, error: scheduleError } = useScheduleList();
3853

3954
if (scheduleError) {

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

Lines changed: 37 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { useNavigate } from '@tanstack/react-router';
12
import { Descriptions, Modal, Spin, Table } from 'antd';
23

34
import { Header } from '~/shared/components';
@@ -27,6 +28,7 @@ export default function UserDetailModal({
2728
graduationUserId,
2829
period,
2930
}: UserDetailModalProps) {
31+
const navigate = useNavigate();
3032
const {
3133
data: studentDetail,
3234
isLoading,
@@ -50,27 +52,32 @@ export default function UserDetailModal({
5052
dataIndex: 'stage',
5153
key: 'stage',
5254
render: (_: string, record: StageData) => {
53-
if (record.isSubmitted) {
54-
return (
55-
<button
56-
type='button'
57-
style={{
58-
background: 'none',
59-
border: 'none',
60-
padding: 0,
61-
cursor: 'pointer',
62-
color: vars.colors.main,
63-
textDecoration: 'underline',
64-
}}
65-
onClick={() => {
66-
console.log('미리보기 구현');
67-
}}
68-
>
69-
{record.stage}
70-
</button>
71-
);
72-
}
73-
return record.stage;
55+
return record.isSubmitted && status ? (
56+
<button
57+
type='button'
58+
style={{
59+
background: 'none',
60+
border: 'none',
61+
padding: 0,
62+
cursor: 'pointer',
63+
color: vars.colors.main,
64+
textDecoration: 'underline',
65+
}}
66+
onClick={() => {
67+
navigate({
68+
to: '/file-preview',
69+
search: {
70+
fileId: record.fileId!,
71+
type: status.type,
72+
},
73+
});
74+
}}
75+
>
76+
{record.stage}
77+
</button>
78+
) : (
79+
<span>{record.stage}</span>
80+
);
7481
},
7582
},
7683
{ title: '일정', dataIndex: 'period', key: 'period' },
@@ -133,17 +140,15 @@ export default function UserDetailModal({
133140
</Descriptions>
134141
</Container>
135142

136-
{status && (
137-
<Container style={{ padding: '0px' }}>
138-
<Table
139-
dataSource={stageData}
140-
columns={columns}
141-
pagination={false}
142-
bordered
143-
rowKey='key'
144-
/>
145-
</Container>
146-
)}
143+
<Container style={{ padding: '0px' }}>
144+
<Table
145+
dataSource={stageData}
146+
columns={columns}
147+
pagination={false}
148+
bordered
149+
rowKey='key'
150+
/>
151+
</Container>
147152
</>
148153
);
149154
};

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export function buildStageData(
2727
createdAt: status.certificate.createdAt,
2828
isSubmitted: status.certificate.submitted,
2929
isApproved: status.certificate.approval,
30+
fileId: status.certificate.fileId,
3031
},
3132
];
3233
} else {
@@ -38,6 +39,7 @@ export function buildStageData(
3839
createdAt: status.midThesis.createdAt,
3940
isSubmitted: status.midThesis.submitted,
4041
isApproved: status.midThesis.approval,
42+
fileId: status.midThesis.fileId,
4143
},
4244
{
4345
key: 'finalthesis',
@@ -46,6 +48,7 @@ export function buildStageData(
4648
createdAt: status.finalThesis.createdAt,
4749
isSubmitted: status.finalThesis.submitted,
4850
isApproved: status.finalThesis.approval,
51+
fileId: status.finalThesis.fileId,
4952
},
5053
];
5154
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { useFile } from './useFile';
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { useQuery } from '@tanstack/react-query';
2+
3+
import { getCertificateFile, getThesisFile } from '~/shared/api/file';
4+
import { KEYS } from '~/shared/constants';
5+
6+
import { transformCertificateResponse } from '../util/transformCertificateFileResponse';
7+
import { transformThesisResponse } from '../util/transformThesisFileResponse';
8+
9+
type FileType = 'CERTIFICATE' | 'THESIS';
10+
11+
export function useFile(fileId: number, type: FileType) {
12+
return useQuery({
13+
queryKey: [KEYS.STUDENT_FILE, type.toLowerCase(), fileId],
14+
queryFn: async () => {
15+
if (type === 'CERTIFICATE') {
16+
const response = await getCertificateFile(fileId);
17+
return transformCertificateResponse(response.data);
18+
} else {
19+
const response = await getThesisFile(fileId);
20+
return transformThesisResponse(response.data);
21+
}
22+
},
23+
enabled: !!fileId,
24+
});
25+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default as FilePreviewPage } from './ui/FilePreviewPage';
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export interface FileItem {
2+
graduationUserid: number;
3+
scheduleId: number;
4+
approval: boolean;
5+
file: {
6+
fileId: number;
7+
physicalPath: string;
8+
};
9+
}

0 commit comments

Comments
 (0)