diff --git a/FE/package-lock.json b/FE/package-lock.json
index a19a33c..d4a950d 100644
--- a/FE/package-lock.json
+++ b/FE/package-lock.json
@@ -13,7 +13,9 @@
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
+ "date-fns": "^2.30.0",
"react": "^18.2.0",
+ "react-datepicker": "^4.24.0",
"react-dom": "^18.2.0",
"react-query": "^3.39.3",
"react-scripts": "5.0.1",
@@ -3354,6 +3356,15 @@
}
}
},
+ "node_modules/@popperjs/core": {
+ "version": "2.11.8",
+ "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
+ "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/popperjs"
+ }
+ },
"node_modules/@react-spring/animated": {
"version": "9.6.1",
"resolved": "https://registry.npmjs.org/@react-spring/animated/-/animated-9.6.1.tgz",
@@ -6440,6 +6451,11 @@
"resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz",
"integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ=="
},
+ "node_modules/classnames": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz",
+ "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw=="
+ },
"node_modules/clean-css": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.2.tgz",
@@ -7178,6 +7194,21 @@
"node": ">=10"
}
},
+ "node_modules/date-fns": {
+ "version": "2.30.0",
+ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
+ "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==",
+ "dependencies": {
+ "@babel/runtime": "^7.21.0"
+ },
+ "engines": {
+ "node": ">=0.11"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/date-fns"
+ }
+ },
"node_modules/debounce": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz",
@@ -15602,6 +15633,23 @@
"react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0"
}
},
+ "node_modules/react-datepicker": {
+ "version": "4.24.0",
+ "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-4.24.0.tgz",
+ "integrity": "sha512-2QUC2pP+x4v3Jp06gnFllxKsJR0yoT/K6y86ItxEsveTXUpsx+NBkChWXjU0JsGx/PL8EQnsxN0wHl4zdA1m/g==",
+ "dependencies": {
+ "@popperjs/core": "^2.11.8",
+ "classnames": "^2.2.6",
+ "date-fns": "^2.30.0",
+ "prop-types": "^15.7.2",
+ "react-onclickoutside": "^6.13.0",
+ "react-popper": "^2.3.0"
+ },
+ "peerDependencies": {
+ "react": "^16.9.0 || ^17 || ^18",
+ "react-dom": "^16.9.0 || ^17 || ^18"
+ }
+ },
"node_modules/react-dev-utils": {
"version": "12.0.1",
"resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz",
@@ -15736,6 +15784,11 @@
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz",
"integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg=="
},
+ "node_modules/react-fast-compare": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz",
+ "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ=="
+ },
"node_modules/react-is": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
@@ -15750,6 +15803,33 @@
"url": "https://github.com/sponsors/gregberge"
}
},
+ "node_modules/react-onclickoutside": {
+ "version": "6.13.0",
+ "resolved": "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.13.0.tgz",
+ "integrity": "sha512-ty8So6tcUpIb+ZE+1HAhbLROvAIJYyJe/1vRrrcmW+jLsaM+/powDRqxzo6hSh9CuRZGSL1Q8mvcF5WRD93a0A==",
+ "funding": {
+ "type": "individual",
+ "url": "https://github.com/Pomax/react-onclickoutside/blob/master/FUNDING.md"
+ },
+ "peerDependencies": {
+ "react": "^15.5.x || ^16.x || ^17.x || ^18.x",
+ "react-dom": "^15.5.x || ^16.x || ^17.x || ^18.x"
+ }
+ },
+ "node_modules/react-popper": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-2.3.0.tgz",
+ "integrity": "sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==",
+ "dependencies": {
+ "react-fast-compare": "^3.0.1",
+ "warning": "^4.0.2"
+ },
+ "peerDependencies": {
+ "@popperjs/core": "^2.0.0",
+ "react": "^16.8.0 || ^17 || ^18",
+ "react-dom": "^16.8.0 || ^17 || ^18"
+ }
+ },
"node_modules/react-query": {
"version": "3.39.3",
"resolved": "https://registry.npmjs.org/react-query/-/react-query-3.39.3.tgz",
@@ -18289,6 +18369,14 @@
"makeerror": "1.0.12"
}
},
+ "node_modules/warning": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
+ "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
+ "dependencies": {
+ "loose-envify": "^1.0.0"
+ }
+ },
"node_modules/watchpack": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz",
diff --git a/FE/package.json b/FE/package.json
index 9c2f622..22e94f7 100644
--- a/FE/package.json
+++ b/FE/package.json
@@ -8,7 +8,9 @@
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
+ "date-fns": "^2.30.0",
"react": "^18.2.0",
+ "react-datepicker": "^4.24.0",
"react-dom": "^18.2.0",
"react-query": "^3.39.3",
"react-scripts": "5.0.1",
diff --git a/FE/src/assets/search.svg b/FE/src/assets/search.svg
new file mode 100644
index 0000000..70ad761
--- /dev/null
+++ b/FE/src/assets/search.svg
@@ -0,0 +1,4 @@
+
diff --git a/FE/src/components/DiaryModal/DiaryCreateModal.js b/FE/src/components/DiaryModal/DiaryCreateModal.js
index 7656e0e..15724e4 100644
--- a/FE/src/components/DiaryModal/DiaryCreateModal.js
+++ b/FE/src/components/DiaryModal/DiaryCreateModal.js
@@ -131,12 +131,10 @@ function DiaryCreateModal(props) {
fontSize='1rem'
placeholder='태그를 입력해주세요.'
onBlur={(e) => addTag(e)}
- onKeyPress={(e) => {
+ onKeyDown={(e) => {
if (e.key === "Enter") {
addTag(e);
}
- }}
- onKeyDown={(e) => {
if (e.key === "Backspace" && e.target.value.length === 0) {
deleteLastTag();
}
diff --git a/FE/src/components/DiaryModal/DiaryListModal.js b/FE/src/components/DiaryModal/DiaryListModal.js
index 397d90e..78127b1 100644
--- a/FE/src/components/DiaryModal/DiaryListModal.js
+++ b/FE/src/components/DiaryModal/DiaryListModal.js
@@ -2,17 +2,38 @@
import React, { useEffect, useLayoutEffect } from "react";
import styled from "styled-components";
-import { useRecoilState } from "recoil";
+import { useRecoilState, useRecoilValue } from "recoil";
+import DatePicker from "react-datepicker";
+import "react-datepicker/dist/react-datepicker.css";
+import { ko } from "date-fns/esm/locale";
import diaryAtom from "../../atoms/diaryAtom";
+import shapeAtom from "../../atoms/shapeAtom";
import zoomIn from "../../assets/zoomIn.svg";
+import search from "../../assets/search.svg";
function DiaryListModal() {
const [selectedDiary, setSelectedDiary] = React.useState(null);
const [diaryState, setDiaryState] = useRecoilState(diaryAtom);
+ const shapeState = useRecoilValue(shapeAtom);
+ const [filterState, setFilterState] = React.useState({
+ date: {
+ start: null,
+ end: null,
+ },
+ emotion: {
+ positive: false,
+ neutral: false,
+ negative: false,
+ },
+ shape: [],
+ tag: [],
+ });
+ const [filteredDiaryList, setFilteredDiaryList] = React.useState([]);
useLayoutEffect(() => {
if (diaryState.diaryList) {
setSelectedDiary(diaryState.diaryList[0]);
+ setFilteredDiaryList(diaryState.diaryList);
}
}, [diaryState.diaryList]);
@@ -26,24 +47,251 @@ function DiaryListModal() {
}
}, [selectedDiary]);
+ useEffect(() => {
+ if (diaryState.diaryList) {
+ const filteredList = diaryState.diaryList
+ .filter((diary) => {
+ if (filterState.date.start && filterState.date.end) {
+ if (
+ new Date(diary.date) >= filterState.date.start &&
+ new Date(diary.date) <= filterState.date.end
+ ) {
+ return true;
+ }
+ } else if (filterState.date.start) {
+ if (new Date(diary.date) >= filterState.date.start) {
+ return true;
+ }
+ } else if (filterState.date.end) {
+ if (new Date(diary.date) <= filterState.date.end) {
+ return true;
+ }
+ } else {
+ return true;
+ }
+ return false;
+ })
+ .filter((diary) => {
+ if (
+ !filterState.emotion.positive &&
+ !filterState.emotion.neutral &&
+ !filterState.emotion.negative
+ ) {
+ return true;
+ }
+ if (filterState.emotion.positive) {
+ if (diary.emotion.sentiment === "positive") {
+ return true;
+ }
+ }
+ if (filterState.emotion.neutral) {
+ if (diary.emotion.sentiment === "neutral") {
+ return true;
+ }
+ }
+ if (filterState.emotion.negative) {
+ if (diary.emotion.sentiment === "negative") {
+ return true;
+ }
+ }
+ return false;
+ })
+ .filter((diary) => {
+ if (filterState.shape.length > 0) {
+ if (filterState.shape.includes(diary.shapeUuid)) {
+ return true;
+ }
+ return false;
+ }
+ return true;
+ })
+ .filter((diary) => {
+ if (filterState.tag.length > 0) {
+ if (filterState.tag.every((tag) => diary.tags.includes(tag))) {
+ return true;
+ }
+ return false;
+ }
+ return true;
+ });
+ setFilteredDiaryList([...filteredList]);
+ }
+ }, [
+ filterState.date,
+ filterState.emotion,
+ filterState.shape,
+ filterState.tag,
+ ]);
+
return (
-
+
날짜
- 필터
+
+ {
+ setFilterState((prev) => ({
+ ...prev,
+ date: {
+ ...prev.date,
+ start: date,
+ },
+ }));
+ }}
+ dateFormat='yyyy-MM-dd'
+ selectsStart
+ startDate={filterState.date.start}
+ endDate={filterState.date.end}
+ locale={ko}
+ isClearable={true}
+ placeholderText='시작 날짜'
+ />
+ ~
+ {
+ setFilterState((prev) => ({
+ ...prev,
+ date: {
+ ...prev.date,
+ end: date,
+ },
+ }));
+ }}
+ dateFormat='yyyy-MM-dd'
+ selectsEnd
+ startDate={filterState.date.start}
+ endDate={filterState.date.end}
+ minDate={filterState.date.start}
+ locale={ko}
+ isClearable={true}
+ placeholderText='종료 날짜'
+ />
+
감정
- 필터
+
+ {
+ setFilterState((prev) => ({
+ ...prev,
+ emotion: {
+ ...prev.emotion,
+ positive: !prev.emotion.positive,
+ },
+ }));
+ }}
+ >
+ 긍정
+
+ {
+ setFilterState((prev) => ({
+ ...prev,
+ emotion: {
+ ...prev.emotion,
+ neutral: !prev.emotion.neutral,
+ },
+ }));
+ }}
+ >
+ 중립
+
+ {
+ setFilterState((prev) => ({
+ ...prev,
+ emotion: {
+ ...prev.emotion,
+ negative: !prev.emotion.negative,
+ },
+ }));
+ }}
+ >
+ 부정
+
+
모양
- 필터
+
+
+ {shapeState?.map((shape) => (
+ {
+ setFilterState((prev) => {
+ const shapeIndex = prev.shape.indexOf(shape.uuid);
+ if (shapeIndex !== -1) {
+ const updatedShape = [...prev.shape];
+ updatedShape.splice(shapeIndex, 1);
+ return {
+ ...prev,
+ shape: updatedShape,
+ };
+ } else {
+ return {
+ ...prev,
+ shape: [...prev.shape, shape.uuid],
+ };
+ }
+ });
+ }}
+ selected={filterState.shape.includes(shape.uuid)}
+ >
+
+
+ ))}
+
+
태그
- 필터
+
+
+
+
+
+ {
+ if (e.key === "Enter") {
+ if (!filterState.tag.includes(e.target.value)) {
+ setFilterState((prev) => ({
+ ...prev,
+ tag: [...prev.tag, e.target.value],
+ }));
+ }
+ }
+ }}
+ />
+
+
+ {filterState.tag.map((tag) => (
+ {
+ setFilterState((prev) => ({
+ ...prev,
+ tag: prev.tag.filter((item) => item !== tag),
+ }));
+ }}
+ >
+ {tag}
+
+ ))}
+
+
@@ -53,7 +301,7 @@ function DiaryListModal() {
e.target.focus();
}}
>
- {diaryState.diaryList?.map((diary) => (
+ {filteredDiaryList.map((diary) => (
{
@@ -121,6 +369,7 @@ const DiaryListModalItem = styled.div`
display: flex;
flex-direction: column;
align-items: center;
+ justify-content: ${(props) => props.justifyContent || "flex-start"};
font-size: 1.3rem;
color: #ffffff;
@@ -134,6 +383,9 @@ const DiaryListModalItem = styled.div`
opacity: 1;
}
}
+
+ overflow: hidden;
+ overflow-y: auto;
`;
const DiaryListModalFilterWrapper = styled.div`
@@ -143,16 +395,172 @@ const DiaryListModalFilterWrapper = styled.div`
flex-direction: column;
align-items: center;
+ margin: 1rem 0;
+ gap: 0.5rem;
+
font-size: 1.1rem;
`;
const DiaryListModalFilterContent = styled.div`
width: 100%;
- height: 4.5rem;
+ height: ${(props) => props.height || "7rem"};
+ padding: 0 1rem;
+
+ display: flex;
+ flex-direction: ${(props) => props.flexDirection || "row"};
+ justify-content: space-evenly;
+ align-items: center;
+`;
+
+const FilterDateInput = styled.input`
+ width: 40%;
+ height: 3rem;
+
+ border: none;
+ border-radius: 0.5rem;
+
+ font-family: "Pretendard-Medium";
+ font-size: 1.1rem;
+ text-align: center;
+`;
+
+const FilterEmotionButton = styled.button`
+ width: 25%;
+ height: 3rem;
+
+ border: none;
+ border-radius: 0.5rem;
+
+ font-family: "Pretendard-Medium";
+ font-size: 1.1rem;
+ text-align: center;
+
+ cursor: pointer;
+
+ &:hover {
+ background-color: rgba(255, 255, 255, 0.3);
+ }
+
+ border: ${(props) =>
+ props.selected ? `3px solid ${props.borderColor}` : "none"};
+ border-radius: 0.5rem;
+`;
+
+const ShapeWrapper = styled.div`
+ width: 90%;
+ height: 10rem;
+
+ display: flex;
+ justify-content: space-evenly;
+ flex-wrap: wrap;
+
+ background-color: #3b455e;
+ border-radius: 0.5rem;
+
+ overflow: auto;
+ overflow-x: hidden;
+`;
+
+const ShapeSelectBoxItem = styled.div`
+ width: 4rem;
+
+ margin: 0.5rem;
+
+ cursor: pointer;
+
+ &:hover {
+ transform: scale(1.2);
+ transition: transform 0.25s;
+ }
+
+ border: ${(props) =>
+ props.selected ? "1px solid #ffffff" : "1px solid transparent"};
+ border-radius: 0.5rem;
+`;
+
+const FilterTagInputWrapper = styled.div`
+ width: 88%;
+ height: 3rem;
+
+ border: none;
+ border-radius: 1.5rem;
+
+ background-color: rgba(255, 255, 255, 0.6);
+
+ font-family: "Pretendard-Medium";
+ font-size: 1.1rem;
+
+ padding: 0.5rem 1rem;
+ box-sizing: border-box;
+
+ display: flex;
+ justify-content: flex-start;
+ align-items: center;
+
+ margin-bottom: 1rem;
+`;
+
+const FilterTagInputIcon = styled.div`
+ width: 2rem;
+ height: 2rem;
+
+ display: flex;
+ justify-content: center;
+ align-items: center;
+`;
+
+const FilterTagInput = styled.input`
+ width: 100%;
+ height: 3rem;
+
+ border: none;
+ border-radius: 1.5rem;
+
+ background-color: transparent;
+
+ font-family: "Pretendard-Medium";
+ font-size: 1.1rem;
+
+ padding: 0.5rem 1rem;
+ box-sizing: border-box;
+
+ &:focus {
+ outline: none;
+ }
+`;
+
+const FilterTagWrapper = styled.div`
+ width: 90%;
+ height: 10rem;
+
+ display: flex;
+ flex-wrap: wrap;
+
+ background-color: transparent;
+ border-radius: 0.5rem;
+
+ overflow: auto;
+ overflow-x: hidden;
+`;
+
+const FilterTagItem = styled.div`
+ height: 2rem;
display: flex;
justify-content: center;
align-items: center;
+
+ background-color: rgba(255, 255, 255, 0.3);
+ border-radius: 1rem;
+
+ padding: 0 1rem;
+ margin: 0.5rem;
+
+ cursor: pointer;
+
+ &:hover {
+ background-color: rgba(255, 255, 255, 0.3);
+ }
`;
const DiaryTitleListHeader = styled.div`
@@ -165,7 +573,7 @@ const DiaryTitleListHeader = styled.div`
flex-shrink: 0;
- font-size: 1.1rem;
+ font-size: 1.6rem;
`;
const DiaryTitleListItemWrapper = styled.div`