From 735567e1665c3428fd580bca3e296ec8fb7d2f88 Mon Sep 17 00:00:00 2001 From: woohyeon-dev Date: Thu, 20 Apr 2023 00:00:04 +0900 Subject: [PATCH] implement: Implement to disable days that do not have a created diary and implement that clicking on a day will take you to the diary details page for that day --- .gitignore | 3 + package-lock.json | 14 -- package.json | 1 - src/apis/diaryApi.ts | 16 +- .../atoms/DatePicker/DatePicker.styled.ts | 1 - .../atoms/DatePicker/DatePicker.tsx | 13 +- .../blocks/DiaryEntryForm/DiaryEntryForm.tsx | 14 +- .../blocks/DiaryHeader/DiaryHeader.styled.tsx | 40 +++++ .../blocks/DiaryHeader/DiaryHeader.tsx | 102 ++++++++++++ src/pages/diary/[id]/edit.tsx | 6 +- src/pages/diary/[id]/index.tsx | 152 +++++++----------- src/pages/diary/index.tsx | 10 +- src/pages/diary/new.tsx | 2 +- src/styles/components/StDiary.styles.ts | 28 +--- src/types/IDiary.ts | 2 + 15 files changed, 236 insertions(+), 168 deletions(-) create mode 100644 src/components/blocks/DiaryHeader/DiaryHeader.styled.tsx create mode 100644 src/components/blocks/DiaryHeader/DiaryHeader.tsx diff --git a/.gitignore b/.gitignore index 5aad37a..c583db8 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,6 @@ yarn-error.log* # typescript *.tsbuildinfo next-env.d.ts + +# sample +/public/sample.* \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 475def0..d65da3b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,6 @@ "dayjs": "^1.11.7", "eslint": "8.31.0", "eslint-config-next": "13.2.4", - "i": "^0.3.7", "next": "13.2.4", "react": "18.2.0", "react-dom": "18.2.0", @@ -2341,14 +2340,6 @@ "react-is": "^16.7.0" } }, - "node_modules/i": { - "version": "0.3.7", - "resolved": "https://registry.npmjs.org/i/-/i-0.3.7.tgz", - "integrity": "sha512-FYz4wlXgkQwIPqhzC5TdNMLSE5+GS1IIDJZY/1ZiEPCT2S3COUVZeT5OW4BmW4r5LHLQuOosSwsvnroG9GR59Q==", - "engines": { - "node": ">=0.4" - } - }, "node_modules/ignore": { "version": "5.2.4", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", @@ -5735,11 +5726,6 @@ "react-is": "^16.7.0" } }, - "i": { - "version": "0.3.7", - "resolved": "https://registry.npmjs.org/i/-/i-0.3.7.tgz", - "integrity": "sha512-FYz4wlXgkQwIPqhzC5TdNMLSE5+GS1IIDJZY/1ZiEPCT2S3COUVZeT5OW4BmW4r5LHLQuOosSwsvnroG9GR59Q==" - }, "ignore": { "version": "5.2.4", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", diff --git a/package.json b/package.json index e8c8e4d..de5214c 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,6 @@ "dayjs": "^1.11.7", "eslint": "8.31.0", "eslint-config-next": "13.2.4", - "i": "^0.3.7", "next": "13.2.4", "react": "18.2.0", "react-dom": "18.2.0", diff --git a/src/apis/diaryApi.ts b/src/apis/diaryApi.ts index 0d7dc85..e09b103 100644 --- a/src/apis/diaryApi.ts +++ b/src/apis/diaryApi.ts @@ -1,7 +1,6 @@ import { QueryFunctionContext } from 'react-query'; import axiosInstance from '@/utils/axiosInstance'; import IDiary from '@/types/IDiary'; -import IDiaryAnalysisRequest from '@/types/IDiaryAnalysisRequest'; export const fetchDiaryListFn = async ({ queryKey }: QueryFunctionContext) => { const [, month, year] = queryKey; @@ -11,17 +10,20 @@ export const fetchDiaryListFn = async ({ queryKey }: QueryFunctionContext) => { return response.data; }; -export const fetchDiaryDetailFn = async ({ - queryKey, -}: QueryFunctionContext) => { +export const fetchDiaryFn = async ({ queryKey }: QueryFunctionContext) => { const [, id] = queryKey; const response = await axiosInstance.get(`diary/${id}`); return response.data; }; -export const fetchDiaryMetaFn = async (data: IDiaryAnalysisRequest) => { - const response = await axiosInstance.get('diary/meta', { - params: data, +export const fetchDiaryDatesFn = async () => { + const response = await axiosInstance.get('diary/dates'); + return response.data; +}; + +export const fetchDiaryWithDateFn = async (date: string) => { + const response = await axiosInstance.get('diary/date', { + params: { date: date }, }); return response.data; }; diff --git a/src/components/atoms/DatePicker/DatePicker.styled.ts b/src/components/atoms/DatePicker/DatePicker.styled.ts index 8836b62..3df4e05 100644 --- a/src/components/atoms/DatePicker/DatePicker.styled.ts +++ b/src/components/atoms/DatePicker/DatePicker.styled.ts @@ -3,7 +3,6 @@ import styled from 'styled-components'; export const StDatePicker = styled.div` position: absolute; z-index: 10; - left: 0; right: 0; margin-top: 0.25rem; box-shadow: ${({ theme }) => theme.boxShadow.normal}; diff --git a/src/components/atoms/DatePicker/DatePicker.tsx b/src/components/atoms/DatePicker/DatePicker.tsx index 2ca896e..58ad74c 100644 --- a/src/components/atoms/DatePicker/DatePicker.tsx +++ b/src/components/atoms/DatePicker/DatePicker.tsx @@ -1,13 +1,14 @@ import '@uvarov.frontend/vanilla-calendar/build/vanilla-calendar.min.css'; import '@uvarov.frontend/vanilla-calendar/build/themes/light.min.css'; import VanillaCalendar from '@uvarov.frontend/vanilla-calendar'; -import { FormatDateString } from '@uvarov.frontend/vanilla-calendar/src/types'; import { StDatePicker } from './DatePicker.styled'; import { Dispatch, SetStateAction, useEffect, useRef } from 'react'; import React from 'react'; +import { FormatDateString } from '@uvarov.frontend/vanilla-calendar/src/types'; interface IDatePickerProps { current: string; + enabled?: Array; selected: string; setSelected: Dispatch>; setIsCalendarOpen: Dispatch>; @@ -16,6 +17,7 @@ interface IDatePickerProps { export default function DatePicker({ current, selected, + enabled = [], setSelected, setIsCalendarOpen, }: IDatePickerProps) { @@ -36,7 +38,6 @@ export default function DatePicker({ year: Number(selected.substring(0, 4)), }, }, - actions: { clickDay(event, dates) { setSelected(dates[0]); @@ -45,7 +46,13 @@ export default function DatePicker({ }, }); datePicker.init(); - }, [datePickerEl, selected, current]); + + if (enabled.length > 0) { + datePicker.settings.range.disableAllDays = true; + datePicker.settings.range.enabled = enabled; + datePicker.update(); + } + }, [datePickerEl, selected]); return ; } diff --git a/src/components/blocks/DiaryEntryForm/DiaryEntryForm.tsx b/src/components/blocks/DiaryEntryForm/DiaryEntryForm.tsx index e0479ae..7333803 100644 --- a/src/components/blocks/DiaryEntryForm/DiaryEntryForm.tsx +++ b/src/components/blocks/DiaryEntryForm/DiaryEntryForm.tsx @@ -24,18 +24,18 @@ import DatePicker from '@/components/atoms/DatePicker/DatePicker'; import Link from 'next/link'; interface IDiaryEntryFormProps { + today: string; title: string; content: string; - today: string; date: string; setDate: Dispatch>; setEntry: Dispatch>; } export default function DiaryEntryForm({ + today, title, content, - today, date, setDate, setEntry, @@ -55,13 +55,11 @@ export default function DiaryEntryForm({ - - + + + {/* 영운씨가 해결 */} {dayjs(date).locale('en-us').format('dddd, MMMM D, YYYY')} - {isCalendarOpen ? : } + {isCalendarOpen ? : } {isCalendarOpen && ( theme.bgclr.primary}; + ${({ theme }) => theme.mixins.flexBox('row', 'center', 'space-between')}; + svg { + width: 2rem; + height: 2rem; + cursor: pointer; + } +`; + +export const StDiaryIconContainer = styled.div` + ${({ theme }) => theme.mixins.flexBox('row', 'baseline')}; + svg { + margin-left: 1.25rem; + } +`; + +export const StCalendarBox = styled.div` + position: relative; +`; + +export const StCalendarLoadingBox = styled.div` + position: absolute; + z-index: 10; + right: 0; + margin-top: 0.25rem; + box-shadow: ${({ theme }) => theme.boxShadow.normal}; + background: ${({ theme }) => theme.bgclr.primary}; + ${({ theme }) => theme.mixins.flexBox('column')}; + min-width: 275px; + min-height: 266px; + border-radius: 0.75rem; +`; diff --git a/src/components/blocks/DiaryHeader/DiaryHeader.tsx b/src/components/blocks/DiaryHeader/DiaryHeader.tsx new file mode 100644 index 0000000..4680957 --- /dev/null +++ b/src/components/blocks/DiaryHeader/DiaryHeader.tsx @@ -0,0 +1,102 @@ +import Link from 'next/link'; +import { ArrowLeft, Calendar, Edit, Trash2 } from 'react-feather'; +import DeleteModal from '../DeleteModal/DeleteModal'; +import useDropdown from '@/hooks/useDropdown'; +import useDeleteModal from '@/hooks/useDeleteModal'; +import { Dispatch, SetStateAction, useRef } from 'react'; +import { useRouter } from 'next/router'; +import { deleteDiaryFn, fetchDiaryDatesFn } from '@/apis/diaryApi'; +import { useMutation, useQuery } from 'react-query'; +import { AxiosError } from 'axios'; +import IDiary from '@/types/IDiary'; +import { + StCalendarBox, + StCalendarLoadingBox, + StDiaryHeader, + StDiaryIconContainer, +} from './DiaryHeader.styled'; +import { ClipLoader } from 'react-spinners'; +import DatePicker from '@/components/atoms/DatePicker/DatePicker'; +import useTodayDate from '@/hooks/useTodayDate'; + +interface IDiaryHeaderProps { + diary: IDiary; + date: string; + setDate: Dispatch>; +} + +export default function DiaryHeader({ + diary, + date, + setDate, +}: IDiaryHeaderProps) { + const router = useRouter(); + const { dateStr } = useTodayDate(); + const dropdownRef = useRef(null); + const [isCalendarOpen, setIsCalendarOpen, handleCalendarOpen] = + useDropdown(dropdownRef); + const { isLoading: datesLoading, data: diaryDates } = useQuery( + 'diaryDates', + fetchDiaryDatesFn, + { retry: 0, enabled: isCalendarOpen } + ); + const { mutate } = useMutation(deleteDiaryFn, { + onSuccess: data => { + alert(data.message); + router.push('/diary'); + }, + onError: (error: AxiosError) => { + alert(error.message); + // message 결과에 따라 input 필드 초기화 구현해야함 + }, + }); + const { isModalOpen, handleOpen, handleClose, handleDelete } = useDeleteModal( + diary?.diary_id, + mutate + ); + + return ( + + + + + + + + + + {isModalOpen && ( + + )} + + + {datesLoading && ( + + + + )} + {!datesLoading && isCalendarOpen && ( + + )} + + + + ); +} diff --git a/src/pages/diary/[id]/edit.tsx b/src/pages/diary/[id]/edit.tsx index 652286a..e34f17a 100644 --- a/src/pages/diary/[id]/edit.tsx +++ b/src/pages/diary/[id]/edit.tsx @@ -1,4 +1,4 @@ -import { fetchDiaryDetailFn } from '@/apis/diaryApi'; +import { fetchDiaryFn } from '@/apis/diaryApi'; import Loading from '@/components/atoms/Loading/Loading'; import DiaryEntryForm from '@/components/blocks/DiaryEntryForm/DiaryEntryForm'; import DiaryMetaForm from '@/components/blocks/DiaryMetaForm/DiaryMetaForm'; @@ -13,7 +13,7 @@ export default function DiaryEditPage() { const { query: { id, data: queryData }, } = useRouter(); - const { isLoading, data } = useQuery(['diary', id], fetchDiaryDetailFn, { + const { isLoading, data } = useQuery(['diary', id], fetchDiaryFn, { enabled: !!id && !queryData, retry: 0, }); @@ -71,9 +71,9 @@ export default function DiaryEditPage() { {isLoading && } {!isLoading && !(emotion && weather) && ( (); + const { isLoading: diaryLoading, data: diary } = useQuery( // queryKey: 쿼리를 고유하게 식별하는 문자열이나 배열으로 쿼리 키가 변경되면 React Query는 새로운 데이터를 가져와 캐시를 업데이트함 ['diary', router.query.id], // queryFn: 쿼리를 호출하는 함수로 Promise를 반환해야하며, 해당 Promise가 resolve되면 데이터가 반환됨 - fetchDiaryDetailFn, + fetchDiaryFn, { - enabled: !!router.query.id, // id가 있는 경우에만 쿼리 실행 + retry: 0, + enabled: !!router.query.id, // id가 있을 때만 실행 onSuccess: data => { // 성공시 호출 // console.log(data); setDate(data.date as string); + setDiaryDate(data.date as string); }, onError: (error: AxiosError) => { // 실패시 호출 (401, 404 같은 error가 아니라 정말 api 호출이 실패한 경우만 호출됨) @@ -55,102 +53,60 @@ export default function DiaryDetailPage() { }, } ); - const { mutate } = useMutation(deleteDiaryFn, { + const { mutate } = useMutation(fetchDiaryWithDateFn, { onSuccess: data => { - alert(data.message); - router.push('/diary'); + router.push(`/diary/${data.diary_id}`); }, onError: (error: AxiosError) => { alert(error.message); // message 결과에 따라 input 필드 초기화 구현해야함 }, }); - const [icons, setIcons] = useState(); - const [date, setDate] = useState(''); - const { dateStr } = useTodayDate(); - const dropdownRef = useRef(null); - const [isCalendarOpen, setIsCalendarOpen, handleCalendarOpen] = - useDropdown(dropdownRef); - const { isModalOpen, handleOpen, handleClose, handleDelete } = useDeleteModal( - data?.diary_id, - mutate - ); // 페이지가 로드될 때마다 스크롤 위치를 페이지 상단으로 이동 // 참고: https://github.com/vercel/next.js/issues/45187 useEffect(() => document.body?.scrollTo(0, 0), []); - useEffect( - () => - setIcons({ - weatherIcon: weather.find(el => el.name === data?.weather)?.icon, - emotionIcon: moods.find(el => el.name === data?.emotion)?.icon, - }), - [data] - ); + useEffect(() => { + setIcons({ + weatherIcon: weather.find(el => el.name === diary?.weather)?.icon, + emotionIcon: moods.find(el => el.name === diary?.emotion)?.icon, + }); + }, [diary]); + + useEffect(() => { + if (date !== '' && date !== diaryDate) { + mutate(date); + } + }, [date]); return ( - {isLoading && } - {data && ( - - <> - - - - - - - - {isCalendarOpen && ( - - )} - - - - - - {isModalOpen && ( - - )} - - - <> - - - - - - {dayjs(data.date) - .locale('en-us') - .format('dddd, MMMM D, YYYY')} - - - {icons?.weatherIcon} - {icons?.emotionIcon} - - - {data.title} - {data.content} - - - + {diaryLoading && } + {diary && ( + + + + + + + + {dayjs(diary.date).locale('en-us').format('dddd, MMMM D, YYYY')} + + + {icons?.weatherIcon} + {icons?.emotionIcon} + + + {diary.title} + {diary.content} + )} ); diff --git a/src/pages/diary/index.tsx b/src/pages/diary/index.tsx index 76abf25..cd8c0ca 100644 --- a/src/pages/diary/index.tsx +++ b/src/pages/diary/index.tsx @@ -82,11 +82,11 @@ export default function DiaryListPage() { {data.map((el: any) => { return ( - - - - - + + + + + ); })} diff --git a/src/pages/diary/new.tsx b/src/pages/diary/new.tsx index dde26d6..351d5b7 100644 --- a/src/pages/diary/new.tsx +++ b/src/pages/diary/new.tsx @@ -29,9 +29,9 @@ export default function CreateDiaryPage() { {!(emotion && weather) && ( theme.bgclr.primary}; - ${({ theme }) => theme.mixins.flexBox('row', 'center', 'space-between')}; - svg { - width: 2rem; - height: 2rem; - cursor: pointer; - } -`; - -export const StDiaryIconContainer = styled.div` - ${({ theme }) => theme.mixins.flexBox()}; - svg { - margin-left: 1.25rem; - } -`; - -export const StCalendarBox = styled.div` - position: relative; -`; - export const StDiaryPictureBox = styled.div` position: relative; width: 480px; diff --git a/src/types/IDiary.ts b/src/types/IDiary.ts index 58fa959..f9791a0 100644 --- a/src/types/IDiary.ts +++ b/src/types/IDiary.ts @@ -1,7 +1,9 @@ export default interface IDiary { + diary_id?: number; title: string; content: string; emotion: string; weather: string; + source?: string; date: string; }