diff --git a/.env b/.env index 14bf74f..75e7fd8 100644 --- a/.env +++ b/.env @@ -12,5 +12,9 @@ DB_URL = "mongodb+srv://chaen328:scrumproject1@cluster0.coq8qi7.mongodb.net/?ret S3_ACCESS_KEY = AKIAQSPQBH2DMWG3ZWPC S3_SECRET_KEY = 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 BUCKET_NAME = khu-cse-page00 +EMAIL_SERVICE=gmail +NODEMAILER_USER= a64494293@gmail.com +NODEMAILER_PW= bbni zfjg yltu jebk JWT_SECRET = 'khu_jwt_secret_key' -REFRESH_JWT_SECRET = 'khu_jwt_refresh_secret_key' \ No newline at end of file +REFRESH_JWT_SECRET = 'khu_jwt_refresh_secret_key' + diff --git a/.gitignore b/.gitignore index b8a3d85..9df0570 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ .env /node_modules -/dist \ No newline at end of file +/dist diff --git a/DTO/projectDTO.ts b/DTO/projectDTO.ts new file mode 100644 index 0000000..90f227c --- /dev/null +++ b/DTO/projectDTO.ts @@ -0,0 +1,209 @@ +import mongoose from 'mongoose' +const projectModel = require('../models/projectSchema'); +const {alertUsers} = require('../middlewares/alertUsers') +const client = require ('../models/connectRedis') + +interface np { + title: String, + writer: String, + id : String, + date: String, + content:{ + image:[String], + video: [String], + text: String, + file: [String] + } + recruit: Array, + deadline : String, + is_done: boolean, + apply : Array +} + +interface applier { + id: string; + date: string; + field: string; + fieldDetail: string; + memo: string; +} + +interface project{ + link: string, + name: string, + reason: string +} + +export async function writeProject(newProject:np, userid:string, username:string){ + try{ + const project = await projectModel.create({ + title: newProject.title, + writer: username, + id : userid, + date: newProject.date, + content: { + image: newProject.content.image, + video: newProject.content.video, + text : newProject.content.text, + file : newProject.content.file + }, + recruit: newProject.recruit, + deadline : newProject.deadline, + is_done: false, + apply : [] + }) + } + catch(err){ + console.log(err) + } +} + +export async function editProject(newProject:np, projectId:string){ + const objectId: mongoose.Types.ObjectId = new mongoose.Types.ObjectId(projectId) + let applier:Array; + let project:project + const oldProject = await projectModel.findOne({_id:objectId}) + oldProject.title= newProject.title; + oldProject.content.image=newProject.content.image; + oldProject.content.video= newProject.content.video; + oldProject.content.text= newProject.content.text; + oldProject.content.file= newProject.content.file; + oldProject.recruit= newProject.recruit; + oldProject.deadline = newProject.deadline; + oldProject.apply = newProject.apply; + applier=oldProject.apply + project={name: oldProject.title, link:`localhost8080/project/detail/${oldProject._id}`, reason: ''} + await oldProject.save() + try{ + console.log('프로젝트가 성공적으로 수정되었습니다.'); + for(let i=0; i{ + useremail=email; + client.hget(applier[i].id, 'username', (err: any, name: any)=>{ + username=name; + alertUsers(useremail, 'edit', username, project) + }) + }) + } + } + catch(error) { + console.error('프로젝트 수정 중 오류 발생:', error); + }; +} + +export async function getList(page: string) { + let itemsPerPage = 10; + let page_int = parseInt(page, 10); + let skip = (page_int - 1) * itemsPerPage; + try { + const result = await projectModel + .find({}) + .sort({ _id: -1 }) + .skip(skip) + .limit(itemsPerPage) + .exec(); + return result; + } catch (err) { + console.error(err); + throw new Error('프로젝트 목록 데이터 가져오기에 실패했습니다.'); + } +} + +export async function getDetail(projectId:string) { + const objectId: mongoose.Types.ObjectId = new mongoose.Types.ObjectId(projectId) + try { + const result = await projectModel + .find({_id:objectId}) + .exec(); + return result; + } catch (err) { + console.error(err); + throw new Error('프로젝트 목록 데이터 가져오기에 실패했습니다.'); + } +} + +export async function endProject(projectId: string){ + let applier:Array; + let project:project + const objectId: mongoose.Types.ObjectId = new mongoose.Types.ObjectId(projectId) + const oldProject=await projectModel.findOne({_id:objectId}) + oldProject.is_done=true + applier=oldProject.apply + project={name: oldProject.title, link:`localhost8080/project/detail/${oldProject._id}`, reason: ''} + await oldProject.save() + try{ + console.log('프로젝트가 성공적으로 마감되었습니다.'); + for(let i=0; i{ + useremail=email; + client.hget(applier[i].id, 'username', (err: any, name: any)=>{ + username=name; + alertUsers(useremail, applier[i].memo, username, project) + }) + }) + } + } + catch(error) { + console.error('프로젝트 마감 중 오류 발생:', error); + }; +} +export async function applyProject(projectId: string, newApply:applier){ + const objectId: mongoose.Types.ObjectId = new mongoose.Types.ObjectId(projectId) + projectModel.findOne({_id:objectId}) + .then((res:any)=>{ + console.log(newApply) + if(!res){ + throw new Error('프로젝트를 찾을 수 없습니다.'); + } + let cnt =0; + for (let i=0; i { + console.error('지원 중 오류 발생:', error); + }); +} + +export async function deleteProject(projectId: string, reason: string){ + let applier:Array; + let project:project + const objectId: mongoose.Types.ObjectId = new mongoose.Types.ObjectId(projectId) + projectModel.findOne({_id:objectId}) + .then((res:any)=>{ + if(!res){ + throw new Error('프로젝트를 찾을 수 없습니다.'); + } + applier=res.apply + project={name: res.title, link:`localhost8080/project/detail/${res._id}`, reason: reason} + }) + .then(() => { + for(let i=0; i{ + useremail=email; + client.hget(applier[i].id, 'username', (err: any, name: any)=>{ + username=name; + alertUsers(useremail,'deletion', username, project) + }) + }) + } + }) + .catch((error: any) => { + console.error('프로젝트 마감 중 오류 발생:', error); + }); + const result = await projectModel.deleteOne({ _id: objectId }); + console.log('프로젝트가 성공적으로 삭제되었습니다') +} diff --git a/DTO/scholarshipoDTO.ts b/DTO/scholarshipoDTO.ts new file mode 100644 index 0000000..9114457 --- /dev/null +++ b/DTO/scholarshipoDTO.ts @@ -0,0 +1,48 @@ +// const scholarshipModel = require('../models/scholarshipSchema'); + +// export async function getList(page: string) { +// let itemsPerPage = 10; +// let page_int = parseInt(page, 10); +// let skip = (page_int - 1) * itemsPerPage; +// try { +// const result = await scholarshipModel +// .find({}) +// .sort({ _id: -1 }) +// .skip(skip) +// .limit(itemsPerPage) +// .exec(); +// return result; +// } catch (err) { +// console.error(err); +// throw new Error('장학 목록 데이터 가져오기에 실패했습니다.'); +// } +// } + + +// export async function getDetail(id:number) { +// var skip_number:number, limit_number:number +// if(id==0){ +// skip_number = id +// limit_number = 2 +// } +// else if (id==499){ +// skip_number = id-1 +// limit_number = 2 +// } +// else{ +// skip_number = id-1 +// limit_number = 3 +// } +// try { +// const result = await scholarshipModel +// .find({}) +// .sort({_id:-1}) +// .skip(skip_number) +// .limit(limit_number) +// .exec() +// return result; +// } catch (err) { +// console.error(err); +// throw new Error('장학 상세 데이터 가져오기에 실패했습니다.'); +// } +// } \ No newline at end of file diff --git a/client/package-lock.json b/client/package-lock.json index 1090beb..3abdfa8 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -8,27 +8,37 @@ "name": "client", "version": "0.1.0", "dependencies": { - "@reduxjs/toolkit": "^2.1.0", + "@reduxjs/toolkit": "^2.0.1", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", "@types/jest": "^27.5.2", "@types/node": "^16.18.71", "@types/react": "^18.2.48", + "@types/react-calendar": "^4.1.0", "@types/react-dom": "^18.2.18", - "@types/react-js-pagination": "^3.0.7", "@types/react-redux": "^7.1.33", "@types/react-router-dom": "^5.3.3", - "axios": "^1.6.7", + "@types/styled-components": "^5.1.34", + "axios": "^1.6.5", + "moment": "^2.30.1", + "mongodb": "^6.5.0", "react": "^18.2.0", + "react-calendar": "^4.8.0", "react-dom": "^18.2.0", "react-icons": "^5.0.1", "react-js-pagination": "^3.0.3", "react-redux": "^9.1.0", + "react-router": "^6.21.3", "react-router-dom": "^6.21.3", "react-scripts": "5.0.1", + "redux": "^5.0.1", + "styled-components": "^6.1.8", "typescript": "^4.9.5", "web-vitals": "^2.1.4" + }, + "devDependencies": { + "@types/react-js-pagination": "^3.0.7" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -2297,6 +2307,24 @@ "postcss-selector-parser": "^6.0.10" } }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz", + "integrity": "sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw==", + "dependencies": { + "@emotion/memoize": "^0.8.1" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" + }, + "node_modules/@emotion/unitless": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.0.tgz", + "integrity": "sha512-VINS5vEYAscRl2ZUDiT3uMPlrFQupiKgHz5AA4bCH1miKBg4qtwkim1qPmJj/4WG6TreYMY111rEFsjupcOKHw==" + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -3229,6 +3257,14 @@ "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==" }, + "node_modules/@mongodb-js/saslprep": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.5.tgz", + "integrity": "sha512-XLNOMH66KhJzUJNwT/qlMnS4WsNDWD5ASdyaSH3EtK+F4r/CFGa3jT4GNi4mfOitGvWXtdLgQJkQjxSVrio+jA==", + "dependencies": { + "sparse-bitfield": "^3.0.3" + } + }, "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { "version": "5.1.1-v1", "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", @@ -3348,12 +3384,12 @@ } }, "node_modules/@reduxjs/toolkit": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.1.0.tgz", - "integrity": "sha512-nfJ/b4ZhzUevQ1ZPKjlDL6CMYxO4o7ZL7OSsvSOxzT/EN11LsBDgTqP7aedHtBrFSVoK7oTP1SbMWUwGb30NLg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.0.1.tgz", + "integrity": "sha512-fxIjrR9934cmS8YXIGd9e7s1XRsEU++aFc9DVNMFMRTM5Vtsg2DCRMj21eslGtDt43IUf9bJL3h5bwUlZleibA==", "dependencies": { "immer": "^10.0.3", - "redux": "^5.0.1", + "redux": "^5.0.0", "redux-thunk": "^3.1.0", "reselect": "^5.0.1" }, @@ -4224,6 +4260,19 @@ "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==" }, + "node_modules/@types/lodash": { + "version": "4.14.202", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.202.tgz", + "integrity": "sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==" + }, + "node_modules/@types/lodash.memoize": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/lodash.memoize/-/lodash.memoize-4.1.9.tgz", + "integrity": "sha512-glY1nQuoqX4Ft8Uk+KfJudOD7DQbbEDF6k9XpGncaohW3RW4eSWBlx6AA0fZCrh40tZcQNH4jS/Oc59J6Eq+aw==", + "dependencies": { + "@types/lodash": "*" + } + }, "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", @@ -4282,6 +4331,15 @@ "csstype": "^3.0.2" } }, + "node_modules/@types/react-calendar": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@types/react-calendar/-/react-calendar-4.1.0.tgz", + "integrity": "sha512-CZL0+bOJ43+ZxnScd6n6SbA3RvoT/FurMIKevvTnRhLdncTRO7LyvBaFu3Bmwoad8ApVBJsMyAiPO79vGWRbbA==", + "deprecated": "This is a stub types definition. react-calendar provides its own type definitions, so you do not need this installed.", + "dependencies": { + "react-calendar": "*" + } + }, "node_modules/@types/react-dom": { "version": "18.2.18", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.18.tgz", @@ -4294,6 +4352,7 @@ "version": "3.0.7", "resolved": "https://registry.npmjs.org/@types/react-js-pagination/-/react-js-pagination-3.0.7.tgz", "integrity": "sha512-h16F5eFcVaTO5LTT5jJrvK8SxTlxkuv03ZKt/e6L3GPng/0TZTqhEKEyD8F5XksLeKBalsS1tTN2foJLWv/6mA==", + "dev": true, "dependencies": { "@types/react": "*" } @@ -4399,6 +4458,21 @@ "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==" }, + "node_modules/@types/styled-components": { + "version": "5.1.34", + "resolved": "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.34.tgz", + "integrity": "sha512-mmiVvwpYklFIv9E8qfxuPyIt/OuyIrn6gMOAMOFUO3WJfSrSE+sGUoa4PiZj77Ut7bKZpaa6o1fBKS/4TOEvnA==", + "dependencies": { + "@types/hoist-non-react-statics": "*", + "@types/react": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-n4sx2bqL0mW1tvDf/loQ+aMX7GQD3lc3fkCMC55VFNDu/vBOabO+LTIeXKM14xK0ppk5TUGcWRjiSpIlUpghKw==" + }, "node_modules/@types/testing-library__jest-dom": { "version": "5.14.9", "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.9.tgz", @@ -4417,6 +4491,19 @@ "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" }, + "node_modules/@types/webidl-conversions": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==" + }, + "node_modules/@types/whatwg-url": { + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.4.tgz", + "integrity": "sha512-lXCmTWSHJvf0TRSO58nm978b8HJ/EdsSsEKLd3ODHFjo+3VGAyyTp4v50nWvwtzBxSMQrVOK7tcuN0zGPLICMw==", + "dependencies": { + "@types/webidl-conversions": "*" + } + }, "node_modules/@types/ws": { "version": "8.5.10", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", @@ -4792,6 +4879,14 @@ "@xtuc/long": "4.2.2" } }, + "node_modules/@wojtekmaj/date-utils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@wojtekmaj/date-utils/-/date-utils-1.5.1.tgz", + "integrity": "sha512-+i7+JmNiE/3c9FKxzWFi2IjRJ+KzZl1QPu6QNrsgaa2MuBgXvUy4gA1TVzf/JMdIIloB76xSKikTWuyYAIVLww==", + "funding": { + "url": "https://github.com/wojtekmaj/date-utils?sponsor=1" + } + }, "node_modules/@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", @@ -5284,9 +5379,9 @@ } }, "node_modules/axios": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz", - "integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==", + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.5.tgz", + "integrity": "sha512-Ii012v05KEVuUoFWmMW/UQv9aRIc3ZwkWDcM+h5Il8izZCtRVpDUfwpoFf7eOtajT3QiGR4yDUx7lPqHJULgbg==", "dependencies": { "follow-redirects": "^1.15.4", "form-data": "^4.0.0", @@ -5784,6 +5879,14 @@ "node-int64": "^0.4.0" } }, + "node_modules/bson": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.5.0.tgz", + "integrity": "sha512-DXf1BTAS8vKyR90BO4x5v3rKVarmkdkzwOrnYDFdjAY694ILNDkmA3uRh1xXJEl+C1DAh8XCvAQ+Gh3kzubtpg==", + "engines": { + "node": ">=16.20.1" + } + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -5857,6 +5960,14 @@ "node": ">= 6" } }, + "node_modules/camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/caniuse-api": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", @@ -6019,6 +6130,14 @@ "wrap-ansi": "^7.0.0" } }, + "node_modules/clsx": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz", + "integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==", + "engines": { + "node": ">=6" + } + }, "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -6297,6 +6416,14 @@ "postcss": "^8.4" } }, + "node_modules/css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", + "engines": { + "node": ">=4" + } + }, "node_modules/css-declaration-sorter": { "version": "6.4.1", "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz", @@ -6478,6 +6605,16 @@ "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==" }, + "node_modules/css-to-react-native": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", + "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", + "dependencies": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, "node_modules/css-tree": { "version": "1.0.0-alpha.37", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", @@ -8748,6 +8885,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-user-locale": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/get-user-locale/-/get-user-locale-2.3.1.tgz", + "integrity": "sha512-VEvcsqKYx7zhZYC1CjecrDC5ziPSpl1gSm0qFFJhHSGDrSC+x4+p1KojWC/83QX//j476gFhkVXP/kNUc9q+bQ==", + "dependencies": { + "@types/lodash.memoize": "^4.1.7", + "lodash.memoize": "^4.1.1" + }, + "funding": { + "url": "https://github.com/wojtekmaj/get-user-locale?sponsor=1" + } + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -12349,6 +12498,11 @@ "node": ">= 4.0.0" } }, + "node_modules/memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==" + }, "node_modules/merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", @@ -12543,6 +12697,99 @@ "mkdirp": "bin/cmd.js" } }, + "node_modules/moment": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "engines": { + "node": "*" + } + }, + "node_modules/mongodb": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.5.0.tgz", + "integrity": "sha512-Fozq68InT+JKABGLqctgtb8P56pRrJFkbhW0ux+x1mdHeyinor8oNzJqwLjV/t5X5nJGfTlluxfyMnOXNggIUA==", + "dependencies": { + "@mongodb-js/saslprep": "^1.1.5", + "bson": "^6.4.0", + "mongodb-connection-string-url": "^3.0.0" + }, + "engines": { + "node": ">=16.20.1" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.1.0", + "gcp-metadata": "^5.2.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=6.0.0 <7", + "snappy": "^7.2.2", + "socks": "^2.7.1" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } + } + }, + "node_modules/mongodb-connection-string-url": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.0.tgz", + "integrity": "sha512-t1Vf+m1I5hC2M5RJx/7AtxgABy1cZmIPQRMXw+gEIPn/cZNF3Oiy+l0UIypUwVB5trcWHq3crg2g3uAR9aAwsQ==", + "dependencies": { + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^13.0.0" + } + }, + "node_modules/mongodb-connection-string-url/node_modules/tr46": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz", + "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==", + "dependencies": { + "punycode": "^2.3.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/mongodb-connection-string-url/node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "engines": { + "node": ">=12" + } + }, + "node_modules/mongodb-connection-string-url/node_modules/whatwg-url": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-13.0.0.tgz", + "integrity": "sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==", + "dependencies": { + "tr46": "^4.1.1", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -14718,6 +14965,31 @@ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" }, + "node_modules/react-calendar": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/react-calendar/-/react-calendar-4.8.0.tgz", + "integrity": "sha512-qFgwo+p58sgv1QYMI1oGNaop90eJVKuHTZ3ZgBfrrpUb+9cAexxsKat0sAszgsizPMVo7vOXedV7Lqa0GQGMvA==", + "dependencies": { + "@wojtekmaj/date-utils": "^1.1.3", + "clsx": "^2.0.0", + "get-user-locale": "^2.2.1", + "prop-types": "^15.6.0", + "warning": "^4.0.0" + }, + "funding": { + "url": "https://github.com/wojtekmaj/react-calendar?sponsor=1" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/react-dev-utils": { "version": "12.0.1", "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz", @@ -15824,6 +16096,11 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -15956,6 +16233,14 @@ "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", "deprecated": "Please use @jridgewell/sourcemap-codec instead" }, + "node_modules/sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", + "dependencies": { + "memory-pager": "^1.0.2" + } + }, "node_modules/spdy": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", @@ -16351,6 +16636,70 @@ "webpack": "^5.0.0" } }, + "node_modules/styled-components": { + "version": "6.1.8", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.8.tgz", + "integrity": "sha512-PQ6Dn+QxlWyEGCKDS71NGsXoVLKfE1c3vApkvDYS5KAK+V8fNWGhbSUEo9Gg2iaID2tjLXegEW3bZDUGpofRWw==", + "dependencies": { + "@emotion/is-prop-valid": "1.2.1", + "@emotion/unitless": "0.8.0", + "@types/stylis": "4.2.0", + "css-to-react-native": "3.2.0", + "csstype": "3.1.2", + "postcss": "8.4.31", + "shallowequal": "1.1.0", + "stylis": "4.3.1", + "tslib": "2.5.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0" + } + }, + "node_modules/styled-components/node_modules/csstype": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", + "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" + }, + "node_modules/styled-components/node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/styled-components/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" + }, "node_modules/stylehacks": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz", @@ -16366,6 +16715,11 @@ "postcss": "^8.2.15" } }, + "node_modules/stylis": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.1.tgz", + "integrity": "sha512-EQepAV+wMsIaGVGX1RECzgrcqRRU/0sYOHkeLsZ3fzHaHXZy4DaOOX0vOlGQdlsjkh3mFHAIlVimpwAs4dslyQ==" + }, "node_modules/sucrase": { "version": "3.35.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", @@ -17285,6 +17639,14 @@ "makeerror": "1.0.12" } }, + "node_modules/warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/watchpack": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", diff --git a/client/package.json b/client/package.json index 5c8d4e8..81abdde 100644 --- a/client/package.json +++ b/client/package.json @@ -3,25 +3,32 @@ "version": "0.1.0", "private": true, "dependencies": { - "@reduxjs/toolkit": "^2.1.0", + "@reduxjs/toolkit": "^2.0.1", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", "@types/jest": "^27.5.2", "@types/node": "^16.18.71", "@types/react": "^18.2.48", + "@types/react-calendar": "^4.1.0", "@types/react-dom": "^18.2.18", - "@types/react-js-pagination": "^3.0.7", "@types/react-redux": "^7.1.33", "@types/react-router-dom": "^5.3.3", - "axios": "^1.6.7", + "@types/styled-components": "^5.1.34", + "axios": "^1.6.5", + "moment": "^2.30.1", + "mongodb": "^6.5.0", "react": "^18.2.0", + "react-calendar": "^4.8.0", "react-dom": "^18.2.0", "react-icons": "^5.0.1", "react-js-pagination": "^3.0.3", "react-redux": "^9.1.0", + "react-router": "^6.21.3", "react-router-dom": "^6.21.3", "react-scripts": "5.0.1", + "redux": "^5.0.1", + "styled-components": "^6.1.8", "typescript": "^4.9.5", "web-vitals": "^2.1.4" }, @@ -48,5 +55,8 @@ "last 1 firefox version", "last 1 safari version" ] + }, + "devDependencies": { + "@types/react-js-pagination": "^3.0.7" } } diff --git a/client/public/images/Polygon 1.svg b/client/public/images/Polygon 1.svg new file mode 100644 index 0000000..abcfcc7 --- /dev/null +++ b/client/public/images/Polygon 1.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/public/images/add_icon.png b/client/public/images/add_icon.png new file mode 100644 index 0000000..50c82c6 Binary files /dev/null and b/client/public/images/add_icon.png differ diff --git a/client/public/images/calendar_icon2.png b/client/public/images/calendar_icon2.png new file mode 100644 index 0000000..aa2af9a Binary files /dev/null and b/client/public/images/calendar_icon2.png differ diff --git a/client/public/images/search_icon.png b/client/public/images/search_icon.png new file mode 100644 index 0000000..48e3bc1 Binary files /dev/null and b/client/public/images/search_icon.png differ diff --git a/client/public/logo192.png b/client/public/logo192.png deleted file mode 100644 index fc44b0a..0000000 Binary files a/client/public/logo192.png and /dev/null differ diff --git a/client/public/logo512.png b/client/public/logo512.png deleted file mode 100644 index a4e47a6..0000000 Binary files a/client/public/logo512.png and /dev/null differ diff --git a/client/src/App.tsx b/client/src/App.tsx index cdc7373..4f7b6dd 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -1,14 +1,23 @@ -import React from "react"; -import "./styles/App.css"; -import Routing from "./routes/Routes"; -import Mynavbar from "./components/navbar"; +import React, { useState } from 'react'; +import logo from './logo.svg'; +import './styles/App.css'; +import Mynavbar from './components/navbar'; +import Prev_next from './components/prev_next'; +import J_List from './pages/J_List'; +import { BrowserRouter, Routes } from 'react-router-dom'; +import { Route } from 'react-router'; +import Detail from './pages/Detail'; +import P_List_add_design from './pages/P_List_add_design'; +import P_calendar from './components/calendar'; +import Routing from './routes/Routes'; function App() { + const scholarshipId = 0; + const [value, onChange] = useState(new Date()); return ( -
- + -
+ ); } diff --git a/client/src/components/calendar.tsx b/client/src/components/calendar.tsx new file mode 100644 index 0000000..0fcb2ea --- /dev/null +++ b/client/src/components/calendar.tsx @@ -0,0 +1,50 @@ +import React, { useState } from "react"; +import styled from "styled-components"; +import Calendar from "react-calendar"; +import "react-calendar/dist/Calendar.css"; +import selectArrow from "../assets/SelectArrow.svg"; +import moment from "moment"; +import P_Calendar_styles from "../styles/calendar.module.css" + +import background from "../components/calendar_icon.jpg" + +const CustomCalendar = ({ onChange , value } : {onChange:any; value:any}) => { + const [nowDate, setNowDate] = useState(""); + const [isOpen, setIsOpen] = useState(false); + + + const handleToggleCalendar = () => { + setIsOpen(!isOpen); + }; + + const handleDateChange = (selectedDate:any) => { + const selectedDateFormat = moment(selectedDate).format("YYYY/MM/DD"); + const currentDate = moment().format("YYYY/MM/DD"); + + if (moment(selectedDateFormat).isBefore(currentDate)) { + + alert("이전 날짜를 선택할 수 없습니다."); + } else { + + onChange(selectedDate); + setIsOpen(false); + setNowDate(selectedDateFormat); + } + + }; + + return ( +
+
+ +
+
+ +
+
+ + + ); +}; + +export default CustomCalendar; \ No newline at end of file diff --git a/client/src/components/calendar_icon.jpg b/client/src/components/calendar_icon.jpg new file mode 100644 index 0000000..5b2c381 Binary files /dev/null and b/client/src/components/calendar_icon.jpg differ diff --git a/client/src/components/customhook.tsx b/client/src/components/customhook.tsx new file mode 100644 index 0000000..235f0e5 --- /dev/null +++ b/client/src/components/customhook.tsx @@ -0,0 +1,9 @@ +import { useSelector } from 'react-redux'; +import { RootState } from './store'; + +function useSomeState() { + return useSelector((state: RootState) => state.p_list); + +} + +export default useSomeState; diff --git a/client/src/components/mojib.tsx b/client/src/components/mojib.tsx new file mode 100644 index 0000000..000e6fc --- /dev/null +++ b/client/src/components/mojib.tsx @@ -0,0 +1,143 @@ +import React, { useEffect, useRef, Ref, useMemo } from 'react'; +import '../styles/App.css'; +import J_List_styles from '../styles/J_List.module.css'; +import JB_styles from '../styles/JB_detail.module.css'; +import P_add_styles from '../styles/P_List_add.module.css' +import Mynavbar from '../components/navbar'; + +import { useState } from 'react'; + +import { RootState, p_addcontent, p_addfield, p_addapply_cnt, p_removerecruit } from "../components/store"; +import { useDispatch, useSelector } from "react-redux"; + +import { FaRegCalendarAlt } from "react-icons/fa"; +import { LuImagePlus } from "react-icons/lu"; +import { RiVideoAddFill } from "react-icons/ri"; +import { BsPaperclip } from "react-icons/bs"; +import { MdCancel } from "react-icons/md"; + +import { PListItem, PListState } from '../components/type'; + +export default function Mojib(props:any) { + + let p_list = useSelector((state: RootState) => state.p_list); + let dispatch = useDispatch(); + let [mojib_cate_val, mojib_cate_change ] = useState("") + + + const handleChangeField = (e:any) => { + /* dispatch(p_addfield( + { + field : e.target.value, + num : props.num, + cate_field : mojib_cate_val + } + + state[0].recruit[action.payload.num].field =action.payload.field; + state[0].recruit[action.payload.num].cate_field =action.payload.cate_field; + )); */ + + props.setTempPList((prevState: { recruit: any; }) => { + const updatedRecruit = [...prevState.recruit]; + updatedRecruit[props.num] = { + ...updatedRecruit[props.num], + field: e.target.value, + cate_field: mojib_cate_val + }; + return { + ...prevState, + recruit: updatedRecruit + }; + + }); + + + }; + + const handleChangeApply_cnt = (e:any) => { + props.setTempPList((prevState: { recruit: any; }) => { + const updatedRecruit = [...prevState.recruit]; // 이전 배열을 복사합니다. + updatedRecruit[props.num] = { + ...updatedRecruit[props.num], // 기존 요소를 복사합니다. + apply_cnt: e.target.value + }; + return { + ...prevState, + recruit: updatedRecruit // 업데이트된 배열을 새로운 상태에 할당합니다. + }; + }); + + }; + + return ( +
+ + + + {/* */} + + { + mojib_cate_val=="" ? null : + + + } + + + +
인원 수
+ + + + { + setTempPList(prevState => { + const updatedRecruit = [...prevState.recruit]; + updatedRecruit.splice(props.num, 1); + return { + ...prevState, + recruit: updatedRecruit + }; + }); + }} className={P_add_styles.mojib_container}/> +
+ + ) + + + +} + +function setTempPList(arg0: (prevState: any) => any) { + throw new Error('Function not implemented.'); +} diff --git a/client/src/components/navbar.tsx b/client/src/components/navbar.tsx index 5c010c8..fbc5bd7 100644 --- a/client/src/components/navbar.tsx +++ b/client/src/components/navbar.tsx @@ -95,4 +95,4 @@ function Mynavbar() { ); } -export default Mynavbar; +export default Mynavbar; \ No newline at end of file diff --git a/client/src/components/navbar_logout.tsx b/client/src/components/navbar_logout.tsx new file mode 100644 index 0000000..4615dae --- /dev/null +++ b/client/src/components/navbar_logout.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import '../styles/App.css'; +import axios from 'axios'; + + +function Mynavbar_logined() { + return ( +
+
+
+

웹이름

+
+
+

학부 연구생

+

팀원 모집

+

장학 및 공모전

+

동아리 공지

+
+
+ +
+
+
+ ); +} + +export default Mynavbar_logined; \ No newline at end of file diff --git a/client/src/components/prev_next.tsx b/client/src/components/prev_next.tsx new file mode 100644 index 0000000..fb402b2 --- /dev/null +++ b/client/src/components/prev_next.tsx @@ -0,0 +1,68 @@ +import React, { useEffect } from 'react'; +import '../styles/App.css'; +import J_List_styles from '../styles/J_List.module.css'; +import Prev_next_styles from '../styles/Prev_next.module.css'; +import { RootState } from './store'; +import { useSelector } from 'react-redux'; + +export default function Prev_next(props:any) { + let p_list = useSelector((state: RootState) => state.p_list); + console.log(props.id, typeof(props.id)); + console.log(p_list); + const num: number = parseInt(props.id, 10); + + if(num==0) { + return ( +
+
+
+
이전글
+
+
+ +
+
다음글 {p_list[1].title}
+
+
+
+ + ) + } + else if(num==p_list.length-1) { + return ( +
+
+
+
이전글 {p_list[num-1].title}
+
+
+ +
+
다음글
+
+
+
+ ) + } + else { + return ( +
+ +
+
+
이전글 {p_list[num-1].title}
+
+
+ +
+
다음글 {p_list[num+1].title}
+
+
+ +
+ ) + } + + + +} \ No newline at end of file diff --git a/client/src/components/store.tsx b/client/src/components/store.tsx new file mode 100644 index 0000000..08b17d3 --- /dev/null +++ b/client/src/components/store.tsx @@ -0,0 +1,141 @@ +import {configureStore, createSlice} from '@reduxjs/toolkit' +import {PListItem, PListState} from './type' + +let IsLogined = createSlice ({ + name : 'IsLogined', + initialState : + { + isLogined: false, + name : "", + profileImage : null, + userid: "" + + }, + + reducers : { + changeIsLogined(state,action) { + state.isLogined = true; + state.name = action.payload.name; + } + } +}) + +let j_list = createSlice ({ + name : 'j_list', + + initialState : + [ + { + title:'', + category:'', + link:'', + writer:'', + date:0, + content:null, + file:'', + fileName:'' + }, + + ], + + reducers : { + setList(state,action) { + return action.payload + }, + resetList(state) { + state.splice(0,state.length) + }, + + removeList(state,action) { + state.splice(action.payload,1) + }, + + } +}) + +const initialState:PListState=[]; + +let p_list = createSlice ({ + name: 'p_list', + + initialState, + reducers: { + p_addList(state,action) { + state.push(action.payload) + }, + p_resetList(state) { + state.splice(0,state.length) + }, + p_removeList(state,action) { + state.splice(action.payload,1) + }, + p_addcontent(state,action) { + state[0].content.text = action.payload; + }, + p_addtitle(state,action) { + state[0].title = action.payload; + }, + p_addrecruit(state) { + state[0].recruit.push({ + field: '', + apply_cnt: 0, + cate_field:'' + }) + }, + p_removerecruit(state,action) { + state[0].recruit.splice(action.payload,1); + }, + p_addimage(state,action) { + state[0].content.image = action.payload; + }, + p_addvideo(state,action) { + state[0].content.video = action.payload; + }, + p_addfile(state,action) { + state[0].content.file = action.payload; + }, + p_addfield(state,action){ + state[0].recruit[action.payload.num].field =action.payload.field; + state[0].recruit[action.payload.num].cate_field =action.payload.cate_field; + }, + p_addapply_cnt(state,action){ + + state[0].recruit[action.payload.num].apply_cnt=action.payload.apply_cnt; + }, + p_cate_change(state,action){ + state[0].category = action.payload; + }, + p_addDate(state,action) { + state[0].date = action.payload; + }, + p_addDeadline(state,action){ + state[0].deadline=action.payload; + }, + p_addUser(state,action) { + state[0].writer = action.payload; + } + + } +}) + + +export const { setList, removeList,resetList } = j_list.actions; +export const { changeIsLogined } = IsLogined.actions; +export const { p_addList, p_removeList, p_resetList, + p_addrecruit, p_addcontent, p_removerecruit, + p_addtitle,p_addimage, p_addfield, p_addapply_cnt, p_cate_change, p_addfile, p_addvideo, p_addDate, p_addUser,p_addDeadline} = p_list.actions; + +// 스토어 설정 +const store = configureStore({ + reducer: { + j_list: j_list.reducer, + IsLogined:IsLogined.reducer, + p_list:p_list.reducer, + + }, + middleware: (getDefaultMiddleware) => getDefaultMiddleware() +}); + +// RootState 타입 정의 +export type RootState = ReturnType; +export default store; \ No newline at end of file diff --git a/client/src/components/type.tsx b/client/src/components/type.tsx new file mode 100644 index 0000000..2aefbdf --- /dev/null +++ b/client/src/components/type.tsx @@ -0,0 +1,30 @@ +export interface PListItem { + _id: any; + title: string; + category: string; + writer: string; + date: string; + id: string; + content: { + image: string[]; + video: string[]; + text: string; + file: string[]; + }; + recruit: { + field: string; + apply_cnt: number; + cate_field: string; + }[]; + deadline: string, + is_done: boolean; + apply: { + id:string; + date: string; + fieldDetail: string; + field: string; + memo: string; + }[]; +} + +export type PListState = PListItem[]; \ No newline at end of file diff --git a/client/src/index.tsx b/client/src/index.tsx index d1b4c76..c9bab13 100644 --- a/client/src/index.tsx +++ b/client/src/index.tsx @@ -1,19 +1,10 @@ import ReactDOM from 'react-dom/client'; import App from './App'; -// import { BrowserRouter as Router} from 'react-router-dom'; -import { Provider } from 'react-redux'; -import store from './store/store'; // Redux 스토어 가져오기 +import reportWebVitals from './reportWebVitals'; +import { Provider, useSelector, useDispatch } from 'react-redux'; // react-redux에서 Provider 가져오기 +import store from './components/store'; // Redux 스토어 가져오기 import { BrowserRouter } from 'react-router-dom'; - -// ReactDOM.hydrateRoot(document.getElementById('root') as HTMLElement, ( -// -// -// -// -// -// )); - const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement); root.render( @@ -21,4 +12,9 @@ root.render( + + ); + + +reportWebVitals(); diff --git a/client/src/pages/Detail.tsx b/client/src/pages/Detail.tsx index dd3900c..a7f5f2c 100644 --- a/client/src/pages/Detail.tsx +++ b/client/src/pages/Detail.tsx @@ -1,21 +1,200 @@ -import React from 'react'; -import '../App.css'; +import React, { useEffect } from 'react'; + +import J_List_styles from '../styles/J_List.module.css'; +import JB_styles from '../styles/JB_detail.module.css'; +import P_add_styles from '../styles/P_List_add.module.css' +import apply_modal from '../styles/apply_modal.module.css' import Mynavbar from '../components/navbar'; -import { text } from 'stream/consumers'; - -//asd -function Detail() { - return ( -
- -
장학
-
- - - -
-
+import Prev_next from '../components/prev_next'; +import { useState } from 'react'; + +import { RootState } from "../components/store"; +import { useDispatch, useSelector } from "react-redux"; +import axios from 'axios'; + +import { useNavigate, useParams } from 'react-router'; +import moment from 'moment'; + + + +export default function Detail(props:any) { + + const { page, id } = useParams() as unknown as { id: number, page:number }; + + let p_list = useSelector((state: RootState) => state.p_list); - ); -} -export default Detail; \ No newline at end of file + const navigate = useNavigate(); + + const [modalOpen, setModalOpen] = useState(false); + + const openModal = () => { + setModalOpen(true); + }; + + const closeModal = () => { + setModalOpen(false); + }; + const todayDate = moment().format('YYYY/MM/DD'); + + + return ( +
+ + + +
프로젝트
+ + + + + + + {modalOpen && ( +
+
+ + {p_list[id].recruit.map((item, index) => ( +
+
+ 모집 분야:{' '} + {item.cate_field}{' '} + 상세:{' '} + {item.field} 에 지원하기{' '} + + +
+ +
+ ))} +
+
+ )} + + + + + +
+ + +
+
+
{p_list[id].title}
+
{p_list[id].date}
+
+ +
+ +
+ +
작성자
+
+
{p_list[id].writer}
+ +
첨부파일
+
+
{p_list[id].content.file}
+
+ +
+ +
+
{p_list[id].content.text}
+ + +
+ +
+
+ {p_list[id].recruit.map((item, index) => ( +
+
+ 모집 분야:{' '} + {item.cate_field}{' '} + 상세:{' '} + {item.field}{' '} + 필요 인원 수:{' '} + {item.apply_cnt}{' '} + +
+ +
+ ))} +
+ + +
+ {p_list[id].content.image&&p_list[id].content.image.map((image, id) => ( +
+ {`${image}-${id}`} +
+ ))} + {p_list[id].content.video&&p_list[id].content.video.map((image, id) => ( +
+ {`${image}-${id}`} +
+ ))} + +
+ + + +
+ + + +
+ + ) +} \ No newline at end of file diff --git a/client/src/pages/J_List.tsx b/client/src/pages/J_List.tsx new file mode 100644 index 0000000..8bf5a39 --- /dev/null +++ b/client/src/pages/J_List.tsx @@ -0,0 +1,128 @@ +import React from 'react'; + +import '../styles/App.css'; +import J_List_styles from '../styles/J_List.module.css'; + +import Mynavbar from '../components/navbar'; +import Mynavbar_logined from '../components/navbar_logout'; + +import { useState,useEffect } from 'react'; +import axios from 'axios'; + +import { RootState, changeIsLogined} from "../components/store"; +import { useDispatch, useSelector } from "react-redux"; + +import '../styles/Paging.css'; +import Pagination from "react-js-pagination"; +import { useNavigate } from 'react-router-dom'; + + +function J_List(props:any) { + const navigate = useNavigate(); + const handleNavigate = (key: string, page: number) => { + navigate(`/scholarship?key=${key}&page=${page}`); + }; + + const IsLogined = useSelector((state: RootState) => state.IsLogined); + let dispatch = useDispatch(); + + const [page, setPage] = useState(1); + + const [list, setList] = useState(props.j_list) + + const handlePageChange = (page: React.SetStateAction) => { + setPage(page); + }; + + useEffect(()=>{ + axios.get(`/scholarship/?key=all&page=${page}`) + .then(response => { + + setList(response.data) + }) + .catch(error => { + }); + + axios.get('/authorization') + .then(response => { + if(response.data.isLogined == 'Logined'){ + dispatch(changeIsLogined(response.data)); + } + }) + .catch(error => { + }); + }) + + + return ( +
+ { + + IsLogined.isLogined ? () : () + + } +
장학
+ +
+ + + + +
+
+
+
+
카테고리
+
제목
+
등록일
+
+
+ { + list&&list.map((a:any,i:any) => { + return( +
+
+
+
+

{a.category}

+
+
+
+ { + handleNavigate('all', i) + }}>{a.title} +
+
+
{a.date}
+
+
+
+
+ ) + }) + } +
+