-
코어멤버 모집 안내
-
✅ 서류 지원 기간 : 2025년 09월 8일 (월) - 2025년 09월 12일 (금) 23:59:59
-
✅ 서류 결과 발표 : ~ 2025년 09월 14일 (일)
-
✅ 면접 진행 기간 : 2025년 09월 15일 (월) - 2025년 09월 19일 (금)
-
✅ 최종 결과 발표 : ~ 2025년 09월 21일 (일)
-
※ 면접은 지원자와 면접관의 일정에 따라 조정되며, 인하대학교 내부 장소에서 진행됩니다.
-
※ 면접은 대면을 원칙으로 하며, 부득이한 경우에만 비대면으로 진행됩니다.
-
❗️운영진으로 활동 시, 매주 화요일 19:00~21:00 정기 운영진 회의에 반드시 참석해야 합니다.
+
+ 마지막으로 공지 및 일정을 확인해주시기 바랍니다!
+
+
+
모집 일정
+
✅ 서류 지원 기간 : 2025년 09월 8일 (월) ~ 2025년 09월 21일 (일) 11:59:59
+
✅ 서류 결과 발표 : ~ 2025년 09월 21일 (일)
+
✅ 면접 진행 기간 : 2025년 09월 22일 (월) ~ 2025년 09월 26일 (금)
+
※ 지원자 및 면접관의 일정에 따라 마감 전 조기 면접 진행이 가능할 수 있습니다.
+
✅ 최종 결과 발표 : ~ 2025년 09월 28일 (일)
+
❗️ 첫 온보딩 : 2025년 09월 30일 (화)
+
+
+
+
면접 안내
+
• 원칙적으로 대면 면접을 진행하며, 부득이한 경우에 한해 비대면으로 조정될 수 있습니다.
+
• 면접은 인하대학교 내부 장소에서 진행됩니다.
+
+
+
+
활동 안내
+
• 운영진으로 활동 시, 매주 화요일 19:00~21:00 정기 운영진 회의에 필수참석해야합니다.
+
• 단, 2시간 전체 참석이 아닌 최소 1시간 이상 필참을 원칙으로 합니다.
+
+
+
diff --git a/src/app/core-recruit/submit/page.jsx b/src/app/core-recruit/submit/page.jsx
index d0f2a1f..3722027 100644
--- a/src/app/core-recruit/submit/page.jsx
+++ b/src/app/core-recruit/submit/page.jsx
@@ -25,14 +25,12 @@ export default function CoreRecruitSubmit() {
코어 멤버 지원서 제출이 완료되었습니다.
-
아래 일정과 안내를 한번더 확인해주세요!
-
✅ 서류 지원 기간 : 2025년 09월 8일 (월) - 2025년 09월 12일 (금) 23:59:59
-
✅ 서류 결과 발표 : ~ 2025년 09월 14일 (일)
-
✅ 면접 진행 기간 : 2025년 09월 15일 (월) - 2025년 09월 19일 (금)
-
✅ 최종 결과 발표 : ~ 2025년 09월 21일 (일)
-
※ 면접은 지원자와 면접관의 일정에 따라 조정되며, 인하대학교 내부 장소에서 진행됩니다.
-
※ 면접은 대면을 원칙으로 하며, 부득이한 경우에만 비대면으로 진행됩니다.
-
❗️운영진으로 활동 시, 매주 화요일 19:00~21:00 정기 운영진 회의에 반드시 참석해야 합니다.
+
아래 일정을 한번 더 확인해주세요!
+
✅ 서류 지원 기간 : 2025년 09월 8일 (월) ~ 2025년 09월 21일 (일) 11:59:59
+
✅ 서류 결과 발표 : ~ 2025년 09월 21일 (일)
+
✅ 면접 진행 기간 : 2025년 09월 22일 (월) ~ 2025년 09월 26일 (금)
+
※ 지원자 및 면접관의 일정에 따라 마감 전 조기 면접 진행이 가능할 수 있습니다.
+
✅ 최종 결과 발표 : ~ 2025년 09월 28일 (일)
diff --git a/src/app/coreadmin/layout.js b/src/app/coreadmin/layout.js
new file mode 100644
index 0000000..3091e43
--- /dev/null
+++ b/src/app/coreadmin/layout.js
@@ -0,0 +1,20 @@
+import MenuHeader from '@/components/ui/common/MenuHeader';
+import ApiCodeGuard from '@/components/auth/ApiCodeGuard.jsx';
+
+export const metadata = {
+ title: 'CoreAdmin',
+ description: 'Core member application management',
+};
+
+export default function CoreAdminLayout({ children }) {
+ return (
+
+ <>
+
+ {children}
+ >
+
+ );
+}
+
+
diff --git a/src/app/coreadmin/page.jsx b/src/app/coreadmin/page.jsx
new file mode 100644
index 0000000..96ca2b1
--- /dev/null
+++ b/src/app/coreadmin/page.jsx
@@ -0,0 +1,194 @@
+"use client";
+
+import React, { useCallback, useEffect, useRef } from 'react';
+import { Table, TableHeader, TableColumn, TableBody, TableRow, TableCell, Chip } from '@nextui-org/react';
+
+import UserDetailsModal from '@/components/admin/UserDetailsModal';
+import AdminTableTopContent from '@/components/admin/AdminTableTopContent';
+import AdminTableBottomContent from '@/components/admin/AdminTableBottomContent';
+import { useAuthenticatedApi } from '@/hooks/useAuthenticatedApi';
+
+const columns = [
+ { name: 'NAME', uid: 'name' },
+ { name: 'MAJOR / ID', uid: 'major' },
+ { name: 'TEAM', uid: 'team' },
+ { name: 'EMAIL', uid: 'email' },
+ { name: 'PHONE', uid: 'phone' },
+];
+
+export default function CoreAdminPage() {
+ const { apiClient } = useAuthenticatedApi();
+
+ const [page, setPage] = React.useState(1);
+ const [modalOpen, setModalOpen] = React.useState(false);
+ const modalClosing = useRef(false);
+
+ const [selectedUser, setSelectedUser] = React.useState(null);
+ const [searchValue, setSearchValue] = React.useState('');
+ const [query, setQuery] = React.useState('');
+ const [loading, setLoading] = React.useState(false);
+ const [error, setError] = React.useState('');
+ const [currentUsers, setCurrentUsers] = React.useState([]);
+ const [totalUsers, setTotalUsers] = React.useState(0);
+ const [totalPages, setTotalPages] = React.useState(0);
+
+ const rowsPerPage = 10;
+
+ const fetchApplicants = useCallback(async () => {
+ setLoading(true);
+ setError('');
+ try {
+ const params = {
+ page: page - 1,
+ size: rowsPerPage,
+ sort: 'createdAt',
+ dir: 'DESC',
+ question: query || undefined,
+ };
+ const res = await apiClient.get('/core-recruit/applicants', { params });
+ const list = Array.isArray(res?.data?.data) ? res.data.data : [];
+ const total = res?.data?.meta?.totalElements ?? list.length;
+ const computedTotalPages = Math.max(1, Math.ceil(total / rowsPerPage));
+
+ setCurrentUsers(list);
+ setTotalUsers(total);
+ setTotalPages(computedTotalPages);
+ } catch (err) {
+ setError(String(err?.message || 'failed to load applicants'));
+ setCurrentUsers([]);
+ setTotalUsers(0);
+ } finally {
+ setLoading(false);
+ }
+ }, [apiClient, page, rowsPerPage, query]);
+
+ const renderCell = useCallback((user, columnKey) => {
+ const value = user[columnKey];
+ switch (columnKey) {
+ case 'name':
+ return
{user?.name ?? ''};
+ case 'major':
+ return (
+
+
{user?.major ?? ''}
+
{user?.studentId ?? ''}
+
+ );
+ case 'team': {
+ const teamLabel = user?.team ?? '';
+ const teamColorMap = {
+ HR: '#EA4335',
+ BD: '#34A853',
+ TECH: '#4285F4',
+ 'PR/DESIGN': '#F9AB00',
+ };
+ const color = teamColorMap[teamLabel] || '#9CA3AF';
+ return (
+
+ {teamLabel}
+
+ );
+ }
+ case 'email':
+ return
{user?.email ?? ''};
+ case 'phone':
+ return
{user?.phone ?? ''};
+ case 'createdAt':
+ return
{user?.createdAt ? new Date(user.createdAt).toLocaleString() : ''};
+ default:
+ return value;
+ }
+ }, []);
+
+ const handleRowClick = async (user) => {
+ if (modalClosing.current) return;
+ try {
+ const id = user?.id;
+ if (!id) throw new Error('지원자 ID 없음');
+ const res = await apiClient.get(`/core-recruit/applicants/${id}`);
+ const detail = res?.data?.data ?? null;
+ if (!detail) throw new Error('상세 정보 없음');
+ setSelectedUser(detail);
+ setModalOpen(true);
+ } catch (e) {
+ alert('상세 정보를 불러오는 중 오류가 발생했습니다.');
+ }
+ };
+
+ const handleSearch = () => {
+ setPage(1);
+ setQuery((searchValue || '').trim());
+ };
+
+ const handleCloseModal = () => {
+ modalClosing.current = true;
+ setModalOpen(false);
+ setTimeout(() => {
+ modalClosing.current = false;
+ }, 300);
+ };
+
+ useEffect(() => {
+ if (searchValue === '' && query !== '') {
+ setPage(1);
+ setQuery('');
+ }
+ }, [searchValue, query]);
+
+ useEffect(() => {
+ fetchApplicants();
+ }, [fetchApplicants]);
+
+ return (
+
+
+ setPage(newPage)}
+ />
+
+ }
+ topContent={
+
+ }
+ >
+
+ {(column) => (
+
+ {column.name}
+
+ )}
+
+
+ {(item) => (
+ handleRowClick(item)}>
+ {(columnKey) => (
+
+ {renderCell(item, columnKey)}
+
+ )}
+
+ )}
+
+
+
+ {/* 기존 UserDetailsModal 재사용: 핵심 질문/자유문항은 response 배열로 표시됨 */}
+
+
+ );
+}
+
+
diff --git a/src/components/admin/UserDetailsModal.jsx b/src/components/admin/UserDetailsModal.jsx
index 62754e5..f48a7f0 100644
--- a/src/components/admin/UserDetailsModal.jsx
+++ b/src/components/admin/UserDetailsModal.jsx
@@ -29,7 +29,7 @@ export default function UserDetailsModal({ user, isOpen, onClose, preventClose }
User Details
- {/* User Basic Info (신 응답 스키마에 맞춤) */}
+ {/* User Basic Info (공통 + 코어 지원 정보 포함) */}
{[
@@ -37,6 +37,10 @@ export default function UserDetailsModal({ user, isOpen, onClose, preventClose }
['전공', user?.major],
['학번', user?.studentId],
['회비 송금 여부', typeof user?.isPayed === 'boolean' ? (user.isPayed ? 'Yes' : 'No') : undefined],
+ ['이메일', user?.email],
+ ['전화번호', user?.phone],
+ ['지원팀', user?.team],
+ ['제출 시각', user?.createdAt],
]
.filter(([, val]) => val !== undefined && val !== null && val !== '')
.map(([label, userValue], idx) => (
@@ -73,6 +77,39 @@ export default function UserDetailsModal({ user, isOpen, onClose, preventClose }
);
})}
+
+ {[
+ ['지원 동기', user?.motivation],
+ ['원하는 업무/프로젝트/비전', user?.wish],
+ ['장점/역량', user?.strengths],
+ ['각오', user?.pledge],
+ ]
+ .filter(([, val]) => val !== undefined && val !== null && String(val).trim() !== '')
+ .map(([label, val], idx) => (
+
+ ))}
+
+ {Array.isArray(user?.fileUrls) && user.fileUrls.length > 0 && (
+
+
+
첨부 파일
+
+ {user.fileUrls.map((url, idx) => (
+
+ ))}
+
+
+ )}
diff --git a/src/components/ui/common/MenuHeader.jsx b/src/components/ui/common/MenuHeader.jsx
index 627102a..d46a7d8 100644
--- a/src/components/ui/common/MenuHeader.jsx
+++ b/src/components/ui/common/MenuHeader.jsx
@@ -15,6 +15,7 @@ export default function MenuHeader() {
const menuItems = [
{ name: "멤버관리", href: "/admin" },
+ { name: "운영자 지원관리", href: "/coreadmin" },
{ name: "스터디", href: "#", onClick: () => alert('준비중입니다.') },
{ name: "공지사항", href: "#", onClick: () => alert('준비중입니다.') },
{ name: "프로젝트", href: "#", onClick: () => alert('준비중입니다.') },
@@ -47,6 +48,11 @@ export default function MenuHeader() {
멤버관리
+
+
+ 운영자 지원관리
+
+
alert('준비중입니다.')}>
스터디