diff --git a/backend/app/routes/memories.py b/backend/app/routes/memories.py index f23cf94dd..0f1a650fa 100644 --- a/backend/app/routes/memories.py +++ b/backend/app/routes/memories.py @@ -25,7 +25,11 @@ db_get_images_by_date_range, db_get_images_by_year_month, ) -from app.utils.memory_clustering import MemoryClustering +<<<<<<< HEAD +from app.utils.memory_clustering import MemoryClustering, generate_clusters_for_weekends +======= +from app.utils.memory_clustering import MemoryClustering, find_total_location_memories +>>>>>>> main from app.logging.setup_logging import get_logger # Initialize router and logger @@ -111,6 +115,11 @@ class LocationsResponse(BaseModel): location_count: int locations: List[LocationCluster] +class WeeklyMemoriesResponse(BaseModel): + success: bool + message: str + weekly_memories: List[Dict] + # API Endpoints @@ -163,7 +172,7 @@ def generate_memories( ) memories = clustering.cluster_memories(images) - + tlm = find_total_location_memories(memories) # Calculate breakdown location_count = sum(1 for m in memories if m.get("type") == "location") date_count = sum(1 for m in memories if m.get("type") == "date") @@ -178,6 +187,7 @@ def generate_memories( "image_count": len(images), "memories": memories, }, + "total_location":tlm, "success": True, "message": f"{len(memories)} memories ({location_count} location, {date_count} date)", } @@ -466,3 +476,24 @@ def get_locations( except Exception: logger.error("Error getting locations", exc_info=True) raise HTTPException(status_code=500, detail="Failed to get locations") + + +@router.get("/weekly-memories", response_model=WeeklyMemoriesResponse) +def get_weekly_memories(): + try: + weekly_clusters = generate_clusters_for_weekends() + if weekly_clusters: + return WeeklyMemoriesResponse( + success= True, + message="Weekly cluster created.", + weekly_memories=weekly_clusters + ) + else: + return WeeklyMemoriesResponse( + success=True, + message="Please add images.", + weekly_memories=weekly_clusters + ) + except Exception as e: + logger.error("Failed to create weekly memories", exc_info=True) + raise HTTPException(status_code=500, detail=f"Failed to create weekly memories:{e}") \ No newline at end of file diff --git a/backend/app/utils/memory_clustering.py b/backend/app/utils/memory_clustering.py index 79928968b..9daf38e11 100644 --- a/backend/app/utils/memory_clustering.py +++ b/backend/app/utils/memory_clustering.py @@ -20,7 +20,7 @@ from sklearn.cluster import DBSCAN from app.logging.setup_logging import get_logger - +from app.database.images import db_get_all_images # Initialize logger logger = get_logger(__name__) @@ -103,6 +103,14 @@ def haversine_distance(lat1: float, lon1: float, lat2: float, lon2: float) -> fl return nearest_city +# function to count total memories which has location +# this can also be done in tsx. +def find_total_location_memories(data:list) -> int: + tlm = 0 # total location memories + for memory in data: + if memory["location_name"] != None: + tlm +=1 + return tlm class MemoryClustering: """ @@ -385,7 +393,7 @@ def _create_simple_memory( title = date_obj.strftime("%B %Y") else: title = "Undated Photos" - location_name = "" + location_name = None center_lat = 0 center_lon = 0 @@ -944,3 +952,43 @@ def _generate_memory_id( hash_input = f"lat:{lat_rounded}|lon:{lon_rounded}" hash_digest = hashlib.sha256(hash_input.encode()).hexdigest()[:8] return f"mem_nodate_{hash_digest}" + + +from datetime import datetime +from typing import List, Dict +import uuid + + +def generate_clusters_for_weekends() -> List[Dict]: + images = db_get_all_images() + + # sort by date + images.sort(key=lambda x: x["metadata"]["date_created"], reverse=True) + + weekend_memories = {} + + for img in images: + date_str: str = img["metadata"]["date_created"] + dt = datetime.fromisoformat(date_str) + + # get year and week number + year, week, _ = dt.isocalendar() + + week_key = f"{year}-W{week}" + + if week_key not in weekend_memories: + weekend_memories[week_key] = { + "mem_id": str(uuid.uuid4()), + "images": [], + "end_date" : date_str.split("T")[0], + "start_date" : "" + } + image_info = { + "id": img["id"], + "path": img["path"], + "thumbnailPath": img["thumbnailPath"] + } + weekend_memories[week_key]["start_date"] = date_str.split("T")[0] + weekend_memories[week_key]["images"].append(image_info) + + return list(weekend_memories.values()) \ No newline at end of file diff --git a/docs/backend/backend_python/openapi.json b/docs/backend/backend_python/openapi.json index 4ab3ffa44..fac853a86 100644 --- a/docs/backend/backend_python/openapi.json +++ b/docs/backend/backend_python/openapi.json @@ -1533,6 +1533,27 @@ } } }, + "/api/memories/weekly-memories": { + "get": { + "tags": [ + "memories" + ], + "summary": "Get Weekly Memories", + "operationId": "get_weekly_memories_api_memories_weekly_memories_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WeeklyMemoriesResponse" + } + } + } + } + } + } + }, "/shutdown": { "post": { "tags": [ @@ -3407,6 +3428,148 @@ "type" ], "title": "ValidationError" +<<<<<<< HEAD + }, + "WeeklyMemoriesResponse": { + "properties": { + "success": { + "type": "boolean", + "title": "Success" + }, + "message": { + "type": "string", + "title": "Message" + }, + "weekly_memories": { + "items": { + "type": "object" + }, + "type": "array", + "title": "Weekly Memories" + } + }, + "type": "object", + "required": [ + "success", + "message", + "weekly_memories" + ], + "title": "WeeklyMemoriesResponse" + }, + "app__schemas__face_clusters__ErrorResponse": { + "properties": { + "success": { + "type": "boolean", + "title": "Success", + "default": false + }, + "message": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Message" + }, + "error": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Error" + } + }, + "type": "object", + "title": "ErrorResponse" + }, + "app__schemas__folders__ErrorResponse": { + "properties": { + "success": { + "type": "boolean", + "title": "Success", + "default": false + }, + "message": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Message" + }, + "error": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Error" + } + }, + "type": "object", + "title": "ErrorResponse" + }, + "app__schemas__images__ErrorResponse": { + "properties": { + "success": { + "type": "boolean", + "title": "Success", + "default": false + }, + "message": { + "type": "string", + "title": "Message" + }, + "error": { + "type": "string", + "title": "Error" + } + }, + "type": "object", + "required": [ + "message", + "error" + ], + "title": "ErrorResponse" + }, + "app__schemas__user_preferences__ErrorResponse": { + "properties": { + "success": { + "type": "boolean", + "title": "Success" + }, + "error": { + "type": "string", + "title": "Error" + }, + "message": { + "type": "string", + "title": "Message" + } + }, + "type": "object", + "required": [ + "success", + "error", + "message" + ], + "title": "ErrorResponse", + "description": "Error response model" +======= +>>>>>>> main } } } diff --git a/frontend/src/api/api-functions/memories.ts b/frontend/src/api/api-functions/memories.ts index ffcfbef79..011d2dde8 100644 --- a/frontend/src/api/api-functions/memories.ts +++ b/frontend/src/api/api-functions/memories.ts @@ -30,7 +30,7 @@ export interface Memory { memory_id: string; title: string; description: string; - location_name: string; + location_name: string | null; date_start: string | null; date_end: string | null; image_count: number; @@ -44,7 +44,7 @@ export interface Memory { * Location cluster with sample images */ export interface LocationCluster { - location_name: string; + location_name: string | null; center_lat: number; center_lon: number; image_count: number; @@ -127,3 +127,42 @@ export const getLocations = async (options?: { const response = await apiClient.get(url); return response.data; }; + +/** + * A single image within a weekend memory cluster + */ +export interface WeeklyMemoryImage { + id: string; + path: string; + thumbnailPath: string; +} + +/** + * A single weekend memory cluster returned by the backend + */ +export interface WeeklyMemory { + mem_id: string; + images: WeeklyMemoryImage[]; + start_date: string; + end_date: string; +} + +/** + * Full response shape from GET /api/memories/weekly-memories + */ +export interface WeeklyMemoriesResponse { + success: boolean; + message: string; + weekly_memories: WeeklyMemory[]; +} + +/** + * Fetch weekend memory clusters from the backend + */ +export const getWeeklyMemories = async (): Promise => { + const response = + await apiClient.get( + memoriesEndpoints.weeklyMemories, + ); + return response.data; +}; diff --git a/frontend/src/api/apiEndpoints.ts b/frontend/src/api/apiEndpoints.ts index f0e749197..188854a5f 100644 --- a/frontend/src/api/apiEndpoints.ts +++ b/frontend/src/api/apiEndpoints.ts @@ -36,4 +36,5 @@ export const memoriesEndpoints = { timeline: '/api/memories/timeline', onThisDay: '/api/memories/on-this-day', locations: '/api/memories/locations', + weeklyMemories: '/api/memories/weekly-memories', }; diff --git a/frontend/src/components/Memories/MemoriesPage.tsx b/frontend/src/components/Memories/MemoriesPage.tsx index a13ad5077..58329c309 100644 --- a/frontend/src/components/Memories/MemoriesPage.tsx +++ b/frontend/src/components/Memories/MemoriesPage.tsx @@ -2,24 +2,35 @@ * MemoriesPage Component * * Main page for the Memories feature. - * Displays memories in sections: On This Day, Recent, This Year, All Memories. - * Includes filter tabs for All/Location/Date memories. + * Displays memories in sections: On This Day, Recent, This Year, All Memories, Weekends. + * Includes filter tabs for All/Location/Date/Weekends memories. * * Uses Tanstack Query for data fetching (not Redux async thunks). */ -import React, { useState, useEffect } from 'react'; -import { useDispatch } from 'react-redux'; +import React, { useState, useEffect, useCallback } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; import { showInfoDialog } from '@/features/infoDialogSlice'; import { useAllMemories, useRecentMemories, useYearMemories, useOnThisDay, + useWeeklyMemories, } from '@/hooks/useMemories'; import { MemoryCard } from './MemoryCard'; import { FeaturedMemoryCard } from './FeaturedMemoryCard'; -import type { Memory } from '@/api/api-functions/memories'; +import type { Memory, WeeklyMemory } from '@/api/api-functions/memories'; +import { convertFileSrc } from '@tauri-apps/api/core'; +import { ArrowLeft } from 'lucide-react'; +import { ImageCard } from '@/components/Media/ImageCard'; +import { MediaView } from '@/components/Media/MediaView'; +import { + setImages, + setCurrentViewIndex, +} from '@/features/imageSlice'; +import { selectImages, selectIsImageViewOpen } from '@/features/imageSelectors'; +import { showLoader, hideLoader } from '@/features/loaderSlice'; /** * Loading skeleton for memory cards @@ -125,6 +136,198 @@ const EmptyState: React.FC<{ message: string }> = ({ message }) => ( ); +/** + * Format a date string (YYYY-MM-DD) to a readable form. + */ +function formatWeekendDate(dateStr: string): string { + try { + const d = new Date(dateStr + 'T00:00:00'); + return d.toLocaleDateString('en-US', { + month: 'short', + day: 'numeric', + year: 'numeric', + }); + } catch { + return dateStr; + } +} + +/** + * Card for a single weekend memory cluster + */ +const WeekendMemoryCard: React.FC<{ + memory: WeeklyMemory; + onClick: (memory: WeeklyMemory) => void; +}> = ({ memory, onClick }) => { + const firstImage = memory.images[0]; + const thumbnailSrc = firstImage?.thumbnailPath + ? convertFileSrc(firstImage.thumbnailPath) + : firstImage?.path + ? convertFileSrc(firstImage.path) + : '/photo.png'; + + const sameDay = memory.start_date === memory.end_date; + const dateLabel = sameDay + ? formatWeekendDate(memory.start_date) + : `${formatWeekendDate(memory.start_date)} – ${formatWeekendDate(memory.end_date)}`; + + return ( +
onClick(memory)} + className="group bg-card transform cursor-pointer overflow-hidden rounded-lg border shadow-md transition-all duration-200 hover:scale-[1.02] hover:shadow-xl" + role="button" + tabIndex={0} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + onClick(memory); + } + }} + aria-label={`View weekend memory: ${dateLabel}`} + > + {/* Thumbnail */} +
+ Weekend memory { e.currentTarget.src = '/photo.png'; }} + className="h-full w-full object-cover transition-transform duration-300 group-hover:scale-110" + loading="lazy" + /> + {/* Weekend badge */} +
+ + + + Weekend +
+ {/* Photo count badge */} +
+ {memory.images.length} {memory.images.length === 1 ? 'photo' : 'photos'} +
+
+ + {/* Card content */} +
+

