From cbf1d678f2f05f71030d1470e2ba365c48730368 Mon Sep 17 00:00:00 2001 From: SimSalabimse <83839581+SimSalabimse@users.noreply.github.com> Date: Mon, 28 Jul 2025 15:31:29 +0200 Subject: [PATCH] Feat: Added whole season watched button --- .../overlays/details/ConfirmOverlay.tsx | 57 ++++++++ .../overlays/details/EpisodeCarousel.tsx | 136 ++++++++++++++++-- 2 files changed, 181 insertions(+), 12 deletions(-) create mode 100644 src/components/overlays/details/ConfirmOverlay.tsx diff --git a/src/components/overlays/details/ConfirmOverlay.tsx b/src/components/overlays/details/ConfirmOverlay.tsx new file mode 100644 index 00000000..9d73d675 --- /dev/null +++ b/src/components/overlays/details/ConfirmOverlay.tsx @@ -0,0 +1,57 @@ +import { Button } from "@/components/buttons/Button"; +import { + OverlayDisplay, + OverlayPortal, +} from "@/components/overlays/OverlayDisplay"; + +interface ConfirmOverlayProps { + isOpen: boolean; + message: string; + onConfirm: (event: React.MouseEvent) => void; + onCancel: () => void; + confirmButtonTheme?: "white" | "purple" | "secondary" | "danger" | "glass"; + cancelButtonTheme?: "white" | "purple" | "secondary" | "danger" | "glass"; + backdropOpacity?: number; + backdropColor?: string; +} + +export function ConfirmOverlay({ + isOpen, + message, + onConfirm, + onCancel, + confirmButtonTheme = "purple", + cancelButtonTheme = "secondary", + backdropOpacity = 0.5, + backdropColor = "black", +}: ConfirmOverlayProps) { + return ( + +
+ +
+

{message}

+
+ + +
+
+
+
+
+ ); +} diff --git a/src/components/overlays/details/EpisodeCarousel.tsx b/src/components/overlays/details/EpisodeCarousel.tsx index b671d019..7be52074 100644 --- a/src/components/overlays/details/EpisodeCarousel.tsx +++ b/src/components/overlays/details/EpisodeCarousel.tsx @@ -6,6 +6,7 @@ import { Link } from "react-router-dom"; import { Button } from "@/components/buttons/Button"; import { Dropdown } from "@/components/form/Dropdown"; import { Icon, Icons } from "@/components/Icon"; +import { ConfirmOverlay } from "@/components/overlays/details/ConfirmOverlay"; import { hasAired } from "@/components/player/utils/aired"; import { useProgressStore } from "@/stores/progress"; @@ -25,6 +26,8 @@ export function EpisodeCarousel({ const [showEpisodeMenu, setShowEpisodeMenu] = useState(false); const [customSeason, setCustomSeason] = useState(""); const [customEpisode, setCustomEpisode] = useState(""); + const [SeasonWatched, setSeasonWatched] = useState(false); + const [isConfirmOpen, setIsConfirmOpen] = useState(false); const [expandedEpisodes, setExpandedEpisodes] = useState<{ [key: number]: boolean; }>({}); @@ -203,10 +206,66 @@ export function EpisodeCarousel({ } }; + // Toggle whole season watch status + const toggleSeasonWatchStatus = (event: React.MouseEvent) => { + event.preventDefault(); + event.stopPropagation(); + + setIsConfirmOpen(true); + }; + + const handleCancel = () => { + setIsConfirmOpen(false); + }; + const currentSeasonEpisodes = episodes.filter( (ep) => ep.season_number === selectedSeason, ); + const handleConfirm = (event: React.MouseEvent) => { + try { + const episodeWatchedStatus: boolean[] = []; + currentSeasonEpisodes.forEach((episode: any) => { + const episodeProgress = + progress[mediaId?.toString() ?? ""]?.episodes?.[episode.id]; + const percentage = episodeProgress + ? (episodeProgress.progress.watched / + episodeProgress.progress.duration) * + 100 + : 0; + const isAired = hasAired(episode.air_date); + const isWatched = percentage > 90; + if (isAired && !isWatched) { + episodeWatchedStatus.push(isWatched); + } + }); + + const hasUnwatched = episodeWatchedStatus.length >= 1; + + currentSeasonEpisodes.forEach((episode: any) => { + const episodeProgress = + progress[mediaId?.toString() ?? ""]?.episodes?.[episode.id]; + const percentage = episodeProgress + ? (episodeProgress.progress.watched / + episodeProgress.progress.duration) * + 100 + : 0; + const isAired = hasAired(episode.air_date); + const isWatched = percentage > 90; + if (hasUnwatched && isAired && !isWatched) { + toggleWatchStatus(episode.id, event); // Mark unwatched as watched + } else if (!hasUnwatched && isAired && isWatched) { + toggleWatchStatus(episode.id, event); // Mark watched as unwatched + } + }); + + setIsConfirmOpen(false); + } catch (error) { + console.error("Error in handleConfirm:", error); + setIsConfirmOpen(false); + } + }; + const toggleEpisodeExpansion = ( episodeId: number, event: React.MouseEvent, @@ -259,6 +318,34 @@ export function EpisodeCarousel({ }; }, [episodes, expandedEpisodes]); + useEffect(() => { + const episodeWatchedStatus: boolean[] = []; + + currentSeasonEpisodes.forEach((episode: any) => { + const episodeProgress = + progress[mediaId?.toString() ?? ""]?.episodes?.[episode.id]; + const percentage = episodeProgress + ? (episodeProgress.progress.watched / + episodeProgress.progress.duration) * + 100 + : 0; + const isAired = hasAired(episode.air_date); + const isWatched = percentage > 90; + + if (isAired && !isWatched) { + episodeWatchedStatus.push(isWatched); + } + }); + + let toggle: boolean; + + if (episodeWatchedStatus.length >= 1) { + setSeasonWatched(true); // If no episodes are watched, we want to mark all as watched + } else { + setSeasonWatched(false); // if all episodes are watched, we want to mark all as unwatched + } + }, [currentSeasonEpisodes, episodes, mediaId, progress]); + return (
{/* Season Selector */} @@ -323,17 +410,43 @@ export function EpisodeCarousel({ )}
- ({ - id: season.season_number.toString(), - name: `${t("details.season")} ${season.season_number}`, - }))} - selectedItem={{ - id: selectedSeason.toString(), - name: `${t("details.season")} ${selectedSeason}`, - }} - setSelectedItem={(item) => onSeasonChange(Number(item.id))} - /> +
+ {isConfirmOpen && ( + + )} + + + ({ + id: season.season_number.toString(), + name: `${t("details.season")} ${season.season_number}`, + }))} + selectedItem={{ + id: selectedSeason.toString(), + name: `${t("details.season")} ${selectedSeason}`, + }} + setSelectedItem={(item) => onSeasonChange(Number(item.id))} + /> +
{/* Episodes Carousel */} @@ -359,7 +472,6 @@ export function EpisodeCarousel({ > {/* Add padding before the first card */}
- {currentSeasonEpisodes.map((episode) => { const isActive = showProgress?.episode?.id === episode.id.toString();