From 4bcf74c294195d075546f54288070dc0a4ddb61c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=80=EC=A7=80?= Date: Thu, 14 Mar 2024 17:01:30 +0900 Subject: [PATCH 01/21] =?UTF-8?q?feat=20:=20=EB=B0=B0=EB=84=88=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1,=20=EC=82=AD=EC=A0=9C,=20=EB=A6=AC=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EC=A1=B0=ED=9A=8C=20=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 83 ++++ package.json | 1 + src/Apis/Banners/index.ts | 55 +++ src/Apis/Banners/request.ts | 10 + src/Apis/Banners/response.ts | 19 + src/Assets/SVG/delete.svg | 3 + .../Banner/BannerDetail.tsx/index.tsx | 81 +++- .../Banner/BannerDetail.tsx/style.ts | 25 + src/Components/Banner/DeleteAlarm/index.tsx | 21 + src/Components/Banner/DeleteAlarm/style.ts | 64 +++ src/Components/Banner/index.tsx | 65 ++- src/Components/CreateBanner/index.tsx | 431 +++++++++++++----- src/Components/CreateBanner/styled.ts | 41 +- src/Pages/CreateBannerPage/index.tsx | 12 +- 14 files changed, 760 insertions(+), 151 deletions(-) create mode 100644 src/Apis/Banners/index.ts create mode 100644 src/Apis/Banners/request.ts create mode 100644 src/Apis/Banners/response.ts create mode 100644 src/Assets/SVG/delete.svg create mode 100644 src/Components/Banner/DeleteAlarm/index.tsx create mode 100644 src/Components/Banner/DeleteAlarm/style.ts diff --git a/package-lock.json b/package-lock.json index 16b6e9e..f034a8a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "@team-return/design-system": "^1.1.13", "axios": "^1.3.4", "file-saver": "^2.0.5", + "html2canvas": "^1.4.1", "react": "^18.2.0", "react-beautiful-dnd": "^13.1.1", "react-cookie": "^4.1.1", @@ -5291,6 +5292,14 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "node_modules/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/batch": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", @@ -6055,6 +6064,14 @@ "postcss": "^8.4" } }, + "node_modules/css-line-break": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", + "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", + "dependencies": { + "utrie": "^1.0.2" + } + }, "node_modules/css-loader": { "version": "6.7.1", "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.1.tgz", @@ -8824,6 +8841,18 @@ "webpack": "^5.20.0" } }, + "node_modules/html2canvas": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", + "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", + "dependencies": { + "css-line-break": "^2.1.0", + "text-segmentation": "^1.0.3" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/htmlparser2": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", @@ -16067,6 +16096,14 @@ "node": ">=8" } }, + "node_modules/text-segmentation": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", + "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", + "dependencies": { + "utrie": "^1.0.2" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -16493,6 +16530,14 @@ "node": ">= 0.4.0" } }, + "node_modules/utrie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", + "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", + "dependencies": { + "base64-arraybuffer": "^1.0.2" + } + }, "node_modules/uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", @@ -21209,6 +21254,11 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==" + }, "batch": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", @@ -21779,6 +21829,14 @@ "postcss-selector-parser": "^6.0.9" } }, + "css-line-break": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", + "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", + "requires": { + "utrie": "^1.0.2" + } + }, "css-loader": { "version": "6.7.1", "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.1.tgz", @@ -23782,6 +23840,15 @@ "tapable": "^2.0.0" } }, + "html2canvas": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", + "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", + "requires": { + "css-line-break": "^2.1.0", + "text-segmentation": "^1.0.3" + } + }, "htmlparser2": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", @@ -28848,6 +28915,14 @@ "minimatch": "^3.0.4" } }, + "text-segmentation": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", + "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", + "requires": { + "utrie": "^1.0.2" + } + }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -29170,6 +29245,14 @@ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==" }, + "utrie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", + "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", + "requires": { + "base64-arraybuffer": "^1.0.2" + } + }, "uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", diff --git a/package.json b/package.json index 27a851a..68d6d5f 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "@team-return/design-system": "^1.1.13", "axios": "^1.3.4", "file-saver": "^2.0.5", + "html2canvas": "^1.4.1", "react": "^18.2.0", "react-beautiful-dnd": "^13.1.1", "react-cookie": "^4.1.1", diff --git a/src/Apis/Banners/index.ts b/src/Apis/Banners/index.ts new file mode 100644 index 0000000..f7cc13c --- /dev/null +++ b/src/Apis/Banners/index.ts @@ -0,0 +1,55 @@ +import { MutationOptions, useMutation, useQuery } from 'react-query'; +import { instance } from '../axios'; + +const router = '/banners'; + +export const useCreateBanners = ( + banner_url: string, + banner_type: string, + start_date: string, + end_date: string, + options: MutationOptions +) => { + const data = { + banner_url, + banner_type, + start_date, + end_date, + }; + return useMutation(async () => instance.post(`${router}`, data), { + ...options, + }); +}; + +export const useGetBannerList = ( + is_opened: boolean, + options: MutationOptions +) => { + const params = { + is_opened, + }; + return useMutation( + async () => + instance.get(`${router}/teacher`, { + params: params, + }), + { + ...options, + } + ); +}; + +export const useDeleteBanner = ( + banner_id: number, + options: MutationOptions +) => { + return useMutation( + async () => { + const { data } = await instance.delete(`${router}/${banner_id}`); + return data; + }, + { + ...options, + } + ); +}; diff --git a/src/Apis/Banners/request.ts b/src/Apis/Banners/request.ts new file mode 100644 index 0000000..9096626 --- /dev/null +++ b/src/Apis/Banners/request.ts @@ -0,0 +1,10 @@ +export interface DateProps { + banner_url: string; + start_date: string; + end_date: string; + banner_type: string; +} + +export interface BannerProps { + banner_id: number; +} diff --git a/src/Apis/Banners/response.ts b/src/Apis/Banners/response.ts new file mode 100644 index 0000000..efb76d9 --- /dev/null +++ b/src/Apis/Banners/response.ts @@ -0,0 +1,19 @@ +export interface BannerListProps { + banner_id: number; + banner_url: string; + start_date: string; + end_date: string; + banner_type: string; +} + +export interface BannerListResponse { + banners: BannerListProps[]; +} + +export interface BannerNameType { + RECRUITMENT: string; + BOOKMARK: string; + NONE: string; + INTERNSHIP: string; + COMPANY: string; +} diff --git a/src/Assets/SVG/delete.svg b/src/Assets/SVG/delete.svg new file mode 100644 index 0000000..cd49e30 --- /dev/null +++ b/src/Assets/SVG/delete.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/Components/Banner/BannerDetail.tsx/index.tsx b/src/Components/Banner/BannerDetail.tsx/index.tsx index f7b2b91..9c80674 100644 --- a/src/Components/Banner/BannerDetail.tsx/index.tsx +++ b/src/Components/Banner/BannerDetail.tsx/index.tsx @@ -1,22 +1,73 @@ import * as _ from './style'; -import banner from '../../../Assets/PNG/banner.png'; +import { useEffect, useState } from 'react'; +import { useGetBannerList } from '../../../Apis/Banners'; +import { BannerListProps } from '../../../Apis/Banners/response'; +import { BannerNameType } from '../../../Apis/Banners/response'; +import deleteimg from '../../../Assets/SVG/delete.svg'; +interface Props { + onDelete: (id: number) => void; +} + +export function BannerDetail({ onDelete }: Props) { + const [banners, setBanners] = useState([]); + const [isOpen, setIsOpen] = useState(true); + + const BannersAPI = useGetBannerList(isOpen, { + onSuccess: (data: any) => { + setBanners(data.data.banners); + }, + onError: () => {}, + }); + console.log(BannersAPI); + + useEffect(() => { + if (isOpen) BannersAPI.mutate(); + }, [isOpen]); + + const BannerType = { + RECRUITMENT: '모집의뢰서', + BOOKMARK: '북마크', + NONE: '페이지 이동 안함', + INTERNSHIP: '체험형 현장실습 페이지', + COMPANY: '기업 상세 페이지', + }; + + Object.freeze(BannerType); -export function BannerDetail() { return ( <_.Container> - <_.BannerImg src={banner} /> - <_.Wrapper> - <_.TextWrapper> - <_.Text>이동 되는 페이지 - <_.TextContent>체험형 현장실습 - - <_.TextWrapper> - <_.Text>표시 기간 - <_.TextContent> - 2024년 2월 24일 ~ 2024년 2월 27일 - - - + {banners && + banners.length > 0 && + banners.map((banner, index) => ( + <_.BannerDetails key={index}> + <_.BannerImg src={banner.banner_url} /> + <_.DeleteBackground + onClick={() => { + onDelete(banner.banner_id); + }} + > + <_.deleteImg src={deleteimg} /> + + <_.Wrapper> + <_.TextWrapper> + <_.Text>이동 되는 페이지 + <_.TextContent> + { + BannerType[ + banner.banner_type as keyof BannerNameType + ] + } + + + <_.TextWrapper> + <_.Text>표시 기간 + <_.TextContent> + {banner.start_date} ~ {banner.end_date} + + + + + ))} ); } diff --git a/src/Components/Banner/BannerDetail.tsx/style.ts b/src/Components/Banner/BannerDetail.tsx/style.ts index 8214974..31fe0f6 100644 --- a/src/Components/Banner/BannerDetail.tsx/style.ts +++ b/src/Components/Banner/BannerDetail.tsx/style.ts @@ -3,9 +3,34 @@ import styled from 'styled-components'; export const Container = styled.div` display: flex; flex-direction: column; + gap: 36px; +`; + +export const BannerDetails = styled.div` + display: flex; + flex-direction: column; + position: relative; gap: 20px; `; +export const DeleteBackground = styled.div` + position: absolute; + top: 15px; + right: 15px; + width: 36px; + height: 36px; + border-radius: 50px; + background: var(--primary-30, #0f4c82); + display: flex; + align-items: center; + justify-content: center; +`; + +export const deleteImg = styled.img` + width: 32px; + height: 32px; +`; + export const Wrapper = styled.div` width: 1000px; display: flex; diff --git a/src/Components/Banner/DeleteAlarm/index.tsx b/src/Components/Banner/DeleteAlarm/index.tsx new file mode 100644 index 0000000..e065218 --- /dev/null +++ b/src/Components/Banner/DeleteAlarm/index.tsx @@ -0,0 +1,21 @@ +import * as _ from './style'; + +interface DeleteAlarmProps { + onCancel: () => void; + onConfirm: () => void; +} + +export function DeleteAlarm({ onCancel, onConfirm }: DeleteAlarmProps) { + return ( + <_.Container> + <_.TextWrapper> + <_.Title>선택한 배너를 삭제하시겠습니까? + <_.Contents>삭제하면 되돌릴 수 없습니다. + + <_.BtnWrapper> + <_.Cancle onClick={onCancel}>취소 + <_.Confirm onClick={onConfirm}>확인 + + + ); +} diff --git a/src/Components/Banner/DeleteAlarm/style.ts b/src/Components/Banner/DeleteAlarm/style.ts new file mode 100644 index 0000000..6a0d99d --- /dev/null +++ b/src/Components/Banner/DeleteAlarm/style.ts @@ -0,0 +1,64 @@ +import styled from 'styled-components'; + +export const Container = styled.div` + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + display: flex; + flex-direction: column; + gap: 40px; + width: 444px; + height: 176px; + border-radius: 10px; + background-color: #fff; + box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.25); + padding: 25px 30px 35px 40px; +`; + +export const TextWrapper = styled.div` + display: flex; + flex-direction: column; + gap: 5px; +`; + +export const Title = styled.p` + font-size: 20px; + font-weight: 500; +`; + +export const Contents = styled.p` + font-size: 16px; + font-weight: 400; +`; + +export const BtnWrapper = styled.div` + display: flex; + justify-content: flex-end; + gap: 10px; +`; + +export const Cancle = styled.div` + display: flex; + align-items: center; + justify-content: center; + width: 70px; + height: 30px; + font-size: 16px; + font-weight: 400; + background: var(--grayscale-30, #f7f7f7); + cursor: pointer; +`; + +export const Confirm = styled.div` + display: flex; + align-items: center; + justify-content: center; + width: 70px; + height: 30px; + font-size: 16px; + font-weight: 400; + color: white; + background: var(--primary-20, #135c9d); + cursor: pointer; +`; diff --git a/src/Components/Banner/index.tsx b/src/Components/Banner/index.tsx index 00bffca..72e481f 100644 --- a/src/Components/Banner/index.tsx +++ b/src/Components/Banner/index.tsx @@ -2,45 +2,84 @@ import { Button } from '@team-return/design-system'; import * as _ from './style'; import { BannerDetail } from './BannerDetail.tsx'; import { useState } from 'react'; +import { useDeleteBanner, useGetBannerList } from '../../Apis/Banners'; +import { Link } from 'react-router-dom'; +import { DeleteAlarm } from './DeleteAlarm'; export function Banner() { - const [bannerStatus, setBannerStatus] = useState('current'); + const [isOpen, setIsOpen] = useState(true); + const [showDeleteAlarm, setShowDeleteAlarm] = useState(false); + const [id, setId] = useState(null); - const handleBtnClick = (status: 'current' | 'notPublished') => { - setBannerStatus(status); + const createBannersAPI = useGetBannerList(isOpen, { + onSuccess: () => {}, + onError: () => {}, + }); + + const handleBtnClick = () => { + setIsOpen(!isOpen); + createBannersAPI.mutate(); + }; + + const onDelete = async () => { + if (id) { + await DeleteBannersAPI.mutateAsync(); + createBannersAPI.mutate(); + window.location.reload(); + } + setShowDeleteAlarm(false); }; + + const DeleteBannersAPI = useDeleteBanner(id!, { + onSuccess: () => { + createBannersAPI.mutate(); + }, + onError: () => {}, + }); + console.log(DeleteBannersAPI); + return ( <_.Container> <_.Wrapper> - - + + + <_.BannerWrapper> <_.Wrapper> - {bannerStatus === 'current' ? ( + {isOpen === true ? ( <_.Title>현재 게시되어있는 배너 ) : ( <_.Title>아직 게시되어있지 않은 배너 )} <_.ButtonWrapper> <_.Btn - onClick={() => handleBtnClick('current')} - active={bannerStatus === 'current'} + onClick={() => handleBtnClick()} + active={isOpen === true} > 현재 게시 된 배너 <_.Btn - onClick={() => handleBtnClick('notPublished')} - active={bannerStatus === 'notPublished'} + onClick={() => handleBtnClick()} + active={isOpen === false} > 아직 게시 안 된 배너 - - - + { + setId(id); + setShowDeleteAlarm(true); + }} + /> + {showDeleteAlarm && ( + setShowDeleteAlarm(false)} + onConfirm={onDelete} + /> + )} ); } diff --git a/src/Components/CreateBanner/index.tsx b/src/Components/CreateBanner/index.tsx index 167a08d..f3b0231 100644 --- a/src/Components/CreateBanner/index.tsx +++ b/src/Components/CreateBanner/index.tsx @@ -1,12 +1,40 @@ import * as _ from './styled'; -import React, { useState } from 'react'; +import React, { Dispatch, useState, SetStateAction, useEffect } from 'react'; import templete from '../../Assets/PNG/templete.png'; import banner from '../../Assets/PNG/banner.png'; import search from '../../Assets/SVG/search.svg'; -import { Button } from '@team-return/design-system'; +import { Button, useToastStore } from '@team-return/design-system'; +import { useCreateBanners } from '../../Apis/Banners'; +import { + useCompanyRecruitmentQueryString, + useRecruitmentFormQueryString, +} from '../../Store/State'; +import { DateProps } from '../../Apis/Banners/request'; +import { useForm } from '../../Hooks/useForm'; +import html2canvas from 'html2canvas'; -export function CreateBanner() { +interface PropType { + date: DateProps; + setDate: Dispatch>; +} + +export function CreateBanner({ date, setDate }: PropType) { + const { append } = useToastStore(); const [logoPreview, setLogoPreview] = useState(null); + const [selectedPage, setSelectedPage] = useState(null); + + const [startDate, setStartDate] = useState(null); + const [endDate, setEndDate] = useState(null); + + const [url, setUrl] = useState(null); + + const { recruitmentFormQueryString, recruitmentFormQueryStringHandler } = + useRecruitmentFormQueryString(); + + const { + companyRecruitmentQueryString, + companyRecruitmentQueryStringHandler, + } = useCompanyRecruitmentQueryString(); const handleFileChange = (event: React.ChangeEvent) => { const selectedFile = event.target.files?.[0]; @@ -21,120 +49,291 @@ export function CreateBanner() { reader.readAsDataURL(selectedFile); } }; + + useEffect(() => { + if (url) createBannersAPI.mutate(); + }, [url]); + + const handleRadioChange = (event: React.ChangeEvent) => { + const selectedRadioValue = event.target.value; + setSelectedPage(selectedRadioValue); + }; + + const handleStartDateChange = ( + event: React.ChangeEvent + ) => { + const selectedStartDate = event.target.value; + setStartDate(selectedStartDate); + const updatedDate = { + ...date, + start_date: selectedStartDate, + }; + setDate(updatedDate); + }; + + const handleEndDateChange = ( + event: React.ChangeEvent + ) => { + const selectedEndDate = event.target.value; + setEndDate(selectedEndDate); + const updatedDate = { + ...date, + end_date: selectedEndDate, + }; + setDate(updatedDate); + }; + + const { form, handleChange } = useForm({ + title1: '', + title2: '', + companyName: '', + description: '', + }); + + const captureImage = async () => { + try { + const elementToCapture = document.getElementById('captureElement'); + + if (elementToCapture) { + const canvas = await html2canvas(elementToCapture); + const dataURL: string = canvas.toDataURL('image/png'); + console.log('Captured Image Data URL:', dataURL); + return dataURL; + } else { + console.error('Element to capture not found'); + return ''; + } + } catch (error) { + console.error('Error capturing image:', error); + return ''; + } + }; + + const createBannersAPI = useCreateBanners( + url!, + { + page_type_1: 'COMPANY', + page_type_2: 'RECRUITENT', + page_type_3: 'INTERNSHIP', + page_type_4: 'BOOKMARK', + page_type_5: 'NONE', + }[selectedPage!]!, + date.start_date, + date.end_date, + + { + onSuccess: () => { + append({ + title: '성공적으로 추가되었습니다.', + message: '', + type: 'GREEN', + }); + }, + onError: () => { + append({ + title: '추가에 실패했습니다.', + message: '', + type: 'RED', + }); + }, + } + ); + console.log(createBannersAPI); + return ( - <> - <_.Container> - <_.TempleteWrapper> - <_.TemplteTitle>템플릿 -
- <_.TempleteImg src={banner} /> - <_.TempleteImg src={banner} /> - <_.TempleteImg src={banner} /> - <_.TempleteImg src={banner} /> - <_.TempleteImg src={banner} /> - <_.TempleteImg src={banner} /> - <_.TempleteImg src={banner} /> - <_.TempleteImg src={banner} /> - <_.TempleteImg src={banner} /> -
- - <_.Right> - <_.CreateWrapper> - <_.Title>배너 내용을 추가해주세요. -
- <_.BannerImg src={templete} /> - <_.InputWrapper> - <_.Input placeholder="제목"> - <_.Input placeholder="제목"> - <_.Input placeholder="회사 이름 "> - <_.Input placeholder="설명"> - - <_.LogoUpload> - <_.FileInputContainer> - {logoPreview ? ( - 로고 미리보기 - ) : ( - <>로고를 업로드 해주세요. - )} - <_.FileInput - type="file" - onChange={handleFileChange} + <_.Container> + <_.TempleteWrapper> + <_.TemplteTitle>템플릿 +
+ <_.TempleteImg src={banner} /> +
+ + <_.Right> + <_.CreateWrapper> + <_.Title>배너 내용을 추가해주세요. +
+ <_.BannerImg src={templete} /> + <_.InputWrapper + hasValue={{ + title1: !!form.title1, + title2: !!form.title2, + description: !!form.description, + companyName: !!form.companyName, + }} + > + <_.Input + placeholder="제목" + name="title1" + value={form.title1} + className="title1" + hasValue={Boolean(form.title1)} + onChange={handleChange} + > + <_.Input + placeholder="제목" + name="title2" + value={form.title2} + className="title2" + hasValue={Boolean(form.title2)} + onChange={handleChange} + > + <_.Input + placeholder="회사 이름 " + name="companyName" + value={form.companyName} + className="companyName" + hasValue={Boolean(form.companyName)} + onChange={handleChange} + > + <_.Input + placeholder="설명" + name="description" + value={form.description} + className="description" + hasValue={Boolean(form.description)} + onChange={handleChange} + > + + <_.LogoUpload> + <_.FileInputContainer> + {logoPreview ? ( + 로고 미리보기 - - -
- - <_.MovePage> - <_.Title> - 배너를 누르면 이동 될 페이지를 선택해 주세요. - - <_.Table> - - <_.Td> - - - <_.Name> - <_.SearchIcon src={search} /> - <_.Search placeholder="기업명 검색 (기업 상세)"> - - - - <_.Td> - - - <_.Name> - <_.SearchIcon src={search} /> - <_.Search placeholder="기업명 검색 (모집 의뢰서)"> - - - - <_.Td> - - - <_.Name>체험형 현장실습 - - - <_.Td> - - - <_.Name>북마크 - - - <_.Td> - - - <_.Name>페이지 이동 안 함 - - - - <_.ButtonWrapper> - <_.TimeWrapper> - <_.Title> - 배너가 보여질 기간을 선택해 주세요. - - <_.Wrapper> - <_.Time> - <_.TimeTitle>시작 - <_.TimeInput type="date"> - - <_.Time> - <_.TimeTitle>끝 - <_.TimeInput type="date"> - - - - - - - - + ) : ( + <>로고를 업로드 해주세요. + )} + <_.FileInput + type="file" + onChange={handleFileChange} + /> + + +
+ + <_.MovePage> + <_.Title> + 배너를 누르면 이동 될 페이지를 선택해 주세요. + + <_.Table> + + <_.Td> + handleRadioChange(e)} + /> + + <_.Name> + <_.SearchIcon src={search} /> + <_.Search + placeholder="기업명 검색 (기업 상세)" + name="company_name" + value={ + companyRecruitmentQueryString.company_name + } + onChange={ + companyRecruitmentQueryStringHandler + } + > + + + + <_.Td> + handleRadioChange(e)} + /> + + <_.Name> + <_.SearchIcon src={search} /> + <_.Search + placeholder="기업명 검색 (모집 의뢰서)" + name="company_name" + value={ + recruitmentFormQueryString.company_name + } + onChange={recruitmentFormQueryStringHandler} + > + + + + <_.Td> + handleRadioChange(e)} + /> + + <_.Name>체험형 현장실습 + + + <_.Td> + handleRadioChange(e)} + /> + + <_.Name>북마크 + + + <_.Td> + handleRadioChange(e)} + /> + + <_.Name>페이지 이동 안 함 + + + + <_.ButtonWrapper> + <_.TimeWrapper> + <_.Title>배너가 보여질 기간을 선택해 주세요. + <_.Wrapper> + <_.Time> + <_.TimeTitle>시작 + <_.TimeInput + type="date" + onChange={handleStartDateChange} + > + + <_.Time> + <_.TimeTitle>끝 + <_.TimeInput + type="date" + onChange={handleEndDateChange} + > + + + + + + + ); } diff --git a/src/Components/CreateBanner/styled.ts b/src/Components/CreateBanner/styled.ts index 8ed96f6..f15c956 100644 --- a/src/Components/CreateBanner/styled.ts +++ b/src/Components/CreateBanner/styled.ts @@ -52,23 +52,54 @@ export const BannerImg = styled.img` z-index: 0; `; -export const InputWrapper = styled.div` +export const InputWrapper = styled.div<{ + hasValue: { + title1: boolean; + title2: boolean; + companyName: boolean; + description: boolean; + }; +}>` position: absolute; top: 35%; left: 90px; display: flex; flex-direction: column; gap: 15px; + + .title1 { + color: white; + font-size: ${(props) => (props.hasValue.title1 ? '32px' : '12px')}; + font-weight: ${(props) => (props.hasValue.title1 ? '700' : '500')}; + } + + .title2 { + color: white; + font-size: ${(props) => (props.hasValue.title2 ? '32px' : '12px')}; + font-weight: ${(props) => (props.hasValue.title2 ? '700' : '500')}; + } + + .companyName { + color: ${(props) => (props.hasValue.companyName ? 'white' : 'black')}; + font-size: ${(props) => (props.hasValue.companyName ? '20px' : '12px')}; + font-weight: 500; + } + + .description { + color: ${(props) => (props.hasValue.description ? '#E5E5E5' : 'black')}; + font-size: ${(props) => (props.hasValue.description ? '14px' : '12px')}; + font-weight: 500; + } `; -export const Input = styled.input` - font-size: 12px; - font-weight: 500; - color: black; +export const Input = styled.input<{ hasValue: boolean }>` width: 250px; height: 34px; padding: 8px; border-radius: 12px; + background-color: ${(props) => (props.hasValue ? 'transparent' : 'white')}; + transition: background-color 0.3s ease; + &::placeholder { font-size: 12px; font-weight: 500; diff --git a/src/Pages/CreateBannerPage/index.tsx b/src/Pages/CreateBannerPage/index.tsx index 3cfc1aa..034c0d7 100644 --- a/src/Pages/CreateBannerPage/index.tsx +++ b/src/Pages/CreateBannerPage/index.tsx @@ -1,11 +1,19 @@ -import { CreateBanner } from '../../Components/CreateBanner'; +import { useState } from 'react'; import { Header } from '../../Components/Header'; import * as _ from './styled'; +import { CreateBanner } from '../../Components/CreateBanner'; + export function CreateBannerPage() { + const [data, setData] = useState({ + banner_url: '', + start_date: '', + end_date: '', + banner_type: '', + }); return ( <_.Container>
- + ); } From d77e20b65da2ad02c002673c09df3847bb995554 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=9D=98=EC=A7=84?= Date: Tue, 2 Apr 2024 12:16:13 +0900 Subject: [PATCH 02/21] =?UTF-8?q?refactor:=20=EA=B3=B5=EC=A7=80=EC=82=AC?= =?UTF-8?q?=ED=95=AD=20=EB=A6=AC=ED=8E=99=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Apis/Files/index.ts | 43 +-------- src/Apis/Login/index.ts | 6 +- src/Apis/Notices/index.ts | 73 +++++++++++++- src/Apis/Notices/request.ts | 4 + src/Apis/Notices/response.ts | 21 +++++ src/Apis/axios.ts | 5 +- src/Components/Notice/AttachedBox/index.tsx | 58 ++++++++++++ src/Components/Notice/AttachedBox/style.tsx | 31 ++++++ .../NoticePage/NoticeDetailPage/index.tsx | 68 ++++++++++++++ .../NoticePage/NoticeDetailPage/style.ts | 94 +++++++++++++++++++ src/Pages/NoticePage/NoticeEditPage/index.tsx | 2 +- src/Pages/NoticePage/NoticeListPage/index.tsx | 39 +++++--- src/Pages/NoticePage/NoticeListPage/style.ts | 1 + .../NoticePage/NoticeWritePage/index.tsx | 31 +++--- src/Pages/NoticePage/NoticeWritePage/style.ts | 1 + src/router.tsx | 5 + 16 files changed, 410 insertions(+), 72 deletions(-) create mode 100644 src/Apis/Notices/response.ts create mode 100644 src/Components/Notice/AttachedBox/index.tsx create mode 100644 src/Components/Notice/AttachedBox/style.tsx create mode 100644 src/Pages/NoticePage/NoticeDetailPage/index.tsx create mode 100644 src/Pages/NoticePage/NoticeDetailPage/style.ts diff --git a/src/Apis/Files/index.ts b/src/Apis/Files/index.ts index cffd77a..a75eef6 100644 --- a/src/Apis/Files/index.ts +++ b/src/Apis/Files/index.ts @@ -1,28 +1,7 @@ -import { useMutation } from "react-query" -import { PresignedUrlRequest } from "./request" -import { PresignedUrlResponse } from "./response" -import { instance } from "../axios" -import axios from "axios" - -// export const usePresignedUrl = () => { -// return useMutation( -// async (attachments: File[]) => { -// const files = attachments.map((item) => ({ -// type: 'EXTENSION_FILE', -// file_name: item.name, -// })); - -// const data = await instance.post(`${process.env.REACT_APP_BASE_URL}/files/pre-signed`, {files}); - -// return data; -// }, { -// onSuccess: ({data}) => { -// console.log(data); -// } -// } -// ) -// } -// propsData: PresignedUrlRequest/ +import { useMutation } from "react-query"; +import { PresignedUrlResponse } from "./response"; +import { instance } from "../axios"; +import axios from "axios"; export const usePresignedUrl = () => { return useMutation( @@ -44,16 +23,4 @@ export const usePresignedUrl = () => { } } ) -} - -// const readFileAsBinaryString = (file: File): Promise => { -// return new Promise((resolve, reject) => { -// const reader = new FileReader(); -// reader.onload = () => { -// const result = reader.result as string; -// resolve(result); -// }; -// reader.onerror = reject; -// reader.readAsBinaryString(file); -// }); -// } +} \ No newline at end of file diff --git a/src/Apis/Login/index.ts b/src/Apis/Login/index.ts index 24edc07..b376bbc 100644 --- a/src/Apis/Login/index.ts +++ b/src/Apis/Login/index.ts @@ -30,7 +30,9 @@ export const Login = (loginData: LoginDataType, checkBoxValue: boolean) => { }); } else { if (checkBoxValue) { - setCookies('account_id', loginData.account_id); + setCookies('account_id', loginData.account_id, { + path: '/' + }); } else { removeCookies('account_id'); } @@ -40,9 +42,11 @@ export const Login = (loginData: LoginDataType, checkBoxValue: boolean) => { ); setCookies('refresh_token', res.data.refresh_token, { expires: refreshExpired, + path: '/' }); setCookies('access_token', res.data.access_token, { expires: accessExpired, + path: '/' }); navigator('/RecruitmentRequest'); } diff --git a/src/Apis/Notices/index.ts b/src/Apis/Notices/index.ts index 63ab259..a5b249f 100644 --- a/src/Apis/Notices/index.ts +++ b/src/Apis/Notices/index.ts @@ -1,7 +1,11 @@ import { NoticeWrite } from "./request"; -import { useMutation } from "react-query"; +import { MutationOptions, useMutation } from "react-query"; import { instance } from "../axios"; +import { NoticeListResponse } from "./response"; +import { useEffect, useState } from "react"; +import { NoticeDetailResponse } from "./response"; +/** 공지사항 작성 */ export const useNoticeWriteData = (noticeData: NoticeWrite) => { const formData = new FormData() noticeData.attachments.forEach((attachment) => { formData.append('file', attachment) }) @@ -13,9 +17,74 @@ export const useNoticeWriteData = (noticeData: NoticeWrite) => { }, { onError: (error: Error) => { - console.error("공지 작성에 실패하였습니다:", error.message); + console.error("notice write error:", error.message); } } ) } +/** 공지사항 상세보기 조회 */ +export const useNoticeDetailData = (noticeId: string) => { + const [noticeDetail, setNoticeDetail] = useState(null); + + useEffect(() => { + const fetchNoticeDetail = async () => { + try { + const response = await instance.get(`${process.env.REACT_APP_BASE_URL}/notices/${noticeId}`); + const data = response.data; + + const fetchedNoticeDetail: NoticeDetailResponse = { + title: data.title, + content: data.content, + created_at: new Date(data.created_at).toISOString(), + attachments: data.attachments.map((attachment: any) => ({ + url: attachment.url, + type: attachment.type + })) + }; + setNoticeDetail(fetchedNoticeDetail); + } catch (error: any) { + console.error('notice detail error:', error.message); + } + }; + fetchNoticeDetail(); + }, [noticeId]); + + return { noticeDetail }; +}; + +/** 공지사항 리스트 조희 */ +export const useNoticeListData = () => { + const [notices, setNotices] = useState([]); + + useEffect(() => { + const fetchNoticeList = async () => { + try { + const response = await instance.get(`${process.env.REACT_APP_BASE_URL}/notices`); + const data = response.data; + + const fetchedNotices: NoticeListResponse[] = data.notices.map((notice: any) => ({ + id: notice.id, + title: notice.title, + created_at: new Date(notice.created_at).toISOString() + })); + setNotices(fetchedNotices); + } catch (error: any) { + console.error('notice list error: ', error.message); + } + }; + fetchNoticeList(); + }, []); + + return { notices }; +} + +/** 공지사항 삭제 */ +export const useDeleteNotice = (noticeId: string, options: MutationOptions) => { + return useMutation( + async () => instance.delete(`${process.env.REACT_APP_BASE_URL}/notices/${noticeId}`, { data: { "notice-id": noticeId } }), + { + ...options + } + ) +} diff --git a/src/Apis/Notices/request.ts b/src/Apis/Notices/request.ts index f720010..4687c91 100644 --- a/src/Apis/Notices/request.ts +++ b/src/Apis/Notices/request.ts @@ -2,4 +2,8 @@ export interface NoticeWrite { title: string; content: string; attachments: string[]; +} + +export interface NoticeDelete { + noticeId: string; } \ No newline at end of file diff --git a/src/Apis/Notices/response.ts b/src/Apis/Notices/response.ts new file mode 100644 index 0000000..bb7a0f5 --- /dev/null +++ b/src/Apis/Notices/response.ts @@ -0,0 +1,21 @@ +export interface NoticeListResponse { + id: number + title: string + created_at: string +} + +export interface NoticeDetailResponse { + title: string; + content: string; + created_at: string; + attachments: AttachmentResponse[]; +} + +type AttachmentType = + | "FILE" + | "URL" + +export interface AttachmentResponse { + url: string; + type: AttachmentType; +} \ No newline at end of file diff --git a/src/Apis/axios.ts b/src/Apis/axios.ts index 94252ae..8265679 100644 --- a/src/Apis/axios.ts +++ b/src/Apis/axios.ts @@ -13,6 +13,8 @@ let flag = false; instance.interceptors.request.use( (config) => { const accessToken = cookies.get('access_token'); + console.log(); + const returnConfig = { ...config }; if (accessToken) { returnConfig.headers!['Authorization'] = `Bearer ${accessToken}`; @@ -28,10 +30,11 @@ instance.interceptors.response.use( if (axios.isAxiosError(error) && error.response) { const { config } = error; const refreshToken = cookies.get('refresh_token'); + console.log(refreshToken); if (!refreshToken) { cookies.remove('access_token'); cookies.remove('refresh_token'); - window.location.href = '/login'; + // window.location.href = '/login'; return; } if ( diff --git a/src/Components/Notice/AttachedBox/index.tsx b/src/Components/Notice/AttachedBox/index.tsx new file mode 100644 index 0000000..2e2d110 --- /dev/null +++ b/src/Components/Notice/AttachedBox/index.tsx @@ -0,0 +1,58 @@ +import { Icon } from '@team-return/design-system'; +import { AttachmentResponse } from '../../../Apis/Notices/response'; +import * as _ from './style'; +import axios from 'axios'; + +interface PropsType { + props: AttachmentResponse[]; +} + +export function AttachedBox({ props }: PropsType) { + const file_name_regex = (url: string) => { + return url.replace(/(?:.*?-){5}(.*)/, '$1').replaceAll('+', ' '); + }; + + const downLoadFile = async (attachment: AttachmentResponse) => { + try { + const response = await axios.get( + `${process.env.REACT_APP_FILE_URL}${attachment.url}`, + { + responseType: 'blob', + } + ); + + const url = window.URL.createObjectURL(new Blob([response.data])); + const a = document.createElement('a'); + a.href = url; + a.download = file_name_regex(attachment.url); + document.body.appendChild(a); + a.click(); + window.URL.revokeObjectURL(url); + document.body.removeChild(a); + } catch (error) { + console.error('파일 다운로드 에러: ', error); + } + }; + + return ( + <> + <_.AttachedWrapper> + <_.AttachmentTitle>첨부자료 + <_.Attachments> + {props.map((attachment, index) => ( + <_.Attachment key={index}> +
{file_name_regex(attachment.url)}
+ downLoadFile(attachment)} + /> + + ))} + + + + ); +} diff --git a/src/Components/Notice/AttachedBox/style.tsx b/src/Components/Notice/AttachedBox/style.tsx new file mode 100644 index 0000000..59613a5 --- /dev/null +++ b/src/Components/Notice/AttachedBox/style.tsx @@ -0,0 +1,31 @@ +import styled from 'styled-components'; + +export const AttachedWrapper = styled.div` + display: flex; + flex-direction: row; + width: 100%; + height: auto; + margin-top: 32px; + border-top: 2px solid #135c9d; + border-bottom: 1px solid #135c9d; + padding: 16px; + gap: 20px; +`; + +export const AttachmentTitle = styled.div` + font-weight: 500; + font-size: 18px; +`; + +export const Attachments = styled.div` + display: flex; + flex-direction: column; + gap: 4px; + justify-content: center; +`; + +export const Attachment = styled.div` + display: flex; + gap: 7px; + align-items: center; +`; diff --git a/src/Pages/NoticePage/NoticeDetailPage/index.tsx b/src/Pages/NoticePage/NoticeDetailPage/index.tsx new file mode 100644 index 0000000..7ba403a --- /dev/null +++ b/src/Pages/NoticePage/NoticeDetailPage/index.tsx @@ -0,0 +1,68 @@ +import { useNoticeDetailData } from '../../../Apis/Notices'; +import { useNavigate, useParams } from 'react-router-dom'; +import { Header } from '../../../Components/Header'; +import { Icon } from '@team-return/design-system'; +import { useDeleteNotice } from '../../../Apis/Notices'; +import * as _ from './style'; +import { AttachedBox } from '../../../Components/Notice/AttachedBox'; + +export function NoticeDetailPage() { + const { id } = useParams<{ id: any }>(); + const { noticeDetail } = useNoticeDetailData(id); + const items = [noticeDetail]; + + const navigate = useNavigate(); + const { mutate: deleteNotice } = useDeleteNotice(id, { + onSuccess: () => { + navigate('/Notice'); + }, + onError: (error) => { + console.error('notice delete error:', error); + }, + }); + + const handleDeleteClick = () => { + if (window.confirm('공지사항을 삭제하시겠습니까?')) { + deleteNotice(); + } + }; + + return ( + <> +
+ <_.Wrapper> + <_.Background> + <_.Box> + <_.HeaderWrapper> + <_.Title>{noticeDetail?.title} + <_.IconWrapper> + <_.IconBox> + + + <_.IconBox> + + + + + <_.Date> + {noticeDetail?.created_at.substring(0, 10)} + + <_.Contents>{noticeDetail?.content} + {items?.map((item) => ( + + ))} + + + + + ); +} diff --git a/src/Pages/NoticePage/NoticeDetailPage/style.ts b/src/Pages/NoticePage/NoticeDetailPage/style.ts new file mode 100644 index 0000000..3f388e2 --- /dev/null +++ b/src/Pages/NoticePage/NoticeDetailPage/style.ts @@ -0,0 +1,94 @@ +import styled from "styled-components"; + +export const Wrapper = styled.div` + width: 100vw; + min-width: 1400px; + background: #fafafa; + display: flex; + flex-direction: column; + align-items: center; + padding-top: 95px; + padding-bottom: 11px; + overflow: scroll; +`; + +export const Background = styled.div` + width: 1311px; + height: 1000px; + background-color: white; + border: 1px solid #E5E5E5; + display: flex; + flex-direction: column; + overflow: scroll; + padding: 40px 62px 40px 62px; +`; + +export const Box = styled.div` + display: flex; + flex-direction: column; + gap: 24px; +`; + +export const Title = styled.div` + font-weight: 700; + font-size: 32px; +`; + +export const Date = styled.div` + font-weight: 500; + font-size: 24px; +`; + +export const Contents = styled.div` + font-size: 16px; + font-weight: 400; + white-space: pre-line; +`; + +export const AttachedBox = styled.div` + display: flex; + /* width: 1187px; */ + width: 100%; + height: auto; + margin-top: 32px; + padding: 16px; + border-top: 2px solid #135C9D; + border-bottom: 1px solid #135C9D; + gap: 28px; +`; + +export const AttachmentTitle = styled.div` + font-weight: 500; + font-size: 18px; +`; + +export const Attachments = styled.div` + display: flex; + flex-direction: column; + gap: 4px; + justify-content: center; +`; + +export const HeaderWrapper = styled.div` + display: flex; + flex-direction: row; +`; + +export const IconWrapper = styled.div` + display: flex; + flex-direction: row; + gap: 12px; + margin-left: auto; +` + +export const IconBox = styled.div` + display: flex; + justify-content: center; + align-items: center; + width: 60px; + height: 60px; + border-radius: 50%; + border: 1px solid black; + background-color: white; + cursor: pointer; +`; \ No newline at end of file diff --git a/src/Pages/NoticePage/NoticeEditPage/index.tsx b/src/Pages/NoticePage/NoticeEditPage/index.tsx index 12f9853..fdb3741 100644 --- a/src/Pages/NoticePage/NoticeEditPage/index.tsx +++ b/src/Pages/NoticePage/NoticeEditPage/index.tsx @@ -88,7 +88,7 @@ export function NoticeEditPage() { const handleNoticeSubmit = () => { // console.log(data); - // writeNotice(); + writeNotice(); getPresignedUrl(attachments); }; diff --git a/src/Pages/NoticePage/NoticeListPage/index.tsx b/src/Pages/NoticePage/NoticeListPage/index.tsx index bbf3bd7..8756b43 100644 --- a/src/Pages/NoticePage/NoticeListPage/index.tsx +++ b/src/Pages/NoticePage/NoticeListPage/index.tsx @@ -1,10 +1,13 @@ import React from 'react'; import * as _ from './style'; import { Header } from '../../../Components/Header'; -import { Link } from 'react-router-dom'; +import { Link, useLocation } from 'react-router-dom'; import { Button } from '@team-return/design-system'; +import { useNoticeListData } from '../../../Apis/Notices'; export function NoticeListPage() { + const { notices } = useNoticeListData(); + return ( <>
@@ -25,17 +28,29 @@ export function NoticeListPage() { <_.HeaderDate>작성일 - - <_.Tbody> - <_.Tr> - <_.NoticeNumber>12 - <_.NoticeTitle> - [중요] 오리엔테이션날 일정 안내 - - <_.NoticeDate>2024-01-16 - - - + <_.Tbody> + {notices.map((notice, index) => ( + + <_.Tr> + <_.NoticeNumber> + {index + 1} + + <_.NoticeTitle> + {notice.title} + + <_.NoticeDate> + {notice.created_at.substring( + 0, + 10 + )} + + + + ))} + <_.Bottom> diff --git a/src/Pages/NoticePage/NoticeListPage/style.ts b/src/Pages/NoticePage/NoticeListPage/style.ts index eceaed9..c86afa2 100644 --- a/src/Pages/NoticePage/NoticeListPage/style.ts +++ b/src/Pages/NoticePage/NoticeListPage/style.ts @@ -63,6 +63,7 @@ export const Tbody = styled.tbody` justify-content: center; align-items: center; border-collapse: collapse; + flex-direction: column; `; export const Tr = styled.tr` diff --git a/src/Pages/NoticePage/NoticeWritePage/index.tsx b/src/Pages/NoticePage/NoticeWritePage/index.tsx index b626102..8786b48 100644 --- a/src/Pages/NoticePage/NoticeWritePage/index.tsx +++ b/src/Pages/NoticePage/NoticeWritePage/index.tsx @@ -1,12 +1,10 @@ import { Header } from '../../../Components/Header'; import * as _ from './style'; -import { ChangeEvent, useEffect, useRef, useState } from 'react'; +import { ChangeEvent, useRef, useState } from 'react'; import { Button } from '@team-return/design-system'; import { useNoticeWriteData } from '../../../Apis/Notices'; -import axios from 'axios'; import { usePresignedUrl } from '../../../Apis/Files'; -import { PresignedUrlRequest } from '../../../Apis/Files/request'; -import { Link } from 'react-router-dom'; +import { Link, useNavigate } from 'react-router-dom'; export function NoticeWritePage() { const fileInputRef = useRef(null); @@ -14,7 +12,8 @@ export function NoticeWritePage() { const [title, setTitle] = useState(''); const [content, setContent] = useState(''); const [attachments, setAttachments] = useState([]); - const [presignedUrls, setPresignedUrls] = useState([]); + const [presignedUrls] = useState([]); + const navigate = useNavigate(); const { mutate: writeNotice } = useNoticeWriteData({ title, @@ -22,10 +21,7 @@ export function NoticeWritePage() { attachments: presignedUrls, }); - // const fileName = jsonData.files[0].file_name; - const { mutate: getPresignedUrl } = usePresignedUrl(); - // const { mutate } = useUpload(attachments, { onSuccess: (e) => {} }); const getTodayDate = (): string => { const today = new Date(); @@ -63,8 +59,6 @@ export function NoticeWritePage() { console.log('내용:', e.target.value); }; - // let files_: any; - const handleFileChange = async (e: ChangeEvent) => { if (e.target.files) { const newAttachments = Array.from(e.target.files); @@ -76,9 +70,9 @@ export function NoticeWritePage() { }; const handleNoticeSubmit = () => { - // console.log(data); - // writeNotice(); getPresignedUrl(attachments); + writeNotice(); + navigate('/Notice'); }; return ( @@ -109,6 +103,7 @@ export function NoticeWritePage() { value={content} onChange={handleContentChange} onInput={onInputHandler} + as="textarea" /> <_.InputCount>{inputCount}자/1000 @@ -127,11 +122,13 @@ export function NoticeWritePage() { /> <_.ButtonWrap> - - - + { + + + + } diff --git a/src/Pages/NoticePage/NoticeWritePage/style.ts b/src/Pages/NoticePage/NoticeWritePage/style.ts index 23fbf65..7788e84 100644 --- a/src/Pages/NoticePage/NoticeWritePage/style.ts +++ b/src/Pages/NoticePage/NoticeWritePage/style.ts @@ -99,6 +99,7 @@ export const TextInput = styled.textarea` font-size: 16px; border: none; resize: none; + white-space: pre-line; `; export const FileWrap = styled.div` diff --git a/src/router.tsx b/src/router.tsx index 40270a8..da6bfb0 100644 --- a/src/router.tsx +++ b/src/router.tsx @@ -10,6 +10,7 @@ import { StudentManagementPage } from './Pages/StudentManagementPage'; import { CompanyDetailPage } from './Pages/CompanyDetailPage'; import { RecruitmentFormDetailPage } from './Pages/RecruitmentFormDetailPage'; import { NoticeListPage } from './Pages/NoticePage/NoticeListPage'; +import { NoticeDetailPage } from './Pages/NoticePage/NoticeDetailPage'; import { NoticeWritePage } from './Pages/NoticePage/NoticeWritePage'; import { NoticeEditPage } from './Pages/NoticePage/NoticeEditPage'; import { BannerPage } from './Pages/BannerPage'; @@ -79,6 +80,10 @@ const Router = createBrowserRouter([ path: 'Notice/Edit', element: , }, + { + path: 'Notice/Detail/:id', + element: , + }, { path: 'CreateBanner', element: , From d3a056484bf56d575ff8a74a9ad5aececa2c1239 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=9D=98=EC=A7=84?= Date: Mon, 11 Mar 2024 23:36:52 +0900 Subject: [PATCH 03/21] =?UTF-8?q?feat=20::=20presigned=5Furl=20&=20?= =?UTF-8?q?=EA=B3=B5=EC=A7=80=EC=82=AC=ED=95=AD=20=ED=8D=BC=EB=B8=94?= =?UTF-8?q?=EB=A6=AC=EC=8B=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 3 + src/Apis/File/index.ts | 15 ++ src/Apis/Files/index.ts | 59 +++++++ src/Apis/Files/request.ts | 6 + src/Apis/Files/response.ts | 8 + src/Apis/Notices/index.ts | 21 +++ src/Apis/Notices/request.ts | 5 + src/Components/Header/index.tsx | 3 + src/Pages/NoticePage/NoticeEditPage/index.tsx | 151 ++++++++++++++++++ src/Pages/NoticePage/NoticeEditPage/style.ts | 126 +++++++++++++++ src/Pages/NoticePage/NoticeListPage/index.tsx | 46 ++++++ src/Pages/NoticePage/NoticeListPage/style.ts | 133 +++++++++++++++ .../NoticePage/NoticeWritePage/index.tsx | 141 ++++++++++++++++ src/Pages/NoticePage/NoticeWritePage/style.ts | 126 +++++++++++++++ src/router.tsx | 18 +++ 15 files changed, 861 insertions(+) create mode 100644 src/Apis/Files/index.ts create mode 100644 src/Apis/Files/request.ts create mode 100644 src/Apis/Files/response.ts create mode 100644 src/Apis/Notices/index.ts create mode 100644 src/Apis/Notices/request.ts create mode 100644 src/Pages/NoticePage/NoticeEditPage/index.tsx create mode 100644 src/Pages/NoticePage/NoticeEditPage/style.ts create mode 100644 src/Pages/NoticePage/NoticeListPage/index.tsx create mode 100644 src/Pages/NoticePage/NoticeListPage/style.ts create mode 100644 src/Pages/NoticePage/NoticeWritePage/index.tsx create mode 100644 src/Pages/NoticePage/NoticeWritePage/style.ts diff --git a/package.json b/package.json index 68d6d5f..0835972 100644 --- a/package.json +++ b/package.json @@ -59,5 +59,8 @@ "@types/styled-components": "^5.1.26", "prettier": "2.8.8", "typescript": "4.5" + }, + "compilerOptions": { + "target": "es6" } } diff --git a/src/Apis/File/index.ts b/src/Apis/File/index.ts index dd1153d..5a5bc56 100644 --- a/src/Apis/File/index.ts +++ b/src/Apis/File/index.ts @@ -54,3 +54,18 @@ export const useFileUpload = (file: File, options: MutationOptions) => { } ); }; + + +/** 선생님 모집의뢰 상태 변경 */ +export const useUpload = (files: File[], options: MutationOptions) => { + const formData = new FormData(); + files.forEach((file) => { + formData.append('file', file); + }) + return useMutation( + async () => instance.post(`${router}?type=EXTENSION_FILE`, formData), + { + ...options, + } + ); +}; \ No newline at end of file diff --git a/src/Apis/Files/index.ts b/src/Apis/Files/index.ts new file mode 100644 index 0000000..cffd77a --- /dev/null +++ b/src/Apis/Files/index.ts @@ -0,0 +1,59 @@ +import { useMutation } from "react-query" +import { PresignedUrlRequest } from "./request" +import { PresignedUrlResponse } from "./response" +import { instance } from "../axios" +import axios from "axios" + +// export const usePresignedUrl = () => { +// return useMutation( +// async (attachments: File[]) => { +// const files = attachments.map((item) => ({ +// type: 'EXTENSION_FILE', +// file_name: item.name, +// })); + +// const data = await instance.post(`${process.env.REACT_APP_BASE_URL}/files/pre-signed`, {files}); + +// return data; +// }, { +// onSuccess: ({data}) => { +// console.log(data); +// } +// } +// ) +// } +// propsData: PresignedUrlRequest/ + +export const usePresignedUrl = () => { + return useMutation( + async (attachments: File[]) => { + const files = attachments.map((item) => ({ + type: 'EXTENSION_FILE', + file_name: item.name, + })); + + const { data: presignedUrls } = await instance.post(`${process.env.REACT_APP_BASE_URL}/files/pre-signed`, { files }); + return {presignedUrls, attachments}; + }, { + onSuccess: async ({ presignedUrls, attachments }) => { + + const uploadPromises = presignedUrls.urls.map(({pre_signed_url}, idx) => { + (async () => await axios.put(pre_signed_url, attachments[idx]))(); + }); + await Promise.all(uploadPromises); + } + } + ) +} + +// const readFileAsBinaryString = (file: File): Promise => { +// return new Promise((resolve, reject) => { +// const reader = new FileReader(); +// reader.onload = () => { +// const result = reader.result as string; +// resolve(result); +// }; +// reader.onerror = reject; +// reader.readAsBinaryString(file); +// }); +// } diff --git a/src/Apis/Files/request.ts b/src/Apis/Files/request.ts new file mode 100644 index 0000000..becdb28 --- /dev/null +++ b/src/Apis/Files/request.ts @@ -0,0 +1,6 @@ +export interface PresignedUrlRequest { + files: { + type: string + file_name: string + }[] +} \ No newline at end of file diff --git a/src/Apis/Files/response.ts b/src/Apis/Files/response.ts new file mode 100644 index 0000000..d090809 --- /dev/null +++ b/src/Apis/Files/response.ts @@ -0,0 +1,8 @@ +export interface PresignedUrlResponse { + urls: [ + { + file_path: string, + pre_signed_url: string + } + ] +} \ No newline at end of file diff --git a/src/Apis/Notices/index.ts b/src/Apis/Notices/index.ts new file mode 100644 index 0000000..63ab259 --- /dev/null +++ b/src/Apis/Notices/index.ts @@ -0,0 +1,21 @@ +import { NoticeWrite } from "./request"; +import { useMutation } from "react-query"; +import { instance } from "../axios"; + +export const useNoticeWriteData = (noticeData: NoticeWrite) => { + const formData = new FormData() + noticeData.attachments.forEach((attachment) => { formData.append('file', attachment) }) + + return useMutation( + async () => { + const { data } = await instance.post(`${process.env.REACT_APP_BASE_URL}/notices`, {...noticeData, attachments: formData}); + return data; + }, + { + onError: (error: Error) => { + console.error("공지 작성에 실패하였습니다:", error.message); + } + } + ) +} + diff --git a/src/Apis/Notices/request.ts b/src/Apis/Notices/request.ts new file mode 100644 index 0000000..f720010 --- /dev/null +++ b/src/Apis/Notices/request.ts @@ -0,0 +1,5 @@ +export interface NoticeWrite { + title: string; + content: string; + attachments: string[]; +} \ No newline at end of file diff --git a/src/Components/Header/index.tsx b/src/Components/Header/index.tsx index f1c3214..ac2afd3 100644 --- a/src/Components/Header/index.tsx +++ b/src/Components/Header/index.tsx @@ -45,6 +45,9 @@ export function Header() { 지원서 + + <_.NavBtn clicked={clickedStatus('Notice')} width={68}> + 공지 <_.NavBtn clicked={clickedStatus('Banner')} width={68}> 배너 diff --git a/src/Pages/NoticePage/NoticeEditPage/index.tsx b/src/Pages/NoticePage/NoticeEditPage/index.tsx new file mode 100644 index 0000000..d76fdb4 --- /dev/null +++ b/src/Pages/NoticePage/NoticeEditPage/index.tsx @@ -0,0 +1,151 @@ +import { Header } from '../../../Components/Header'; +import * as _ from './style'; +import { ChangeEvent, useEffect, useRef, useState } from 'react'; +import { Button } from '@team-return/design-system'; +import { useNoticeWriteData } from '../../../Apis/Notices'; +import axios from 'axios'; +import { usePresignedUrl } from '../../../Apis/Files'; +import { PresignedUrlRequest } from '../../../Apis/Files/request'; +import { Link } from 'react-router-dom'; + +export function NoticeEditPage() { + const fileInputRef = useRef(null); + const [inputCount, setInputCount] = useState(0); + const [title, setTitle] = useState(''); + const [content, setContent] = useState(''); + const [attachments, setAttachments] = useState([]); + const [presignedUrls, setPresignedUrls] = useState([]); + + const { mutate: writeNotice } = useNoticeWriteData({ + title, + content, + attachments: presignedUrls, + }); + + // const fileName = jsonData.files[0].file_name; + + const { mutate: getPresignedUrl } = usePresignedUrl(); + // const { mutate } = useUpload(attachments, { onSuccess: (e) => {} }); + + const getTodayDate = (): string => { + const today = new Date(); + const year = today.getFullYear(); + let month = (today.getMonth() + 1).toString(); + let day = today.getDate().toString(); + + if (month.length === 1) { + month = '0' + month; + } + if (day.length === 1) { + day = '0' + day; + } + + return `${year}-${month}-${day}`; + }; + + const handleAddFileClick = () => { + if (fileInputRef.current) { + fileInputRef.current.click(); + } + }; + + const onInputHandler = (e: any) => { + setInputCount(e.target.value.length); + }; + + const handleTitleChange = (e: ChangeEvent) => { + setTitle(e.target.value); + console.log('제목:', e.target.value); + }; + + const handleContentChange = (e: ChangeEvent) => { + setContent(e.target.value); + console.log('내용:', e.target.value); + }; + + let files_: any; + + const handleFileChange = async (e: ChangeEvent) => { + if (e.target.files) { + const newAttachments = Array.from(e.target.files); + setAttachments((prevAttachments) => [ + ...prevAttachments, + ...newAttachments, + ]); + + files_ = attachments.map((item) => ({ + type: 'EXTENSION_FILE', + file_name: item.name, + })); + + // const presignedUrls = await Promise.all(promises); + + // // setPresignedUrls((prevPresignedUrls) => [ + // // ...prevPresignedUrls, + // // ...presignedUrls, + // // ]); + + // console.log('첨부파일: ', attachments, newAttachments); + } + }; + + const handleNoticeSubmit = () => { + // console.log(data); + // writeNotice(); + getPresignedUrl(attachments); + }; + + return ( + <> +
+ <_.Wrapper> + <_.Box> + <_.Title>공지 작성하기 + <_.ContentWrap> + <_.WriteDateWrap> + <_.Text>작성일 + <_.Text>{getTodayDate()} + + <_.TitleWrap> + <_.Text>제목 + <_.Input placeholder="공지 제목을 입력하세요" /> + + <_.TextWrap> + <_.Text>내용 + <_.InputWrapper> + <_.TextInput + placeholder="공지 내용을 입력하세요" + maxLength={999} + value={content} + onChange={handleContentChange} + onInput={onInputHandler} + /> + <_.InputCount>{inputCount}자/1000 + + + <_.FileWrap> + <_.Text>첨부파일 + <_.AddFile onClick={handleAddFileClick}> + 파일 추가하기 + + + + <_.ButtonWrap> + + + + + + + + + ); +} diff --git a/src/Pages/NoticePage/NoticeEditPage/style.ts b/src/Pages/NoticePage/NoticeEditPage/style.ts new file mode 100644 index 0000000..23fbf65 --- /dev/null +++ b/src/Pages/NoticePage/NoticeEditPage/style.ts @@ -0,0 +1,126 @@ +import styled from 'styled-components'; + +export const Wrapper = styled.div` + width: 100vw; + min-width: 1400px; + background: #fafafa; + display: flex; + flex-direction: column; + align-items: center; + padding-top: 95px; + padding-bottom: 11px; + overflow: scroll; +`; + +export const InputCount = styled.div` + font-size: 16px; + font-weight: 500; + margin-left: auto; +`; + +export const Box = styled.div` + width: 980px; + height: 850px; + border: 1px solid #E5E5E5; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + background-color: white; +`; + +export const Title = styled.div` + font-weight: 600; + font-size: 23px; + line-height: 36px; + margin-left: 24px; + align-self: flex-start; + margin-bottom: 25px; +`; + +export const ContentWrap = styled.div` + display: flex; + flex-direction: column; + gap: 30px; + justify-content: center; + align-items: flex-start; + text-align: left; +`; + +export const WriteDateWrap = styled.div` + display: flex; + flex-direction: row; + gap: 56px; + align-items: center; +`; + +export const Text = styled.div` + font-size: 23px; + font-weight: 600; +`; + +export const TitleWrap = styled.div` + display: flex; + flex-direction: row; + gap: 80px; + align-items: center; +`; + +export const Input = styled.input` + width: 545px; + height: 43px; + border: 1px solid #999999; + padding-left: 10px; + font-size: 16px; + border-radius: 4px; +`; + +export const TextWrap = styled.div` + display: flex; + flex-direction: row; + gap: 80px; +`; + +export const InputWrapper = styled.div` + width: 545px; + height: 400px; + border: 1px solid #999999; + border-radius: 4px; + padding: 10px; + overflow: auto; + background-color: white; + display: flex; + flex-direction: column; +`; + +export const TextInput = styled.textarea` + width: 100%; + height: 90%; + font-size: 16px; + border: none; + resize: none; +`; + +export const FileWrap = styled.div` + display: flex; + flex-direction: row; + gap: 37px; + align-items: center; +`; + +export const AddFile = styled.div` + width: 550px; + height: 43px; + background-color: #FAFAFA; + cursor: pointer; + font-size: 16px; + align-items: center; + display: flex; + padding-left: 16px; + font-weight: 400; +`; + +export const ButtonWrap = styled.div` + margin-top: 68px; + margin-left: auto; +`; diff --git a/src/Pages/NoticePage/NoticeListPage/index.tsx b/src/Pages/NoticePage/NoticeListPage/index.tsx new file mode 100644 index 0000000..bbf3bd7 --- /dev/null +++ b/src/Pages/NoticePage/NoticeListPage/index.tsx @@ -0,0 +1,46 @@ +import React from 'react'; +import * as _ from './style'; +import { Header } from '../../../Components/Header'; +import { Link } from 'react-router-dom'; +import { Button } from '@team-return/design-system'; + +export function NoticeListPage() { + return ( + <> +
+ <_.Wrapper> + <_.Background> + <_.TitleWrapper> + <_.Title>공지사항 + + + + + <_.Box> + <_.Table> + <_.Thead> + + <_.HeaderNumber>번호 + <_.HeaderTitle>제목 + <_.HeaderDate>작성일 + + + + <_.Tbody> + <_.Tr> + <_.NoticeNumber>12 + <_.NoticeTitle> + [중요] 오리엔테이션날 일정 안내 + + <_.NoticeDate>2024-01-16 + + + + + <_.Bottom> + + + + + ); +} diff --git a/src/Pages/NoticePage/NoticeListPage/style.ts b/src/Pages/NoticePage/NoticeListPage/style.ts new file mode 100644 index 0000000..eceaed9 --- /dev/null +++ b/src/Pages/NoticePage/NoticeListPage/style.ts @@ -0,0 +1,133 @@ +import styled from "styled-components"; + +export const Wrapper = styled.div` + width: 100vw; + min-width: 1400px; + background: #fafafa; + display: flex; + flex-direction: column; + align-items: center; + padding-top: 95px; + padding-bottom: 11px; + overflow: scroll; +`; + +export const Box = styled.div` + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + margin-top: 48px; + gap: 40px; +`; + +export const Table = styled.table` + display: flex; + table-layout: fixed; + flex-direction: column; + justify-content: center; + align-items: center; +`; + +export const Thead = styled.thead` + display: flex; + justify-content: center; + align-items: center; + border-top: 2px solid #7F7F7F; + width: 1139px; + height: 70px; + background-color: #F7F7F7; + border-bottom: 0.5px solid #7F7F7F; +`; + +export const HeaderNumber = styled.th` + width: 192px; + font-size: 16px; + padding: 2px 4px 2px 4px; +`; + +export const HeaderTitle = styled.th` + width: 755px; + font-size: 16px; + padding: 2px 4px 2px 4px; +`; + +export const HeaderDate = styled.th` + width: 192px; + font-size: 16px; + padding: 2px 4px 2px 4px; +`; + +export const Tbody = styled.tbody` + display: flex; + justify-content: center; + align-items: center; + border-collapse: collapse; +`; + +export const Tr = styled.tr` + height: 70px; + border-bottom: 0.5px solid #7F7F7F; + display: flex; + flex-direction: row; + align-items: center; +`; + +export const NoticeNumber = styled.td` + border: none; + width: 192px; + font-size: 16px; + color: #002C53; + display: flex; + justify-content: center; + font-weight: 500; +`; + +export const NoticeTitle = styled.td` + border: none; + width: 755px; + color: 16px; + display: flex; + justify-content: center; + font-weight: 500; +`; + +export const NoticeDate = styled.td` + border: none; + width: 192px; + color: 16px; + display: flex; + justify-content: center; + font-weight: 500; +`; + +export const Bottom = styled.div` + margin-bottom: 98px; +`; + +export const Background = styled.div` + width: 1298px; + height: 971px; + background-color: white; + border: 1px solid #E5E5E5; + display: flex; + flex-direction: column; + align-items: center; + overflow: scroll; +`; + +export const TitleWrapper = styled.div` + display: flex; + margin-top: 24px; +`; + +export const Title = styled.div` + font-size: 40px; + color: #333333; + font-weight: 700; + margin-right: 800px; +`; + +export const Button = styled.button` + padding: 8px 49px; +`; \ No newline at end of file diff --git a/src/Pages/NoticePage/NoticeWritePage/index.tsx b/src/Pages/NoticePage/NoticeWritePage/index.tsx new file mode 100644 index 0000000..b626102 --- /dev/null +++ b/src/Pages/NoticePage/NoticeWritePage/index.tsx @@ -0,0 +1,141 @@ +import { Header } from '../../../Components/Header'; +import * as _ from './style'; +import { ChangeEvent, useEffect, useRef, useState } from 'react'; +import { Button } from '@team-return/design-system'; +import { useNoticeWriteData } from '../../../Apis/Notices'; +import axios from 'axios'; +import { usePresignedUrl } from '../../../Apis/Files'; +import { PresignedUrlRequest } from '../../../Apis/Files/request'; +import { Link } from 'react-router-dom'; + +export function NoticeWritePage() { + const fileInputRef = useRef(null); + const [inputCount, setInputCount] = useState(0); + const [title, setTitle] = useState(''); + const [content, setContent] = useState(''); + const [attachments, setAttachments] = useState([]); + const [presignedUrls, setPresignedUrls] = useState([]); + + const { mutate: writeNotice } = useNoticeWriteData({ + title, + content, + attachments: presignedUrls, + }); + + // const fileName = jsonData.files[0].file_name; + + const { mutate: getPresignedUrl } = usePresignedUrl(); + // const { mutate } = useUpload(attachments, { onSuccess: (e) => {} }); + + const getTodayDate = (): string => { + const today = new Date(); + const year = today.getFullYear(); + let month = (today.getMonth() + 1).toString(); + let day = today.getDate().toString(); + + if (month.length === 1) { + month = '0' + month; + } + if (day.length === 1) { + day = '0' + day; + } + + return `${year}-${month}-${day}`; + }; + + const handleAddFileClick = () => { + if (fileInputRef.current) { + fileInputRef.current.click(); + } + }; + + const onInputHandler = (e: any) => { + setInputCount(e.target.value.length); + }; + + const handleTitleChange = (e: ChangeEvent) => { + setTitle(e.target.value); + console.log('제목:', e.target.value); + }; + + const handleContentChange = (e: ChangeEvent) => { + setContent(e.target.value); + console.log('내용:', e.target.value); + }; + + // let files_: any; + + const handleFileChange = async (e: ChangeEvent) => { + if (e.target.files) { + const newAttachments = Array.from(e.target.files); + setAttachments((prevAttachments) => [ + ...prevAttachments, + ...newAttachments, + ]); + } + }; + + const handleNoticeSubmit = () => { + // console.log(data); + // writeNotice(); + getPresignedUrl(attachments); + }; + + return ( + <> +
+ <_.Wrapper> + <_.Box> + <_.Title>공지 작성하기 + <_.ContentWrap> + <_.WriteDateWrap> + <_.Text>작성일 + <_.Text>{getTodayDate()} + + <_.TitleWrap> + <_.Text>제목 + <_.Input + placeholder="공지 제목을 입력하세요" + value={title} + onChange={handleTitleChange} + /> + + <_.TextWrap> + <_.Text>내용 + <_.InputWrapper> + <_.TextInput + placeholder="공지 내용을 입력하세요" + maxLength={999} + value={content} + onChange={handleContentChange} + onInput={onInputHandler} + /> + <_.InputCount>{inputCount}자/1000 + + + <_.FileWrap> + <_.Text>첨부파일 + <_.AddFile onClick={handleAddFileClick}> + 파일 추가하기 + + + + <_.ButtonWrap> + + + + + + + + + ); +} diff --git a/src/Pages/NoticePage/NoticeWritePage/style.ts b/src/Pages/NoticePage/NoticeWritePage/style.ts new file mode 100644 index 0000000..23fbf65 --- /dev/null +++ b/src/Pages/NoticePage/NoticeWritePage/style.ts @@ -0,0 +1,126 @@ +import styled from 'styled-components'; + +export const Wrapper = styled.div` + width: 100vw; + min-width: 1400px; + background: #fafafa; + display: flex; + flex-direction: column; + align-items: center; + padding-top: 95px; + padding-bottom: 11px; + overflow: scroll; +`; + +export const InputCount = styled.div` + font-size: 16px; + font-weight: 500; + margin-left: auto; +`; + +export const Box = styled.div` + width: 980px; + height: 850px; + border: 1px solid #E5E5E5; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + background-color: white; +`; + +export const Title = styled.div` + font-weight: 600; + font-size: 23px; + line-height: 36px; + margin-left: 24px; + align-self: flex-start; + margin-bottom: 25px; +`; + +export const ContentWrap = styled.div` + display: flex; + flex-direction: column; + gap: 30px; + justify-content: center; + align-items: flex-start; + text-align: left; +`; + +export const WriteDateWrap = styled.div` + display: flex; + flex-direction: row; + gap: 56px; + align-items: center; +`; + +export const Text = styled.div` + font-size: 23px; + font-weight: 600; +`; + +export const TitleWrap = styled.div` + display: flex; + flex-direction: row; + gap: 80px; + align-items: center; +`; + +export const Input = styled.input` + width: 545px; + height: 43px; + border: 1px solid #999999; + padding-left: 10px; + font-size: 16px; + border-radius: 4px; +`; + +export const TextWrap = styled.div` + display: flex; + flex-direction: row; + gap: 80px; +`; + +export const InputWrapper = styled.div` + width: 545px; + height: 400px; + border: 1px solid #999999; + border-radius: 4px; + padding: 10px; + overflow: auto; + background-color: white; + display: flex; + flex-direction: column; +`; + +export const TextInput = styled.textarea` + width: 100%; + height: 90%; + font-size: 16px; + border: none; + resize: none; +`; + +export const FileWrap = styled.div` + display: flex; + flex-direction: row; + gap: 37px; + align-items: center; +`; + +export const AddFile = styled.div` + width: 550px; + height: 43px; + background-color: #FAFAFA; + cursor: pointer; + font-size: 16px; + align-items: center; + display: flex; + padding-left: 16px; + font-weight: 400; +`; + +export const ButtonWrap = styled.div` + margin-top: 68px; + margin-left: auto; +`; diff --git a/src/router.tsx b/src/router.tsx index 9261d66..aff0b73 100644 --- a/src/router.tsx +++ b/src/router.tsx @@ -9,6 +9,10 @@ import { ApplicationViewPage } from './Pages/ApplicationViewPage'; import { StudentManagementPage } from './Pages/StudentManagementPage'; import { CompanyDetailPage } from './Pages/CompanyDetailPage'; import { RecruitmentFormDetailPage } from './Pages/RecruitmentFormDetailPage'; +import { NoticeListPage } from './Pages/NoticePage/NoticeListPage'; +import { NoticeDetailPage } from './Pages/NoticePage/NoticeDetailPage'; +import { NoticeWritePage } from './Pages/NoticePage/NoticeWritePage'; +import { NoticeEditPage } from './Pages/NoticePage/NoticeEditPage'; import { BannerPage } from './Pages/BannerPage'; import { CreateBannerPage } from './Pages/CreateBannerPage'; @@ -61,9 +65,23 @@ const Router = createBrowserRouter([ element: , }, { + path: 'Notice', + element: , path: 'Banner', element: , }, + { + path: 'Notice/Detail', + element: , + }, + { + path: 'Notice/Write', + element: , + }, + { + path: 'Notice/Edit', + element: , + }, { path: 'CreateBanner', element: , From 93859e101ec0c2a53524a707916f98f8e005e83d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=9D=98=EC=A7=84?= Date: Tue, 12 Mar 2024 18:27:28 +0900 Subject: [PATCH 04/21] =?UTF-8?q?fix:=20=EC=BB=B4=ED=94=8C=EB=A6=AD?= =?UTF-8?q?=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Components/Header/index.tsx | 2 ++ src/Pages/NoticePage/NoticeEditPage/index.tsx | 8 ++++---- src/router.tsx | 7 ++----- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/Components/Header/index.tsx b/src/Components/Header/index.tsx index ac2afd3..b08e82b 100644 --- a/src/Components/Header/index.tsx +++ b/src/Components/Header/index.tsx @@ -48,6 +48,8 @@ export function Header() { <_.NavBtn clicked={clickedStatus('Notice')} width={68}> 공지 + + <_.NavBtn clicked={clickedStatus('Banner')} width={68}> 배너 diff --git a/src/Pages/NoticePage/NoticeEditPage/index.tsx b/src/Pages/NoticePage/NoticeEditPage/index.tsx index d76fdb4..12f9853 100644 --- a/src/Pages/NoticePage/NoticeEditPage/index.tsx +++ b/src/Pages/NoticePage/NoticeEditPage/index.tsx @@ -22,10 +22,7 @@ export function NoticeEditPage() { attachments: presignedUrls, }); - // const fileName = jsonData.files[0].file_name; - const { mutate: getPresignedUrl } = usePresignedUrl(); - // const { mutate } = useUpload(attachments, { onSuccess: (e) => {} }); const getTodayDate = (): string => { const today = new Date(); @@ -108,7 +105,10 @@ export function NoticeEditPage() { <_.TitleWrap> <_.Text>제목 - <_.Input placeholder="공지 제목을 입력하세요" /> + <_.Input + onChange={handleTitleChange} + placeholder="공지 제목을 입력하세요" + /> <_.TextWrap> <_.Text>내용 diff --git a/src/router.tsx b/src/router.tsx index aff0b73..40270a8 100644 --- a/src/router.tsx +++ b/src/router.tsx @@ -10,7 +10,6 @@ import { StudentManagementPage } from './Pages/StudentManagementPage'; import { CompanyDetailPage } from './Pages/CompanyDetailPage'; import { RecruitmentFormDetailPage } from './Pages/RecruitmentFormDetailPage'; import { NoticeListPage } from './Pages/NoticePage/NoticeListPage'; -import { NoticeDetailPage } from './Pages/NoticePage/NoticeDetailPage'; import { NoticeWritePage } from './Pages/NoticePage/NoticeWritePage'; import { NoticeEditPage } from './Pages/NoticePage/NoticeEditPage'; import { BannerPage } from './Pages/BannerPage'; @@ -65,14 +64,12 @@ const Router = createBrowserRouter([ element: , }, { - path: 'Notice', - element: , path: 'Banner', element: , }, { - path: 'Notice/Detail', - element: , + path: 'Notice', + element: , }, { path: 'Notice/Write', From 5f91be1774155177125f84be1f179efc1f7df255 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=9D=98=EC=A7=84?= Date: Sun, 12 May 2024 22:55:57 +0900 Subject: [PATCH 05/21] =?UTF-8?q?feat:=20=EA=B3=B5=EC=A7=80=EC=82=AC?= =?UTF-8?q?=ED=95=AD=20api=20=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Apis/Files/index.ts | 16 +- src/Apis/Files/response.ts | 9 +- src/Apis/Notices/index.ts | 28 ++- src/Apis/Notices/request.ts | 15 +- src/Apis/Notices/response.ts | 2 +- src/Apis/axios.ts | 3 - .../NoticePage/NoticeDetailPage/index.tsx | 50 +++-- src/Pages/NoticePage/NoticeEditPage/index.tsx | 189 ++++++++++-------- src/Pages/NoticePage/NoticeEditPage/style.ts | 58 ++++-- src/Pages/NoticePage/NoticeListPage/index.tsx | 2 +- src/Pages/NoticePage/NoticeListPage/style.ts | 4 + .../NoticePage/NoticeWritePage/index.tsx | 106 +++++++--- src/Pages/NoticePage/NoticeWritePage/style.ts | 57 ++++-- src/router.tsx | 2 +- 14 files changed, 346 insertions(+), 195 deletions(-) diff --git a/src/Apis/Files/index.ts b/src/Apis/Files/index.ts index a75eef6..c39ebaa 100644 --- a/src/Apis/Files/index.ts +++ b/src/Apis/Files/index.ts @@ -3,11 +3,15 @@ import { PresignedUrlResponse } from "./response"; import { instance } from "../axios"; import axios from "axios"; -export const usePresignedUrl = () => { - return useMutation( - async (attachments: File[]) => { +export type PresignedUrlType = + | "LOGO_IMAGE" + | "EXTENSION_FILE"; + +export const usePresignedUrl = (type: PresignedUrlType) => { + const res = useMutation( + async (attachments: File[] ) => { const files = attachments.map((item) => ({ - type: 'EXTENSION_FILE', + type: "EXTENSION_FILE", file_name: item.name, })); @@ -16,11 +20,13 @@ export const usePresignedUrl = () => { }, { onSuccess: async ({ presignedUrls, attachments }) => { - const uploadPromises = presignedUrls.urls.map(({pre_signed_url}, idx) => { + const uploadPromises = await presignedUrls.urls.map(({pre_signed_url}, idx) => { (async () => await axios.put(pre_signed_url, attachments[idx]))(); }); await Promise.all(uploadPromises); + return; } } ) + return res } \ No newline at end of file diff --git a/src/Apis/Files/response.ts b/src/Apis/Files/response.ts index d090809..d28a002 100644 --- a/src/Apis/Files/response.ts +++ b/src/Apis/Files/response.ts @@ -1,8 +1,7 @@ export interface PresignedUrlResponse { - urls: [ - { + urls: { file_path: string, - pre_signed_url: string - } - ] + pre_signed_url: string, + url: string + }[] } \ No newline at end of file diff --git a/src/Apis/Notices/index.ts b/src/Apis/Notices/index.ts index a5b249f..80d10dc 100644 --- a/src/Apis/Notices/index.ts +++ b/src/Apis/Notices/index.ts @@ -1,4 +1,4 @@ -import { NoticeWrite } from "./request"; +import { NoticeWrite, NoticeEdit } from "./request"; import { MutationOptions, useMutation } from "react-query"; import { instance } from "../axios"; import { NoticeListResponse } from "./response"; @@ -6,13 +6,10 @@ import { useEffect, useState } from "react"; import { NoticeDetailResponse } from "./response"; /** 공지사항 작성 */ -export const useNoticeWriteData = (noticeData: NoticeWrite) => { - const formData = new FormData() - noticeData.attachments.forEach((attachment) => { formData.append('file', attachment) }) - +export const useNoticeWriteData = () => { return useMutation( - async () => { - const { data } = await instance.post(`${process.env.REACT_APP_BASE_URL}/notices`, {...noticeData, attachments: formData}); + async (noticeData: NoticeWrite) => { + const { data } = await instance.post(`/notices`, noticeData); return data; }, { @@ -23,12 +20,29 @@ export const useNoticeWriteData = (noticeData: NoticeWrite) => { ) } +/** 공지사항 수정 */ +export const useNoticeEditData = (noticeId: string) => { + return useMutation( + async (noticeData: NoticeEdit) => { + const { data } = await instance.patch(`/notices/${noticeId}`, noticeData); + return data; + }, + { + onError: (error: Error) => { + console.error("notice edit error: ", error); + + } + } + ) +} + /** 공지사항 상세보기 조회 */ export const useNoticeDetailData = (noticeId: string) => { const [noticeDetail, setNoticeDetail] = useState(null); useEffect(() => { const fetchNoticeDetail = async () => { + try { const response = await instance.get(`${process.env.REACT_APP_BASE_URL}/notices/${noticeId}`); const data = response.data; diff --git a/src/Apis/Notices/request.ts b/src/Apis/Notices/request.ts index 4687c91..07ef2fe 100644 --- a/src/Apis/Notices/request.ts +++ b/src/Apis/Notices/request.ts @@ -1,7 +1,20 @@ +import { AttachmentType } from "./response"; + export interface NoticeWrite { title: string; content: string; - attachments: string[]; + attachments: AttachmentRequest[]; +} + +export interface NoticeEdit { + title: string; + content: string; + attachments: AttachmentRequest[]; +} + +export interface AttachmentRequest { + url: string; + type: AttachmentType; } export interface NoticeDelete { diff --git a/src/Apis/Notices/response.ts b/src/Apis/Notices/response.ts index bb7a0f5..c6fcb03 100644 --- a/src/Apis/Notices/response.ts +++ b/src/Apis/Notices/response.ts @@ -11,7 +11,7 @@ export interface NoticeDetailResponse { attachments: AttachmentResponse[]; } -type AttachmentType = +export type AttachmentType = | "FILE" | "URL" diff --git a/src/Apis/axios.ts b/src/Apis/axios.ts index 8265679..1e62d5f 100644 --- a/src/Apis/axios.ts +++ b/src/Apis/axios.ts @@ -13,8 +13,6 @@ let flag = false; instance.interceptors.request.use( (config) => { const accessToken = cookies.get('access_token'); - console.log(); - const returnConfig = { ...config }; if (accessToken) { returnConfig.headers!['Authorization'] = `Bearer ${accessToken}`; @@ -30,7 +28,6 @@ instance.interceptors.response.use( if (axios.isAxiosError(error) && error.response) { const { config } = error; const refreshToken = cookies.get('refresh_token'); - console.log(refreshToken); if (!refreshToken) { cookies.remove('access_token'); cookies.remove('refresh_token'); diff --git a/src/Pages/NoticePage/NoticeDetailPage/index.tsx b/src/Pages/NoticePage/NoticeDetailPage/index.tsx index 7ba403a..e270d41 100644 --- a/src/Pages/NoticePage/NoticeDetailPage/index.tsx +++ b/src/Pages/NoticePage/NoticeDetailPage/index.tsx @@ -5,14 +5,15 @@ import { Icon } from '@team-return/design-system'; import { useDeleteNotice } from '../../../Apis/Notices'; import * as _ from './style'; import { AttachedBox } from '../../../Components/Notice/AttachedBox'; +import { Link } from 'react-router-dom'; +import { useEffect } from 'react'; export function NoticeDetailPage() { - const { id } = useParams<{ id: any }>(); - const { noticeDetail } = useNoticeDetailData(id); - const items = [noticeDetail]; + const { id } = useParams<{ id: string }>(); + const { noticeDetail } = useNoticeDetailData(id || ''); const navigate = useNavigate(); - const { mutate: deleteNotice } = useDeleteNotice(id, { + const { mutate: deleteNotice } = useDeleteNotice(id || '', { onSuccess: () => { navigate('/Notice'); }, @@ -27,6 +28,12 @@ export function NoticeDetailPage() { } }; + const date = noticeDetail?.created_at.substring(0, 10); + + useEffect(() => { + console.log(noticeDetail); + }, [noticeDetail]); + return ( <>
@@ -44,22 +51,31 @@ export function NoticeDetailPage() { onClick={handleDeleteClick} > - <_.IconBox> - - + + <_.IconBox> + + + - <_.Date> - {noticeDetail?.created_at.substring(0, 10)} - + <_.Date>{date} <_.Contents>{noticeDetail?.content} - {items?.map((item) => ( - - ))} + diff --git a/src/Pages/NoticePage/NoticeEditPage/index.tsx b/src/Pages/NoticePage/NoticeEditPage/index.tsx index fdb3741..2d1e520 100644 --- a/src/Pages/NoticePage/NoticeEditPage/index.tsx +++ b/src/Pages/NoticePage/NoticeEditPage/index.tsx @@ -2,43 +2,55 @@ import { Header } from '../../../Components/Header'; import * as _ from './style'; import { ChangeEvent, useEffect, useRef, useState } from 'react'; import { Button } from '@team-return/design-system'; -import { useNoticeWriteData } from '../../../Apis/Notices'; -import axios from 'axios'; import { usePresignedUrl } from '../../../Apis/Files'; -import { PresignedUrlRequest } from '../../../Apis/Files/request'; -import { Link } from 'react-router-dom'; +import { useLocation, useNavigate, useParams } from 'react-router-dom'; +import { AttachmentRequest } from '../../../Apis/Notices/request'; +import { Icon } from '@team-return/design-system'; +import { useNoticeEditData } from '../../../Apis/Notices'; +import { useForm } from '../../../Hooks/useForm'; export function NoticeEditPage() { const fileInputRef = useRef(null); + const location = useLocation(); const [inputCount, setInputCount] = useState(0); - const [title, setTitle] = useState(''); - const [content, setContent] = useState(''); - const [attachments, setAttachments] = useState([]); - const [presignedUrls, setPresignedUrls] = useState([]); - const { mutate: writeNotice } = useNoticeWriteData({ - title, - content, - attachments: presignedUrls, + const { title, content, created_at, attachments } = location.state || {}; + const { form: noticeEditForm, handleChange } = useForm({ + editTitle: title || '', + editContent: content || '', + editCreated_at: created_at || '', + editAttachments: attachments || [], }); - const { mutate: getPresignedUrl } = usePresignedUrl(); - - const getTodayDate = (): string => { - const today = new Date(); - const year = today.getFullYear(); - let month = (today.getMonth() + 1).toString(); - let day = today.getDate().toString(); + const [editAttachments, setEditAttachments] = useState( + noticeEditForm.editAttachments + ); - if (month.length === 1) { - month = '0' + month; - } - if (day.length === 1) { - day = '0' + day; + const [presignedUrls, setPresignedUrls] = useState([]); + const navigate = useNavigate(); + const { id } = useParams<{ id: string }>(); + + const { mutate: editNotice } = useNoticeEditData(id || ''); + const { mutate: getPresignedUrl, data } = usePresignedUrl('EXTENSION_FILE'); + + useEffect(() => { + if (data) { + const { presignedUrls } = data; + setPresignedUrls( + presignedUrls.urls.map(({ file_path }) => ({ + type: 'FILE', + url: file_path, + })) + ); } + }, [data]); - return `${year}-${month}-${day}`; - }; + useEffect(() => { + if (presignedUrls.length !== 0) { + const editedAttachments = [...editAttachments, ...presignedUrls]; + setEditAttachments(editedAttachments); + } + }, [presignedUrls]); const handleAddFileClick = () => { if (fileInputRef.current) { @@ -46,50 +58,30 @@ export function NoticeEditPage() { } }; - const onInputHandler = (e: any) => { - setInputCount(e.target.value.length); - }; - - const handleTitleChange = (e: ChangeEvent) => { - setTitle(e.target.value); - console.log('제목:', e.target.value); - }; - - const handleContentChange = (e: ChangeEvent) => { - setContent(e.target.value); - console.log('내용:', e.target.value); - }; - - let files_: any; - - const handleFileChange = async (e: ChangeEvent) => { + const handleFileChange = (e: ChangeEvent) => { if (e.target.files) { const newAttachments = Array.from(e.target.files); - setAttachments((prevAttachments) => [ - ...prevAttachments, - ...newAttachments, - ]); - - files_ = attachments.map((item) => ({ - type: 'EXTENSION_FILE', - file_name: item.name, - })); - - // const presignedUrls = await Promise.all(promises); - - // // setPresignedUrls((prevPresignedUrls) => [ - // // ...prevPresignedUrls, - // // ...presignedUrls, - // // ]); - - // console.log('첨부파일: ', attachments, newAttachments); + getPresignedUrl(newAttachments); } }; + const handleFileDelete = (index: number) => { + const newAttachments = [...noticeEditForm.editAttachments]; + newAttachments.splice(index, 1); + setEditAttachments(newAttachments); + }; + const handleNoticeSubmit = () => { - // console.log(data); - writeNotice(); - getPresignedUrl(attachments); + editNotice({ + title: noticeEditForm.editTitle, + content: noticeEditForm.editContent, + attachments: editAttachments, + }); + navigate('/Notice'); + }; + + const file_name_regex = (url: string) => { + return url?.replace(/(?:.*?-){5}(.*)/, '$1').replaceAll('+', ' '); }; return ( @@ -97,17 +89,19 @@ export function NoticeEditPage() {
<_.Wrapper> <_.Box> - <_.Title>공지 작성하기 + <_.Title>공지 수정하기 <_.ContentWrap> <_.WriteDateWrap> <_.Text>작성일 - <_.Text>{getTodayDate()} + <_.Text>{noticeEditForm.editCreated_at} <_.TitleWrap> <_.Text>제목 <_.Input - onChange={handleTitleChange} placeholder="공지 제목을 입력하세요" + value={noticeEditForm.editTitle} + name="editTitle" + onChange={handleChange} /> <_.TextWrap> @@ -116,32 +110,57 @@ export function NoticeEditPage() { <_.TextInput placeholder="공지 내용을 입력하세요" maxLength={999} - value={content} - onChange={handleContentChange} - onInput={onInputHandler} + value={noticeEditForm.editContent} + onChange={handleChange} + name="editContent" + onInput={(e: any) => + setInputCount(e.target.value.length) + } + as="textarea" /> <_.InputCount>{inputCount}자/1000 - <_.FileWrap> + {/* <_.FileWrap> <_.Text>첨부파일 - <_.AddFile onClick={handleAddFileClick}> - 파일 추가하기 - - - + <_.AddFileWrapper> + {editAttachments.map( + (file: any, index: number) => ( + <_.AddFile key={index}> + {file.url + ? file_name_regex(file.url) + : file_name_regex(file.name)} + <_.IconWrapper + onClick={() => + handleFileDelete(index) + } + > + + + + ) + )} + <_.AddFile onClick={handleAddFileClick}> + 파일 추가하기 + + + + */} <_.ButtonWrap> - +
- +
diff --git a/src/Pages/NoticePage/NoticeEditPage/style.ts b/src/Pages/NoticePage/NoticeEditPage/style.ts index 23fbf65..d38b408 100644 --- a/src/Pages/NoticePage/NoticeEditPage/style.ts +++ b/src/Pages/NoticePage/NoticeEditPage/style.ts @@ -19,21 +19,22 @@ export const InputCount = styled.div` `; export const Box = styled.div` - width: 980px; - height: 850px; - border: 1px solid #E5E5E5; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - background-color: white; + width: 1298px; + height: 971px; + background-color: white; + border: 1px solid #E5E5E5; + display: flex; + flex-direction: column; + align-items: center; + overflow: scroll; `; export const Title = styled.div` - font-weight: 600; - font-size: 23px; + font-weight: 500; + font-size: 24px; line-height: 36px; margin-left: 24px; + margin-top: 24px; align-self: flex-start; margin-bottom: 25px; `; @@ -55,8 +56,8 @@ export const WriteDateWrap = styled.div` `; export const Text = styled.div` - font-size: 23px; - font-weight: 600; + font-size: 24px; + font-weight: 500; `; export const TitleWrap = styled.div` @@ -67,8 +68,8 @@ export const TitleWrap = styled.div` `; export const Input = styled.input` - width: 545px; - height: 43px; + width: 575px; + height: 46px; border: 1px solid #999999; padding-left: 10px; font-size: 16px; @@ -82,7 +83,7 @@ export const TextWrap = styled.div` `; export const InputWrapper = styled.div` - width: 545px; + width: 575px; height: 400px; border: 1px solid #999999; border-radius: 4px; @@ -99,28 +100,47 @@ export const TextInput = styled.textarea` font-size: 16px; border: none; resize: none; + white-space: pre-line; `; export const FileWrap = styled.div` display: flex; flex-direction: row; gap: 37px; - align-items: center; + /* align-items: center; */ `; export const AddFile = styled.div` - width: 550px; - height: 43px; + width: 575px; + height: 46px; background-color: #FAFAFA; cursor: pointer; font-size: 16px; align-items: center; display: flex; - padding-left: 16px; + padding: 16px; font-weight: 400; + justify-content: space-between; `; export const ButtonWrap = styled.div` margin-top: 68px; margin-left: auto; `; + +export const AddFileWrapper = styled.div` + display: flex; + flex-direction: column; + gap: 12px; +`; + +export const IconWrapper = styled.div` + display: flex; + justify-content: center; + align-items: center; + width: 28px; + height: 28px; + border-radius: 50%; + background-color: #135C9D; + cursor: pointer; +`; \ No newline at end of file diff --git a/src/Pages/NoticePage/NoticeListPage/index.tsx b/src/Pages/NoticePage/NoticeListPage/index.tsx index 8756b43..1fc9f15 100644 --- a/src/Pages/NoticePage/NoticeListPage/index.tsx +++ b/src/Pages/NoticePage/NoticeListPage/index.tsx @@ -1,7 +1,7 @@ import React from 'react'; import * as _ from './style'; import { Header } from '../../../Components/Header'; -import { Link, useLocation } from 'react-router-dom'; +import { Link } from 'react-router-dom'; import { Button } from '@team-return/design-system'; import { useNoticeListData } from '../../../Apis/Notices'; diff --git a/src/Pages/NoticePage/NoticeListPage/style.ts b/src/Pages/NoticePage/NoticeListPage/style.ts index c86afa2..832b35b 100644 --- a/src/Pages/NoticePage/NoticeListPage/style.ts +++ b/src/Pages/NoticePage/NoticeListPage/style.ts @@ -27,6 +27,7 @@ export const Table = styled.table` flex-direction: column; justify-content: center; align-items: center; + border: none; `; export const Thead = styled.thead` @@ -44,18 +45,21 @@ export const HeaderNumber = styled.th` width: 192px; font-size: 16px; padding: 2px 4px 2px 4px; + border: none; `; export const HeaderTitle = styled.th` width: 755px; font-size: 16px; padding: 2px 4px 2px 4px; + border: none; `; export const HeaderDate = styled.th` width: 192px; font-size: 16px; padding: 2px 4px 2px 4px; + border: none; `; export const Tbody = styled.tbody` diff --git a/src/Pages/NoticePage/NoticeWritePage/index.tsx b/src/Pages/NoticePage/NoticeWritePage/index.tsx index 8786b48..da2f727 100644 --- a/src/Pages/NoticePage/NoticeWritePage/index.tsx +++ b/src/Pages/NoticePage/NoticeWritePage/index.tsx @@ -1,10 +1,12 @@ import { Header } from '../../../Components/Header'; import * as _ from './style'; -import { ChangeEvent, useRef, useState } from 'react'; +import { ChangeEvent, useEffect, useRef, useState } from 'react'; import { Button } from '@team-return/design-system'; import { useNoticeWriteData } from '../../../Apis/Notices'; import { usePresignedUrl } from '../../../Apis/Files'; -import { Link, useNavigate } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; +import { AttachmentRequest } from '../../../Apis/Notices/request'; +import { Icon } from '@team-return/design-system'; export function NoticeWritePage() { const fileInputRef = useRef(null); @@ -12,16 +14,37 @@ export function NoticeWritePage() { const [title, setTitle] = useState(''); const [content, setContent] = useState(''); const [attachments, setAttachments] = useState([]); - const [presignedUrls] = useState([]); + const [presignedUrls, setpresignedUrls] = useState([]); const navigate = useNavigate(); - const { mutate: writeNotice } = useNoticeWriteData({ - title, - content, - attachments: presignedUrls, - }); + const { mutate: writeNotice } = useNoticeWriteData(); - const { mutate: getPresignedUrl } = usePresignedUrl(); + const { mutate: getPresignedUrl, data } = usePresignedUrl('EXTENSION_FILE'); + + useEffect(() => { + if (data) { + const { presignedUrls } = data; + setpresignedUrls( + presignedUrls.urls.map(({ file_path }) => ({ + type: 'FILE', + url: file_path, + })) + ); + } + }, [data]); + + useEffect(() => { + if (presignedUrls.length !== 0) { + console.log('6', presignedUrls); + + writeNotice({ + title, + content, + attachments: presignedUrls, + }); + navigate('/Notice'); + } + }, [presignedUrls]); const getTodayDate = (): string => { const today = new Date(); @@ -51,12 +74,10 @@ export function NoticeWritePage() { const handleTitleChange = (e: ChangeEvent) => { setTitle(e.target.value); - console.log('제목:', e.target.value); }; const handleContentChange = (e: ChangeEvent) => { setContent(e.target.value); - console.log('내용:', e.target.value); }; const handleFileChange = async (e: ChangeEvent) => { @@ -70,9 +91,15 @@ export function NoticeWritePage() { }; const handleNoticeSubmit = () => { - getPresignedUrl(attachments); - writeNotice(); - navigate('/Notice'); + if (attachments.length > 0) { + getPresignedUrl(attachments); + } + }; + + const handleFileDelete = (index: number) => { + const newAttachments = [...attachments]; + newAttachments.splice(index, 1); + setAttachments(newAttachments); }; return ( @@ -110,25 +137,42 @@ export function NoticeWritePage() { <_.FileWrap> <_.Text>첨부파일 - <_.AddFile onClick={handleAddFileClick}> - 파일 추가하기 - - + <_.AddFileWrapper> + {attachments.map( + (file: File, index: number) => ( + <_.AddFile key={index}> + {file.name} + <_.IconWrapper + onClick={() => + handleFileDelete(index) + } + > + + + + ) + )} + <_.AddFile onClick={handleAddFileClick}> + 파일 추가하기 + + + <_.ButtonWrap> - { - - - - } +
+ +
diff --git a/src/Pages/NoticePage/NoticeWritePage/style.ts b/src/Pages/NoticePage/NoticeWritePage/style.ts index 7788e84..d38b408 100644 --- a/src/Pages/NoticePage/NoticeWritePage/style.ts +++ b/src/Pages/NoticePage/NoticeWritePage/style.ts @@ -19,21 +19,22 @@ export const InputCount = styled.div` `; export const Box = styled.div` - width: 980px; - height: 850px; - border: 1px solid #E5E5E5; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - background-color: white; + width: 1298px; + height: 971px; + background-color: white; + border: 1px solid #E5E5E5; + display: flex; + flex-direction: column; + align-items: center; + overflow: scroll; `; export const Title = styled.div` - font-weight: 600; - font-size: 23px; + font-weight: 500; + font-size: 24px; line-height: 36px; margin-left: 24px; + margin-top: 24px; align-self: flex-start; margin-bottom: 25px; `; @@ -55,8 +56,8 @@ export const WriteDateWrap = styled.div` `; export const Text = styled.div` - font-size: 23px; - font-weight: 600; + font-size: 24px; + font-weight: 500; `; export const TitleWrap = styled.div` @@ -67,8 +68,8 @@ export const TitleWrap = styled.div` `; export const Input = styled.input` - width: 545px; - height: 43px; + width: 575px; + height: 46px; border: 1px solid #999999; padding-left: 10px; font-size: 16px; @@ -82,7 +83,7 @@ export const TextWrap = styled.div` `; export const InputWrapper = styled.div` - width: 545px; + width: 575px; height: 400px; border: 1px solid #999999; border-radius: 4px; @@ -106,22 +107,40 @@ export const FileWrap = styled.div` display: flex; flex-direction: row; gap: 37px; - align-items: center; + /* align-items: center; */ `; export const AddFile = styled.div` - width: 550px; - height: 43px; + width: 575px; + height: 46px; background-color: #FAFAFA; cursor: pointer; font-size: 16px; align-items: center; display: flex; - padding-left: 16px; + padding: 16px; font-weight: 400; + justify-content: space-between; `; export const ButtonWrap = styled.div` margin-top: 68px; margin-left: auto; `; + +export const AddFileWrapper = styled.div` + display: flex; + flex-direction: column; + gap: 12px; +`; + +export const IconWrapper = styled.div` + display: flex; + justify-content: center; + align-items: center; + width: 28px; + height: 28px; + border-radius: 50%; + background-color: #135C9D; + cursor: pointer; +`; \ No newline at end of file diff --git a/src/router.tsx b/src/router.tsx index da6bfb0..3830e08 100644 --- a/src/router.tsx +++ b/src/router.tsx @@ -77,7 +77,7 @@ const Router = createBrowserRouter([ element: , }, { - path: 'Notice/Edit', + path: 'Notice/Edit/:id', element: , }, { From 3fba9ec686cd64c098202d8eb909953e19215d7a Mon Sep 17 00:00:00 2001 From: phyuna0525 <128463899+phyuna0525@users.noreply.github.com> Date: Mon, 27 May 2024 19:11:10 +0900 Subject: [PATCH 06/21] =?UTF-8?q?feat=20::=20=EB=AA=A8=EC=A7=91=EC=9D=98?= =?UTF-8?q?=EB=A2=B0=EC=84=9C=20=EC=97=91=EC=85=80=20=EB=8B=A4=EC=9A=B4?= =?UTF-8?q?=EB=A1=9C=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RecruitmentForm/Table/index.tsx | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/Components/RecruitmentForm/Table/index.tsx b/src/Components/RecruitmentForm/Table/index.tsx index 910adba..4b1e1a6 100644 --- a/src/Components/RecruitmentForm/Table/index.tsx +++ b/src/Components/RecruitmentForm/Table/index.tsx @@ -14,6 +14,8 @@ import { getValueByKey } from '../../../Utils/useGetPropertyKey'; import { searchInArray } from '../../../Utils/useSearchForArray'; import { Link } from 'react-router-dom'; import { useRecruitmentFormQueryString } from '../../../Store/State'; +import { useDownloadData } from '../../../Apis/File'; +import { DownloadDataPropsType } from '../../../Apis/File/request'; interface PropsType { recruitmentForm: RecruitmentFormResponse; @@ -39,6 +41,10 @@ export function RecruitmentFormTable({ const dataLength = recruitmentForm?.recruitments.length; const [clickedData, setClickedData] = useState([]); const [changeStatus, setChangeStatus] = useState(''); + const [downloadUrl, setDownloadUrl] = useState({ + fileUrl: '', + fileName: '', + }); /** 지원서 상태를 변경하는 api를 호출합니다. */ const { mutate: changeStatusAPI, isLoading } = useChangeRecruitmentsStatus( @@ -225,6 +231,16 @@ export function RecruitmentFormTable({ <_.TitleText>모집종료일, ]; + const { mutate: downloadExcel } = useDownloadData(downloadUrl); + + const fileDownloadAPI = () => { + setDownloadUrl({ + fileUrl: '/recruitments/file', + fileName: '모집의뢰서리스트.xlsx', + }); + setTimeout(downloadExcel); + }; + /** 테이블의 width입니다. */ const tableWidth: number[] = [3, 7, 18, 30, 6, 6, 6, 6, 10, 10]; @@ -233,6 +249,9 @@ export function RecruitmentFormTable({ return ( <_.Container> <_.BtnWrapper> + diff --git a/src/Components/CreateBanner/styled.ts b/src/Components/CreateBanner/styled.ts index f15c956..f0aa9c0 100644 --- a/src/Components/CreateBanner/styled.ts +++ b/src/Components/CreateBanner/styled.ts @@ -52,6 +52,11 @@ export const BannerImg = styled.img` z-index: 0; `; +export const CaptureWrapper = styled.div` + width: 1000px; + height: 320px; +`; + export const InputWrapper = styled.div<{ hasValue: { title1: boolean; @@ -69,13 +74,13 @@ export const InputWrapper = styled.div<{ .title1 { color: white; - font-size: ${(props) => (props.hasValue.title1 ? '32px' : '12px')}; + font-size: ${(props) => (props.hasValue.title1 ? '24px' : '12px')}; font-weight: ${(props) => (props.hasValue.title1 ? '700' : '500')}; } .title2 { color: white; - font-size: ${(props) => (props.hasValue.title2 ? '32px' : '12px')}; + font-size: ${(props) => (props.hasValue.title1 ? '24px' : '12px')}; font-weight: ${(props) => (props.hasValue.title2 ? '700' : '500')}; } @@ -154,6 +159,15 @@ export const Table = styled.table` } `; +export const CompanyNameSearch = styled.tr` + position: relative; +`; + +export const RecruitmentSearch = styled.tr` + position: relative; + z-index: 1; +`; + export const Td = styled.td` padding: 13px 9px; border-right: 1px solid #e5e5e5; @@ -171,7 +185,51 @@ export const SearchIcon = styled.img` `; export const Search = styled.input` - width: 200px; + width: 400px; +`; + +export const SearchEx = styled.div` + background-color: #f7f7f7; + width: 967px; + z-index: 2; + position: absolute; + left: 32px; + top: 46px; + border-top: 1px solid #cccccc; + > div { + padding: 11.5px 9px; + display: flex; + gap: 16px; + border-right: 1px solid #cccccc; + border-bottom: 1px solid #cccccc; + border-left: 1px solid #cccccc; + } + > li { + font-size: 22px; + cursor: pointer; + } +`; + +export const recruitmentEx = styled.div` + background-color: #f7f7f7; + width: 967px; + z-index: 2; + position: absolute; + left: 32px; + top: 46px; + border-top: 1px solid #cccccc; + > div { + padding: 11.5px 9px; + display: flex; + gap: 16px; + border-right: 1px solid #cccccc; + border-bottom: 1px solid #cccccc; + border-left: 1px solid #cccccc; + } + > li { + font-size: 22px; + cursor: pointer; + } `; export const TimeWrapper = styled.div` diff --git a/src/Hooks/useOutsideClick.tsx b/src/Hooks/useOutsideClick.tsx new file mode 100644 index 0000000..f53272c --- /dev/null +++ b/src/Hooks/useOutsideClick.tsx @@ -0,0 +1,19 @@ +import { useEffect, useRef } from 'react'; + +export const useOutsideClick = (callback: () => void) => { + const ref = useRef(null); + + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (ref.current && !ref.current.contains(event.target as Node)) { + callback(); + } + }; + document.addEventListener('mousedown', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, [callback]); + + return ref; +}; diff --git a/src/Pages/NoticePage/NoticeEditPage/index.tsx b/src/Pages/NoticePage/NoticeEditPage/index.tsx index 12f9853..3487ae0 100644 --- a/src/Pages/NoticePage/NoticeEditPage/index.tsx +++ b/src/Pages/NoticePage/NoticeEditPage/index.tsx @@ -52,12 +52,10 @@ export function NoticeEditPage() { const handleTitleChange = (e: ChangeEvent) => { setTitle(e.target.value); - console.log('제목:', e.target.value); }; const handleContentChange = (e: ChangeEvent) => { setContent(e.target.value); - console.log('내용:', e.target.value); }; let files_: any; diff --git a/src/Pages/NoticePage/NoticeWritePage/index.tsx b/src/Pages/NoticePage/NoticeWritePage/index.tsx index b626102..e0081c8 100644 --- a/src/Pages/NoticePage/NoticeWritePage/index.tsx +++ b/src/Pages/NoticePage/NoticeWritePage/index.tsx @@ -55,12 +55,10 @@ export function NoticeWritePage() { const handleTitleChange = (e: ChangeEvent) => { setTitle(e.target.value); - console.log('제목:', e.target.value); }; const handleContentChange = (e: ChangeEvent) => { setContent(e.target.value); - console.log('내용:', e.target.value); }; // let files_: any; From fc956349d53063303146022dc688afd8c7ae2b40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=9D=98=EC=A7=84?= Date: Tue, 28 May 2024 08:19:12 +0900 Subject: [PATCH 08/21] =?UTF-8?q?feat:=20=EB=A6=AC=EB=B7=B0=20=EB=B0=98?= =?UTF-8?q?=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Apis/Files/index.ts | 6 +- src/Apis/Files/response.ts | 1 - src/Apis/Notices/index.ts | 201 ++++++++++-------- src/Components/Notice/AttachedBox/index.tsx | 16 +- .../NoticePage/NoticeDetailPage/index.tsx | 7 +- src/Pages/NoticePage/NoticeEditPage/index.tsx | 35 +-- .../NoticePage/NoticeWritePage/index.tsx | 6 +- src/Utils/Regex.tsx | 3 + 8 files changed, 140 insertions(+), 135 deletions(-) create mode 100644 src/Utils/Regex.tsx diff --git a/src/Apis/Files/index.ts b/src/Apis/Files/index.ts index c39ebaa..11c2eb2 100644 --- a/src/Apis/Files/index.ts +++ b/src/Apis/Files/index.ts @@ -3,11 +3,7 @@ import { PresignedUrlResponse } from "./response"; import { instance } from "../axios"; import axios from "axios"; -export type PresignedUrlType = - | "LOGO_IMAGE" - | "EXTENSION_FILE"; - -export const usePresignedUrl = (type: PresignedUrlType) => { +export const usePresignedUrl = () => { const res = useMutation( async (attachments: File[] ) => { const files = attachments.map((item) => ({ diff --git a/src/Apis/Files/response.ts b/src/Apis/Files/response.ts index d28a002..9819096 100644 --- a/src/Apis/Files/response.ts +++ b/src/Apis/Files/response.ts @@ -2,6 +2,5 @@ export interface PresignedUrlResponse { urls: { file_path: string, pre_signed_url: string, - url: string }[] } \ No newline at end of file diff --git a/src/Apis/Notices/index.ts b/src/Apis/Notices/index.ts index 80d10dc..afee857 100644 --- a/src/Apis/Notices/index.ts +++ b/src/Apis/Notices/index.ts @@ -1,104 +1,139 @@ -import { NoticeWrite, NoticeEdit } from "./request"; -import { MutationOptions, useMutation } from "react-query"; -import { instance } from "../axios"; -import { NoticeListResponse } from "./response"; -import { useEffect, useState } from "react"; -import { NoticeDetailResponse } from "./response"; +import { NoticeWrite, NoticeEdit } from './request'; +import { MutationOptions, useMutation } from 'react-query'; +import { instance } from '../axios'; +import { NoticeListResponse } from './response'; +import { useEffect, useState } from 'react'; +import { NoticeDetailResponse } from './response'; +import { useToastStore } from '@team-return/design-system'; + +const router = '/notices'; /** 공지사항 작성 */ export const useNoticeWriteData = () => { - return useMutation( - async (noticeData: NoticeWrite) => { - const { data } = await instance.post(`/notices`, noticeData); - return data; - }, - { - onError: (error: Error) => { - console.error("notice write error:", error.message); - } - } - ) -} + const { append } = useToastStore(); + return useMutation( + async (noticeData: NoticeWrite) => { + const { data } = await instance.post(`${router}`, noticeData); + return data; + }, + { + onError: () => { + append({ + title: '공지 작성에 실패했습니다.', + message: '', + type: 'RED', + }); + }, + } + ); +}; /** 공지사항 수정 */ export const useNoticeEditData = (noticeId: string) => { - return useMutation( - async (noticeData: NoticeEdit) => { - const { data } = await instance.patch(`/notices/${noticeId}`, noticeData); - return data; - }, - { - onError: (error: Error) => { - console.error("notice edit error: ", error); - - } - } - ) -} + const { append } = useToastStore(); + return useMutation( + async (noticeData: NoticeEdit) => { + const { data } = await instance.patch( + `${router}/${noticeId}`, + noticeData + ); + return data; + }, + { + onError: () => { + append({ + title: '공지 수정에 실패했습니다.', + message: '', + type: 'RED', + }); + }, + } + ); +}; /** 공지사항 상세보기 조회 */ export const useNoticeDetailData = (noticeId: string) => { - const [noticeDetail, setNoticeDetail] = useState(null); - - useEffect(() => { - const fetchNoticeDetail = async () => { - - try { - const response = await instance.get(`${process.env.REACT_APP_BASE_URL}/notices/${noticeId}`); - const data = response.data; + const [noticeDetail, setNoticeDetail] = + useState(null); + const { append } = useToastStore(); + + const fetchNoticeDetail = (noticeId: string) => { + return instance + .get(`${router}/${noticeId}`) + .then((response) => { + const data = response.data; + const fetchedNoticeDetail: NoticeDetailResponse = { + title: data.title, + content: data.content, + created_at: new Date(data.created_at).toISOString(), + attachments: data.attachments.map((attachment: any) => ({ + url: attachment.url, + type: attachment.type, + })), + }; + setNoticeDetail(fetchedNoticeDetail); + }) + .catch(() => { + append({ + title: '공지 상세보기에 실패했습니다.', + message: '', + type: 'RED', + }); + }); + }; - const fetchedNoticeDetail: NoticeDetailResponse = { - title: data.title, - content: data.content, - created_at: new Date(data.created_at).toISOString(), - attachments: data.attachments.map((attachment: any) => ({ - url: attachment.url, - type: attachment.type - })) - }; - setNoticeDetail(fetchedNoticeDetail); - } catch (error: any) { - console.error('notice detail error:', error.message); - } - }; - fetchNoticeDetail(); - }, [noticeId]); + useState(() => { + fetchNoticeDetail(noticeId); + }); - return { noticeDetail }; + return { noticeDetail }; }; /** 공지사항 리스트 조희 */ export const useNoticeListData = () => { - const [notices, setNotices] = useState([]); + const [notices, setNotices] = useState([]); + const { append } = useToastStore(); - useEffect(() => { - const fetchNoticeList = async () => { - try { - const response = await instance.get(`${process.env.REACT_APP_BASE_URL}/notices`); - const data = response.data; + const fetchNoticeList = () => { + return instance + .get(`${router}`) + .then((response) => { + const data = response.data; - const fetchedNotices: NoticeListResponse[] = data.notices.map((notice: any) => ({ - id: notice.id, - title: notice.title, - created_at: new Date(notice.created_at).toISOString() - })); - setNotices(fetchedNotices); - } catch (error: any) { - console.error('notice list error: ', error.message); - } - }; - fetchNoticeList(); - }, []); + const fetchedNotices: NoticeListResponse[] = data.notices.map( + (notice: any) => ({ + id: notice.id, + title: notice.title, + created_at: new Date(notice.created_at).toISOString(), + }) + ); + setNotices(fetchedNotices); + }) + .catch(() => { + append({ + title: '공지 상세보기에 실패했습니다.', + message: '', + type: 'RED', + }); + }); + }; - return { notices }; -} + useEffect(() => { + fetchNoticeList(); + }, []); + + return { notices }; +}; /** 공지사항 삭제 */ export const useDeleteNotice = (noticeId: string, options: MutationOptions) => { - return useMutation( - async () => instance.delete(`${process.env.REACT_APP_BASE_URL}/notices/${noticeId}`, { data: { "notice-id": noticeId } }), - { - ...options - } - ) -} + return useMutation( + async () => + instance.delete(`${router}/${noticeId}`, { + data: { 'notice-id': noticeId }, + }), + { + ...options, + } + ); +}; diff --git a/src/Components/Notice/AttachedBox/index.tsx b/src/Components/Notice/AttachedBox/index.tsx index 2e2d110..54f9c5a 100644 --- a/src/Components/Notice/AttachedBox/index.tsx +++ b/src/Components/Notice/AttachedBox/index.tsx @@ -1,4 +1,4 @@ -import { Icon } from '@team-return/design-system'; +import { Icon, useToastStore } from '@team-return/design-system'; import { AttachmentResponse } from '../../../Apis/Notices/response'; import * as _ from './style'; import axios from 'axios'; @@ -11,17 +11,17 @@ export function AttachedBox({ props }: PropsType) { const file_name_regex = (url: string) => { return url.replace(/(?:.*?-){5}(.*)/, '$1').replaceAll('+', ' '); }; + const { append } = useToastStore(); const downLoadFile = async (attachment: AttachmentResponse) => { try { - const response = await axios.get( + const { data } = await axios.get( `${process.env.REACT_APP_FILE_URL}${attachment.url}`, { responseType: 'blob', } ); - - const url = window.URL.createObjectURL(new Blob([response.data])); + const url = window.URL.createObjectURL(new Blob([data])); const a = document.createElement('a'); a.href = url; a.download = file_name_regex(attachment.url); @@ -29,8 +29,12 @@ export function AttachedBox({ props }: PropsType) { a.click(); window.URL.revokeObjectURL(url); document.body.removeChild(a); - } catch (error) { - console.error('파일 다운로드 에러: ', error); + } catch { + append({ + title: '파일 다운로드에 실패했습니다.', + message: '', + type: 'RED', + }); } }; diff --git a/src/Pages/NoticePage/NoticeDetailPage/index.tsx b/src/Pages/NoticePage/NoticeDetailPage/index.tsx index e270d41..4c970e8 100644 --- a/src/Pages/NoticePage/NoticeDetailPage/index.tsx +++ b/src/Pages/NoticePage/NoticeDetailPage/index.tsx @@ -6,7 +6,6 @@ import { useDeleteNotice } from '../../../Apis/Notices'; import * as _ from './style'; import { AttachedBox } from '../../../Components/Notice/AttachedBox'; import { Link } from 'react-router-dom'; -import { useEffect } from 'react'; export function NoticeDetailPage() { const { id } = useParams<{ id: string }>(); @@ -30,10 +29,6 @@ export function NoticeDetailPage() { const date = noticeDetail?.created_at.substring(0, 10); - useEffect(() => { - console.log(noticeDetail); - }, [noticeDetail]); - return ( <>
@@ -68,7 +63,7 @@ export function NoticeDetailPage() { icon="EditPencil" color={'gray90'} size={26} - > + /> diff --git a/src/Pages/NoticePage/NoticeEditPage/index.tsx b/src/Pages/NoticePage/NoticeEditPage/index.tsx index 2d1e520..227e952 100644 --- a/src/Pages/NoticePage/NoticeEditPage/index.tsx +++ b/src/Pages/NoticePage/NoticeEditPage/index.tsx @@ -1,16 +1,14 @@ import { Header } from '../../../Components/Header'; import * as _ from './style'; -import { ChangeEvent, useEffect, useRef, useState } from 'react'; +import { useEffect, useState } from 'react'; import { Button } from '@team-return/design-system'; import { usePresignedUrl } from '../../../Apis/Files'; import { useLocation, useNavigate, useParams } from 'react-router-dom'; import { AttachmentRequest } from '../../../Apis/Notices/request'; -import { Icon } from '@team-return/design-system'; import { useNoticeEditData } from '../../../Apis/Notices'; import { useForm } from '../../../Hooks/useForm'; export function NoticeEditPage() { - const fileInputRef = useRef(null); const location = useLocation(); const [inputCount, setInputCount] = useState(0); @@ -31,7 +29,7 @@ export function NoticeEditPage() { const { id } = useParams<{ id: string }>(); const { mutate: editNotice } = useNoticeEditData(id || ''); - const { mutate: getPresignedUrl, data } = usePresignedUrl('EXTENSION_FILE'); + const { mutate: getPresignedUrl, data } = usePresignedUrl(); useEffect(() => { if (data) { @@ -52,25 +50,6 @@ export function NoticeEditPage() { } }, [presignedUrls]); - const handleAddFileClick = () => { - if (fileInputRef.current) { - fileInputRef.current.click(); - } - }; - - const handleFileChange = (e: ChangeEvent) => { - if (e.target.files) { - const newAttachments = Array.from(e.target.files); - getPresignedUrl(newAttachments); - } - }; - - const handleFileDelete = (index: number) => { - const newAttachments = [...noticeEditForm.editAttachments]; - newAttachments.splice(index, 1); - setEditAttachments(newAttachments); - }; - const handleNoticeSubmit = () => { editNotice({ title: noticeEditForm.editTitle, @@ -80,10 +59,6 @@ export function NoticeEditPage() { navigate('/Notice'); }; - const file_name_regex = (url: string) => { - return url?.replace(/(?:.*?-){5}(.*)/, '$1').replaceAll('+', ' '); - }; - return ( <>
@@ -113,9 +88,9 @@ export function NoticeEditPage() { value={noticeEditForm.editContent} onChange={handleChange} name="editContent" - onInput={(e: any) => - setInputCount(e.target.value.length) - } + onInput={( + e: React.ChangeEvent + ) => setInputCount(e.target.value.length)} as="textarea" /> <_.InputCount>{inputCount}자/1000 diff --git a/src/Pages/NoticePage/NoticeWritePage/index.tsx b/src/Pages/NoticePage/NoticeWritePage/index.tsx index da2f727..e20e78a 100644 --- a/src/Pages/NoticePage/NoticeWritePage/index.tsx +++ b/src/Pages/NoticePage/NoticeWritePage/index.tsx @@ -19,7 +19,7 @@ export function NoticeWritePage() { const { mutate: writeNotice } = useNoticeWriteData(); - const { mutate: getPresignedUrl, data } = usePresignedUrl('EXTENSION_FILE'); + const { mutate: getPresignedUrl, data } = usePresignedUrl(); useEffect(() => { if (data) { @@ -35,8 +35,6 @@ export function NoticeWritePage() { useEffect(() => { if (presignedUrls.length !== 0) { - console.log('6', presignedUrls); - writeNotice({ title, content, @@ -44,7 +42,7 @@ export function NoticeWritePage() { }); navigate('/Notice'); } - }, [presignedUrls]); + }); const getTodayDate = (): string => { const today = new Date(); diff --git a/src/Utils/Regex.tsx b/src/Utils/Regex.tsx new file mode 100644 index 0000000..bdc37a0 --- /dev/null +++ b/src/Utils/Regex.tsx @@ -0,0 +1,3 @@ +export const file_name_regex = (url: string) => { + return url.replace(/(?:.*?-){5}(.*)/, '$1').replaceAll('+', ' '); +}; From 85771b90386b4a32c71ad604fd3d8a36c0ef23dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=9D=98=EC=A7=84?= Date: Tue, 28 May 2024 10:37:31 +0900 Subject: [PATCH 09/21] =?UTF-8?q?fix:=20=EB=B9=8C=EB=93=9C=20=EC=98=A4?= =?UTF-8?q?=EB=A5=98=20=ED=94=BD=EC=8A=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Apis/Files/index.ts | 56 ++++++++++--------- src/Apis/Notices/index.ts | 8 +-- src/Pages/NoticePage/NoticeEditPage/index.tsx | 7 ++- 3 files changed, 40 insertions(+), 31 deletions(-) diff --git a/src/Apis/Files/index.ts b/src/Apis/Files/index.ts index 11c2eb2..885d128 100644 --- a/src/Apis/Files/index.ts +++ b/src/Apis/Files/index.ts @@ -1,28 +1,34 @@ -import { useMutation } from "react-query"; -import { PresignedUrlResponse } from "./response"; -import { instance } from "../axios"; -import axios from "axios"; +import { useMutation } from 'react-query'; +import { PresignedUrlResponse } from './response'; +import { instance } from '../axios'; +import axios from 'axios'; export const usePresignedUrl = () => { - const res = useMutation( - async (attachments: File[] ) => { - const files = attachments.map((item) => ({ - type: "EXTENSION_FILE", - file_name: item.name, - })); + const res = useMutation( + async (attachments: File[]) => { + const files = attachments.map((item) => ({ + type: 'EXTENSION_FILE', + file_name: item.name, + })); - const { data: presignedUrls } = await instance.post(`${process.env.REACT_APP_BASE_URL}/files/pre-signed`, { files }); - return {presignedUrls, attachments}; - }, { - onSuccess: async ({ presignedUrls, attachments }) => { - - const uploadPromises = await presignedUrls.urls.map(({pre_signed_url}, idx) => { - (async () => await axios.put(pre_signed_url, attachments[idx]))(); - }); - await Promise.all(uploadPromises); - return; - } - } - ) - return res -} \ No newline at end of file + const { data: presignedUrls } = + await instance.post( + `${process.env.REACT_APP_BASE_URL}/files/pre-signed`, + { files } + ); + return { presignedUrls, attachments }; + }, + { + onSuccess: async ({ presignedUrls, attachments }) => { + const uploadPromises = await Promise.all( + presignedUrls.urls.map(({ pre_signed_url }, idx) => { + return axios.put(pre_signed_url, attachments[idx]); + }) + ); + await Promise.all(uploadPromises); + return; + }, + } + ); + return res; +}; diff --git a/src/Apis/Notices/index.ts b/src/Apis/Notices/index.ts index afee857..b532cec 100644 --- a/src/Apis/Notices/index.ts +++ b/src/Apis/Notices/index.ts @@ -2,7 +2,7 @@ import { NoticeWrite, NoticeEdit } from './request'; import { MutationOptions, useMutation } from 'react-query'; import { instance } from '../axios'; import { NoticeListResponse } from './response'; -import { useEffect, useState } from 'react'; +import { useCallback, useEffect, useState } from 'react'; import { NoticeDetailResponse } from './response'; import { useToastStore } from '@team-return/design-system'; @@ -94,7 +94,7 @@ export const useNoticeListData = () => { const [notices, setNotices] = useState([]); const { append } = useToastStore(); - const fetchNoticeList = () => { + const fetchNoticeList = useCallback(() => { return instance .get(`${router}`) .then((response) => { @@ -116,11 +116,11 @@ export const useNoticeListData = () => { type: 'RED', }); }); - }; + }, [append]); useEffect(() => { fetchNoticeList(); - }, []); + }, [fetchNoticeList]); return { notices }; }; diff --git a/src/Pages/NoticePage/NoticeEditPage/index.tsx b/src/Pages/NoticePage/NoticeEditPage/index.tsx index 227e952..96b8286 100644 --- a/src/Pages/NoticePage/NoticeEditPage/index.tsx +++ b/src/Pages/NoticePage/NoticeEditPage/index.tsx @@ -24,12 +24,14 @@ export function NoticeEditPage() { noticeEditForm.editAttachments ); - const [presignedUrls, setPresignedUrls] = useState([]); const navigate = useNavigate(); + const { id } = useParams<{ id: string }>(); const { mutate: editNotice } = useNoticeEditData(id || ''); + const { mutate: getPresignedUrl, data } = usePresignedUrl(); + const [presignedUrls, setPresignedUrls] = useState([]); useEffect(() => { if (data) { @@ -48,7 +50,7 @@ export function NoticeEditPage() { const editedAttachments = [...editAttachments, ...presignedUrls]; setEditAttachments(editedAttachments); } - }, [presignedUrls]); + }, [presignedUrls, editAttachments]); const handleNoticeSubmit = () => { editNotice({ @@ -56,6 +58,7 @@ export function NoticeEditPage() { content: noticeEditForm.editContent, attachments: editAttachments, }); + getPresignedUrl(attachments); navigate('/Notice'); }; From 76ae8bd139f4504623b0ca4a8c12687c6453dc27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=80=EC=A7=80?= Date: Tue, 28 May 2024 12:05:24 +0900 Subject: [PATCH 10/21] =?UTF-8?q?fix=20::=20=EB=B9=8C=EB=93=9C=20=EC=98=A4?= =?UTF-8?q?=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Apis/Banners/index.ts | 10 ++-------- src/Apis/Files/index.ts | 3 +-- src/Components/Banner/BannerDetail.tsx/index.tsx | 3 +-- src/Components/Banner/index.tsx | 5 ++--- src/Components/CreateBanner/index.tsx | 6 +++--- src/Pages/NoticePage/NoticeEditPage/index.tsx | 14 +++----------- src/Pages/NoticePage/NoticeWritePage/index.tsx | 12 +----------- 7 files changed, 13 insertions(+), 40 deletions(-) diff --git a/src/Apis/Banners/index.ts b/src/Apis/Banners/index.ts index c5d1332..bcf9585 100644 --- a/src/Apis/Banners/index.ts +++ b/src/Apis/Banners/index.ts @@ -1,12 +1,6 @@ -import { - MutationOptions, - QueryOptions, - UseQueryOptions, - useMutation, - useQuery, -} from 'react-query'; +import { MutationOptions, useMutation, useQuery } from 'react-query'; import { instance } from '../axios'; -import { BannerListProps, BannerListResponse } from './response'; +import { BannerListResponse } from './response'; const router = '/banners'; diff --git a/src/Apis/Files/index.ts b/src/Apis/Files/index.ts index fe38a56..39acda3 100644 --- a/src/Apis/Files/index.ts +++ b/src/Apis/Files/index.ts @@ -1,5 +1,4 @@ -import { MutateOptions, useMutation } from 'react-query'; -import { PresignedUrlRequest } from './request'; +import { useMutation } from 'react-query'; import { PresignedUrlResponse } from './response'; import { instance } from '../axios'; import axios from 'axios'; diff --git a/src/Components/Banner/BannerDetail.tsx/index.tsx b/src/Components/Banner/BannerDetail.tsx/index.tsx index 0883897..aa1f8d7 100644 --- a/src/Components/Banner/BannerDetail.tsx/index.tsx +++ b/src/Components/Banner/BannerDetail.tsx/index.tsx @@ -1,7 +1,6 @@ import * as _ from './style'; -import { useEffect, useState } from 'react'; +import { useEffect } from 'react'; import { useGetBannerList } from '../../../Apis/Banners'; -import { BannerListProps } from '../../../Apis/Banners/response'; import { BannerNameType } from '../../../Apis/Banners/response'; import deleteimg from '../../../Assets/SVG/delete.svg'; interface Props { diff --git a/src/Components/Banner/index.tsx b/src/Components/Banner/index.tsx index 80d89bd..6687fb3 100644 --- a/src/Components/Banner/index.tsx +++ b/src/Components/Banner/index.tsx @@ -1,8 +1,8 @@ import { Button } from '@team-return/design-system'; import * as _ from './style'; import { BannerDetail } from './BannerDetail.tsx'; -import { useEffect, useState } from 'react'; -import { useDeleteBanner, useGetBannerList } from '../../Apis/Banners'; +import { useState } from 'react'; +import { useDeleteBanner } from '../../Apis/Banners'; import { Link } from 'react-router-dom'; import { DeleteAlarm } from './DeleteAlarm'; import { useQueryClient } from 'react-query'; @@ -12,7 +12,6 @@ export function Banner() { const [showDeleteAlarm, setShowDeleteAlarm] = useState(false); const [id, setId] = useState(null); - const createBannersAPI = useGetBannerList(isOpen); const queryClient = useQueryClient(); const handleBtnClick = () => { diff --git a/src/Components/CreateBanner/index.tsx b/src/Components/CreateBanner/index.tsx index a6bf451..c89d1f2 100644 --- a/src/Components/CreateBanner/index.tsx +++ b/src/Components/CreateBanner/index.tsx @@ -29,8 +29,8 @@ export function CreateBanner({ date, setDate }: PropType) { const { append } = useToastStore(); const [logoPreview, setLogoPreview] = useState(null); const [selectedPage, setSelectedPage] = useState(null); - const [startDate, setStartDate] = useState(null); - const [endDate, setEndDate] = useState(null); + const [, setStartDate] = useState(null); + const [, setEndDate] = useState(null); const [showSearchEx, setShowSearchEx] = useState(false); const [showRecruitmentSearchEx, setShowRecruitmentSearchEx] = useState(false); @@ -153,7 +153,7 @@ export function CreateBanner({ date, setDate }: PropType) { } }, [attachments]); - const [companyQueryResult, pageCountResult] = useGetCompanyRecruitments( + const [companyQueryResult] = useGetCompanyRecruitments( companyRecruitmentQueryString ); diff --git a/src/Pages/NoticePage/NoticeEditPage/index.tsx b/src/Pages/NoticePage/NoticeEditPage/index.tsx index 3487ae0..0ba0e92 100644 --- a/src/Pages/NoticePage/NoticeEditPage/index.tsx +++ b/src/Pages/NoticePage/NoticeEditPage/index.tsx @@ -1,26 +1,18 @@ import { Header } from '../../../Components/Header'; import * as _ from './style'; -import { ChangeEvent, useEffect, useRef, useState } from 'react'; +import { ChangeEvent, useRef, useState } from 'react'; import { Button } from '@team-return/design-system'; -import { useNoticeWriteData } from '../../../Apis/Notices'; -import axios from 'axios'; import { usePresignedUrl } from '../../../Apis/Files'; -import { PresignedUrlRequest } from '../../../Apis/Files/request'; import { Link } from 'react-router-dom'; export function NoticeEditPage() { const fileInputRef = useRef(null); const [inputCount, setInputCount] = useState(0); - const [title, setTitle] = useState(''); + const [, setTitle] = useState(''); const [content, setContent] = useState(''); const [attachments, setAttachments] = useState([]); - const [presignedUrls, setPresignedUrls] = useState([]); - const { mutate: writeNotice } = useNoticeWriteData({ - title, - content, - attachments: presignedUrls, - }); + const { mutate: getPresignedUrl } = usePresignedUrl(); diff --git a/src/Pages/NoticePage/NoticeWritePage/index.tsx b/src/Pages/NoticePage/NoticeWritePage/index.tsx index e0081c8..570beb5 100644 --- a/src/Pages/NoticePage/NoticeWritePage/index.tsx +++ b/src/Pages/NoticePage/NoticeWritePage/index.tsx @@ -1,11 +1,8 @@ import { Header } from '../../../Components/Header'; import * as _ from './style'; -import { ChangeEvent, useEffect, useRef, useState } from 'react'; +import { ChangeEvent, useRef, useState } from 'react'; import { Button } from '@team-return/design-system'; -import { useNoticeWriteData } from '../../../Apis/Notices'; -import axios from 'axios'; import { usePresignedUrl } from '../../../Apis/Files'; -import { PresignedUrlRequest } from '../../../Apis/Files/request'; import { Link } from 'react-router-dom'; export function NoticeWritePage() { @@ -14,13 +11,6 @@ export function NoticeWritePage() { const [title, setTitle] = useState(''); const [content, setContent] = useState(''); const [attachments, setAttachments] = useState([]); - const [presignedUrls, setPresignedUrls] = useState([]); - - const { mutate: writeNotice } = useNoticeWriteData({ - title, - content, - attachments: presignedUrls, - }); // const fileName = jsonData.files[0].file_name; From 936effe5d0f8ff7952d84d3b57185c046842fa3d Mon Sep 17 00:00:00 2001 From: hyuna Date: Tue, 28 May 2024 16:44:17 +0900 Subject: [PATCH 11/21] =?UTF-8?q?feat=20::=20=EB=AA=A8=EC=A7=91=EC=9D=98?= =?UTF-8?q?=EB=A2=B0=EC=84=9C,=EA=B8=B0=EC=97=85,=EC=A7=80=EC=9B=90?= =?UTF-8?q?=EC=84=9C=20=EC=B4=9D=20=EA=B0=9C=EC=88=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Apis/Applications/index.ts | 8 ++ src/Apis/Applications/response.ts | 4 + src/Apis/Companies/index.ts | 9 +++ src/Apis/Companies/response.ts | 4 + src/Apis/Recruitments/index.ts | 9 +++ src/Apis/Recruitments/response.ts | 4 + .../ApplicationView/Table/index.tsx | 28 +++++-- src/Components/ApplicationView/Table/style.ts | 20 ++++- .../CompanyRecruitment/Table/index.tsx | 77 +++++++++++-------- .../CompanyRecruitment/Table/style.ts | 20 ++++- .../RecruitmentForm/Table/index.tsx | 27 +++++-- src/Components/RecruitmentForm/Table/style.ts | 20 ++++- 12 files changed, 179 insertions(+), 51 deletions(-) diff --git a/src/Apis/Applications/index.ts b/src/Apis/Applications/index.ts index 5bd5c7b..cee893c 100644 --- a/src/Apis/Applications/index.ts +++ b/src/Apis/Applications/index.ts @@ -101,3 +101,11 @@ export const useRejectApplication = ( } ); }; + +/**지원서 총 개수 조회 */ +export const useApplicationCnt = () => { + return useQuery(['applicationCnt'], async () => { + const data = await instance.get(`${router}/teacher/count`); + return data.data; + }); +}; diff --git a/src/Apis/Applications/response.ts b/src/Apis/Applications/response.ts index 3d825a6..3d65035 100644 --- a/src/Apis/Applications/response.ts +++ b/src/Apis/Applications/response.ts @@ -26,3 +26,7 @@ export interface InternshipStudentType { student_name: string; student_gcn: string; } + +export interface CntType { + count: number; +} diff --git a/src/Apis/Companies/index.ts b/src/Apis/Companies/index.ts index bc669ad..82daf5f 100644 --- a/src/Apis/Companies/index.ts +++ b/src/Apis/Companies/index.ts @@ -12,6 +12,7 @@ import { CompanyInfoEditType, } from './request'; import { + CntType, CompanyDetailResponse, CompanyRecruitmentResponse, EmployableCompaniesResponse, @@ -163,3 +164,11 @@ export const useChangeCompanyInfo = ( } ); }; + +/**기업 총 개수 조회 */ +export const useCompanyCnt = () => { + return useQuery(['CompanyCnt'], async () => { + const data = await instance.get(`${router}/count`); + return data.data; + }); +}; diff --git a/src/Apis/Companies/response.ts b/src/Apis/Companies/response.ts index 7aaf437..b9bef92 100644 --- a/src/Apis/Companies/response.ts +++ b/src/Apis/Companies/response.ts @@ -57,3 +57,7 @@ export interface CompanyDetailResponse { business_area_code: number; biz_registration_url: string; } + +export interface CntType { + count: number; +} diff --git a/src/Apis/Recruitments/index.ts b/src/Apis/Recruitments/index.ts index f112018..3b3eef9 100644 --- a/src/Apis/Recruitments/index.ts +++ b/src/Apis/Recruitments/index.ts @@ -5,6 +5,7 @@ import { RecruitmentFormQueryStringType, } from './request'; import { + CntType, RecruitmentFormDetailResponse, RecruitmentFormResponse, } from './response'; @@ -166,3 +167,11 @@ export const useEditRecruitment = ( } ); }; + +/**모집의뢰서 총 개수 조회 */ +export const useRecruitmentCnt = () => { + return useQuery(['RecruitmentCnt'], async () => { + const response = await instance.get(`${router}/count`); + return response.data; + }); +}; diff --git a/src/Apis/Recruitments/response.ts b/src/Apis/Recruitments/response.ts index 4ea6a65..46116d2 100644 --- a/src/Apis/Recruitments/response.ts +++ b/src/Apis/Recruitments/response.ts @@ -50,3 +50,7 @@ export interface AreasType { major_task: string; preferential_treatment: string | null; } + +export interface CntType { + count: number; +} diff --git a/src/Components/ApplicationView/Table/index.tsx b/src/Components/ApplicationView/Table/index.tsx index 624e514..c99be0b 100644 --- a/src/Components/ApplicationView/Table/index.tsx +++ b/src/Components/ApplicationView/Table/index.tsx @@ -7,7 +7,7 @@ import { useToastStore, } from '@team-return/design-system'; import * as _ from './style'; -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import { Pagination } from '../../../Utils/Pagination'; import { ApplicationResponse } from '../../../Apis/Applications/response'; import { selectStudent } from '../../../Apis/Applications/request'; @@ -15,6 +15,7 @@ import { DownloadDataPropsType } from '../../../Apis/File/request'; import { useDownloadData } from '../../../Apis/File'; import { useModalContext } from '../../../Utils/Modal'; import { + useApplicationCnt, useChangeRequestStatus, useRejectApplication, } from '../../../Apis/Applications'; @@ -59,6 +60,7 @@ export function ApplicationViewTable({ const [clickedData, setClickedData] = useState([]); const [changeStatus, setChangeStatus] = useState(''); const [downloadBoxView, setDownloadBoxView] = useState(0); + const [applicationCnt, setApplicationCnt] = useState(0); const [rejectReason, setRejectReason] = useState(''); const { form: trainDate, handleChange: trainDateChange } = useForm({ start_date: '', @@ -114,6 +116,13 @@ export function ApplicationViewTable({ } ); const { isLoading: requestStatusIsLoading } = changeStatusAPI; + const { data: cnt } = useApplicationCnt(); + + useEffect(() => { + if (cnt) { + setApplicationCnt(cnt.count); + } + }, [cnt]); /** 지원서 상태 변경할 때 확인하는 확인 모달을 여는 함수입니다. */ const openChangeStatusModal = (statusName: string) => { @@ -483,9 +492,13 @@ export function ApplicationViewTable({ return ( <_.Container> - <_.BtnWrapper> - - + 현장실습 + + + <_.TableWrapper> ([]); const [changeStatus, setChangeStatus] = useState(''); + const [companyCnt, setCompanyCnt] = useState(0); const { companyRecruitmentQueryString, setCompanyRecruitmentQueryString } = useCompanyRecruitmentQueryString(); + const { data: cnt } = useCompanyCnt(); + + useEffect(() => { + if (cnt) { + setCompanyCnt(cnt.count); + } + }, [cnt]); + /** 전체 선택 & 전체 선택 해제하는 함수입니다. */ const checkAllBox = () => { if (searchInArray(allSelectFormId, clickedData).length === dataLength) { @@ -251,36 +261,41 @@ export function CompanyRecruitmentTable({ return ( <_.Container> - <_.BtnWrapper> - - - - + <_.BtnContentWrapper> + <_.TitleText> + 총 <_.CntContent>{companyCnt}개 + + <_.BtnWrapper> + + + + + <_.TableWrapper>
` text-align: center; word-break: keep-all; `; + +export const CntContent = styled.p` + color: #135c9d; + margin-left: 4px; +`; + +export const CntTitle = styled.p` + font-weight: 600; + font-size: 24px; + display: flex; +`; diff --git a/src/Components/RecruitmentForm/Table/index.tsx b/src/Components/RecruitmentForm/Table/index.tsx index 910adba..53dfb23 100644 --- a/src/Components/RecruitmentForm/Table/index.tsx +++ b/src/Components/RecruitmentForm/Table/index.tsx @@ -5,7 +5,7 @@ import { useToastStore, } from '@team-return/design-system'; import * as _ from './style'; -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import { RecruitmentFormResponse } from '../../../Apis/Recruitments/response'; import { Pagination } from '../../../Utils/Pagination'; import { useChangeRecruitmentsStatus } from '../../../Apis/Recruitments/index'; @@ -31,6 +31,7 @@ export function RecruitmentFormTable({ recruitmentFormIsLoading, }: PropsType) { const { append } = useToastStore(); + const { data: cnt } = useRecruitmentCnt(); const { recruitmentFormQueryString, setRecruitmentFormQueryString } = useRecruitmentFormQueryString(); @@ -39,6 +40,7 @@ export function RecruitmentFormTable({ const dataLength = recruitmentForm?.recruitments.length; const [clickedData, setClickedData] = useState([]); const [changeStatus, setChangeStatus] = useState(''); + const [recruitmentCnt, setRecruitmentCnt] = useState(0); /** 지원서 상태를 변경하는 api를 호출합니다. */ const { mutate: changeStatusAPI, isLoading } = useChangeRecruitmentsStatus( @@ -64,6 +66,12 @@ export function RecruitmentFormTable({ } ); + useEffect(() => { + if (cnt) { + setRecruitmentCnt(cnt.count); + } + }, [cnt]); + /** 전체 선택 & 전체 선택 해제를 하는 함수입니다. */ const checkAllBox = () => { if (searchInArray(allSelectFormId, clickedData).length === dataLength) { @@ -232,9 +240,13 @@ export function RecruitmentFormTable({ return ( <_.Container> - <_.BtnWrapper> - - + 모집종료 + + + <_.TableWrapper>
` text-align: center; word-break: keep-all; `; + +export const CntContent = styled.p` + color: #135c9d; + margin-left: 4px; +`; + +export const CntTitle = styled.p` + font-weight: 600; + font-size: 24px; + display: flex; +`; From b114b1a9189b5c7c96ec761b963fa029dd1ee90b Mon Sep 17 00:00:00 2001 From: hyuna Date: Tue, 28 May 2024 16:47:22 +0900 Subject: [PATCH 12/21] =?UTF-8?q?fix=20::=20=EB=B9=8C=EB=93=9C=20=EC=98=A4?= =?UTF-8?q?=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Apis/Applications/index.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Apis/Applications/index.ts b/src/Apis/Applications/index.ts index cee893c..57abbec 100644 --- a/src/Apis/Applications/index.ts +++ b/src/Apis/Applications/index.ts @@ -1,11 +1,11 @@ -import { - useMutation, - MutationOptions, - useQuery, -} from 'react-query'; +import { useMutation, MutationOptions, useQuery } from 'react-query'; import { instance } from '../axios'; import { ApplicantInfoQueryStringType } from './request'; -import { ApplicationResponse, InternshipStudentResponse } from './response'; +import { + ApplicationResponse, + CntType, + InternshipStudentResponse, +} from './response'; const router = '/applications'; From c00780f8a58479ca267a0dc1546481b971cb3d71 Mon Sep 17 00:00:00 2001 From: hyuna Date: Tue, 28 May 2024 16:51:28 +0900 Subject: [PATCH 13/21] =?UTF-8?q?fix=20::=20=EB=B9=8C=EB=93=9C=20=EC=98=A4?= =?UTF-8?q?=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RecruitmentForm/Table/index.tsx | 57 ++++++++++--------- 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/src/Components/RecruitmentForm/Table/index.tsx b/src/Components/RecruitmentForm/Table/index.tsx index 53dfb23..46ded5b 100644 --- a/src/Components/RecruitmentForm/Table/index.tsx +++ b/src/Components/RecruitmentForm/Table/index.tsx @@ -8,7 +8,10 @@ import * as _ from './style'; import { useEffect, useState } from 'react'; import { RecruitmentFormResponse } from '../../../Apis/Recruitments/response'; import { Pagination } from '../../../Utils/Pagination'; -import { useChangeRecruitmentsStatus } from '../../../Apis/Recruitments/index'; +import { + useChangeRecruitmentsStatus, + useRecruitmentCnt, +} from '../../../Apis/Recruitments/index'; import { companyStatus, companyType } from '../../../Utils/Translation'; import { getValueByKey } from '../../../Utils/useGetPropertyKey'; import { searchInArray } from '../../../Utils/useSearchForArray'; @@ -247,32 +250,32 @@ export function RecruitmentFormTable({ <_.BtnWrapper> - - + + From be1fc6326b361c32aaf3f745182718dc1edb842a Mon Sep 17 00:00:00 2001 From: hyuna Date: Wed, 29 May 2024 16:51:58 +0900 Subject: [PATCH 14/21] =?UTF-8?q?feat=20::=20=EB=A6=AC=EB=B7=B0=20?= =?UTF-8?q?=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Apis/Applications/index.ts | 10 +- src/Apis/Applications/response.ts | 2 +- src/Apis/Companies/index.ts | 8 +- src/Apis/Companies/response.ts | 2 +- src/Apis/Recruitments/index.ts | 10 +- src/Apis/Recruitments/response.ts | 2 +- .../ApplicationView/Table/index.tsx | 110 +++++++++--------- src/Components/ApplicationView/Table/style.ts | 4 +- .../CompanyRecruitment/Table/index.tsx | 21 ++-- .../CompanyRecruitment/Table/style.ts | 4 +- .../RecruitmentForm/Table/index.tsx | 18 +-- src/Components/RecruitmentForm/Table/style.ts | 4 +- 12 files changed, 103 insertions(+), 92 deletions(-) diff --git a/src/Apis/Applications/index.ts b/src/Apis/Applications/index.ts index 57abbec..a210279 100644 --- a/src/Apis/Applications/index.ts +++ b/src/Apis/Applications/index.ts @@ -3,7 +3,7 @@ import { instance } from '../axios'; import { ApplicantInfoQueryStringType } from './request'; import { ApplicationResponse, - CntType, + applicationCountType, InternshipStudentResponse, } from './response'; @@ -103,9 +103,11 @@ export const useRejectApplication = ( }; /**지원서 총 개수 조회 */ -export const useApplicationCnt = () => { - return useQuery(['applicationCnt'], async () => { - const data = await instance.get(`${router}/teacher/count`); +export const useApplicationCount = () => { + return useQuery(['applicationCount'], async () => { + const data = await instance.get( + `${router}/teacher/count` + ); return data.data; }); }; diff --git a/src/Apis/Applications/response.ts b/src/Apis/Applications/response.ts index 3d65035..d2e6887 100644 --- a/src/Apis/Applications/response.ts +++ b/src/Apis/Applications/response.ts @@ -27,6 +27,6 @@ export interface InternshipStudentType { student_gcn: string; } -export interface CntType { +export interface applicationCountType { count: number; } diff --git a/src/Apis/Companies/index.ts b/src/Apis/Companies/index.ts index 82daf5f..99578e8 100644 --- a/src/Apis/Companies/index.ts +++ b/src/Apis/Companies/index.ts @@ -12,7 +12,7 @@ import { CompanyInfoEditType, } from './request'; import { - CntType, + CompanyCountType, CompanyDetailResponse, CompanyRecruitmentResponse, EmployableCompaniesResponse, @@ -166,9 +166,9 @@ export const useChangeCompanyInfo = ( }; /**기업 총 개수 조회 */ -export const useCompanyCnt = () => { - return useQuery(['CompanyCnt'], async () => { - const data = await instance.get(`${router}/count`); +export const useCompanyCount = () => { + return useQuery(['CompanyCount'], async () => { + const data = await instance.get(`${router}/count`); return data.data; }); }; diff --git a/src/Apis/Companies/response.ts b/src/Apis/Companies/response.ts index b9bef92..d668798 100644 --- a/src/Apis/Companies/response.ts +++ b/src/Apis/Companies/response.ts @@ -58,6 +58,6 @@ export interface CompanyDetailResponse { biz_registration_url: string; } -export interface CntType { +export interface CompanyCountType { count: number; } diff --git a/src/Apis/Recruitments/index.ts b/src/Apis/Recruitments/index.ts index 3b3eef9..ef3284c 100644 --- a/src/Apis/Recruitments/index.ts +++ b/src/Apis/Recruitments/index.ts @@ -5,7 +5,7 @@ import { RecruitmentFormQueryStringType, } from './request'; import { - CntType, + RecruitmentCountType, RecruitmentFormDetailResponse, RecruitmentFormResponse, } from './response'; @@ -169,9 +169,11 @@ export const useEditRecruitment = ( }; /**모집의뢰서 총 개수 조회 */ -export const useRecruitmentCnt = () => { - return useQuery(['RecruitmentCnt'], async () => { - const response = await instance.get(`${router}/count`); +export const useRecruitmentCount = () => { + return useQuery(['RecruitmentCount'], async () => { + const response = await instance.get( + `${router}/count` + ); return response.data; }); }; diff --git a/src/Apis/Recruitments/response.ts b/src/Apis/Recruitments/response.ts index 46116d2..b38a850 100644 --- a/src/Apis/Recruitments/response.ts +++ b/src/Apis/Recruitments/response.ts @@ -51,6 +51,6 @@ export interface AreasType { preferential_treatment: string | null; } -export interface CntType { +export interface RecruitmentCountType { count: number; } diff --git a/src/Components/ApplicationView/Table/index.tsx b/src/Components/ApplicationView/Table/index.tsx index c99be0b..26cd011 100644 --- a/src/Components/ApplicationView/Table/index.tsx +++ b/src/Components/ApplicationView/Table/index.tsx @@ -15,7 +15,7 @@ import { DownloadDataPropsType } from '../../../Apis/File/request'; import { useDownloadData } from '../../../Apis/File'; import { useModalContext } from '../../../Utils/Modal'; import { - useApplicationCnt, + useApplicationCount, useChangeRequestStatus, useRejectApplication, } from '../../../Apis/Applications'; @@ -60,7 +60,7 @@ export function ApplicationViewTable({ const [clickedData, setClickedData] = useState([]); const [changeStatus, setChangeStatus] = useState(''); const [downloadBoxView, setDownloadBoxView] = useState(0); - const [applicationCnt, setApplicationCnt] = useState(0); + const [applicationCount, setApplicationCount] = useState(0); const [rejectReason, setRejectReason] = useState(''); const { form: trainDate, handleChange: trainDateChange } = useForm({ start_date: '', @@ -116,13 +116,13 @@ export function ApplicationViewTable({ } ); const { isLoading: requestStatusIsLoading } = changeStatusAPI; - const { data: cnt } = useApplicationCnt(); + const { data: applicationCountData } = useApplicationCount(); useEffect(() => { - if (cnt) { - setApplicationCnt(cnt.count); + if (applicationCountData) { + setApplicationCount(applicationCountData.count); } - }, [cnt]); + }, [applicationCountData]); /** 지원서 상태 변경할 때 확인하는 확인 모달을 여는 함수입니다. */ const openChangeStatusModal = (statusName: string) => { @@ -493,56 +493,60 @@ export function ApplicationViewTable({ return ( <_.Container> <_.BtnContentWrapper> - <_.TitleText> - 총 <_.CntContent>{applicationCnt}개 - + <_.CountTitle> + 총 <_.CountContent>{applicationCount}개 + <_.BtnWrapper> - - - - - + + + + + diff --git a/src/Components/ApplicationView/Table/style.ts b/src/Components/ApplicationView/Table/style.ts index 2f05ae6..c058d31 100644 --- a/src/Components/ApplicationView/Table/style.ts +++ b/src/Components/ApplicationView/Table/style.ts @@ -101,12 +101,12 @@ export const MiddleText = styled.div` color: black; `; -export const CntContent = styled.p` +export const CountContent = styled.p` color: #135c9d; margin-left: 4px; `; -export const CntTitle = styled.p` +export const CountTitle = styled.p` font-weight: 600; font-size: 24px; display: flex; diff --git a/src/Components/CompanyRecruitment/Table/index.tsx b/src/Components/CompanyRecruitment/Table/index.tsx index 9d0ae49..9f470a2 100644 --- a/src/Components/CompanyRecruitment/Table/index.tsx +++ b/src/Components/CompanyRecruitment/Table/index.tsx @@ -10,7 +10,7 @@ import { Pagination } from '../../../Utils/Pagination'; import { useChangeCompanyStatus, useChangeContractCompany, - useCompanyCnt, + useCompanyCount, } from '../../../Apis/Companies'; import { CompanyRecruitmentResponse } from '../../../Apis/Companies/response'; import { companyType } from '../../../Utils/Translation'; @@ -37,18 +37,18 @@ export function CompanyRecruitmentTable({ const dataLength = companyRecruitment?.companies.length; const [clickedData, setClickedData] = useState([]); const [changeStatus, setChangeStatus] = useState(''); - const [companyCnt, setCompanyCnt] = useState(0); + const [companyCount, setCompanyCount] = useState(0); const { companyRecruitmentQueryString, setCompanyRecruitmentQueryString } = useCompanyRecruitmentQueryString(); - const { data: cnt } = useCompanyCnt(); + const { data: CompanyCountData } = useCompanyCount(); useEffect(() => { - if (cnt) { - setCompanyCnt(cnt.count); + if (CompanyCountData) { + setCompanyCount(CompanyCountData.count); } - }, [cnt]); + }, [CompanyCountData]); /** 전체 선택 & 전체 선택 해제하는 함수입니다. */ const checkAllBox = () => { @@ -262,10 +262,13 @@ export function CompanyRecruitmentTable({ return ( <_.Container> <_.BtnContentWrapper> - <_.TitleText> - 총 <_.CntContent>{companyCnt}개 - + <_.CountTitle> + 총 <_.CountContent>{companyCount}개 + <_.BtnWrapper> + - - - - + <_.BtnWrapper> + + + + + + <_.TableWrapper>
총 <_.CountContent>{recruitmentCount}개 - <_.BtnWrapper> - - - - - + <_.BtnWrapper> + + + + + + <_.TableWrapper>
Date: Sat, 8 Jun 2024 23:34:11 +0900 Subject: [PATCH 21/21] =?UTF-8?q?feat=20::=20=EB=B0=B0=EB=84=88=20?= =?UTF-8?q?=EB=A6=AC=EB=B7=B0=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 - src/Apis/Files/index.ts | 4 +++- src/Apis/Files/response.ts | 12 +++++------- src/Components/Banner/index.tsx | 5 +++-- src/Components/CreateBanner/styled.ts | 1 - 5 files changed, 11 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index 75cbde0..68d6d5f 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,6 @@ "version": "0.1.0", "private": true, "dependencies": { - "@tanstack/react-query": "^4.36.1", "@team-return/design-system": "^1.1.13", "axios": "^1.3.4", "file-saver": "^2.0.5", diff --git a/src/Apis/Files/index.ts b/src/Apis/Files/index.ts index fbe1b29..8bc216e 100644 --- a/src/Apis/Files/index.ts +++ b/src/Apis/Files/index.ts @@ -3,11 +3,13 @@ import { PresignedUrlResponse } from './response'; import { instance } from '../axios'; import axios from 'axios'; +const EXTENSION_FILE = 'EXTENSION_FILE'; + export const usePresignedUrl = () => { return useMutation( async (attachments: File[]) => { const files = attachments.map((item) => ({ - type: 'EXTENSION_FILE', + type: EXTENSION_FILE, file_name: item.name, })); diff --git a/src/Apis/Files/response.ts b/src/Apis/Files/response.ts index d090809..25f0631 100644 --- a/src/Apis/Files/response.ts +++ b/src/Apis/Files/response.ts @@ -1,8 +1,6 @@ export interface PresignedUrlResponse { - urls: [ - { - file_path: string, - pre_signed_url: string - } - ] -} \ No newline at end of file + urls: { + file_path: string; + pre_signed_url: string; + }[]; +} diff --git a/src/Components/Banner/index.tsx b/src/Components/Banner/index.tsx index b49472d..6b1102c 100644 --- a/src/Components/Banner/index.tsx +++ b/src/Components/Banner/index.tsx @@ -22,13 +22,14 @@ export function Banner() { if (id) { await DeleteBannersAPI.mutateAsync(); queryClient.fetchQuery(['getBannerList']); - window.location.reload(); } setShowDeleteAlarm(false); }; const DeleteBannersAPI = useDeleteBanner(id!, { - onSuccess: () => {}, + onSuccess: () => { + window.location.reload(); + }, onError: () => {}, }); diff --git a/src/Components/CreateBanner/styled.ts b/src/Components/CreateBanner/styled.ts index f0aa9c0..7a6f085 100644 --- a/src/Components/CreateBanner/styled.ts +++ b/src/Components/CreateBanner/styled.ts @@ -165,7 +165,6 @@ export const CompanyNameSearch = styled.tr` export const RecruitmentSearch = styled.tr` position: relative; - z-index: 1; `; export const Td = styled.td`