Skip to content
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
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@
"react-query": "^3.39.3",
"react-router-dom": "^6.22.2",
"sonner": "^2.0.5",
"styled-components": "^5.3.11",
"tailwind-merge": "^3.3.1",
"tailwindcss": "^4.1.10",
"use-debounce": "^10.0.5",
Expand Down
6 changes: 3 additions & 3 deletions src/apis/manager.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Group } from 'src/interface/group';
import axiosInstance from '../utils/axiosInstance';
import { SimpleUser, StudyApplyUser, UnAssignedUser } from 'src/interface/user';
import { SimpleUser, StudyEnrollee, UnAssignedUser } from 'src/interface/user';
import { Report, SimpleReport } from 'src/interface/report';

export interface EditUserRequest {
Expand All @@ -23,7 +23,7 @@ export const readAllGroups = async (): Promise<Group[]> => {
return response.data;
};

export const readAllStudyApplyUsers = async (): Promise<StudyApplyUser[]> => {
export const readAllStudyEnrollees = async (): Promise<StudyEnrollee[]> => {
const response = await axiosInstance.get(`/api/admin/allUsers`);
return response.data;
};
Expand All @@ -33,7 +33,7 @@ export const readGroupReport = async (id: number): Promise<GroupReportResponse>
return response.data;
};

export const readApplicants = async (): Promise<UnAssignedUser[]> => {
export const readEnrollees = async (): Promise<UnAssignedUser[]> => {
const response = await axiosInstance.get(`/api/admin/users/unassigned`);
return response.data;
};
Expand Down
8 changes: 4 additions & 4 deletions src/apis/study.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,22 @@ import { Course } from 'src/interface/course';
import { SimpleUser } from 'src/interface/user';
import axiosInstance from '../utils/axiosInstance';

interface StudyEnrollRequest {
interface StudyEnrollmentRequest {
courseIds: number[];
friendIds: number[];
}

export interface StudyEnrollResponse {
export interface StudyEnrollmentResponse {
friends: SimpleUser[];
courses: Course[];
}

export const studyEnroll = async (data: StudyEnrollRequest): Promise<StudyEnrollResponse> => {
export const studyEnrollment = async (data: StudyEnrollmentRequest): Promise<StudyEnrollmentResponse> => {
const response = await axiosInstance.post(`/api/v2/forms`, data);
return response.data;
};

export const getMyGroup = async (): Promise<StudyEnrollResponse> => {
export const getMyGroup = async (): Promise<StudyEnrollmentResponse> => {
const response = await axiosInstance.get(`/api/v2/users/me/forms`);
return response.data;
};
8 changes: 4 additions & 4 deletions src/components/ARouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,15 @@ export const router = createBrowserRouter([
}),
},
{
path: paths.application.root,
path: paths.enrollment.root,
lazy: async () => ({
Component: (await import('@/pages/OverviewApplication/Page')).default,
Component: (await import('@/pages/OverviewEnrollment/Page')).default,
}),
},
{
path: paths.application.add,
path: paths.enrollment.add,
lazy: async () => ({
Component: (await import('@/pages/StudyApplication/Page')).default,
Component: (await import('@/pages/StudyEnrollment/Page')).default,
}),
},
{
Expand Down
2 changes: 1 addition & 1 deletion src/components/PrivateRoute.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const validateByAuth = (access: Role, pathname: string) => {
switch (true) {
case pathname.includes(paths.reports.root):
return access === 'MEMBER' || access === 'ADMIN';
case pathname.includes(paths.application.root):
case pathname.includes(paths.enrollment.root):
return access === 'USER';
case pathname === paths.myGroup.root:
return access === 'MEMBER';
Expand Down
2 changes: 1 addition & 1 deletion src/components/SideBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ const navGroupsData: NavGroup[] = [
{
name: '스터디 신청',
icon: LifeBuoy,
href: paths.application.root,
href: paths.enrollment.root,
allowedRoles: ['USER'],
},
],
Expand Down
8 changes: 4 additions & 4 deletions src/const/paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const ROOTS = {
home: '/',
reports: '/reports',
admin: '/admin',
application: '/application',
enrollment: '/enrollment',
ranks: '/ranks',
profile: '/profile',
myGroup: '/my-group',
Expand All @@ -18,9 +18,9 @@ export const paths = {
oneReport: (reportId: string) => `${ROOTS.reports}/${reportId}`,
edit: (reportId: string) => `${ROOTS.reports}/${reportId}/edit`,
},
application: {
root: `${ROOTS.application}`,
add: `${ROOTS.application}/new`,
enrollment: {
root: `${ROOTS.enrollment}`,
add: `${ROOTS.enrollment}/new`,
},
admin: {
manageClass: `${ROOTS.admin}/manage-class`,
Expand Down
2 changes: 1 addition & 1 deletion src/interface/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export type UnAssignedUser = User & {
};

/** for 어드민 페이지 */
export interface StudyApplyUser {
export interface StudyEnrollee {
id: number;
name: string;
sid: string;
Expand Down
34 changes: 17 additions & 17 deletions src/pages/Admin/CreateGroup/Page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { deleteUserForm, readApplicants, teamMatch } from '@/apis/manager';
import { deleteUserForm, readEnrollees, teamMatch } from '@/apis/manager';
import SpinnerLoading from '@/components/SpinnerLoading';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
Expand All @@ -12,7 +12,7 @@ const cleanCourseName = (name: string) => name.replace(/\n/g, ' ');
const cleanProfName = (prof: string) => prof.replace(/\n/g, '').trim();

export default function CreateGroupPage() {
const { data, refetch, isLoading } = useQuery(['readApplicants'], readApplicants, {
const { data, refetch, isLoading } = useQuery(['readEnrollees'], readEnrollees, {
cacheTime: 5 * 60 * 1000,
});

Expand All @@ -26,7 +26,7 @@ export default function CreateGroupPage() {
},
});

const handleDeleteApplicant = (sid: string) => {
const handleDeleteEnrollee = (sid: string) => {
deleteUserFormMutation(sid);
};

Expand All @@ -44,7 +44,7 @@ export default function CreateGroupPage() {
teamMatchMutation();
};

const applicants = useMemo(() => {
const enrollees = useMemo(() => {
if (!data) return [];
return data;
}, [data]);
Expand Down Expand Up @@ -75,22 +75,22 @@ export default function CreateGroupPage() {
</TableRow>
</TableHeader>
<TableBody>
{applicants.length > 0 ? (
applicants.map((applicant) => (
<TableRow key={applicant.id}>
{enrollees.length > 0 ? (
enrollees.map((enrollee) => (
<TableRow key={enrollee.id}>
<TableCell>
<div className="font-medium">{applicant.name}</div>
<div className="text-xs text-muted-foreground">{applicant.sid}</div>
<div className="text-xs text-muted-foreground">{applicant.email}</div>
<div className="font-medium">{enrollee.name}</div>
<div className="text-xs text-muted-foreground">{enrollee.sid}</div>
<div className="text-xs text-muted-foreground">{enrollee.email}</div>
</TableCell>
{[0, 1, 2].map((index) => (
<TableCell key={index}>
{applicant.courses[index] ? (
{enrollee.courses[index] ? (
<div>
{cleanCourseName(applicant.courses[index].name)}
{cleanCourseName(enrollee.courses[index].name)}
<span className="text-xs text-muted-foreground">
{' '}
({cleanProfName(applicant.courses[index].prof)})
({cleanProfName(enrollee.courses[index].prof)})
</span>
</div>
) : (
Expand All @@ -99,9 +99,9 @@ export default function CreateGroupPage() {
</TableCell>
))}
<TableCell>
{applicant.friends.length > 0 ? (
{enrollee.friends.length > 0 ? (
<div className="flex flex-wrap gap-1">
{applicant.friends.map((friend) => (
{enrollee.friends.map((friend) => (
<Badge key={friend.id} variant="secondary">
{friend.name} ({friend.sid})
</Badge>
Expand All @@ -115,8 +115,8 @@ export default function CreateGroupPage() {
<Button
variant="ghost"
size="icon"
onClick={() => handleDeleteApplicant(applicant.sid)}
aria-label={`신청자 ${applicant.name} 삭제`}
onClick={() => handleDeleteEnrollee(enrollee.sid)}
aria-label={`신청자 ${enrollee.name} 삭제`}
>
<XIcon className="h-4 w-4 text-destructive" />
</Button>
Expand Down
4 changes: 2 additions & 2 deletions src/pages/Admin/ManageGroup/Page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { readAllGroups, readApplicants } from '@/apis/manager';
import { readAllGroups, readEnrollees } from '@/apis/manager';
import { useMemo } from 'react';
import { useQueries } from 'react-query';
import GroupTable from './components/GroupTable';
Expand All @@ -15,7 +15,7 @@ export default function MatchedGroupListPage() {
},
{
queryKey: ['ungroups'],
queryFn: readApplicants,
queryFn: readEnrollees,
cacheTime: 5 * 60 * 1000,
},
]);
Expand Down
79 changes: 39 additions & 40 deletions src/pages/Admin/ManageStudent/Page.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import * as xlsx from 'xlsx';
import * as React from 'react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell } from '@/components/ui/table';
import { PencilIcon, SaveIcon, XIcon, SearchIcon } from 'lucide-react';
import { editUser, readAllStudyApplyUsers } from '@/apis/manager';
import { editUser, readAllStudyEnrollees } from '@/apis/manager';
import { useQuery } from 'react-query';
import { StudyApplyUser } from '@/interface/user';
import { StudyEnrollee } from '@/interface/user';
import SpinnerLoading from '@/components/SpinnerLoading';
import { toast } from 'sonner';
import { downloadExcelFromSheetData } from '@/utils/excel';
Expand All @@ -17,23 +16,23 @@ const cleanProfName = (prof: string) => prof.replace(/\n/g, '').trim();

export default function ManageStudentPage() {
const {
data: applicants,
data: enrollees,
refetch,
isLoading,
} = useQuery(['allStudyApplyUsers'], readAllStudyApplyUsers, {
} = useQuery(['allStudyEnrollees'], readAllStudyEnrollees, {
cacheTime: 5 * 60 * 1000,
});

const [editingId, setEditingId] = React.useState<number | null>(null);
const [formData, setFormData] = React.useState<Partial<StudyApplyUser>>({});
const [formData, setFormData] = React.useState<Partial<StudyEnrollee>>({});
const [searchTerm, setSearchTerm] = React.useState('');

const handleEdit = (applicant: StudyApplyUser) => {
setEditingId(applicant.id);
const handleEdit = (enrollee: StudyEnrollee) => {
setEditingId(enrollee.id);
// 희망과목은 첫 번째 과목의 이름만 수정 가능하도록 단순화
setFormData({
...applicant,
courses: applicant.courses.length > 0 ? [{ ...applicant.courses[0] }] : [],
...enrollee,
courses: enrollee.courses.length > 0 ? [{ ...enrollee.courses[0] }] : [],
});
};

Expand Down Expand Up @@ -75,24 +74,24 @@ export default function ManageStudentPage() {
}
};

const filteredApplicants = React.useMemo(() => {
if (!applicants) return [];
return applicants.filter(
(applicant) =>
applicant.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
(applicant.group !== null && applicant.group.toString().includes(searchTerm.toLowerCase())) ||
applicant.sid.toLowerCase().includes(searchTerm.toLowerCase()) ||
applicant.email.toLowerCase().includes(searchTerm.toLowerCase()),
const filteredEnrollees = React.useMemo(() => {
if (!enrollees) return [];
return enrollees.filter(
(enrollee) =>
enrollee.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
(enrollee.group !== null && enrollee.group.toString().includes(searchTerm.toLowerCase())) ||
enrollee.sid.toLowerCase().includes(searchTerm.toLowerCase()) ||
enrollee.email.toLowerCase().includes(searchTerm.toLowerCase()),
);
}, [applicants, searchTerm]);
}, [enrollees, searchTerm]);

const handleExcelDownload = () => {
if (!applicants) return;
if (!enrollees) return;

downloadExcelFromSheetData(buildApplicantsSheetData(applicants), '스터디신청자목록.xlsx');
downloadExcelFromSheetData(buildEnrolleesSheetData(enrollees), '스터디신청자목록.xlsx');

function buildApplicantsSheetData(applicants: StudyApplyUser[]) {
return applicants.map((student) => ({
function buildEnrolleesSheetData(enrollees: StudyEnrollee[]) {
return enrollees.map((student) => ({
ID: student.id,
Name: student.name,
StudentId: student.sid,
Expand Down Expand Up @@ -141,10 +140,10 @@ export default function ManageStudentPage() {
</TableRow>
</TableHeader>
<TableBody>
{filteredApplicants.map((applicant) =>
editingId === applicant.id ? (
{filteredEnrollees.map((enrollee) =>
editingId === enrollee.id ? (
// 편집 모드
<TableRow key={applicant.id}>
<TableRow key={enrollee.id}>
<TableCell>
<Input
name="group"
Expand All @@ -165,14 +164,14 @@ export default function ManageStudentPage() {
<TableCell>
<Input name="sid" value={formData.sid ?? ''} onChange={handleChange} className="h-8" />
</TableCell>
<TableCell>{applicant.email}</TableCell>
<TableCell>{enrollee.email}</TableCell>
<TableCell>
{applicant.courses.length > 0 ? (
{enrollee.courses.length > 0 ? (
<>
{cleanCourseName(applicant.courses[0].name)}
{cleanCourseName(enrollee.courses[0].name)}
<span className="text-xs text-muted-foreground">
{' '}
({cleanProfName(applicant.courses[0].prof)})
({cleanProfName(enrollee.courses[0].prof)})
</span>
</>
) : (
Expand All @@ -194,18 +193,18 @@ export default function ManageStudentPage() {
</TableRow>
) : (
// 일반 모드
<TableRow key={applicant.id}>
<TableCell>{applicant.group !== null ? `Group${applicant.group}` : '-'}</TableCell>
<TableCell>{applicant.name}</TableCell>
<TableCell>{applicant.sid}</TableCell>
<TableCell>{applicant.email}</TableCell>
<TableRow key={enrollee.id}>
<TableCell>{enrollee.group !== null ? `Group${enrollee.group}` : '-'}</TableCell>
<TableCell>{enrollee.name}</TableCell>
<TableCell>{enrollee.sid}</TableCell>
<TableCell>{enrollee.email}</TableCell>
<TableCell>
{applicant.courses.length > 0 ? (
{enrollee.courses.length > 0 ? (
<>
{cleanCourseName(applicant.courses[0].name)}
{cleanCourseName(enrollee.courses[0].name)}
<span className="text-xs text-muted-foreground">
{' '}
({cleanProfName(applicant.courses[0].prof)})
({cleanProfName(enrollee.courses[0].prof)})
</span>
</>
) : (
Expand All @@ -217,15 +216,15 @@ export default function ManageStudentPage() {
variant="outline"
size="icon"
className="h-8 w-8"
onClick={() => handleEdit(applicant)}
onClick={() => handleEdit(enrollee)}
>
<PencilIcon className="h-4 w-4" />
</Button>
</TableCell>
</TableRow>
),
)}
{filteredApplicants.length === 0 && (
{filteredEnrollees.length === 0 && (
<TableRow>
<TableCell colSpan={6} className="h-24 text-center text-muted-foreground">
신청자가 없거나 검색 결과가 없습니다.
Expand Down
Loading