From 5d49f3db4f373193053e4a8635c1bd63b3f0bc6d Mon Sep 17 00:00:00 2001 From: yukicoder0509 Date: Sun, 19 Jan 2025 00:03:10 +0800 Subject: [PATCH 01/15] feat: add timeline modal prototype --- .../Conversation/Issues/IssueCard.tsx | 33 ++++++++++-- .../Conversation/Issues/TimelineModal.tsx | 52 +++++++++++++++++++ 2 files changed, 81 insertions(+), 4 deletions(-) create mode 100644 src/components/Conversation/Issues/TimelineModal.tsx diff --git a/src/components/Conversation/Issues/IssueCard.tsx b/src/components/Conversation/Issues/IssueCard.tsx index e274762..c986462 100644 --- a/src/components/Conversation/Issues/IssueCard.tsx +++ b/src/components/Conversation/Issues/IssueCard.tsx @@ -1,17 +1,24 @@ "use client"; -import { RectangleStackIcon } from "@heroicons/react/24/outline"; -import { InformationCircleIcon } from "@heroicons/react/24/outline"; +import { + RectangleStackIcon, + InformationCircleIcon, + FilmIcon, +} from "@heroicons/react/24/outline"; import Link from "next/link"; import EmptyIssueCard from "@/components/Conversation/Issues/EmptyIssueCard"; import type { Issue } from "@/types/conversations.types"; -import { Tooltip } from "@mantine/core"; +import { Tooltip, Button } from "@mantine/core"; +import { useState } from "react"; +import TimelineModal from "@/components/Conversation/Issues/TimelineModal"; type IssueCardProps = { issue: Issue; }; export default function IssueCard({ issue }: IssueCardProps) { + const [isTimelimeModalOpen, setIsTimelimeModalOpen] = useState(false); + return (

{issue.title}

@@ -34,12 +41,30 @@ export default function IssueCard({ issue }: IssueCardProps) {
查看所有事實
+
+ +
+
) : ( diff --git a/src/components/Conversation/Issues/TimelineModal.tsx b/src/components/Conversation/Issues/TimelineModal.tsx new file mode 100644 index 0000000..0f555bf --- /dev/null +++ b/src/components/Conversation/Issues/TimelineModal.tsx @@ -0,0 +1,52 @@ +"use client"; + +import { Modal, Timeline } from "@mantine/core"; +import type { Dispatch, SetStateAction } from "react"; + +type TimeLineModalProps = { + isOpen: boolean; + setIsOpen: Dispatch>; + issueTitle: string; +}; + +export default function TimeLineModal({ + isOpen, + setIsOpen, + issueTitle, +}: TimeLineModalProps) { + return ( + setIsOpen(false)} + title={

{`《${issueTitle}》的演進`}

} + classNames={{ + content: "w-[620px]", + }} + > + + + + + } + title="first event" + > + This is the first event + + + This is the second event + + + This is the third event + + +
+ ); +} From d542602f464b4bb6132c5367d381de4927f38db1 Mon Sep 17 00:00:00 2001 From: yukicoder0509 Date: Sun, 19 Jan 2025 15:45:25 +0800 Subject: [PATCH 02/15] feat: add
--- .../Conversation/Issues/TimelineModal.tsx | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/components/Conversation/Issues/TimelineModal.tsx b/src/components/Conversation/Issues/TimelineModal.tsx index 0f555bf..94c0e28 100644 --- a/src/components/Conversation/Issues/TimelineModal.tsx +++ b/src/components/Conversation/Issues/TimelineModal.tsx @@ -26,15 +26,18 @@ export default function TimeLineModal({ - - +
+ + + +
+
} title="first event" > From 72a095462840d9de297d71c9615d0889f94b2996 Mon Sep 17 00:00:00 2001 From: yukicoder0509 Date: Sun, 19 Jan 2025 16:46:19 +0800 Subject: [PATCH 03/15] feat: integrate timeline api --- src/app/issues/[id]/page.tsx | 10 ++- .../Conversation/Issues/IssueCard.tsx | 5 +- .../Conversation/Issues/TimelineModal.tsx | 66 +++++++++++-------- src/lib/requests/timeline/getIssueTimeline.ts | 46 +++++++++++++ 4 files changed, 99 insertions(+), 28 deletions(-) create mode 100644 src/lib/requests/timeline/getIssueTimeline.ts diff --git a/src/app/issues/[id]/page.tsx b/src/app/issues/[id]/page.tsx index 40052b6..4c09f76 100644 --- a/src/app/issues/[id]/page.tsx +++ b/src/app/issues/[id]/page.tsx @@ -4,6 +4,10 @@ import type { Metadata } from "next"; import { cookies } from "next/headers"; import type { Issue } from "@/types/conversations.types"; import { getIssue } from "@/lib/requests/issues/getIssue"; +import { + getIssueTimeline, + type getIssueTimelineResponse, +} from "@/lib/requests/timeline/getIssueTimeline"; import AddViewpointBar from "@/components/Conversation/Viewpoints/AddViewpointBar"; type IssueViewProps = { @@ -36,11 +40,15 @@ export default async function IssueView({ params }: IssueViewProps) { const auth_token = cookieStore.get("auth_token")?.value || ""; const issue: Issue = await getIssue({ issueId, auth_token }); + const timeline: getIssueTimelineResponse = await getIssueTimeline({ + issueId, + user_token: auth_token, + }); return (
- +
diff --git a/src/components/Conversation/Issues/IssueCard.tsx b/src/components/Conversation/Issues/IssueCard.tsx index c986462..f0bd3d3 100644 --- a/src/components/Conversation/Issues/IssueCard.tsx +++ b/src/components/Conversation/Issues/IssueCard.tsx @@ -8,15 +8,17 @@ import { import Link from "next/link"; import EmptyIssueCard from "@/components/Conversation/Issues/EmptyIssueCard"; import type { Issue } from "@/types/conversations.types"; +import type { TimelineNode } from "@/lib/requests/timeline/getIssueTimeline"; import { Tooltip, Button } from "@mantine/core"; import { useState } from "react"; import TimelineModal from "@/components/Conversation/Issues/TimelineModal"; type IssueCardProps = { issue: Issue; + timeline: TimelineNode[]; }; -export default function IssueCard({ issue }: IssueCardProps) { +export default function IssueCard({ issue, timeline }: IssueCardProps) { const [isTimelimeModalOpen, setIsTimelimeModalOpen] = useState(false); return ( @@ -64,6 +66,7 @@ export default function IssueCard({ issue }: IssueCardProps) { isOpen={isTimelimeModalOpen} setIsOpen={setIsTimelimeModalOpen} issueTitle={issue.title} + timeline={timeline} />
) : ( diff --git a/src/components/Conversation/Issues/TimelineModal.tsx b/src/components/Conversation/Issues/TimelineModal.tsx index 94c0e28..ee59eb7 100644 --- a/src/components/Conversation/Issues/TimelineModal.tsx +++ b/src/components/Conversation/Issues/TimelineModal.tsx @@ -2,17 +2,20 @@ import { Modal, Timeline } from "@mantine/core"; import type { Dispatch, SetStateAction } from "react"; +import type { TimelineNode } from "@/lib/requests/timeline/getIssueTimeline"; type TimeLineModalProps = { isOpen: boolean; setIsOpen: Dispatch>; issueTitle: string; + timeline: TimelineNode[]; }; export default function TimeLineModal({ isOpen, setIsOpen, issueTitle, + timeline, }: TimeLineModalProps) { return ( - - - - - -
- - } - title="first event" - > - This is the first event -
- - This is the second event - - - This is the third event - + + {timeline.map((node) => ( + + + + +
+ + } + title={node.title} + > + {node.description} +
+ ))}
); diff --git a/src/lib/requests/timeline/getIssueTimeline.ts b/src/lib/requests/timeline/getIssueTimeline.ts new file mode 100644 index 0000000..72c9dba --- /dev/null +++ b/src/lib/requests/timeline/getIssueTimeline.ts @@ -0,0 +1,46 @@ +import { parseJsonWhileHandlingErrors } from "../transformers"; + +export type TimelineNode = { + id: string; + createdAt: Date; + updatedAt: Date; + title: string; + description: string; + date: Date; +}; + +export type getIssueTimelineResponse = { + content: TimelineNode[]; +}; + +type getIssueTimelineParams = { + issueId: string; + user_token: string; +}; + +export function getIssueTimeline({ + issueId, + user_token, +}: getIssueTimelineParams): Promise { + return fetch( + `${process.env.NEXT_PUBLIC_BACKEND_URL}/api/issue/${issueId}/timeline`, + { + method: "GET", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${user_token}`, + }, + }, + ) + .then(parseJsonWhileHandlingErrors) + .then((res: getIssueTimelineResponse) => { + return { + content: res.content.map((node) => ({ + ...node, + createdAt: new Date(node.createdAt), + updatedAt: new Date(node.updatedAt), + date: new Date(node.date), + })), + }; + }); +} From b95294790fbe31d67926786815c26f91a83eff41 Mon Sep 17 00:00:00 2001 From: yukicoder0509 Date: Mon, 20 Jan 2025 16:35:55 +0800 Subject: [PATCH 04/15] feat: add mock timeline and sort the timeline node by datetime --- .../Conversation/Issues/TimelineModal.tsx | 15 ++-- src/mock/conversationMock.ts | 69 +++++++++++++++++++ src/types/conversations.types.ts | 9 +++ 3 files changed, 87 insertions(+), 6 deletions(-) diff --git a/src/components/Conversation/Issues/TimelineModal.tsx b/src/components/Conversation/Issues/TimelineModal.tsx index ee59eb7..5cf1e8e 100644 --- a/src/components/Conversation/Issues/TimelineModal.tsx +++ b/src/components/Conversation/Issues/TimelineModal.tsx @@ -3,6 +3,7 @@ import { Modal, Timeline } from "@mantine/core"; import type { Dispatch, SetStateAction } from "react"; import type { TimelineNode } from "@/lib/requests/timeline/getIssueTimeline"; +import { mockTimeline } from "@/mock/conversationMock"; type TimeLineModalProps = { isOpen: boolean; @@ -17,26 +18,28 @@ export default function TimeLineModal({ issueTitle, timeline, }: TimeLineModalProps) { + const sortedTimeline = mockTimeline.sort( + (a, b) => new Date(a.date).getTime() - new Date(b.date).getTime(), + ); + return ( setIsOpen(false)} title={

{`《${issueTitle}》的演進`}

} - classNames={{ - content: "w-[620px]", - }} + size="620px" > - {timeline.map((node) => ( + {sortedTimeline.map((node) => ( } - title={node.title} + title={`${node.date.toLocaleDateString()} ${node.title}`} > {node.description} diff --git a/src/mock/conversationMock.ts b/src/mock/conversationMock.ts index c9fa91b..23b5a44 100644 --- a/src/mock/conversationMock.ts +++ b/src/mock/conversationMock.ts @@ -4,6 +4,7 @@ import type { ViewPoint, FactReference, Reply, + TimelineNode, } from "@/types/conversations.types"; import { Reaction } from "@/types/conversations.types"; import type { User } from "@/types/users.types"; @@ -133,3 +134,71 @@ export const mockReply: Reply = { quotes: [], title: "Example Reply", }; + +export const mockTimeline: TimelineNode[] = [ + { + id: "00000000-0000-0000-0000-000000000010", + createdAt: new Date(), + updatedAt: new Date(), + title: "Example Timeline Node 1", + description: "This is an example timeline node description.", + date: new Date(2023, 9, 1, 12, 0, 0), + }, + { + id: "00000000-0000-0000-0000-000000000011", + createdAt: new Date(), + updatedAt: new Date(), + title: "Example Timeline Node 2", + description: "This is an example timeline node description.", + date: new Date(2020, 0, 1, 12, 0, 0), + }, + { + id: "00000000-0000-0000-0000-000000000012", + createdAt: new Date(), + updatedAt: new Date(), + title: "Example Timeline Node 3", + description: "This is an example timeline node description.", + date: new Date(2024, 4, 9, 5, 0, 0), + }, + { + id: "00000000-0000-0000-0000-000000000013", + createdAt: new Date(), + updatedAt: new Date(), + title: "Example Timeline Node 4", + description: + "This is an example timeline node description. A longer description to test the layout. lorem ipsum dolor sit amet, consectetur adipiscing elit. lorem ipsum dolor sit amet, consectetur adipiscing elit. lorem ipsum dolor sit amet, consectetur adipiscing elit", + date: new Date(2023, 1, 8, 7, 23, 1), + }, + { + id: "00000000-0000-0000-0000-000000000014", + createdAt: new Date(), + updatedAt: new Date(), + title: "Example Timeline Node 5", + description: "This is an example timeline node description.", + date: new Date(2025, 0, 20, 4, 30, 0, 0), + }, + { + id: "00000000-0000-0000-0000-000000000015", + createdAt: new Date(), + updatedAt: new Date(), + title: "Example Timeline Node 6", + description: "This is an example timeline node description.", + date: new Date(2024, 10, 27, 11, 27, 0, 0), + }, + { + id: "00000000-0000-0000-0000-000000000016", + createdAt: new Date(), + updatedAt: new Date(), + title: "Example Timeline Node 7", + description: "This is an example timeline node description.", + date: new Date(2023, 0, 1, 1, 1, 1, 1), + }, + { + id: "00000000-0000-0000-0000-000000000017", + createdAt: new Date(), + updatedAt: new Date(), + title: "Example Timeline Node 8", + description: "This is an example timeline node description.", + date: new Date(2023, 0, 5, 6, 7, 8, 8), + }, +]; diff --git a/src/types/conversations.types.ts b/src/types/conversations.types.ts index 0a10f23..39634ee 100644 --- a/src/types/conversations.types.ts +++ b/src/types/conversations.types.ts @@ -83,3 +83,12 @@ export interface Reply { quotes: Quote[]; facts: Fact[]; } + +export interface TimelineNode { + id: string; + createdAt: Date; + updatedAt: Date; + title: string; + description: string; + date: Date; +} From 6d1ee7478d76d194f586bc03fe05db895118391a Mon Sep 17 00:00:00 2001 From: yukicoder0509 Date: Mon, 20 Jan 2025 17:02:18 +0800 Subject: [PATCH 05/15] feat: add arrow at the bottom of the timeline --- .../Conversation/Issues/TimelineModal.tsx | 25 ++++++++----------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/src/components/Conversation/Issues/TimelineModal.tsx b/src/components/Conversation/Issues/TimelineModal.tsx index 5cf1e8e..31c3e7b 100644 --- a/src/components/Conversation/Issues/TimelineModal.tsx +++ b/src/components/Conversation/Issues/TimelineModal.tsx @@ -3,6 +3,7 @@ import { Modal, Timeline } from "@mantine/core"; import type { Dispatch, SetStateAction } from "react"; import type { TimelineNode } from "@/lib/requests/timeline/getIssueTimeline"; +import { ArrowDownIcon } from "@heroicons/react/16/solid"; import { mockTimeline } from "@/mock/conversationMock"; type TimeLineModalProps = { @@ -26,7 +27,7 @@ export default function TimeLineModal({ setIsOpen(false)} - title={

{`《${issueTitle}》的演進`}

} + title={

{`《${issueTitle}》的演進`}

} size="620px" > - - -
} @@ -66,6 +53,14 @@ export default function TimeLineModal({ {node.description}
))} + +
+ +
+ } + />
); From 8dd00edb0cdc8b44dcf3892b4adfdd4c7d7fe2bb Mon Sep 17 00:00:00 2001 From: yukicoder0509 Date: Sun, 26 Jan 2025 17:01:19 +0800 Subject: [PATCH 06/15] feat: use the data comes from backend --- src/components/Conversation/Issues/TimelineModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Conversation/Issues/TimelineModal.tsx b/src/components/Conversation/Issues/TimelineModal.tsx index 31c3e7b..ddbd433 100644 --- a/src/components/Conversation/Issues/TimelineModal.tsx +++ b/src/components/Conversation/Issues/TimelineModal.tsx @@ -19,7 +19,7 @@ export default function TimeLineModal({ issueTitle, timeline, }: TimeLineModalProps) { - const sortedTimeline = mockTimeline.sort( + const sortedTimeline = timeline.sort( (a, b) => new Date(a.date).getTime() - new Date(b.date).getTime(), ); From 94d1ed3d3eded394e6f4dece71efa5a0ce58e1b2 Mon Sep 17 00:00:00 2001 From: yukicoder0509 Date: Sun, 26 Jan 2025 17:02:37 +0800 Subject: [PATCH 07/15] fix: fix eslint and next.js warnings --- src/components/Conversation/Issues/TimelineModal.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/Conversation/Issues/TimelineModal.tsx b/src/components/Conversation/Issues/TimelineModal.tsx index ddbd433..3304214 100644 --- a/src/components/Conversation/Issues/TimelineModal.tsx +++ b/src/components/Conversation/Issues/TimelineModal.tsx @@ -4,7 +4,6 @@ import { Modal, Timeline } from "@mantine/core"; import type { Dispatch, SetStateAction } from "react"; import type { TimelineNode } from "@/lib/requests/timeline/getIssueTimeline"; import { ArrowDownIcon } from "@heroicons/react/16/solid"; -import { mockTimeline } from "@/mock/conversationMock"; type TimeLineModalProps = { isOpen: boolean; @@ -27,7 +26,7 @@ export default function TimeLineModal({ setIsOpen(false)} - title={

{`《${issueTitle}》的演進`}

} + title={

{`《${issueTitle}》的演進`}

} size="620px" > Date: Tue, 28 Jan 2025 18:09:20 +0800 Subject: [PATCH 08/15] fix: fix error : h3 cannot be the child of h2 --- src/components/Conversation/Issues/TimelineModal.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/Conversation/Issues/TimelineModal.tsx b/src/components/Conversation/Issues/TimelineModal.tsx index 3304214..bd83184 100644 --- a/src/components/Conversation/Issues/TimelineModal.tsx +++ b/src/components/Conversation/Issues/TimelineModal.tsx @@ -26,8 +26,11 @@ export default function TimeLineModal({ setIsOpen(false)} - title={

{`《${issueTitle}》的演進`}

} + title={`《${issueTitle}》的演進`} size="620px" + classNames={{ + title: "font-bold", + }} > Date: Tue, 28 Jan 2025 18:12:06 +0800 Subject: [PATCH 09/15] refactor: import timenode type from conversation.types --- src/components/Conversation/Issues/IssueCard.tsx | 2 +- src/components/Conversation/Issues/TimelineModal.tsx | 2 +- src/lib/requests/timeline/getIssueTimeline.ts | 10 +--------- 3 files changed, 3 insertions(+), 11 deletions(-) diff --git a/src/components/Conversation/Issues/IssueCard.tsx b/src/components/Conversation/Issues/IssueCard.tsx index f0bd3d3..8a390be 100644 --- a/src/components/Conversation/Issues/IssueCard.tsx +++ b/src/components/Conversation/Issues/IssueCard.tsx @@ -8,7 +8,7 @@ import { import Link from "next/link"; import EmptyIssueCard from "@/components/Conversation/Issues/EmptyIssueCard"; import type { Issue } from "@/types/conversations.types"; -import type { TimelineNode } from "@/lib/requests/timeline/getIssueTimeline"; +import type { TimelineNode } from "@/types/conversations.types"; import { Tooltip, Button } from "@mantine/core"; import { useState } from "react"; import TimelineModal from "@/components/Conversation/Issues/TimelineModal"; diff --git a/src/components/Conversation/Issues/TimelineModal.tsx b/src/components/Conversation/Issues/TimelineModal.tsx index bd83184..29f103e 100644 --- a/src/components/Conversation/Issues/TimelineModal.tsx +++ b/src/components/Conversation/Issues/TimelineModal.tsx @@ -2,7 +2,7 @@ import { Modal, Timeline } from "@mantine/core"; import type { Dispatch, SetStateAction } from "react"; -import type { TimelineNode } from "@/lib/requests/timeline/getIssueTimeline"; +import type { TimelineNode } from "@/types/conversations.types"; import { ArrowDownIcon } from "@heroicons/react/16/solid"; type TimeLineModalProps = { diff --git a/src/lib/requests/timeline/getIssueTimeline.ts b/src/lib/requests/timeline/getIssueTimeline.ts index 72c9dba..8777173 100644 --- a/src/lib/requests/timeline/getIssueTimeline.ts +++ b/src/lib/requests/timeline/getIssueTimeline.ts @@ -1,13 +1,5 @@ import { parseJsonWhileHandlingErrors } from "../transformers"; - -export type TimelineNode = { - id: string; - createdAt: Date; - updatedAt: Date; - title: string; - description: string; - date: Date; -}; +import { TimelineNode } from "@/types/conversations.types"; export type getIssueTimelineResponse = { content: TimelineNode[]; From 169696f722173cb89a12b208776a86dd494c027a Mon Sep 17 00:00:00 2001 From: yukicoder0509 Date: Wed, 29 Jan 2025 10:10:52 +0800 Subject: [PATCH 10/15] make timeline client-side fetch --- src/app/issues/[id]/page.tsx | 10 +- .../Conversation/Issues/IssueCard.tsx | 6 +- .../Conversation/Issues/TimelineModal.tsx | 91 ++++++++++++------- 3 files changed, 59 insertions(+), 48 deletions(-) diff --git a/src/app/issues/[id]/page.tsx b/src/app/issues/[id]/page.tsx index 4c09f76..40052b6 100644 --- a/src/app/issues/[id]/page.tsx +++ b/src/app/issues/[id]/page.tsx @@ -4,10 +4,6 @@ import type { Metadata } from "next"; import { cookies } from "next/headers"; import type { Issue } from "@/types/conversations.types"; import { getIssue } from "@/lib/requests/issues/getIssue"; -import { - getIssueTimeline, - type getIssueTimelineResponse, -} from "@/lib/requests/timeline/getIssueTimeline"; import AddViewpointBar from "@/components/Conversation/Viewpoints/AddViewpointBar"; type IssueViewProps = { @@ -40,15 +36,11 @@ export default async function IssueView({ params }: IssueViewProps) { const auth_token = cookieStore.get("auth_token")?.value || ""; const issue: Issue = await getIssue({ issueId, auth_token }); - const timeline: getIssueTimelineResponse = await getIssueTimeline({ - issueId, - user_token: auth_token, - }); return (
- +
diff --git a/src/components/Conversation/Issues/IssueCard.tsx b/src/components/Conversation/Issues/IssueCard.tsx index 8a390be..9b7f821 100644 --- a/src/components/Conversation/Issues/IssueCard.tsx +++ b/src/components/Conversation/Issues/IssueCard.tsx @@ -8,17 +8,15 @@ import { import Link from "next/link"; import EmptyIssueCard from "@/components/Conversation/Issues/EmptyIssueCard"; import type { Issue } from "@/types/conversations.types"; -import type { TimelineNode } from "@/types/conversations.types"; import { Tooltip, Button } from "@mantine/core"; import { useState } from "react"; import TimelineModal from "@/components/Conversation/Issues/TimelineModal"; type IssueCardProps = { issue: Issue; - timeline: TimelineNode[]; }; -export default function IssueCard({ issue, timeline }: IssueCardProps) { +export default function IssueCard({ issue }: IssueCardProps) { const [isTimelimeModalOpen, setIsTimelimeModalOpen] = useState(false); return ( @@ -65,8 +63,8 @@ export default function IssueCard({ issue, timeline }: IssueCardProps) {
) : ( diff --git a/src/components/Conversation/Issues/TimelineModal.tsx b/src/components/Conversation/Issues/TimelineModal.tsx index 29f103e..7dbc6ff 100644 --- a/src/components/Conversation/Issues/TimelineModal.tsx +++ b/src/components/Conversation/Issues/TimelineModal.tsx @@ -1,26 +1,39 @@ "use client"; import { Modal, Timeline } from "@mantine/core"; -import type { Dispatch, SetStateAction } from "react"; -import type { TimelineNode } from "@/types/conversations.types"; +import { Dispatch, SetStateAction, useMemo } from "react"; import { ArrowDownIcon } from "@heroicons/react/16/solid"; +import { useQuery } from "@tanstack/react-query"; +import { useCookies } from "react-cookie"; +import { getIssueTimeline } from "@/lib/requests/timeline/getIssueTimeline"; type TimeLineModalProps = { isOpen: boolean; setIsOpen: Dispatch>; + issueId: string; issueTitle: string; - timeline: TimelineNode[]; }; export default function TimeLineModal({ isOpen, setIsOpen, + issueId, issueTitle, - timeline, }: TimeLineModalProps) { - const sortedTimeline = timeline.sort( - (a, b) => new Date(a.date).getTime() - new Date(b.date).getTime(), - ); + const [cookie] = useCookies(["auth_token"]); + + const { isPending, error, data } = useQuery({ + queryKey: ["issueTimeline", issueId], + queryFn: () => + getIssueTimeline({ issueId, user_token: cookie.auth_token }), + }); + + const sortedTimeline = useMemo(() => { + if (!data) return []; + return data.content.sort( + (a, b) => new Date(a.date).getTime() - new Date(b.date).getTime(), + ); + }, [data]); return ( - - {sortedTimeline.map((node) => ( + {isPending ? ( +
載入中...
+ ) : error ? ( +
載入失敗
+ ) : sortedTimeline.length === 0 ? ( +
目前議題的資料還不足以產生時間軸,稍後再回來看看吧!
+ ) : ( + + {sortedTimeline.map((node) => ( + +
+ + } + title={`${node.date.toLocaleDateString()} ${node.title}`} + > + {node.description} +
+ ))} -
+
+
+
} - title={`${node.date.toLocaleDateString()} ${node.title}`} - > - {node.description} - - ))} - -
- -
- } - /> - + /> + + )} ); } From 16fac779bd59857c3fab9244accc0c6e6d05f8f7 Mon Sep 17 00:00:00 2001 From: yukicoder0509 Date: Wed, 29 Jan 2025 10:52:58 +0800 Subject: [PATCH 11/15] feat: add timeline skeleton and fetch error toast --- .../Conversation/Issues/EmptyTimeline.tsx | 0 .../Conversation/Issues/TimelineModal.tsx | 11 ++++++++--- .../Conversation/Issues/TimelineSkeleton.tsx | 14 ++++++++++++++ 3 files changed, 22 insertions(+), 3 deletions(-) create mode 100644 src/components/Conversation/Issues/EmptyTimeline.tsx create mode 100644 src/components/Conversation/Issues/TimelineSkeleton.tsx diff --git a/src/components/Conversation/Issues/EmptyTimeline.tsx b/src/components/Conversation/Issues/EmptyTimeline.tsx new file mode 100644 index 0000000..e69de29 diff --git a/src/components/Conversation/Issues/TimelineModal.tsx b/src/components/Conversation/Issues/TimelineModal.tsx index 7dbc6ff..b0f3dc0 100644 --- a/src/components/Conversation/Issues/TimelineModal.tsx +++ b/src/components/Conversation/Issues/TimelineModal.tsx @@ -6,6 +6,8 @@ import { ArrowDownIcon } from "@heroicons/react/16/solid"; import { useQuery } from "@tanstack/react-query"; import { useCookies } from "react-cookie"; import { getIssueTimeline } from "@/lib/requests/timeline/getIssueTimeline"; +import TimelineSkeleton from "@/components/Conversation/Issues/TimelineSkeleton"; +import { toast } from "sonner"; type TimeLineModalProps = { isOpen: boolean; @@ -35,6 +37,11 @@ export default function TimeLineModal({ ); }, [data]); + if (error) { + toast.error("載入事件時間軸時發生問題,請再試一次"); + return null; + } + return ( {isPending ? ( -
載入中...
- ) : error ? ( -
載入失敗
+ ) : sortedTimeline.length === 0 ? (
目前議題的資料還不足以產生時間軸,稍後再回來看看吧!
) : ( diff --git a/src/components/Conversation/Issues/TimelineSkeleton.tsx b/src/components/Conversation/Issues/TimelineSkeleton.tsx new file mode 100644 index 0000000..2dd6445 --- /dev/null +++ b/src/components/Conversation/Issues/TimelineSkeleton.tsx @@ -0,0 +1,14 @@ +import { Skeleton } from "@mantine/core"; + +export default function TimelineSkeleton() { + return ( +
+ +
+ + + +
+
+ ); +} From cdf9885689bd52f58df363ceb753b820943262e1 Mon Sep 17 00:00:00 2001 From: yukicoder0509 Date: Wed, 29 Jan 2025 11:27:22 +0800 Subject: [PATCH 12/15] feat: add empty timeline UI --- .../Conversation/Issues/EmptyTimeline.tsx | 15 +++++++++++++++ .../Conversation/Issues/TimelineModal.tsx | 5 +++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/components/Conversation/Issues/EmptyTimeline.tsx b/src/components/Conversation/Issues/EmptyTimeline.tsx index e69de29..8a0f02c 100644 --- a/src/components/Conversation/Issues/EmptyTimeline.tsx +++ b/src/components/Conversation/Issues/EmptyTimeline.tsx @@ -0,0 +1,15 @@ +import { ArrowDownIcon } from "@heroicons/react/24/outline"; + +export default function EmptyTimeline() { + return ( +
+
+
+ +
+

+ 目前議題的資料還不足以產生時間軸,稍後再回來看看吧! +

+
+ ); +} diff --git a/src/components/Conversation/Issues/TimelineModal.tsx b/src/components/Conversation/Issues/TimelineModal.tsx index b0f3dc0..b701d5e 100644 --- a/src/components/Conversation/Issues/TimelineModal.tsx +++ b/src/components/Conversation/Issues/TimelineModal.tsx @@ -6,8 +6,9 @@ import { ArrowDownIcon } from "@heroicons/react/16/solid"; import { useQuery } from "@tanstack/react-query"; import { useCookies } from "react-cookie"; import { getIssueTimeline } from "@/lib/requests/timeline/getIssueTimeline"; -import TimelineSkeleton from "@/components/Conversation/Issues/TimelineSkeleton"; import { toast } from "sonner"; +import TimelineSkeleton from "@/components/Conversation/Issues/TimelineSkeleton"; +import EmptyTimeline from "@/components/Conversation/Issues/EmptyTimeline"; type TimeLineModalProps = { isOpen: boolean; @@ -55,7 +56,7 @@ export default function TimeLineModal({ {isPending ? ( ) : sortedTimeline.length === 0 ? ( -
目前議題的資料還不足以產生時間軸,稍後再回來看看吧!
+ ) : ( Date: Thu, 30 Jan 2025 20:30:49 +0800 Subject: [PATCH 13/15] feat: make empty timeline UI display only text --- .../Conversation/Issues/EmptyTimeline.tsx | 15 --------------- .../Conversation/Issues/TimelineModal.tsx | 5 +++-- 2 files changed, 3 insertions(+), 17 deletions(-) delete mode 100644 src/components/Conversation/Issues/EmptyTimeline.tsx diff --git a/src/components/Conversation/Issues/EmptyTimeline.tsx b/src/components/Conversation/Issues/EmptyTimeline.tsx deleted file mode 100644 index 8a0f02c..0000000 --- a/src/components/Conversation/Issues/EmptyTimeline.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { ArrowDownIcon } from "@heroicons/react/24/outline"; - -export default function EmptyTimeline() { - return ( -
-
-
- -
-

- 目前議題的資料還不足以產生時間軸,稍後再回來看看吧! -

-
- ); -} diff --git a/src/components/Conversation/Issues/TimelineModal.tsx b/src/components/Conversation/Issues/TimelineModal.tsx index b701d5e..b482d88 100644 --- a/src/components/Conversation/Issues/TimelineModal.tsx +++ b/src/components/Conversation/Issues/TimelineModal.tsx @@ -8,7 +8,6 @@ import { useCookies } from "react-cookie"; import { getIssueTimeline } from "@/lib/requests/timeline/getIssueTimeline"; import { toast } from "sonner"; import TimelineSkeleton from "@/components/Conversation/Issues/TimelineSkeleton"; -import EmptyTimeline from "@/components/Conversation/Issues/EmptyTimeline"; type TimeLineModalProps = { isOpen: boolean; @@ -56,7 +55,9 @@ export default function TimeLineModal({ {isPending ? ( ) : sortedTimeline.length === 0 ? ( - +

+ 目前議題的資料還不足以產生時間軸,稍後再回來看看吧! +

) : ( Date: Sun, 2 Feb 2025 15:21:55 +0800 Subject: [PATCH 14/15] fix: fix minor display bug --- src/components/Conversation/Issues/TimelineModal.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/Conversation/Issues/TimelineModal.tsx b/src/components/Conversation/Issues/TimelineModal.tsx index b482d88..ff99edf 100644 --- a/src/components/Conversation/Issues/TimelineModal.tsx +++ b/src/components/Conversation/Issues/TimelineModal.tsx @@ -56,7 +56,7 @@ export default function TimeLineModal({ ) : sortedTimeline.length === 0 ? (

- 目前議題的資料還不足以產生時間軸,稍後再回來看看吧! + 目前議題的資料還不足以產生時間軸,稍後再回來看看吧!

) : ( )} +
); } From fffae7d87459c78c90831f13c4d98f48802da15e Mon Sep 17 00:00:00 2001 From: yukicoder0509 Date: Sun, 2 Feb 2025 15:23:24 +0800 Subject: [PATCH 15/15] fix: make api request async --- src/lib/requests/timeline/getIssueTimeline.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/requests/timeline/getIssueTimeline.ts b/src/lib/requests/timeline/getIssueTimeline.ts index 8777173..40cefb6 100644 --- a/src/lib/requests/timeline/getIssueTimeline.ts +++ b/src/lib/requests/timeline/getIssueTimeline.ts @@ -10,7 +10,7 @@ type getIssueTimelineParams = { user_token: string; }; -export function getIssueTimeline({ +export async function getIssueTimeline({ issueId, user_token, }: getIssueTimelineParams): Promise {