From 218f4904d3e3e0ee1af13546086f4c2782243957 Mon Sep 17 00:00:00 2001 From: heejin Date: Mon, 25 Nov 2024 17:42:38 +0900 Subject: [PATCH 1/7] =?UTF-8?q?=E2=9C=A8=20feat:=20add=20optional=20props?= =?UTF-8?q?=20on=20Modal?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dashboard/[id]/components/Card.tsx | 38 ++++++++++++++----- .../[id]/components/CardDetail.module.css | 0 .../dashboard/[id]/components/CardDetail.tsx | 9 +++++ .../mydashboard/_components/modal/Modal.tsx | 31 +++++++++------ 4 files changed, 56 insertions(+), 22 deletions(-) create mode 100644 src/app/(with-header-sidebar)/dashboard/[id]/components/CardDetail.module.css create mode 100644 src/app/(with-header-sidebar)/dashboard/[id]/components/CardDetail.tsx diff --git a/src/app/(with-header-sidebar)/dashboard/[id]/components/Card.tsx b/src/app/(with-header-sidebar)/dashboard/[id]/components/Card.tsx index a6930bc..c280d7e 100644 --- a/src/app/(with-header-sidebar)/dashboard/[id]/components/Card.tsx +++ b/src/app/(with-header-sidebar)/dashboard/[id]/components/Card.tsx @@ -1,6 +1,9 @@ import React from 'react'; import { Draggable } from 'react-beautiful-dnd'; import { CardData } from '@/types/dashboardView'; +import { useModal } from '@/app/(with-header-sidebar)/mydashboard/_hooks/useModal'; +import Modal from '@/app/(with-header-sidebar)/mydashboard/_components/modal/Modal'; +import CardDetail from './CardDetail'; import styles from './Card.module.css'; interface Props { @@ -9,23 +12,38 @@ interface Props { } function Card({ item, index }: Props) { + const { isOpen, openModal, isClosing, closeModal } = useModal(); + if (!item || !item.id) { return null; } return ( - - {(provided, snapshot) => ( -
+ + {(provided, snapshot) => ( +
+
{item.title}
+
+ )} +
+ {isOpen && ( + -
{item.title}
-
+ + )} -
+ ); } diff --git a/src/app/(with-header-sidebar)/dashboard/[id]/components/CardDetail.module.css b/src/app/(with-header-sidebar)/dashboard/[id]/components/CardDetail.module.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/(with-header-sidebar)/dashboard/[id]/components/CardDetail.tsx b/src/app/(with-header-sidebar)/dashboard/[id]/components/CardDetail.tsx new file mode 100644 index 0000000..f649db2 --- /dev/null +++ b/src/app/(with-header-sidebar)/dashboard/[id]/components/CardDetail.tsx @@ -0,0 +1,9 @@ +import styles from './CardDetail.module.css'; + +interface CardDetailProps { + closeModal: () => void; +} + +export default function CardDetail({ closeModal }: CardDetailProps) { + return
카드 디테일
; +} diff --git a/src/app/(with-header-sidebar)/mydashboard/_components/modal/Modal.tsx b/src/app/(with-header-sidebar)/mydashboard/_components/modal/Modal.tsx index 8a4e455..5787627 100644 --- a/src/app/(with-header-sidebar)/mydashboard/_components/modal/Modal.tsx +++ b/src/app/(with-header-sidebar)/mydashboard/_components/modal/Modal.tsx @@ -11,6 +11,8 @@ interface ModalProps { onClose: () => void; allowDimClose?: boolean; title?: string; + hasCloseButton?: boolean; + headerComponent?: React.ComponentType; children: ReactNode; } @@ -19,6 +21,8 @@ export default function Modal({ onClose, allowDimClose = true, title, + hasCloseButton = false, + headerComponent: Component, children, }: ModalProps) { const handleOnClickBackground = (e: MouseEvent) => { @@ -53,18 +57,21 @@ export default function Modal({
{title &&

{title}

} - {/* */} + {Component && } + {hasCloseButton && ( + + )}
{children}
From 8660fd5d5f9ce231517059a932636633e3e4c939 Mon Sep 17 00:00:00 2001 From: heejin Date: Tue, 26 Nov 2024 01:51:09 +0900 Subject: [PATCH 2/7] =?UTF-8?q?=E2=9C=A8=20feat:=20add=20card=20view=20ass?= =?UTF-8?q?ignment=20component?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/icons/x_lg.svg | 7 +- .../dashboard/[id]/components/Card.tsx | 4 +- .../[id]/components/CardDetail.module.css | 0 .../dashboard/[id]/components/CardDetail.tsx | 9 --- .../card-detail/Assignment.module.css | 64 +++++++++++++++++++ .../components/card-detail/Assignment.tsx | 45 +++++++++++++ .../card-detail/CardInfo.module.css | 26 ++++++++ .../[id]/components/card-detail/CardInfo.tsx | 14 ++++ src/types/dashboardView.ts | 2 +- src/utils/dateUtils.ts | 13 ++++ 10 files changed, 168 insertions(+), 16 deletions(-) delete mode 100644 src/app/(with-header-sidebar)/dashboard/[id]/components/CardDetail.module.css delete mode 100644 src/app/(with-header-sidebar)/dashboard/[id]/components/CardDetail.tsx create mode 100644 src/app/(with-header-sidebar)/dashboard/[id]/components/card-detail/Assignment.module.css create mode 100644 src/app/(with-header-sidebar)/dashboard/[id]/components/card-detail/Assignment.tsx create mode 100644 src/app/(with-header-sidebar)/dashboard/[id]/components/card-detail/CardInfo.module.css create mode 100644 src/app/(with-header-sidebar)/dashboard/[id]/components/card-detail/CardInfo.tsx create mode 100644 src/utils/dateUtils.ts diff --git a/public/icons/x_lg.svg b/public/icons/x_lg.svg index 4b83730..016d5b5 100644 --- a/public/icons/x_lg.svg +++ b/public/icons/x_lg.svg @@ -1,4 +1,3 @@ - - - - + + + \ No newline at end of file diff --git a/src/app/(with-header-sidebar)/dashboard/[id]/components/Card.tsx b/src/app/(with-header-sidebar)/dashboard/[id]/components/Card.tsx index c280d7e..e8ad2f2 100644 --- a/src/app/(with-header-sidebar)/dashboard/[id]/components/Card.tsx +++ b/src/app/(with-header-sidebar)/dashboard/[id]/components/Card.tsx @@ -3,7 +3,7 @@ import { Draggable } from 'react-beautiful-dnd'; import { CardData } from '@/types/dashboardView'; import { useModal } from '@/app/(with-header-sidebar)/mydashboard/_hooks/useModal'; import Modal from '@/app/(with-header-sidebar)/mydashboard/_components/modal/Modal'; -import CardDetail from './CardDetail'; +import CardInfo from './card-detail/CardInfo'; import styles from './Card.module.css'; interface Props { @@ -40,7 +40,7 @@ function Card({ item, index }: Props) { title={item.title} hasCloseButton={true} > - + )} diff --git a/src/app/(with-header-sidebar)/dashboard/[id]/components/CardDetail.module.css b/src/app/(with-header-sidebar)/dashboard/[id]/components/CardDetail.module.css deleted file mode 100644 index e69de29..0000000 diff --git a/src/app/(with-header-sidebar)/dashboard/[id]/components/CardDetail.tsx b/src/app/(with-header-sidebar)/dashboard/[id]/components/CardDetail.tsx deleted file mode 100644 index f649db2..0000000 --- a/src/app/(with-header-sidebar)/dashboard/[id]/components/CardDetail.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import styles from './CardDetail.module.css'; - -interface CardDetailProps { - closeModal: () => void; -} - -export default function CardDetail({ closeModal }: CardDetailProps) { - return
카드 디테일
; -} diff --git a/src/app/(with-header-sidebar)/dashboard/[id]/components/card-detail/Assignment.module.css b/src/app/(with-header-sidebar)/dashboard/[id]/components/card-detail/Assignment.module.css new file mode 100644 index 0000000..0ed9468 --- /dev/null +++ b/src/app/(with-header-sidebar)/dashboard/[id]/components/card-detail/Assignment.module.css @@ -0,0 +1,64 @@ +.assignment { + padding: 6px 16px; + display: flex; + justify-content: space-between; + align-items: center; + border-radius: 8px; + border: 1px solid var(--gray-300); + background: var(--white); +} + +.wrapper { + display: flex; + gap: 62px; +} + +.assignee { + display: flex; + flex-direction: column; + justify-content: flex-start; +} + +.label { + color: var(--black); + font-size: 12px; + font-weight: 600; + line-height: 20px; +} + +.description { + color: var(--black-100); + font-size: 12px; + font-weight: 400; +} + +.assignee .description { + display: flex; + align-items: center; + gap: 8px; +} + +.avatar { + width: 26px; + height: 26px; +} + +.dueDate .description { + margin-top: 8px; +} + +@media screen and (min-width: 768px) { + .assignment { + padding: 13px; + } + + .wrapper { + flex-direction: column; + gap: 16px; + } + + .avatar { + width: 34px; + height: 34px; + } +} diff --git a/src/app/(with-header-sidebar)/dashboard/[id]/components/card-detail/Assignment.tsx b/src/app/(with-header-sidebar)/dashboard/[id]/components/card-detail/Assignment.tsx new file mode 100644 index 0000000..56bc8d6 --- /dev/null +++ b/src/app/(with-header-sidebar)/dashboard/[id]/components/card-detail/Assignment.tsx @@ -0,0 +1,45 @@ +import type { CardData } from '@/types/dashboardView'; +import Avatar from '@/components/Avatar'; +import { formatDateToCustomFormat } from '@/utils/dateUtils'; +import styles from './Assignment.module.css'; + +interface AssignmentProps { + card: Pick; +} + +export default function Assignment({ card }: AssignmentProps) { + if (!card) return null; + + const { assignee, dueDate } = { + dueDate: '2024-11-30 12:00', + assignee: { profileImageUrl: null, nickname: 'manta', id: 1 }, + }; + + return ( +
+
+ {assignee && ( +
+
담당자
+
+ + {assignee.nickname} +
+
+ )} + {dueDate && ( +
+
마감일
+
+ {formatDateToCustomFormat(dueDate)} +
+
+ )} +
+
+ ); +} diff --git a/src/app/(with-header-sidebar)/dashboard/[id]/components/card-detail/CardInfo.module.css b/src/app/(with-header-sidebar)/dashboard/[id]/components/card-detail/CardInfo.module.css new file mode 100644 index 0000000..415e976 --- /dev/null +++ b/src/app/(with-header-sidebar)/dashboard/[id]/components/card-detail/CardInfo.module.css @@ -0,0 +1,26 @@ +.cardInfo { + margin-top: 20px; +} + +@media screen and (min-width: 768px) { + .cardInfo { + display: flex; + justify-content: space-between; + gap: 14px; + } + + .assignmentContainer { + order: 1; + flex: 3; + } + + .infoContainer { + flex: 7; + } +} + +@media screen and (min-width: 1200px) { + .cardInfo { + gap: 39px; + } +} diff --git a/src/app/(with-header-sidebar)/dashboard/[id]/components/card-detail/CardInfo.tsx b/src/app/(with-header-sidebar)/dashboard/[id]/components/card-detail/CardInfo.tsx new file mode 100644 index 0000000..7dc4e97 --- /dev/null +++ b/src/app/(with-header-sidebar)/dashboard/[id]/components/card-detail/CardInfo.tsx @@ -0,0 +1,14 @@ +import { CardData } from '@/types/dashboardView'; +import Assignment from './Assignment'; +import styles from './CardInfo.module.css'; + +export default function CardInfo({ card }: { card: CardData }) { + return ( +
+
+ +
+
카드 상세내용 + 댓글 영역
+
+ ); +} diff --git a/src/types/dashboardView.ts b/src/types/dashboardView.ts index c4a5fcf..4574308 100644 --- a/src/types/dashboardView.ts +++ b/src/types/dashboardView.ts @@ -10,7 +10,7 @@ export interface CardData { description: string; tags: string[]; dueDate: string; - assignee: Assignee; + assignee?: Assignee; imageUrl: string; teamId: string; columnId: number; diff --git a/src/utils/dateUtils.ts b/src/utils/dateUtils.ts new file mode 100644 index 0000000..4d32f1b --- /dev/null +++ b/src/utils/dateUtils.ts @@ -0,0 +1,13 @@ +export function formatDateToCustomFormat(targetDate: string): string { + if (!targetDate) return ''; + + const date = new Date(targetDate); + + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const day = String(date.getDate()).padStart(2, '0'); + const hours = String(date.getHours()).padStart(2, '0'); + const minutes = String(date.getMinutes()).padStart(2, '0'); + + return `${year}.${month}.${day} ${hours}:${minutes}`; +} From dfce15fc7f26c6f1ae778d83b0a2e14db24c54ed Mon Sep 17 00:00:00 2001 From: heejin Date: Tue, 26 Nov 2024 01:54:32 +0900 Subject: [PATCH 3/7] =?UTF-8?q?=F0=9F=92=A1=20chore:=20comment=20sample=20?= =?UTF-8?q?data?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../[id]/components/card-detail/Assignment.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/app/(with-header-sidebar)/dashboard/[id]/components/card-detail/Assignment.tsx b/src/app/(with-header-sidebar)/dashboard/[id]/components/card-detail/Assignment.tsx index 56bc8d6..56281cc 100644 --- a/src/app/(with-header-sidebar)/dashboard/[id]/components/card-detail/Assignment.tsx +++ b/src/app/(with-header-sidebar)/dashboard/[id]/components/card-detail/Assignment.tsx @@ -10,10 +10,12 @@ interface AssignmentProps { export default function Assignment({ card }: AssignmentProps) { if (!card) return null; - const { assignee, dueDate } = { - dueDate: '2024-11-30 12:00', - assignee: { profileImageUrl: null, nickname: 'manta', id: 1 }, - }; + // const { assignee, dueDate } = { + // dueDate: '2024-11-30 12:00', + // assignee: { profileImageUrl: null, nickname: 'manta', id: 1 }, + // }; + + const { assignee, dueDate } = card; return (
From b35e67da95d25b63fb5c36c5955c8c61d875ddeb Mon Sep 17 00:00:00 2001 From: heejin Date: Tue, 26 Nov 2024 02:12:04 +0900 Subject: [PATCH 4/7] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20extract=20observer=20t?= =?UTF-8?q?o=20hook?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../_hooks/useIntersectionObserver.ts | 30 +++++++++++++++++++ .../mydashboard/_hooks/useMyInvitations.ts | 29 +++++------------- 2 files changed, 37 insertions(+), 22 deletions(-) create mode 100644 src/app/(with-header-sidebar)/mydashboard/_hooks/useIntersectionObserver.ts diff --git a/src/app/(with-header-sidebar)/mydashboard/_hooks/useIntersectionObserver.ts b/src/app/(with-header-sidebar)/mydashboard/_hooks/useIntersectionObserver.ts new file mode 100644 index 0000000..cf6b0ac --- /dev/null +++ b/src/app/(with-header-sidebar)/mydashboard/_hooks/useIntersectionObserver.ts @@ -0,0 +1,30 @@ +import { useEffect, useRef } from 'react'; + +export const useIntersectionObserver = ( + onIntersect: () => void, + isLoading: boolean, + cursorId: number | null, + threshold: number = 0.5 +) => { + const observerRef = useRef(null); + + useEffect(() => { + const observer = new IntersectionObserver( + (entries) => { + if (entries[0].isIntersecting && cursorId != null && !isLoading) { + onIntersect(); + } + }, + { threshold } + ); + + const current = observerRef.current; + if (current) observer.observe(current); + + return () => { + if (current) observer.unobserve(current); + }; + }, [isLoading, cursorId, onIntersect]); + + return observerRef; +}; diff --git a/src/app/(with-header-sidebar)/mydashboard/_hooks/useMyInvitations.ts b/src/app/(with-header-sidebar)/mydashboard/_hooks/useMyInvitations.ts index a1c172f..8e998cb 100644 --- a/src/app/(with-header-sidebar)/mydashboard/_hooks/useMyInvitations.ts +++ b/src/app/(with-header-sidebar)/mydashboard/_hooks/useMyInvitations.ts @@ -1,5 +1,6 @@ -import { useEffect, useRef, useState } from 'react'; +import { useState, useEffect } from 'react'; import { getMyInvitations } from '../_lib/myInvitationService'; +import { useIntersectionObserver } from './useIntersectionObserver'; import type { Invitation } from '@/types/invitation'; const PAGE_SIZE = 10; @@ -9,7 +10,6 @@ export const useMyInvitations = (title?: string | null, reloadKey?: number) => { const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); const [cursorId, setCursorId] = useState(null); - const observerRef = useRef(null); const fetchMyInvitations = async () => { if (isLoading) return; @@ -25,7 +25,6 @@ export const useMyInvitations = (title?: string | null, reloadKey?: number) => { try { const response = await getMyInvitations(params); - setMyInvitations((prev) => [...prev, ...response.invitations]); setCursorId(response.cursorId || null); } catch (err) { @@ -33,34 +32,20 @@ export const useMyInvitations = (title?: string | null, reloadKey?: number) => { setError('Failed to fetch invitations'); } finally { setIsLoading(false); - setIsLoading(false); } }; useEffect(() => { setMyInvitations([]); setCursorId(null); - setIsLoading(true); fetchMyInvitations(); }, [title, reloadKey]); - useEffect(() => { - const observer = new IntersectionObserver( - (entries) => { - if (entries[0].isIntersecting && cursorId != null && !isLoading) { - fetchMyInvitations(); - } - }, - { threshold: 0.5 } - ); - - const current = observerRef.current; - if (current) observer.observe(current); - - return () => { - if (current) observer.unobserve(current); - }; - }, [isLoading]); + const observerRef = useIntersectionObserver( + fetchMyInvitations, + isLoading, + cursorId + ); return { myInvitations, From ab830cb3f98384517077563227888b41a36011c7 Mon Sep 17 00:00:00 2001 From: heejin Date: Tue, 26 Nov 2024 02:16:45 +0900 Subject: [PATCH 5/7] =?UTF-8?q?=E2=8F=AA=20revert:=20revert=20x=5Flg=20svg?= =?UTF-8?q?=20changes=20and=20modify=20color=20only?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/icons/x_lg.svg | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/public/icons/x_lg.svg b/public/icons/x_lg.svg index 016d5b5..8c79219 100644 --- a/public/icons/x_lg.svg +++ b/public/icons/x_lg.svg @@ -1,3 +1,4 @@ - - - \ No newline at end of file + + + + From 7efd79859497593fa7f02267dad12fdc9ebef4a6 Mon Sep 17 00:00:00 2001 From: heejin Date: Tue, 26 Nov 2024 02:29:39 +0900 Subject: [PATCH 6/7] =?UTF-8?q?=E2=9C=A8=20feat:=20add=20header=20setting?= =?UTF-8?q?=20onClick=20event?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/header/Header.tsx | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/components/header/Header.tsx b/src/components/header/Header.tsx index d541140..87b3dca 100644 --- a/src/components/header/Header.tsx +++ b/src/components/header/Header.tsx @@ -7,6 +7,7 @@ import Button from '../Button'; import UserInfo from './UserInfo'; import Title from './Title'; import styles from './Header.module.css'; +import useDashboardStore from '@/store/dashboardStore'; interface HeaderProps { component?: React.ComponentType; @@ -14,12 +15,17 @@ interface HeaderProps { export default function Header({ component: Component }: HeaderProps) { const router = useRouter(); + const dashboard = useDashboardStore((state) => state.dashboard); const [isMenuVisible, setIsMenuVisible] = useState(false); const handleUserInfoClick = () => { setIsMenuVisible(!isMenuVisible); }; + const handleSettingsClick = () => { + router.push(`/dashboard/${dashboard?.id}/edit`); + }; + const navigateTo = (href: string) => { router.push(href); handleUserInfoClick(); @@ -29,16 +35,18 @@ export default function Header({ component: Component }: HeaderProps) {
<div className={styles.buttonContainer}> - <Button className={styles.button}> - <Image - src="/icons/settings.svg" - alt="관리" - width={20} - height={20} - className={styles.icon} - /> - 관리 - </Button> + {dashboard?.createdByMe && ( + <Button className={styles.button} onClick={handleSettingsClick}> + <Image + src="/icons/settings.svg" + alt="관리" + width={20} + height={20} + className={styles.icon} + /> + 관리 + </Button> + )} <Button className={styles.button}> <Image src="/icons/add_box.svg" From 59110a4d7b5fbe8c8cdc397985079463d51ee544 Mon Sep 17 00:00:00 2001 From: heejin <dlwlrgkwk82@gmail.com> Date: Tue, 26 Nov 2024 02:56:42 +0900 Subject: [PATCH 7/7] =?UTF-8?q?=E2=9C=A8=20feat:=20redirect=20to=20new=20d?= =?UTF-8?q?ashboard=20main=20after=20dashboard=20is=20added?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../_components/dashboards/CreateDashboardForm.tsx | 5 ++++- src/lib/boardService.ts | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/app/(with-header-sidebar)/mydashboard/_components/dashboards/CreateDashboardForm.tsx b/src/app/(with-header-sidebar)/mydashboard/_components/dashboards/CreateDashboardForm.tsx index 32fc4f5..9c74d3d 100644 --- a/src/app/(with-header-sidebar)/mydashboard/_components/dashboards/CreateDashboardForm.tsx +++ b/src/app/(with-header-sidebar)/mydashboard/_components/dashboards/CreateDashboardForm.tsx @@ -4,6 +4,7 @@ import DashboardInput from '@/components/DashboardInput'; import styles from './CreateDashboardForm.module.css'; import { CreateDashboardRequestBody } from '@/types/dashboards'; import { createDashboard } from '@/lib/boardService'; +import { useRouter } from 'next/navigation'; interface CreateDashboardFormProps { closeModal: () => void; @@ -17,10 +18,12 @@ export default function CreateDashboardForm({ handleSubmit, formState: { errors, isValid }, } = useForm<CreateDashboardRequestBody>({ mode: 'onChange' }); + const router = useRouter(); const onSubmit = async (newDashboard: CreateDashboardRequestBody) => { - await createDashboard(newDashboard); + const response = await createDashboard(newDashboard); closeModal(); + router.push(`/dashboard/${response.id}`); }; return ( diff --git a/src/lib/boardService.ts b/src/lib/boardService.ts index db4451d..896b9ca 100644 --- a/src/lib/boardService.ts +++ b/src/lib/boardService.ts @@ -2,6 +2,7 @@ import axiosInstance from '@/lib/axiosInstance'; import { UpdateDashboardRequestParams, CreateDashboardRequestBody, + Dashboard, } from '@/types/dashboards'; export const getBoard = async (id: string) => { @@ -31,7 +32,7 @@ export const updateBoard = async ( export const createDashboard = async ({ title, color, -}: CreateDashboardRequestBody) => { +}: CreateDashboardRequestBody): Promise<Dashboard> => { try { const response = await axiosInstance.post(`/dashboards`, { title,