diff --git a/functions/tsconfig.json b/functions/tsconfig.json index 7ba135d..d3d8876 100644 --- a/functions/tsconfig.json +++ b/functions/tsconfig.json @@ -13,5 +13,6 @@ "skipLibCheck": true }, "compileOnSave": true, - "include": ["src/*", "functions/src/**/*", "src/**/*.ts"] + "include": ["src/*", "functions/src/**/*", "src/**/*.ts"], + "exclude": ["node_modules"] } diff --git a/src/Components/DashBoard/DashBoard.tsx b/src/Components/DashBoard/DashBoard.tsx index fd6dab5..ed64a01 100644 --- a/src/Components/DashBoard/DashBoard.tsx +++ b/src/Components/DashBoard/DashBoard.tsx @@ -5,6 +5,7 @@ import { fetchResult, handleFetchResultError, } from "@/utils/API/Result/fetchResult"; +import { useResults } from "@/utils/ResultContext"; import { useUser } from "@/utils/UserContext"; import CircularProgress from "@mui/joy/CircularProgress"; import Typography from "@mui/joy/Typography"; @@ -30,23 +31,22 @@ export default function DashBoard({ pending?: boolean; orderBy?: "asc" | "desc"; }) { - const [successResults, setSuccessResults] = useState( - [] - ); - const [failedResults, setFailedResults] = useState( - [] - ); - const [pendingResults, setPendingResults] = useState( - [] - ); + const { + successResults, + setSuccessResults, + failedResults, + setFailedResults, + pendingResults, + setPendingResults, + } = useResults(); const [noResult, setNoResult] = useState(false); const [isLoading, setIsLoading] = useState(true); const [reachedBottom, setReachedBottom] = useState(false); const [isLoadingMore, setIsLoadingMore] = useState(false); const bottomRef = useRef(null); const isAlreadyFetching = useRef(false); - const offset = useRef(0); - const noMore = useRef(false); + const [noMorePending, setNoMorePending] = useState(false); + const [noMoreFinished, setNoMoreFinished] = useState(false); const [lastPostDate, setLastPostDate] = useState(null); // 投稿が0の場合はnull @@ -55,38 +55,9 @@ export default function DashBoard({ const limit = 10; // limitずつ表示 - useEffect(() => { - setTimeout(() => { - const observer = new IntersectionObserver( - (entries) => { - if (entries[0].isIntersecting && !isLoading && !noMore.current) { - setReachedBottom(true); - fetchData(); - } - }, - { threshold: 1 } - ); - - if (bottomRef.current) { - observer.observe(bottomRef.current); - } - - return () => { - if (bottomRef.current) { - observer.disconnect(); - } - }; - }, 1000); - }, [isLoading, noMore.current, bottomRef.current, bottomRef]); - - useEffect(() => { - offset.current = - successResults.length + failedResults.length + pendingResults.length; - }, [successResults, failedResults, pendingResults]); - const fetchData = () => { - if (noMore.current) { - return; + if ((pending && noMorePending) || (success && failed && noMoreFinished)) { + return; // TODO: うまく動作していない } if (isAlreadyFetching.current) { return; @@ -96,12 +67,15 @@ export default function DashBoard({ if (reachedBottom && !isLoadingMore) { setIsLoadingMore(true); } + const offset = pending + ? pendingResults.length + : successResults.length + failedResults.length; fetchResult({ userId, success, failed, pending, - offset: offset.current, + offset, limit, }) .then((data) => { @@ -128,13 +102,16 @@ export default function DashBoard({ return [...prev, ...newResults]; }); + if (pending && data.pendingResults.length < limit) { + setNoMorePending(true); + } + if ( - data.successResults.length + - data.failedResults.length + - data.pendingResults.length < - limit + success && + failed && + data.successResults.length + data.failedResults.length < limit ) { - noMore.current = true; + setNoMoreFinished(true); } setIsLoading(false); @@ -153,6 +130,36 @@ export default function DashBoard({ }); }; + // 画面下に到達したことを検知 + useEffect(() => { + setTimeout(() => { + const observer = new IntersectionObserver( + (entries) => { + if ( + entries[0].isIntersecting && + !isLoading && + ((pending && !noMorePending) || + (success && failed && !noMoreFinished)) + ) { + setReachedBottom(true); + fetchData(); + } + }, + { threshold: 1 } + ); + + if (bottomRef.current) { + observer.observe(bottomRef.current); + } + + return () => { + if (bottomRef.current) { + observer.disconnect(); + } + }; + }, 1000); + }, [isLoading, noMorePending, noMoreFinished, bottomRef.current, bottomRef]); + useEffect(() => { rerenderDashBoard = fetchData; if (userId) { @@ -197,6 +204,7 @@ export default function DashBoard({ return ( <> {isLoading ? ( + // ロード中 ) : noResult ? ( + // 目標や投稿が無い場合
- {!noMore.current && + {/* 下に到達した時に続きを表示 */} + {((pending && !noMorePending) || + (success && failed && !noMoreFinished)) && (reachedBottom ? (
{ @@ -56,7 +56,7 @@ export default function CreateGoalModal({ localDate.setDate(localDate.getDate() + 1); // 明日にする setDeadline(localDate.toISOString().slice(0, 16)); } else { - resetDeadline(); + setDeadline(resetDeadline()); } }, [defaultText, defaultDeadline]); @@ -78,8 +78,8 @@ export default function CreateGoalModal({ }); triggerDashBoardRerender(); - setText(""); - resetDeadline(); + setText(defaultText || ""); + setDeadline(defaultDeadline || resetDeadline()); setOpen(false); } catch (error: unknown) { console.error("Error creating goal:", error); diff --git a/src/app/discover/page.tsx b/src/app/discover/page.tsx index 2663059..1a219c6 100644 --- a/src/app/discover/page.tsx +++ b/src/app/discover/page.tsx @@ -3,6 +3,7 @@ import DashBoard, { triggerDashBoardRerender, } from "@/Components/DashBoard/DashBoard"; import GoalModalButton from "@/Components/GoalModal/GoalModalButton"; +import { ResultProvider } from "@/utils/ResultContext"; import { useEffect } from "react"; export default function Discover() { @@ -12,7 +13,9 @@ export default function Discover() { return ( <> - + + + ); diff --git a/src/app/mycontent/page.tsx b/src/app/mycontent/page.tsx index 007afa1..b50ed1d 100644 --- a/src/app/mycontent/page.tsx +++ b/src/app/mycontent/page.tsx @@ -1,6 +1,7 @@ "use client"; import DashBoard from "@/Components/DashBoard/DashBoard"; import GoalModalButton from "@/Components/GoalModal/GoalModalButton"; +import { ResultProvider } from "@/utils/ResultContext"; import { useUser } from "@/utils/UserContext"; import Typography from "@mui/joy/Typography"; import { styled } from "@mui/material/styles"; @@ -62,15 +63,21 @@ export default function MyContent() { メールに届いた認証リンクを確認してください。 ) : value === "pending" ? ( - + + + ) : ( - + value === "finished" && ( + + + + ) )} diff --git a/src/utils/ResultContext.tsx b/src/utils/ResultContext.tsx new file mode 100644 index 0000000..787f6fd --- /dev/null +++ b/src/utils/ResultContext.tsx @@ -0,0 +1,54 @@ +import { GoalWithIdAndUserData } from "@/types/types"; +import React, { createContext, ReactNode, useContext, useState } from "react"; + +interface ResultContextType { + successResults: GoalWithIdAndUserData[]; + setSuccessResults: React.Dispatch< + React.SetStateAction + >; + failedResults: GoalWithIdAndUserData[]; + setFailedResults: React.Dispatch< + React.SetStateAction + >; + pendingResults: GoalWithIdAndUserData[]; + setPendingResults: React.Dispatch< + React.SetStateAction + >; +} + +const ResultContext = createContext(undefined); + +export const ResultProvider = ({ children }: { children: ReactNode }) => { + const [successResults, setSuccessResults] = useState( + [] + ); + const [failedResults, setFailedResults] = useState( + [] + ); + const [pendingResults, setPendingResults] = useState( + [] + ); + + return ( + + {children} + + ); +}; + +export const useResults = () => { + const context = useContext(ResultContext); + if (!context) { + throw new Error("useResults must be used within a ResultProvider"); + } + return context; +};