Happy Weekends 🎉

+

{dateLabel}

+
+
+ ); +}; + +/** + * Full-screen detail view for a selected weekend memory. + * Mirrors MemoryDetail: loads images into Redux, renders ImageCard grid + MediaView. + */ +const WeekendMemoryDetail: React.FC<{ + memory: WeeklyMemory; + onClose: () => void; +}> = ({ memory, onClose }) => { + const dispatch = useDispatch(); + const images = useSelector(selectImages); + const isImageViewOpen = useSelector(selectIsImageViewOpen); + + const sameDay = memory.start_date === memory.end_date; + const dateLabel = sameDay + ? formatWeekendDate(memory.start_date) + : `${formatWeekendDate(memory.start_date)} – ${formatWeekendDate(memory.end_date)}`; + + // Load images into Redux on mount (same pattern as MemoryDetail) + useEffect(() => { + dispatch(showLoader('Loading weekend memory')); + + const formattedImages = memory.images.map((img) => ({ + id: img.id, + path: img.path, + thumbnailPath: img.thumbnailPath, + folder_id: '', + isTagged: false, + isFavourite: false, + tags: [], + metadata: { + name: img.path.split('/').pop() || img.path.split('\\').pop() || '', + date_created: null as any, + width: 0, + height: 0, + file_location: img.path, + file_size: 0, + item_type: 'image' as const, + }, + })); + + requestAnimationFrame(() => { + dispatch(setImages(formattedImages)); + dispatch(hideLoader()); + }); + }, [memory, dispatch]); + + // Close on Escape key + useEffect(() => { + const handleKey = (e: KeyboardEvent) => { + if (e.key === 'Escape') onClose(); + }; + window.addEventListener('keydown', handleKey); + return () => window.removeEventListener('keydown', handleKey); + }, [onClose]); + + return ( +
+ {/* Header */} +
+
+ +
+
+

+
+ Happy Weekends 🎉 +
{dateLabel}
+

+
+

+ {memory.images.length} {memory.images.length === 1 ? 'photo' : 'photos'} +

+
+ + {/* Scrollable Images Grid — same layout as MemoryDetail */} +
+ {memory.images.length === 0 ? ( + + ) : ( +
+ {images.map((image, index) => ( + dispatch(setCurrentViewIndex(index))} + /> + ))} +
+ )} +
+ + {/* Full-screen viewer */} + {isImageViewOpen && ( + { }} + images={images} + /> + )} +
+ ); +}; + /** * Main Memories Page Component * Uses Tanstack Query hooks for data fetching @@ -137,6 +340,7 @@ export const MemoriesPage: React.FC = () => { const recentMemoriesQuery = useRecentMemories(30); const yearMemoriesQuery = useYearMemories(365); const onThisDayQuery = useOnThisDay(); + const weeklyMemoriesQuery = useWeeklyMemories(); // Extract data const allMemories = (allMemoriesQuery.data?.data?.memories as any) || []; @@ -146,17 +350,22 @@ export const MemoriesPage: React.FC = () => { const onThisDayImages = (onThisDayQuery.data?.data?.images as any) || []; const onThisDayMeta = onThisDayQuery.data?.data ? { - today: (onThisDayQuery.data.data as any).today, - years: (onThisDayQuery.data.data as any).years, - } + today: (onThisDayQuery.data.data as any).today, + years: (onThisDayQuery.data.data as any).years, + } : null; + // Weekend memories – the backend returns them directly (no .data wrapper) + const weeklyMemories: WeeklyMemory[] = + (weeklyMemoriesQuery.data as any)?.weekly_memories || []; + // Loading states const loading = { all: allMemoriesQuery.isLoading, recent: recentMemoriesQuery.isLoading, year: yearMemoriesQuery.isLoading, onThisDay: onThisDayQuery.isLoading, + weekends: weeklyMemoriesQuery.isLoading, }; // Error handling with InfoDialog @@ -211,8 +420,37 @@ export const MemoriesPage: React.FC = () => { } }, [onThisDayQuery.isError, onThisDayQuery.errorMessage, dispatch]); - // Simple filter state: 'all' | 'location' | 'date' - const [filter, setFilter] = useState<'all' | 'location' | 'date'>('all'); + useEffect(() => { + if (weeklyMemoriesQuery.isError) { + dispatch( + showInfoDialog({ + title: 'Error Loading Weekend Memories', + message: + weeklyMemoriesQuery.errorMessage || + 'Failed to load weekend memories', + variant: 'error', + }), + ); + } + }, [ + weeklyMemoriesQuery.isError, + weeklyMemoriesQuery.errorMessage, + dispatch, + ]); + + // Filter state: 'all' | 'location' | 'date' | 'weekends' + const [filter, setFilter] = useState<'all' | 'location' | 'date' | 'weekends'>('all'); + + // Selected weekend memory for detail view + const [selectedWeekend, setSelectedWeekend] = useState(null); + + const handleWeekendCardClick = useCallback((memory: WeeklyMemory) => { + setSelectedWeekend(memory); + }, []); + + const handleCloseWeekendDetail = useCallback(() => { + setSelectedWeekend(null); + }, []); // Filter out memories with only 1 image (same as backend min_images=2) const memoriesWithMultipleImages = (memories: Memory[]) => @@ -220,12 +458,21 @@ export const MemoriesPage: React.FC = () => { // Calculate counts (only memories with 2+ images) const totalCount = memoriesWithMultipleImages(allMemories).length; +<<<<<<< HEAD const locationCount = memoriesWithMultipleImages(allMemories).filter( (m) => m.center_lat != null && m.center_lon != null, ).length; const dateCount = memoriesWithMultipleImages(allMemories).filter( (m) => m.center_lat == null || m.center_lon == null, ).length; + const weekendsCount = weeklyMemories.length; +======= + const locationCount = + (allMemoriesQuery.data as any)?.total_location ?? + memoriesWithMultipleImages(allMemories).filter((m) => m.location_name !== null) + .length; + const dateCount = totalCount - locationCount; +>>>>>>> main // Simple filter function const applyFilter = (memories: Memory[]) => { @@ -233,16 +480,12 @@ export const MemoriesPage: React.FC = () => { const multiImageMemories = memoriesWithMultipleImages(memories); if (filter === 'location') { - return multiImageMemories.filter( - (m) => m.center_lat != null && m.center_lon != null, - ); + return multiImageMemories.filter((m) => m.location_name !== null); } if (filter === 'date') { - return multiImageMemories.filter( - (m) => m.center_lat == null || m.center_lon == null, - ); + return multiImageMemories.filter((m) => m.location_name === null); } - return multiImageMemories; // 'all' + return multiImageMemories; // 'all' and 'weekends' don't filter regular memories }; // Apply filter @@ -255,13 +498,25 @@ export const MemoriesPage: React.FC = () => { const handleRetryRecent = () => recentMemoriesQuery.refetch(); const handleRetryYear = () => yearMemoriesQuery.refetch(); const handleRetryOnThisDay = () => onThisDayQuery.refetch(); + const handleRetryWeekends = () => weeklyMemoriesQuery.refetch(); // Check if any data exists const hasAnyData = onThisDayImages.length > 0 || recentMemories.length > 0 || yearMemories.length > 0 || - allMemories.length > 0; + allMemories.length > 0 || + weeklyMemories.length > 0; + + // If a weekend memory detail is selected, show it full-screen + if (selectedWeekend) { + return ( + + ); + } return (
@@ -272,37 +527,58 @@ export const MemoriesPage: React.FC = () => {
{/* Filter Buttons */} {hasAnyData && ( -
+
+
)} @@ -334,130 +610,175 @@ export const MemoriesPage: React.FC = () => { )} {/* ==================================================================== - SECTION 1: On This Day + WEEKENDS SECTION (shown when Weekends filter is active) ==================================================================== */} - {onThisDayImages.length > 0 && onThisDayMeta && ( -
- - {loading.onThisDay ? ( - - ) : onThisDayQuery.isError ? ( - - ) : ( - - )} -
- )} - - {/* ==================================================================== - SECTION 2: Recent Memories (Last 30 days) - ==================================================================== */} - {filteredRecentMemories.length > 0 && ( + {filter === 'weekends' && (
- {loading.recent ? ( + {loading.weekends ? (
{[...Array(4)].map((_, i) => ( ))}
- ) : recentMemoriesQuery.isError ? ( + ) : weeklyMemoriesQuery.isError ? ( + ) : weeklyMemories.length === 0 ? ( + ) : (
- {filteredRecentMemories.map((memory: Memory) => ( - + {weeklyMemories.map((wm) => ( + ))}
)}
)} - {/* ==================================================================== - SECTION 3: Past Year (Last 365 days) - ==================================================================== */} - {filteredYearMemories.length > 0 && ( -
- - {loading.year ? ( -
- {[...Array(4)].map((_, i) => ( - - ))} -
- ) : yearMemoriesQuery.isError ? ( - - ) : ( -
- {filteredYearMemories.map((memory: Memory) => ( - - ))} -
+ {/* Hide all other sections while Weekends tab is active */} + {filter !== 'weekends' && ( + <> + {/* ==================================================================== + SECTION 1: On This Day + ==================================================================== */} + {onThisDayImages.length > 0 && onThisDayMeta && ( +
+ + {loading.onThisDay ? ( + + ) : onThisDayQuery.isError ? ( + + ) : ( + + )} +
)} -
- )} - {/* ==================================================================== - SECTION 4: All Memories - ==================================================================== */} - {filteredAllMemories.length > 0 && ( -
- - {loading.all ? ( -
- {[...Array(8)].map((_, i) => ( - - ))} -
- ) : allMemoriesQuery.isError ? ( - - ) : ( -
- {filteredAllMemories.map((memory: Memory) => ( - - ))} -
+ {/* ==================================================================== + SECTION 2: Recent Memories (Last 30 days) + ==================================================================== */} + {filteredRecentMemories.length > 0 && ( +
+ + {loading.recent ? ( +
+ {[...Array(4)].map((_, i) => ( + + ))} +
+ ) : recentMemoriesQuery.isError ? ( + + ) : ( +
+ {filteredRecentMemories.map((memory: Memory) => ( + + ))} +
+ )} +
)} -
+ + {/* ==================================================================== + SECTION 3: Past Year (Last 365 days) + ==================================================================== */} + {filteredYearMemories.length > 0 && ( +
+ + {loading.year ? ( +
+ {[...Array(4)].map((_, i) => ( + + ))} +
+ ) : yearMemoriesQuery.isError ? ( + + ) : ( +
+ {filteredYearMemories.map((memory: Memory) => ( + + ))} +
+ )} +
+ )} + + {/* ==================================================================== + SECTION 4: All Memories + ==================================================================== */} + {filteredAllMemories.length > 0 && ( +
+ + {loading.all ? ( +
+ {[...Array(8)].map((_, i) => ( + + ))} +
+ ) : allMemoriesQuery.isError ? ( + + ) : ( +
+ {filteredAllMemories.map((memory: Memory) => ( + + ))} +
+ )} +
+ )} + )}
diff --git a/frontend/src/components/Memories/MemoryCard.tsx b/frontend/src/components/Memories/MemoryCard.tsx index d5fa7a78d..d82b4c81f 100644 --- a/frontend/src/components/Memories/MemoryCard.tsx +++ b/frontend/src/components/Memories/MemoryCard.tsx @@ -39,8 +39,8 @@ export const MemoryCard = React.memo(({ memory }) => { : '/photo.png'; // Default placeholder // Determine memory type - // Backend uses 0,0 as sentinel for date-based memories (no GPS data) - const isDateBased = memory.center_lat == null || memory.center_lon == null; + // Use location_name to distinguish between location-based and date-based memories + const isDateBased = memory.location_name === null; // Format title based on memory type let displayTitle = memory.title || 'Untitled Memory'; diff --git a/frontend/src/hooks/useMemories.tsx b/frontend/src/hooks/useMemories.tsx index 673b5b593..b5b0ead47 100644 --- a/frontend/src/hooks/useMemories.tsx +++ b/frontend/src/hooks/useMemories.tsx @@ -3,6 +3,7 @@ import { generateMemories, getTimeline, getOnThisDay, + getWeeklyMemories, } from '@/api/api-functions/memories'; /** @@ -48,3 +49,14 @@ export const useOnThisDay = () => { staleTime: 60 * 60 * 1000, // 1 hour (this data is date-specific) }); }; + +/** + * Custom hook for fetching weekend memory clusters + */ +export const useWeeklyMemories = () => { + return usePictoQuery({ + queryKey: ['memories', 'weekends'], + queryFn: () => getWeeklyMemories(), + staleTime: 5 * 60 * 1000, // 5 minutes + }); +};