diff --git a/README.md b/README.md index 64c1a33..2deccb0 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ | **분야** | **이름** | **포지션** | | --- | --- | --- | -| PM | 최도현 | **프론트엔드 리드**, 프론트 인프라 구축 & 서버 연동 및 배포, 화면 UI 구현,
UI/UX, GUI 디자인, 백엔드 API 및 DB 구축 | +| PM | 최도현 | **프론트엔드 리드**, 프론트 인프라 구축 & 서버 연동 및 배포, 화면 UI 구현,
UI/UX, GUI 디자인, 백엔드 API 및 DB 구축 | | 백엔드 | 유승완 | **백엔드 리드**, 백엔드 인프라 구축 & 서버 연동 및 배포, API 및 DB 구축 | | 백엔드 | 윤도훈 | **백엔드**, API 및 DB 구축 | | 프론트엔드 | 김수현 | **프론트엔드**, 화면 UI 구현, API 연동 | diff --git a/src/App.tsx b/src/App.tsx index e24ee21..42dffe3 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -24,7 +24,10 @@ import FAQ from './pages/FAQ' import { RecruitSubmit } from './pages/RecruitSubmit' import RecruitCheck from './pages/RecruitCheck' import RecruitCheckFinal from './pages/RecruitCheckFinal' +import SomkathonRecruit from './pages/SomkathonRecruit' +import SomkathonSubmit from './pages/SomkathonSubmit' import ProtectedRoute from './components/layout/ProtectRoute' +import SomkatonApplicants from './pages/admin/SomkatonApplicants' function App() { return ( @@ -59,6 +62,8 @@ function AppContent() { } /> } /> } /> + } /> + } /> {/* 관리자 페이지 */} } /> @@ -69,6 +74,7 @@ function AppContent() { } /> } /> } /> + }/> ) diff --git a/src/components/UI/Header.tsx b/src/components/UI/Header.tsx index 2a90c35..c7eb173 100644 --- a/src/components/UI/Header.tsx +++ b/src/components/UI/Header.tsx @@ -132,29 +132,15 @@ export const Header = (): JSX.Element => { > FAQ - - {/* 모집 기간에 따라 버튼 다르게 표시 */} - {isRecruiting ? ( -
  • { - navigate('/recruit') - toggleMenu() - }} - > - 34기 지원하기 -
  • - ) : ( -
  • { - navigate('/recruit/result') - toggleMenu() - }} - > - 34기 합격여부 확인하기 -
  • - )} +
  • { + navigate('/somkathon') + toggleMenu() + }} + > + 솜커톤 지원하기 +
  • )} diff --git a/src/components/UI/RecruitUI.tsx b/src/components/UI/RecruitUI.tsx index 88b3209..add893e 100644 --- a/src/components/UI/RecruitUI.tsx +++ b/src/components/UI/RecruitUI.tsx @@ -188,4 +188,38 @@ export const RecruitUI_FINAL2: React.FC = ({ name }) => {

    ) +} + +export const SomRecruitUI: React.FC = () => { + return( +
    +

    + 솜커톤에서 멋진 프로젝트를 만들어 주실 학우 여러분들을 모집합니다! +

    +
    +

    📅 모집 일정 :

    + 4월 11일 (금) ~ 4월 16일 (수) +
    + +
    +

    📝 모집 대상 :

    + 25년도 1학기 솜커톤에 참가하는 학우 여러분 +
    + +
    +

    🌿 신청 조건 :

    + 컴퓨터공학부 학생 +
    + +
    +

    🍀 참가비 :

    + 10,000원 +

    + 참가비는 솜커톤 행사 운영 자금으로 사용됩니다. + 납부 방법은 추후 안내드리겠습니다. +

    +
    +

    👀 의지가 있으며 교류를 중시하는 분을 기다립니다.

    +
    + ) } \ No newline at end of file diff --git a/src/pages/CoreMembers.tsx b/src/pages/CoreMembers.tsx index f816405..cbf5ce7 100644 --- a/src/pages/CoreMembers.tsx +++ b/src/pages/CoreMembers.tsx @@ -14,7 +14,7 @@ const profiles: Profile[] = [ { id: 1, name: '윤도훈', roll: '회장', github_username: 'hodoon' }, { id: 2, name: '공석', roll: '부회장', github_username: '' }, { id: 3, name: '유승완', roll: '기술팀장', github_username: 'ysw789' }, - { id: 4, name: '최도현', roll: '학술팀장', github_username: 'titeotty' }, + { id: 4, name: '최도현', roll: '학술팀장', github_username: 'dohy-eon' }, { id: 5, name: '공석', roll: '학술차장', github_username: '' }, { id: 6, name: '김수현', roll: '홍보팀장', github_username: 'sooh329' }, { id: 7, name: '임성환', roll: '서기', github_username: 'limtjdghks' }, @@ -127,4 +127,4 @@ const CoreMembers: React.FC = () => { ) } -export default CoreMembers \ No newline at end of file +export default CoreMembers diff --git a/src/pages/SomkathonRecruit.tsx b/src/pages/SomkathonRecruit.tsx new file mode 100644 index 0000000..8c514d4 --- /dev/null +++ b/src/pages/SomkathonRecruit.tsx @@ -0,0 +1,182 @@ +import React, { useState, useEffect, useRef } from 'react' +import axios from 'axios' +import MobileLayout from '../components/layout/MobileLayout' +import { useNavigate } from 'react-router-dom' +import { SomRecruitUI, RecruitHeader } from '../components/UI/RecruitUI' +import { InputField } from '../components/UI/Recruit_InputField' +import { Button } from '../components/UI/Recruit_Button' + + +const SomkathonRecruit: React.FC = () => { + const navigate = useNavigate() + const [contact, setContact] = useState('') + const [isRecruiting, setIsRecruiting] = useState(null) + const alertShown = useRef(false) + + const [formData, setFormData] = useState({ + participantName: '', + studentId: '', + department: '', + grade: '1', + contact: '', + email: '', + }) + + useEffect(() => { + const checkRecruitmentPeriod = async () => { + const startDate = new Date('2025-04-08T00:00:00') + const endDate = new Date('2025-04-17T00:00:00') + const now = new Date() + + if (now >= startDate && now <= endDate) { + setIsRecruiting(true) + } else { + setIsRecruiting(false) + if (!alertShown.current) { + alertShown.current = true + alert('현재 모집 기간이 아닙니다.') + navigate('/') + } + } + } + + checkRecruitmentPeriod() + }, [navigate]) + + if (isRecruiting === false) return null + + // 입력값들 제약조건 설정 + const handleInputChange = (e: React.ChangeEvent) => { + const { name, value } = e.target + let newValue = value + + if (name === 'contact') { + let formattedValue = value.replace(/[^0-9]/g, '') + + + if (formattedValue.length > 11) { + formattedValue = formattedValue.slice(0, 11) + } + + if (formattedValue.length === 10) { + formattedValue = formattedValue.replace(/^(\d{3})(\d{3})(\d{4})$/, '$1-$2-$3') + } else if (formattedValue.length === 11) { + formattedValue = formattedValue.replace(/^(\d{3})(\d{4})(\d{4})$/, '$1-$2-$3') + } + + setContact(formattedValue) + setFormData((prevData) => ({ + ...prevData, + contact: formattedValue + })) + } + else if (name === 'participantName') { + if (value.length <= 16) { + setFormData((prevData) => ({ + ...prevData, + participantName: value + })) + } + } else if (name === 'studentId') { + let formattedValue = value.replace(/[^0-9]/g, '') + formattedValue = formattedValue.slice(0, 8) + + setFormData((prevData) => ({ + ...prevData, + studentId: formattedValue + })) + } else { + setFormData((prevData) => ({ + ...prevData, + [name]: newValue + })) + } + } + + const handleKeyPress = (e: React.KeyboardEvent) => { + if (e.key === 'Enter') { + e.preventDefault() + + const form = e.currentTarget.form + if (!form) return + + const elements = Array.from(form.elements) as HTMLElement[] + const index = elements.indexOf(e.currentTarget) + + + for (let i = index + 1; i < elements.length; i++) { + const nextElement = elements[i] + if (nextElement instanceof HTMLInputElement || nextElement instanceof HTMLTextAreaElement || nextElement instanceof HTMLSelectElement) { + nextElement.focus() + break + } + } + } + } + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault() + if (!formData.participantName || !formData.studentId || !formData.contact || !formData.email || !formData.department) { + alert('모든 필수 정보를 입력해주세요.') + return + } + + try { + await axios.post('https://dmu-dasom-api.or.kr/api/somkathon/participants/create', formData, { + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json' + } + }) + + navigate('/somkathon/submit') + } catch (error: any) { + if (error.response) { + const errorData = error.response.data + + if (error.response.status === 400 && errorData.code === 'C001' || errorData.code === 'C007') { + alert('입력 정보를 다시 확인해주세요.') + } else if (error.response.status === 400 && errorData.code === 'C013'){ + alert('이미 등록된 학번입니다.') + } + } else { + console.error('API 요청 중 오류 발생:', error) + alert('네트워크 오류가 발생했습니다.') + } + } + } + + return ( + + + +
    +
    + + + + + + +
    +
    + ) +} + +export default SomkathonRecruit \ No newline at end of file diff --git a/src/pages/SomkathonSubmit.tsx b/src/pages/SomkathonSubmit.tsx new file mode 100644 index 0000000..aa7e0a9 --- /dev/null +++ b/src/pages/SomkathonSubmit.tsx @@ -0,0 +1,21 @@ +import React from 'react' +import MobileLayout from '../components/layout/MobileLayout' +import { Header } from '../components/UI/Header' +import { SomRecruitUI, RecruitHeader } from '../components/UI/RecruitUI' +import { Recruit_InfoBanner } from '../components/UI/Recruit_InfoBanner' + +const SomkathonSubmit: React.FC = () => { + return( + +
    + + + + + ) +} + +export default SomkathonSubmit \ No newline at end of file diff --git a/src/pages/admin/AdminMain.tsx b/src/pages/admin/AdminMain.tsx index aa32bdc..819976d 100644 --- a/src/pages/admin/AdminMain.tsx +++ b/src/pages/admin/AdminMain.tsx @@ -42,6 +42,7 @@ const AdminMain: React.FC = () => { +
    { + const [applicants, setApplicants] = useState([]) + const [detailInfo, setDetailInfo] = useState(null) + const [selectedId, setSelectedId] = useState (null) + const [count, setCount] = useState(0) + const accessToken = localStorage.getItem('accessToken') + + // 지원자 전체 조회 + const getData = async () => { + try { + if(!accessToken) { + alert('로그인이 필요합니다.') + return + } + const response = await axios.get('https://dmu-dasom-api.or.kr/api/somkathon/participants') + console.log(response.data) + setApplicants(response.data) + setCount(response.data.length) + } catch (e:any) { + alert('지원자 목록 불러오기 실패') + console.log(e) + } + } + + useEffect(() => { + getData() + },[]) + + // 지원자 상세 조회 + const toggleDetail = async (id:number) => { + if(selectedId === id) { + setSelectedId(null) + setDetailInfo(null) + return + } + + try { + const response = await axios.get(`https://dmu-dasom-api.or.kr/api/somkathon/participants/${id}`) + setDetailInfo(response.data) + setSelectedId(id) + } catch (e:any) { + console.log(e) + alert('지원자 상세정보 불러오기 실패') + } + } + + const handleDelete = async (id:number) => { + try { + await axios.delete(`https://dmu-dasom-api.or.kr/api/somkathon/participants/${id}`) + setDetailInfo(null) + getData() + } catch (e:any) { + console.log(e) + alert('지원자 삭제 실패') + } + } + + const ApplicantInfo = ({applicant} : {applicant:any}) => { + return( + + {applicant.id} + {applicant.participantName} + {applicant.studentId} + +
    + + {selectedId === applicant.id && (
    + {detailInfo && } +
    )} +
    + + + ) + } + + const DetailItem = ({label, value} : {label:string, value:string}) => { + return ( +
    +
    {label}
    +
    {value}
    +
    + ) + } + + const ApplicantDetailInfo = ({applicant} : {applicant:any}) => { + if(!applicant) return null + return ( +
    + + + + +
    + + +
    +
    + ) + } + + return ( +
    +
    +
    {count}명의 지원자가 있습니다.
    +
    + + + + + + + + + + + {applicants.map((applicant) => ( + + ))} + +
    ID이름학번상세정보
    +
    + ) +} + +export default SomkatonApplicants \ No newline at end of file