diff --git a/src/components/JD/ContentInput.tsx b/src/components/JD/ContentInput.tsx index 589d05c..b3168dc 100644 --- a/src/components/JD/ContentInput.tsx +++ b/src/components/JD/ContentInput.tsx @@ -49,7 +49,7 @@ const Content = styled.textarea` outline: none; resize: none; padding: 0.5rem; - font-size: 1rem; + font-size: 14px; font-size: 0.8125rem; font-style: normal; font-weight: 400; diff --git a/src/components/JD/Experience.tsx b/src/components/JD/Experience.tsx index bcd92b4..005169c 100644 --- a/src/components/JD/Experience.tsx +++ b/src/components/JD/Experience.tsx @@ -7,6 +7,9 @@ import bookmarkFillIcon from "../../assets/icons/icon_bookmark_fill.svg"; import bookmarkBlankIcon from "../../assets/icons/icon_bookmark_blank.svg"; import dayjs from "dayjs"; import { KeywordType, QuestionType } from "../../types/experience"; +import { bookmarkpatch } from "../../services/JD/bookmarkApi"; +import { getCookie } from "../../services/cookie"; +import { useParams } from "react-router-dom"; interface ExpProps { type?: "card" | "section"; @@ -42,7 +45,8 @@ const Experience: React.FC = ({ }) => { const [detailId, setDetailId] = useRecoilState(detailStore); const [localbookmark, setLocalbookmark] = useState(bookmark); - + const user = getCookie("user"); + const jdId = useParams().jdId; // 카드 타입, 섹션 타입 구분 const isSection = type === "section"; @@ -56,10 +60,26 @@ const Experience: React.FC = ({ } }; + const handleBookmarkPost = async ( + token: string, + jobId: string, + expId: string + ) => { + try { + const response = await bookmarkpatch(token, jobId, expId); + console.log(response); + } catch (error) { + console.error(error); + alert(JSON.stringify(error)); + } + }; + const handleBookmarkClick = (event: React.MouseEvent) => { event.stopPropagation(); + if (id && jdId) { + handleBookmarkPost(user.token, jdId, id.toString()); + } setLocalbookmark(!localbookmark); - //북마크 요청 api }; return ( = ({ const [showDetail, setshowDetail] = useState(false); //경험 상세 보여주기 const [showTagPopup, setShowTagPopup] = useState(false); // 태그 필터링 const [searchText, setSearchText] = useState(""); //검색 입력 - const [mainTag, setMainTag] = useState(""); // 선택된 상위태그 - const [subTag, setSubTag] = useState(""); //선택된 하위태그 + const [mainTag, setMainTag] = useState({ id: "", name: "" }); // 선택된 상위태그 + const [subTag, setSubTag] = useState({ id: "", name: "" }); //선택된 하위태그 const [filterCount, setfilterCount] = useState(-1); //검색된 경험의 숫자, 검색 안된 상태에서는 -1 const [keywordTabOption, setKeywordTabOption] = React.useState("basic"); const user = getCookie("user"); - const [experienceData, setExperienceData] = useState([ - { - id: "fa0a5813-c879-432d-b276-24364847534c", - title: "경험 제목1 ", - parentTag: { - id: "c191d753-0c59-42eb-8245-79ee5c9c5797", - name: "상위 태그 이름", - }, - childTag: { - id: "860c446b-a021-43d5-9da6-5034a5bdaee7", - name: "하위 태그 이름", - }, - strongPoints: [ - { - id: "fdbf03bf-c1a3-4442-997e-467605868052", - name: "역량 키워드 이름 1", - }, - { - id: "096c3d2e-4073-4724-9a15-c1d6617c63a1", - name: "역량 키워드 이름 2", - }, - ], - contents: [ - { - question: "질문1", - answer: "답변1", - }, - { - question: "질문2", - answer: "답변2", - }, - ], - startedAt: "2024-05-22T07:45:23.720822702", - endedAt: "2024-05-23T07:45:23.720832019", - bookmarked: "ON", - }, - { - id: "7694c6e7-b7a8-4ee8-a698-67c345932663", - title: "경험 제목 2", - parentTag: { - id: "c191d753-0c59-42eb-8245-79ee5c9c5797", - name: "상위 태그 이름", - }, - childTag: { - id: "860c446b-a021-43d5-9da6-5034a5bdaee7", - name: "하위 태그 이름", - }, - strongPoints: [ - { - id: "fdbf03bf-c1a3-4442-997e-467605868052", - name: "역량 키워드 이름 1", - }, - { - id: "096c3d2e-4073-4724-9a15-c1d6617c63a1", - name: "역량 키워드 이름 2", - }, - ], - contents: [ - { - question: "질문1", - answer: "답변1", - }, - { - question: "질문2", - answer: "답변2", - }, - ], - startedAt: "2023-05-22T07:45:23.720822702", - endedAt: "2024-05-23T07:45:23.720832019", - bookmarked: "OFF", - }, - { - id: "7694c6e7-b7a8-4ee8-a698-67c345932663", - title: "경험 제목 3", - parentTag: { - id: "c191d753-0c59-42eb-8245-79ee5c9c5797", - name: "상위 태그 이름", - }, - childTag: { - id: "860c446b-a021-43d5-9da6-5034a5bdaee7", - name: "하위 태그 이름", - }, - strongPoints: [ - { - id: "fdbf03bf-c1a3-4442-997e-467605868052", - name: "역량 키워드 이름 1", - }, - { - id: "096c3d2e-4073-4724-9a15-c1d6617c63a1", - name: "역량 키워드 이름 2", - }, - ], - contents: [ - { - question: "질문1", - answer: "답변1", - }, - { - question: "질문2", - answer: "답변2", - }, - ], - startedAt: "2023-05-22T07:45:23.720822702", - endedAt: "2024-05-23T07:45:23.720832019", - bookmarked: "ON", - }, - ]); + const [experienceData, setExperienceData] = useState([]); const jdId = useParams().jdId; - - const [anchorEl, setAnchorEl] = React.useState(null); - const open = Boolean(anchorEl); - const id = open ? "tag-popper" : undefined; - - // 기본 역량 키워드 페이지네이션 - const keywordsPerPage = 9; - const [currentBasicKeywordPage, setCurrentBasicKeywordPage] = - React.useState(1); - const firstBasicKeywordIndex = - (currentBasicKeywordPage - 1) * keywordsPerPage; - const lastBasicKeywordIndex = firstBasicKeywordIndex + keywordsPerPage; - const currentBasicKeywords = basicKeywords.slice( - firstBasicKeywordIndex, - lastBasicKeywordIndex - ); - // My 역량 키워드 페이지네이션 - const [currentMyKeywordPage, setCurrentMyKeywordPage] = React.useState(1); - const firstMyKeywordIndex = (currentMyKeywordPage - 1) * keywordsPerPage; - const lastMyKeywordIndex = firstMyKeywordIndex + keywordsPerPage; - const currentMyKeywords = myKeywords.slice( - firstMyKeywordIndex, - lastMyKeywordIndex - ); + const [searching, setSearching] = useState(false); useEffect(() => { if (jdId) { - // getExperienceList(jdId, user.token); + getExperienceList(jdId, user.token); } }, []); @@ -218,6 +94,7 @@ const ExperienceList: React.FC = ({ try { const response = await getAllExperienceList(jdId, token); console.log(response); + setExperienceData(response.data.experiences); } catch (error) { console.error(error); alert(JSON.stringify(error)); @@ -229,15 +106,15 @@ const ExperienceList: React.FC = ({ searchText: string, token: string ) => { - // try { - // const response = await searchTextExperienceList(jdId, searchText, token); - // console.log(response); - // setExperienceData(response.data.experiences); - // console.log(experienceData); - // } catch (error) { - // console.error(error); - // alert(JSON.stringify(error)); - // } + try { + setSearching(true); + const response = await searchTextExperienceList(jdId, searchText, token); + console.log(response); + setExperienceData(response.data.experiences); + } catch (error) { + console.error(error); + alert(JSON.stringify(error)); + } }; const getFilteredExperienceList = async ( @@ -254,90 +131,154 @@ const ExperienceList: React.FC = ({ token ); console.log(response); + setExperienceData(response.data.experiences); } catch (error) { console.error(error); alert(JSON.stringify(error)); } }; - // 체크된 역량 키워드 리스트 - const [checkedKeywords, setCheckedKeywords] = React.useState([]); - - // 키워드 체크박스 관리 함수 - const handleCheckedKeywords = (e: React.ChangeEvent) => { - if (e.target) { - e.target.checked - ? setCheckedKeywords([...checkedKeywords, e.target.value]) - : setCheckedKeywords( - checkedKeywords.filter((choice) => choice !== e.target.value) - ); - } - }; - - // 역량 키워드 클릭 함수 - const handleTagPopper = (event: React.MouseEvent) => { - setAnchorEl(anchorEl ? null : event.currentTarget); - }; - - // 역량 키워드 필터된 경험 데이터, 북마크 - const filteredExpData = experienceData.filter((experience) => { - const strongPointNames = experience.strongPoints.map((point) => point.name); - const matchedKeywords = strongPointNames.filter((name) => - checkedKeywords.includes(name) - ); - return matchedKeywords.length > 0; - }); - const bookedData = experienceData.filter((experience) => { - // 북마크가 ON인 요소만 필터링 - return experience.bookmarked === "ON"; - }); - const filteredBookedData = experienceData.filter((experience) => { - const strongPointNames = experience.strongPoints.map((point) => point.name); - const matchedKeywords = strongPointNames.filter((name) => - checkedKeywords.includes(name) - ); - return experience.bookmarked === "ON" && matchedKeywords.length > 0; - }); - //상위태그 하위태그 필터링 const handleTagSelection = ( - selectedmainTag: string, - selectedsubTag?: string + selectedmainTag: MyTagAPI, + selectedsubTag?: MyTagAPI ): void => { setSearchText(""); - setMainTag(selectedmainTag); + setMainTag({ id: selectedmainTag.id, name: selectedmainTag.name }); if (selectedsubTag) { - setSubTag(selectedsubTag); + setSubTag({ id: selectedsubTag.id, name: selectedsubTag.name }); setShowTagPopup(false); } else { - setSubTag(""); + setSubTag({ id: "", name: "" }); } }; useEffect(() => { - if (mainTag) { - console.log("maintag: " + mainTag); + if (mainTag.id !== "") { if (jdId) { - getFilteredExperienceList(jdId, mainTag, null, user.token); + getFilteredExperienceList(jdId, mainTag.id, null, user.token); } } }, [mainTag]); useEffect(() => { - if (subTag) { - console.log("subtag: " + subTag); + if (subTag.id !== "") { if (jdId) { - getFilteredExperienceList(jdId, mainTag, subTag, user.token); + getFilteredExperienceList(jdId, mainTag.id, subTag.id, user.token); } } }, [subTag]); + //역량키워드 관련 팝업 + const [myKeywordList, setMyKeywordList] = React.useState([]); + const [anchorEl, setAnchorEl] = React.useState(null); + const open = Boolean(anchorEl); + const id = open ? "tag-popper" : undefined; + + // 기본 역량 키워드 페이지네이션 + const keywordsPerPage = 9; + const [currentBasicKeywordPage, setCurrentBasicKeywordPage] = + React.useState(1); + const firstBasicKeywordIndex = + (currentBasicKeywordPage - 1) * keywordsPerPage; + const lastBasicKeywordIndex = firstBasicKeywordIndex + keywordsPerPage; + const currentBasicKeywords = basicKeywords.slice( + firstBasicKeywordIndex, + lastBasicKeywordIndex + ); + // My 역량 키워드 페이지네이션 + const [currentMyKeywordPage, setCurrentMyKeywordPage] = React.useState(1); + const firstMyKeywordIndex = (currentMyKeywordPage - 1) * keywordsPerPage; + const lastMyKeywordIndex = firstMyKeywordIndex + keywordsPerPage; + const currentMyKeywords = myKeywordList.slice( + firstMyKeywordIndex, + lastMyKeywordIndex + ); + + // 체크된 역량 키워드 리스트 + const [checkedKeywords, setCheckedKeywords] = useState([]); + + // 키워드 체크박스 체크 여부 + const isKeywordChecked = (item: KeywordType) => { + return checkedKeywords.some( + (keyword) => keyword.id === item.id && keyword.name === item.name + ); + }; + + // 키워드 체크박스 핸들러 + const handleKeywordChange = ( + e: React.ChangeEvent, + type: TabType + ) => { + if (e.target) { + if (e.target.checked) { + const keywordId = e.target.value; + const selectedKeyword = ( + type === "basic" ? basicKeywords : myKeywordList + ).find((item) => item.id === keywordId); + setCheckedKeywords([ + ...checkedKeywords, + { id: keywordId, name: selectedKeyword?.name || "" }, + ]); + } else { + setCheckedKeywords( + checkedKeywords.filter((item) => item.id !== e.target.value) + ); + } + } + }; + + useEffect(() => { + if (selectedTab === "경험검색" && (searching || mainTag.id !== "")) { + if (checkedKeywords.length !== 0) { + setfilterCount(filteredExpData.length); + } else { + setfilterCount(experienceData.length); + } + } else if (selectedTab === "북마크" && (searching || mainTag.id !== "")) { + if (checkedKeywords.length !== 0) { + setfilterCount(filteredBookedData.length); + } else { + setfilterCount(bookedData.length); + } + } else if (searchText === "" && mainTag.id === "") { + setfilterCount(-1); + } + }, [searching, mainTag, subTag, selectedTab, experienceData]); + useEffect(() => { - if (searchText) { - console.log("search: " + searchText); + if (searchText === "") { + setSearching(false); } }, [searchText]); + // My 역량 키워드 조회 + useEffect(() => { + if (user?.token) { + getKeywords(user?.token) + .then((res) => setMyKeywordList(res.data.strongPoints)) + .catch((err) => console.log(err)); + } + }, [user?.token]); + + // 역량 키워드 클릭 함수 + const handleTagPopper = (event: React.MouseEvent) => { + setAnchorEl(anchorEl ? null : event.currentTarget); + }; + + // 역량 키워드 필터된 경험 데이터, 북마크 + const filteredExpData = experienceData.filter((experience) => + experience.strongPoints.some((item: KeywordType) => isKeywordChecked(item)) + ); + + const bookedData = experienceData.filter((experience) => { + return experience.bookmarked === "ON"; + }); + + const filteredBookedData = bookedData.filter((experience) => + experience.strongPoints.some((item: KeywordType) => isKeywordChecked(item)) + ); + return ( {!showDetail ? ( @@ -359,7 +300,7 @@ const ExperienceList: React.FC = ({ - {mainTag ? ( + {mainTag.id !== "" ? ( ) : ( = ({ /> )} - {mainTag} - {subTag && Arrow} - {subTag && subTag} - {mainTag && ( + {mainTag.name} + {subTag.name && Arrow} + {subTag.name && subTag.name} + {mainTag.id && ( remove { - setMainTag(""); - setSubTag(""); + setMainTag({ id: "", name: "" }); + setSubTag({ id: "", name: "" }); }} /> )} filter { setShowTagPopup(!showTagPopup); - setMainTag(""); - setSubTag(""); + setMainTag({ id: "", name: "" }); + setSubTag({ id: "", name: "" }); }} /> = ({ /> ) : ( = ({ handleKeywordChange(e, "basic")} /> )) : currentMyKeywords.map((item) => ( handleKeywordChange(e, "my")} /> ))} @@ -489,7 +430,7 @@ const ExperienceList: React.FC = ({ 총 
{checkedKeywords.length === 0 - ? ExpData.length + ? experienceData?.length : filteredExpData.length} 개
@@ -516,7 +457,9 @@ const ExperienceList: React.FC = ({ tags={post.strongPoints.map((point) => point.name)} period={formatDateRange(post.startedAt, post.endedAt)} bookmark={post.bookmarked === "ON" ? true : false} - checkedKeywords={checkedKeywords} + checkedKeywords={checkedKeywords.map( + (item: KeywordType) => item.name + )} onClick={() => setshowDetail(true)} /> ))} @@ -537,7 +480,9 @@ const ExperienceList: React.FC = ({ tags={post.strongPoints.map((point) => point.name)} period={formatDateRange(post.startedAt, post.endedAt)} bookmark={post.bookmarked === "ON" ? true : false} - checkedKeywords={checkedKeywords} + checkedKeywords={checkedKeywords.map( + (item: KeywordType) => item.name + )} onClick={() => setshowDetail(true)} /> ))} @@ -554,7 +499,7 @@ const ExperienceList: React.FC = ({ export default ExperienceList; interface TagPopupProps { - onSelect: (mainTag: string, subTag?: string) => void; + onSelect: (mainTag: MyTagAPI, subTag?: MyTagAPI) => void; } interface ChildTag { @@ -605,20 +550,25 @@ const TagPopup: React.FC = ({ onSelect }) => { {tagList.map((tag) => (
- { toggleSubTags(tag.id); - onSelect(tag.name); + onSelect({ id: tag.id, name: tag.name }); }} > arrow {tag.name} - + {visibleSubTag === tag.id && tag.childTags.map((child) => ( onSelect(tag.name, child.name)} + onClick={() => + onSelect( + { id: tag.id, name: tag.name }, + { id: child.id, name: child.name } + ) + } > arrow {child.name} @@ -645,7 +595,7 @@ const PopupContainer = styled.div` border: 1px solid ${(props) => props.theme.colors.neutral200}; `; -const Tag = styled.div` +const TagWrapper = styled.div` cursor: pointer; display: flex; padding: 0.75rem 0.5rem; @@ -656,7 +606,7 @@ const Tag = styled.div` } `; -const SubTag = styled(Tag)` +const SubTag = styled(TagWrapper)` padding-left: 20px; `; diff --git a/src/components/JD/JDDeleteModal.tsx b/src/components/JD/JDDeleteModal.tsx index 09fa8ec..f8add14 100644 --- a/src/components/JD/JDDeleteModal.tsx +++ b/src/components/JD/JDDeleteModal.tsx @@ -1,14 +1,14 @@ import React, { FC } from "react"; import styled from "styled-components"; import warning from "../../assets/icons/icon_delete_warning.svg"; -import { useNavigate } from "react-router-dom"; interface ModalProps { isOpen: boolean; onClose: () => void; + onCancel: () => void; } -const JDDeleteModal: FC = ({ isOpen, onClose }) => { +const JDDeleteModal: FC = ({ isOpen, onClose, onCancel }) => { if (!isOpen) { return null; } @@ -25,7 +25,7 @@ const JDDeleteModal: FC = ({ isOpen, onClose }) => {
- +
취소
void; }; const TimePicker: React.FC = ({ time, setTime }) => { - const [startDate, setStartDate] = useState(new Date()); + const [startDate, setStartDate] = useState(null); const handleDateChange = (date: Date | null) => { if (date) { @@ -39,7 +39,7 @@ const TimePicker: React.FC = ({ time, setTime }) => { onChange={handleDateChange} showTimeSelect showTimeSelectOnly - timeIntervals={15} + timeIntervals={30} timeCaption="Time" dateFormat="h:mm aa" /> diff --git a/src/pages/JDDetailPage.tsx b/src/pages/JDDetailPage.tsx index e794fe6..9b18bc8 100644 --- a/src/pages/JDDetailPage.tsx +++ b/src/pages/JDDetailPage.tsx @@ -54,6 +54,10 @@ const JDDetailPage: React.FC = () => { document.body.style.overflow = "auto"; }; + const cancelModal = () => { + setIsModalOpen(false); + }; + const ExptoggleContainer = () => { if (!active) { setActive(!active); @@ -129,6 +133,7 @@ const JDDetailPage: React.FC = () => { } {!isLoading ? ( diff --git a/src/pages/JDEditPage.tsx b/src/pages/JDEditPage.tsx index daae7e6..b997791 100644 --- a/src/pages/JDEditPage.tsx +++ b/src/pages/JDEditPage.tsx @@ -139,6 +139,7 @@ const JDEditPage: React.FC = () => { endedAt: response.data.endedAt, }; setJdData(jdApiData); + setSelectedTime(response.data.endedAt); console.log(jdData); } catch (error) { console.error(error); @@ -311,6 +312,9 @@ const CancelButton = styled.button` padding: 0.625rem 4rem; justify-content: center; align-items: center; + font-size: 16px; + font-style: normal; + font-weight: 600; border-radius: 0.5rem; border: none; color:var(--white); @@ -323,6 +327,9 @@ const SaveButton = styled.button` justify-content: center; align-items: center; border-radius: 0.5rem; + font-size: 16px; + font-style: normal; + font-weight: 600; border: none; margin-left: 1rem; color:var(--white); diff --git a/src/pages/JDPlusPage.tsx b/src/pages/JDPlusPage.tsx index 15efbff..55b2d7f 100644 --- a/src/pages/JDPlusPage.tsx +++ b/src/pages/JDPlusPage.tsx @@ -12,7 +12,7 @@ import { JobAPI } from "../types/type"; import { getCookie } from "../services/cookie"; const JDPlusPage: React.FC = () => { - const [selectedTime, setSelectedTime] = useState("10:00"); + const [selectedTime, setSelectedTime] = useState(null); const [isModalOpen, setIsModalOpen] = useState(false); const nav = useNavigate(); const user = getCookie("user"); @@ -48,14 +48,15 @@ const JDPlusPage: React.FC = () => { // endTime 계산 const getEndTime = () => { if (!jobData.endedAt) return null; // endDate가 null이면 null 반환 - - const hours = parseInt(selectedTime.split(":")[0]); - const minutes = parseInt(selectedTime.split(":")[1]); - - const endTime = new Date(jobData.endedAt); // endDate를 기반으로 새 Date 객체 생성 - endTime.setHours(hours, minutes, 0); // 시간과 분 설정 - console.log("최종시간은", endTime); - return endTime; + if (selectedTime) { + const hours = parseInt(selectedTime.split(":")[0]); + const minutes = parseInt(selectedTime.split(":")[1]); + + const endTime = new Date(jobData.endedAt); // endDate를 기반으로 새 Date 객체 생성 + endTime.setHours(hours, minutes, 0); // 시간과 분 설정 + console.log("최종시간은", endTime); + return endTime; + } }; const endTime = getEndTime(); @@ -79,7 +80,6 @@ const JDPlusPage: React.FC = () => { } else if (jobData.endedAt && date < jobData.endedAt) { setJobData({ ...jobData, startAt: date }); } else { - alert("시작 날짜는 끝나는 날짜보다 앞이여야합니다."); setJobData({ ...jobData, startAt: jobData.endedAt }); } }; @@ -90,8 +90,6 @@ const JDPlusPage: React.FC = () => { } else if (jobData.startAt && jobData.startAt) { setJobData({ ...jobData, endedAt: date }); } else { - alert("끝나는 날짜는 시작 날짜보다 뒤여야합니다."); - setJobData({ ...jobData, endedAt: jobData.startAt }); } }; @@ -192,10 +190,7 @@ const JDPlusPage: React.FC = () => { ) : (
- +
)}
~
@@ -208,13 +203,11 @@ const JDPlusPage: React.FC = () => { ) : (
- +
)} clock +