diff --git a/public/icon/search_icon.svg b/public/icon/search_icon.svg new file mode 100644 index 0000000..063f9a4 --- /dev/null +++ b/public/icon/search_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/mydashboardPage/empty_invitation.svg b/public/mydashboardPage/empty_invitation.svg new file mode 100644 index 0000000..13a25dc --- /dev/null +++ b/public/mydashboardPage/empty_invitation.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/app/(after-login)/dashboard/[dashboardid]/_components/Column.tsx b/src/app/(after-login)/dashboard/[dashboardid]/_components/Column.tsx index 2eecc34..574239b 100644 --- a/src/app/(after-login)/dashboard/[dashboardid]/_components/Column.tsx +++ b/src/app/(after-login)/dashboard/[dashboardid]/_components/Column.tsx @@ -1,6 +1,6 @@ "use client"; -import { useCallback, useEffect, useState, useRef } from "react"; +import { useEffect, useState, useRef } from "react"; import { useIntersection } from "@/lib/hooks/useIntersection"; import { DashboardColumn, TaskCardList } from "@/lib/types"; import { fetchTaskCardList } from "@/lib/apis/cardsApi"; @@ -19,7 +19,7 @@ export default function Column({ id, title }: DashboardColumn) { const observerRef = useRef(null); const accessToken = localStorage.getItem("accessToken") ?? ""; - const handleLoad = useCallback(async () => { + const handleLoad = async () => { if (isLoading || isLast) return; setIsLoading(true); @@ -45,11 +45,11 @@ export default function Column({ id, title }: DashboardColumn) { } finally { setIsLoading(false); } - }, [accessToken, isLoading, isLast, cursorId, id]); + }; useEffect(() => { handleLoad(); - }, [handleLoad]); + }, []); useIntersection({ target: observerRef, diff --git a/src/app/(after-login)/dashboard/[dashboardid]/edit/_components/DeleteButton.tsx b/src/app/(after-login)/dashboard/[dashboardid]/edit/_components/DeleteButton.tsx index 42314ac..a1f94b5 100644 --- a/src/app/(after-login)/dashboard/[dashboardid]/edit/_components/DeleteButton.tsx +++ b/src/app/(after-login)/dashboard/[dashboardid]/edit/_components/DeleteButton.tsx @@ -29,7 +29,7 @@ export default function DeleteButton({ diff --git a/src/app/(after-login)/dashboard/[dashboardid]/edit/_components/InvitationSection.tsx b/src/app/(after-login)/dashboard/[dashboardid]/edit/_components/InvitationSection.tsx index a812ee8..051c730 100644 --- a/src/app/(after-login)/dashboard/[dashboardid]/edit/_components/InvitationSection.tsx +++ b/src/app/(after-login)/dashboard/[dashboardid]/edit/_components/InvitationSection.tsx @@ -3,7 +3,7 @@ import { useEffect, useState } from "react"; import { Invitation } from "@/lib/types"; import { fetchInvitationList } from "@/lib/apis/dashboardsApi"; -import Pagination from "@/components/common/pagenation-button/PagenationButton"; +import Pagination from "@/components/common/pagination-button/PaginationButton"; import InvitationCard from "./InvitationCard"; import InviteModalButton from "./InviteModalButton"; diff --git a/src/app/(after-login)/dashboard/[dashboardid]/edit/_components/MemberSection.tsx b/src/app/(after-login)/dashboard/[dashboardid]/edit/_components/MemberSection.tsx index 27d976d..b9d5b5b 100644 --- a/src/app/(after-login)/dashboard/[dashboardid]/edit/_components/MemberSection.tsx +++ b/src/app/(after-login)/dashboard/[dashboardid]/edit/_components/MemberSection.tsx @@ -1,7 +1,7 @@ "use client"; import { useEffect, useState } from "react"; -import Pagination from "@/components/common/pagenation-button/PagenationButton"; +import Pagination from "@/components/common/pagination-button/PaginationButton"; import { DashboardMember } from "@/lib/types"; import Button from "@/components/common/button/Button"; import UserIcon from "@/components/common/user-icon/UserIcon"; @@ -64,7 +64,6 @@ export default function MemberSection({ setIsLoading(true); await deleteDashboardMember({ token, - dashboardId: id, memberId, }); @@ -126,7 +125,7 @@ export default function MemberSection({ img={item.profileImageUrl} size="md" /> - {item.email} + {item.nickname}

내 대시보드

- {loading ? ( -

로딩 중...

- ) : myDashboards.length > 0 ? ( + {myDashboards.length > 0 ? ( ) : ( -
-

아직 생성한 대시보드가 없어요.

+ !loading && ( +
+

아직 생성한 대시보드가 없어요.

+
+ ) + )} + + {loading && ( +
+
)} + +
+ setPage((prev) => Math.max(prev - 1, 1))} + onNextClick={() => + setPage((prev) => Math.min(prev + 1, totalPages)) + } + /> +
- +
); } diff --git a/src/app/(after-login)/mydashboard/_components/InvitationCard.tsx b/src/app/(after-login)/mydashboard/_components/InvitationCard.tsx new file mode 100644 index 0000000..edd884c --- /dev/null +++ b/src/app/(after-login)/mydashboard/_components/InvitationCard.tsx @@ -0,0 +1,82 @@ +import { useRouter } from "next/navigation"; +import { useDashboardStore } from "@/lib/store/useDashboardStore"; +import { Invitation } from "@/lib/types"; +import { putInvitation } from "@/lib/apis/invitationsApi"; +import Button from "@/components/common/button/Button"; + +type InvitationCardProps = Invitation & { + token: string; +}; + +export default function InvitationCard({ + id, + inviter, + dashboard, + token, +}: InvitationCardProps) { + const newDashboardId = String(dashboard.id); + const router = useRouter(); + const setDashboardId = useDashboardStore((state) => state.setDashboardId); + + const handleApproveInvite = async () => { + await putInvitation({ + token, + invitationId: id, + inviteAccepted: true, + }); + + router.push(`/dashboard/${newDashboardId}`); + setDashboardId(newDashboardId); + }; + + const handleRejectInvite = async () => { + await putInvitation({ + token, + invitationId: id, + inviteAccepted: false, + }); + + window.location.reload(); + }; + + return ( +
+
+
+
+ 이름 +
+
+ {dashboard.title} +
+
+
+
+ 초대자 +
+
+ {inviter.nickname} +
+
+
+
+ + +
+
+ ); +} diff --git a/src/app/(after-login)/mydashboard/_components/InvitationSection.tsx b/src/app/(after-login)/mydashboard/_components/InvitationSection.tsx index 69824d9..b5e8c22 100644 --- a/src/app/(after-login)/mydashboard/_components/InvitationSection.tsx +++ b/src/app/(after-login)/mydashboard/_components/InvitationSection.tsx @@ -1,17 +1,128 @@ +"use client"; + +import { useEffect, useState, useRef } from "react"; +import { useIntersection } from "@/lib/hooks/useIntersection"; +import { Invitation } from "@/lib/types"; +import { fetchInvitationList } from "@/lib/apis/invitationsApi"; +import Input from "@/components/common/input/Input"; +import InvitationCard from "./InvitationCard"; +import Image from "next/image"; +import SearchIcon from "../../../../../public/icon/search_icon.svg"; +import EmptyImg from "../../../../../public/mydashboardPage/empty_invitation.svg"; + +const PAGE_SIZE = 6; + export default function InvitationSection({ token }: { token: string }) { + const [inputValue, setInputValue] = useState(""); + const [searchQuery, setSearchQuery] = useState(""); + const [items, setItems] = useState([]); + const [cursorId, setCursorId] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [isLast, setIsLast] = useState(false); + const observerRef = useRef(null); + + const handleLoad = async () => { + if (isLoading || isLast) return; + setIsLoading(true); + + try { + const { invitations: newInvitations, cursorId: nextCursorId } = + await fetchInvitationList({ + token: token, + size: PAGE_SIZE, + cursorId, + title: searchQuery, + }); + + setItems((prev) => [...prev, ...newInvitations]); + setCursorId(nextCursorId); + + if (newInvitations.length < PAGE_SIZE || nextCursorId === null) { + setIsLast(true); + } + } finally { + setIsLoading(false); + } + }; + + useEffect(() => { + setItems([]); + setCursorId(null); + setIsLast(false); + }, [searchQuery]); + + useEffect(() => { + handleLoad(); + }, [items, cursorId]); + + useIntersection({ + target: observerRef, + onIntersect: handleLoad, + disabled: isLast, + }); + + const handleChange = (e: React.ChangeEvent) => { + setInputValue(e.target.value); + }; + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === "Enter") { + setSearchQuery(inputValue); + } + }; + return ( -
-
-
-

- 초대받은 대시보드 -

-

{token}

-
-
-
-
+
+
+

+ 초대받은 대시보드 +

+
+ {items.length === 0 && ( +
+
+ +
+
+ 아직 초대받은 대시보드가 없어요 +
+
+ )} + {items.length > 0 && ( + <> +
+
+ 이름 + 초대자 + 수락 여부 +
+
+ {items.map((item, index) => ( +
+ +
+ ))} +
+
+ + )}
); } diff --git a/src/components/common/pagenation-button/PagenationButton.tsx b/src/components/common/pagination-button/PaginationButton.tsx similarity index 100% rename from src/components/common/pagenation-button/PagenationButton.tsx rename to src/components/common/pagination-button/PaginationButton.tsx diff --git a/src/components/modal/task-detail/CommentCard.tsx b/src/components/modal/task-detail/CommentCard.tsx index 3bf651c..56c7abf 100644 --- a/src/components/modal/task-detail/CommentCard.tsx +++ b/src/components/modal/task-detail/CommentCard.tsx @@ -49,8 +49,8 @@ export default function CommentCard({ onChange(); }; - const handleDeleteComment = () => { - deleteComment({ + const handleDeleteComment = async () => { + await deleteComment({ token: accessToken, commentId: id, }); diff --git a/src/lib/apis/invitationsApi.ts b/src/lib/apis/invitationsApi.ts new file mode 100644 index 0000000..a93330b --- /dev/null +++ b/src/lib/apis/invitationsApi.ts @@ -0,0 +1,54 @@ +import { BASE_URL } from "@/lib/constants/urls"; + +export async function fetchInvitationList({ + token, + size, + cursorId, + title, +}: { + token: string; + size: number; + cursorId: number | null; + title: string; +}) { + let query = `size=${size}`; + if (cursorId !== null) { + query += `&cursorId=${cursorId}`; + } + if (title !== "") { + query += `&title=${title}`; + } + const res = await fetch(`${BASE_URL}/invitations?${query}`, { + headers: { + Accept: "application/json", + Authorization: `Bearer ${token}`, + }, + cache: "no-store", + }); + + return res.json(); +} + +export async function putInvitation({ + token, + invitationId, + inviteAccepted, +}: { + token: string; + invitationId: number; + inviteAccepted: boolean; +}) { + const res = await fetch(`${BASE_URL}/invitations/${invitationId}`, { + method: "PUT", + headers: { + Accept: "application/json", + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + inviteAccepted, + }), + }); + + return res.json(); +} diff --git a/src/lib/apis/membersApi.ts b/src/lib/apis/membersApi.ts index dfe4b10..345715d 100644 --- a/src/lib/apis/membersApi.ts +++ b/src/lib/apis/membersApi.ts @@ -29,14 +29,12 @@ export async function fetchDashboardMember({ export async function deleteDashboardMember({ token, - dashboardId, memberId, }: { token: string; - dashboardId: number; memberId: number; }) { - await fetch(`${BASE_URL}/dashboards/${dashboardId}/members/${memberId}`, { + await fetch(`${BASE_URL}/members/${memberId}`, { method: "DELETE", headers: { Accept: "application/json",