diff --git a/src/app/(with-header-sidebar)/dashboard/[id]/components/Card.module.css b/src/app/(with-header-sidebar)/dashboard/[id]/components/Card.module.css index 3aedcb3..6c1c53d 100644 --- a/src/app/(with-header-sidebar)/dashboard/[id]/components/Card.module.css +++ b/src/app/(with-header-sidebar)/dashboard/[id]/components/Card.module.css @@ -65,15 +65,17 @@ } .optionalInfo { + flex: 1; display: flex; - justify-content: space-between; + justify-content: flex-end; align-items: center; } .dueDateWrapper { + flex: 1; display: flex; - gap: 4px; align-items: center; + gap: 4px; width: 125px; } @@ -116,15 +118,11 @@ .contentContainer { flex-direction: row; - align-items: flex-end; + align-items: center; justify-content: space-between; gap: 0; } - .tagContainer { - max-width: 50%; - } - .imageWrapper { width: 91px; height: 53px; @@ -140,6 +138,10 @@ gap: 16px; } + .dueDateWrapper { + justify-content: flex-end; + } + .modal { width: 680px; } @@ -181,4 +183,8 @@ justify-content: space-between; gap: 0; } + + .dueDateWrapper { + justify-content: flex-start; + } } diff --git a/src/app/(with-header-sidebar)/dashboard/[id]/components/UpdateCardModal.tsx b/src/app/(with-header-sidebar)/dashboard/[id]/components/UpdateCardModal.tsx index fefedce..c57f5a0 100644 --- a/src/app/(with-header-sidebar)/dashboard/[id]/components/UpdateCardModal.tsx +++ b/src/app/(with-header-sidebar)/dashboard/[id]/components/UpdateCardModal.tsx @@ -72,7 +72,7 @@ export default function UpdateTaskModal() { options={members} setValue={setValue} defaultAssignee={ - members?.filter((member) => member.userId == card?.assignee.id)[0] + members?.filter((member) => member.userId == card?.assignee?.id)[0] } className={styles.dropdown} /> 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 index 5608cf5..507b8b4 100644 --- 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 @@ -25,9 +25,11 @@ export default function CardInfo({ card, columnTitle }: CardInfoProps) { return (
-
- -
+ {card.assignee && ( +
+ +
+ )}
diff --git a/src/app/(with-header-sidebar)/dashboard/[id]/components/card-detail/HeaderMenu.module.css b/src/app/(with-header-sidebar)/dashboard/[id]/components/card-detail/HeaderMenu.module.css index 36db845..fabc813 100644 --- a/src/app/(with-header-sidebar)/dashboard/[id]/components/card-detail/HeaderMenu.module.css +++ b/src/app/(with-header-sidebar)/dashboard/[id]/components/card-detail/HeaderMenu.module.css @@ -10,6 +10,10 @@ align-items: center; } +.moreButton:focus { + outline: none; +} + .menuContainer { position: absolute; z-index: 999; diff --git a/src/app/(with-header-sidebar)/dashboard/[id]/components/card-detail/comments/CommentDetail.tsx b/src/app/(with-header-sidebar)/dashboard/[id]/components/card-detail/comments/CommentDetail.tsx index d448371..7e41e92 100644 --- a/src/app/(with-header-sidebar)/dashboard/[id]/components/card-detail/comments/CommentDetail.tsx +++ b/src/app/(with-header-sidebar)/dashboard/[id]/components/card-detail/comments/CommentDetail.tsx @@ -1,10 +1,4 @@ -import { - useState, - ChangeEvent, - KeyboardEvent, - MouseEvent, - useRef, -} from 'react'; +import { useState, ChangeEvent, KeyboardEvent, useRef } from 'react'; import Avatar from '@/components/Avatar'; import Button from '@/components/Button'; import { formatDateToCustomFormat } from '@/utils/dateUtils'; @@ -58,7 +52,6 @@ export default function CommentDetail({ e.preventDefault(); handleSave(); toggleEditing(); - // todo: 수정성공시 토스트 박스 } }; @@ -80,7 +73,6 @@ export default function CommentDetail({ const handleDeleteOnClick = async () => { await deleteComment(id); onDelete(id); - // todo: 삭제성공시 토스트 박스 }; return ( diff --git a/src/app/(with-header-sidebar)/dashboard/[id]/edit/_components/DeleteButton.tsx b/src/app/(with-header-sidebar)/dashboard/[id]/edit/_components/DeleteButton.tsx index 17957f2..766f903 100644 --- a/src/app/(with-header-sidebar)/dashboard/[id]/edit/_components/DeleteButton.tsx +++ b/src/app/(with-header-sidebar)/dashboard/[id]/edit/_components/DeleteButton.tsx @@ -1,17 +1,21 @@ 'use client'; import { useRouter } from 'next/navigation'; -import useIdStore from '@/store/idStore'; import Button from '@/components/Button'; import { deleteDashboard } from '@/lib/boardService'; import styles from './DeleteButton.module.css'; +import useDashboardStore from '@/store/dashboardStore'; +import useDashboardTriggerStore from '@/store/dashboardTriggerStore'; export default function DeleteButton() { - const id = useIdStore((state) => state.id); + const { dashboard, setDashboard } = useDashboardStore(); + const { updateTrigger } = useDashboardTriggerStore(); const router = useRouter(); const handleClick = async () => { - await deleteDashboard(id); + await deleteDashboard(dashboard!.id.toString()); + setDashboard(null); + updateTrigger(); router.replace('/mydashboard'); }; diff --git a/src/app/(with-header-sidebar)/dashboard/[id]/edit/_components/EditForm.tsx b/src/app/(with-header-sidebar)/dashboard/[id]/edit/_components/EditForm.tsx index 7e0a6ad..dcb94bb 100644 --- a/src/app/(with-header-sidebar)/dashboard/[id]/edit/_components/EditForm.tsx +++ b/src/app/(with-header-sidebar)/dashboard/[id]/edit/_components/EditForm.tsx @@ -8,8 +8,10 @@ import { Dashboard, UpdateDashboardRequestParams } from '@/types/dashboards'; import Button from '@/components/Button'; import DashboardInput from '@/components/DashboardInput'; import styles from './EditForm.module.css'; +import useDashboardTriggerStore from '@/store/dashboardTriggerStore'; export default function EditForm() { + const { updateTrigger } = useDashboardTriggerStore(); const [board, setBoard] = useState(); const id = useIdStore((state) => state.id); const { @@ -26,6 +28,7 @@ export default function EditForm() { const onSubmit = async (data: UpdateDashboardRequestParams) => { const response = await updateBoard(id, data); + updateTrigger(); setBoard(response); }; 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 9c74d3d..a6db510 100644 --- a/src/app/(with-header-sidebar)/mydashboard/_components/dashboards/CreateDashboardForm.tsx +++ b/src/app/(with-header-sidebar)/mydashboard/_components/dashboards/CreateDashboardForm.tsx @@ -1,10 +1,11 @@ import { useForm } from 'react-hook-form'; import Button from '@/components/Button'; import DashboardInput from '@/components/DashboardInput'; -import styles from './CreateDashboardForm.module.css'; -import { CreateDashboardRequestBody } from '@/types/dashboards'; +import type { CreateDashboardRequestBody } from '@/types/dashboards'; import { createDashboard } from '@/lib/boardService'; import { useRouter } from 'next/navigation'; +import useDashboardTriggerStore from '@/store/dashboardTriggerStore'; +import styles from './CreateDashboardForm.module.css'; interface CreateDashboardFormProps { closeModal: () => void; @@ -13,6 +14,8 @@ interface CreateDashboardFormProps { export default function CreateDashboardForm({ closeModal, }: CreateDashboardFormProps) { + const { updateTrigger } = useDashboardTriggerStore(); + const { register, handleSubmit, @@ -23,6 +26,7 @@ export default function CreateDashboardForm({ const onSubmit = async (newDashboard: CreateDashboardRequestBody) => { const response = await createDashboard(newDashboard); closeModal(); + updateTrigger(); router.push(`/dashboard/${response.id}`); }; diff --git a/src/app/(with-header-sidebar)/mydashboard/_components/invitations/MyInvitationCard.tsx b/src/app/(with-header-sidebar)/mydashboard/_components/invitations/MyInvitationCard.tsx index 51eede0..e8ba325 100644 --- a/src/app/(with-header-sidebar)/mydashboard/_components/invitations/MyInvitationCard.tsx +++ b/src/app/(with-header-sidebar)/mydashboard/_components/invitations/MyInvitationCard.tsx @@ -3,8 +3,8 @@ import type { Invitation, AcceptMyInvitationRequestBody, } from '@/types/invitation'; -import styles from './MyInvitationCard.module.css'; import { updateMyInvitation } from '../../_lib/myInvitationService'; +import styles from './MyInvitationCard.module.css'; interface MyInvitationCardProps extends Invitation { onActionComplete: () => void; diff --git a/src/app/(with-header-sidebar)/mydashboard/_components/modal/Modal.module.css b/src/app/(with-header-sidebar)/mydashboard/_components/modal/Modal.module.css index fad776c..21ffc08 100644 --- a/src/app/(with-header-sidebar)/mydashboard/_components/modal/Modal.module.css +++ b/src/app/(with-header-sidebar)/mydashboard/_components/modal/Modal.module.css @@ -14,7 +14,9 @@ } .container { + max-height: 580px; min-width: 330px; + overflow-y: auto; display: flex; flex-direction: column; align-items: flex-start; @@ -98,6 +100,7 @@ @media screen and (min-width: 768px) { .container { min-width: 580px; + max-height: 850px; padding: 32px; } diff --git a/src/app/(with-header-sidebar)/mydashboard/_hooks/useDashboards.ts b/src/app/(with-header-sidebar)/mydashboard/_hooks/useDashboards.ts index f03e3ea..a8289c8 100644 --- a/src/app/(with-header-sidebar)/mydashboard/_hooks/useDashboards.ts +++ b/src/app/(with-header-sidebar)/mydashboard/_hooks/useDashboards.ts @@ -8,7 +8,7 @@ interface UseDashboardsParams { export default function useDashboards({ pageSize }: UseDashboardsParams) { const [page, setPage] = useState(1); - const { data } = useApi('/dashboards', { + const { data, refetch } = useApi('/dashboards', { method: 'GET', params: { navigationMethod: 'pagination', page, size: pageSize }, }); @@ -25,5 +25,5 @@ export default function useDashboards({ pageSize }: UseDashboardsParams) { }); }; - return { page, dashboards, totalPages, handlePageChange }; + return { page, dashboards, totalPages, handlePageChange, refetch }; } diff --git a/src/app/(with-header-sidebar)/mydashboard/_lib/myInvitationService.ts b/src/app/(with-header-sidebar)/mydashboard/_lib/myInvitationService.ts index 7c6c282..28f613c 100644 --- a/src/app/(with-header-sidebar)/mydashboard/_lib/myInvitationService.ts +++ b/src/app/(with-header-sidebar)/mydashboard/_lib/myInvitationService.ts @@ -1,4 +1,5 @@ import axiosInstance from '@/lib/axiosInstance'; +import { toast } from '@/store/toastStore'; import { GetMyInvitationsRequestParam, GetMyInvitationsResponse, @@ -45,6 +46,10 @@ export const updateMyInvitation = async ({ requestBody ); + toast.success({ + message: `${requestBody.inviteAccepted ? '수락' : '거절'} 완료!`, + }); + return response.data; } catch (error) { throw error; diff --git a/src/components/card/ColumnLabel.module.css b/src/components/card/ColumnLabel.module.css index d3b51b3..bddb91d 100644 --- a/src/components/card/ColumnLabel.module.css +++ b/src/components/card/ColumnLabel.module.css @@ -8,4 +8,5 @@ display: flex; gap: 6px; padding: 4px 10px; + white-space: nowrap; } diff --git a/src/components/header/DashboardMembers.module.css b/src/components/header/DashboardMembers.module.css index a93dd07..dbad012 100644 --- a/src/components/header/DashboardMembers.module.css +++ b/src/components/header/DashboardMembers.module.css @@ -2,6 +2,7 @@ display: flex; align-items: center; gap: 0; + transform: translateX(5px); } .avatar, @@ -13,3 +14,9 @@ color: #d25b68; background: #f4d7da; } + +@media screen and (min-width: 768px) { + .avatarWrapper { + transform: none; + } +} diff --git a/src/components/header/InvitationButton.module.css b/src/components/header/InvitationButton.module.css index fb2450d..0a195fb 100644 --- a/src/components/header/InvitationButton.module.css +++ b/src/components/header/InvitationButton.module.css @@ -15,3 +15,13 @@ .button.button:hover { background-color: var(--violet-light); } + +.icon { + display: none; +} + +@media screen and (min-width: 768px) { + .icon { + display: inline-block; + } +} diff --git a/src/components/header/UserSection.module.css b/src/components/header/UserSection.module.css index b012e5b..38bf1a4 100644 --- a/src/components/header/UserSection.module.css +++ b/src/components/header/UserSection.module.css @@ -20,7 +20,8 @@ .myMenu { position: absolute; top: 75px; - right: 0; + right: 10px; + z-index: 999; } @media screen and (min-width: 768px) { diff --git a/src/components/header/UserSection.tsx b/src/components/header/UserSection.tsx index 5199fea..e66a064 100644 --- a/src/components/header/UserSection.tsx +++ b/src/components/header/UserSection.tsx @@ -1,17 +1,22 @@ +import { useRef, useEffect } from 'react'; import UserInfo from './UserInfo'; import Button from '../Button'; import type { Menu } from '@/types/menu'; import { useMenu } from '@/hooks/useMenu'; import { useRouter } from 'next/navigation'; import MenuDropdown from '../MenuDropdown'; -import styles from './UserSection.module.css'; import useMe from '@/hooks/useMe'; +import useClickOutside from '@/hooks/useClickOutside'; +import styles from './UserSection.module.css'; export default function UserSection() { const router = useRouter(); const { clearUser } = useMe(); const { isMenuVisible, toggleMenu, closeMenu } = useMenu(); + const containerRef = useRef(null); + useClickOutside(containerRef, closeMenu); + const navigateTo = (href: string) => { router.push(href); closeMenu(); @@ -29,7 +34,7 @@ export default function UserSection() { ]; return ( -
+
diff --git a/src/components/header/skeleton/UserInfoSkeleton.module.css b/src/components/header/skeleton/UserInfoSkeleton.module.css index 4afea0f..7b0fcef 100644 --- a/src/components/header/skeleton/UserInfoSkeleton.module.css +++ b/src/components/header/skeleton/UserInfoSkeleton.module.css @@ -8,6 +8,34 @@ background-color: var(--gray-300); } +.userIcon::before { + content: ''; + position: absolute; + top: 0; + left: -100%; + width: 50%; + height: 100%; + background: linear-gradient( + 90deg, + rgba(255, 255, 255, 0.2) 0%, + rgba(255, 255, 255, 0.6) 50%, + rgba(255, 255, 255, 0.2) 100% + ); + animation: shine 1.5s infinite; +} + +@keyframes shine { + 0% { + left: -100%; + } + 50% { + left: 100%; + } + 100% { + left: -100%; + } +} + @media screen and (min-width: 768px) { .userInfo { display: flex; diff --git a/src/components/sidebar/Dashboards.tsx b/src/components/sidebar/Dashboards.tsx index e688f85..ff02fc3 100644 --- a/src/components/sidebar/Dashboards.tsx +++ b/src/components/sidebar/Dashboards.tsx @@ -6,13 +6,22 @@ import Button from '../Button'; import useDashboards from '@/app/(with-header-sidebar)/mydashboard/_hooks/useDashboards'; import useDashboardStore from '@/store/dashboardStore'; import styles from './Dashboards.module.css'; +import { useEffect } from 'react'; +import useDashboardTriggerStore from '@/store/dashboardTriggerStore'; const PAGE_SIZE = 12; export default function Dashboards() { - const { page, dashboards, totalPages, handlePageChange } = useDashboards({ - pageSize: PAGE_SIZE, - }); + const { trigger } = useDashboardTriggerStore(); + + const { page, dashboards, totalPages, handlePageChange, refetch } = + useDashboards({ + pageSize: PAGE_SIZE, + }); + + useEffect(() => { + refetch(); + }, [trigger]); if (dashboards.length === 0) { return null; diff --git a/src/hooks/useClickOutside.ts b/src/hooks/useClickOutside.ts new file mode 100644 index 0000000..5e743fb --- /dev/null +++ b/src/hooks/useClickOutside.ts @@ -0,0 +1,23 @@ +import { useEffect } from 'react'; + +type ClickOutsideCallback = () => void; + +function useClickOutside( + ref: React.RefObject, + callback: ClickOutsideCallback +) { + 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); + }; + }, [ref, callback]); +} + +export default useClickOutside; diff --git a/src/lib/boardService.ts b/src/lib/boardService.ts index 0f730ba..a391173 100644 --- a/src/lib/boardService.ts +++ b/src/lib/boardService.ts @@ -24,7 +24,7 @@ export const updateBoard = async ( title, color, }); - toast.success({ message: '변경되었습니다.' }); + toast.success({ message: '대시보드가 수정되었습니다.' }); return response.data; } catch (error) { if (error instanceof Error) { @@ -42,6 +42,7 @@ export const createDashboard = async ({ title, color, }); + toast.success({ message: '대시보드가 생성되었습니다.' }); return response.data; } catch (error) { throw error; diff --git a/src/lib/commentService.ts b/src/lib/commentService.ts index 88316aa..86cb584 100644 --- a/src/lib/commentService.ts +++ b/src/lib/commentService.ts @@ -1,4 +1,5 @@ import axiosInstance from '@/lib/axiosInstance'; +import { toast } from '@/store/toastStore'; import type { CreateCommentRequestBody, Comment, @@ -50,6 +51,8 @@ export const updateComment = async ({ const response = await axiosInstance.put(`/comments/${commentId}`, { ...data, }); + toast.success({ message: '댓글이 수정되었습니다.' }); + return response.data; } catch (error) { throw error; @@ -59,6 +62,7 @@ export const updateComment = async ({ export const deleteComment = async (commentId: number) => { try { await axiosInstance.delete(`/comments/${commentId}`); + toast.success({ message: '댓글이 삭제되었습니다.' }); } catch (error) { throw error; } diff --git a/src/store/dashboardTriggerStore.ts b/src/store/dashboardTriggerStore.ts new file mode 100644 index 0000000..46f30ce --- /dev/null +++ b/src/store/dashboardTriggerStore.ts @@ -0,0 +1,13 @@ +import { create } from 'zustand'; + +interface DashboardTriggerStore { + trigger: boolean; + updateTrigger: () => void; +} + +const useDashboardTriggerStore = create((set) => ({ + trigger: false, + updateTrigger: () => set((state) => ({ trigger: !state.trigger })), +})); + +export default useDashboardTriggerStore; diff --git a/src/store/toastStore.ts b/src/store/toastStore.ts index c2e360e..6a8928c 100644 --- a/src/store/toastStore.ts +++ b/src/store/toastStore.ts @@ -34,7 +34,7 @@ const useToastStore = create((set) => ({ type: ToastState['type'], duration = 2000, showButton = true, - theme = 'dark' + theme = 'light' ) => { const id = Date.now(); @@ -158,7 +158,7 @@ const createToast = message, duration = 2000, showButton = true, - theme = 'dark', + theme = 'light', }: CreateToastProps) => useToastStore .getState()