From 65d6c90707f6879eaf351b4d26b671a19a4cb460 Mon Sep 17 00:00:00 2001 From: Varun Raj Date: Wed, 29 Oct 2025 23:10:55 +0530 Subject: [PATCH] feat: add new analytics endpoints and charts for enhanced data visualization - Introduced new API routes for people names, geo photos, and album photos statistics. - Updated analytics handler to include functions for fetching new statistics. - Added new chart components for visualizing people names, geo photos, and album photos data in the Exif data analytics page. - Modified asset deletion function to accept an options parameter for forced deletion. --- .../analytics/exif/AlbumPhotosChart.tsx | 39 +++++++++++++++ .../analytics/exif/GeoPhotosChart.tsx | 39 +++++++++++++++ .../analytics/exif/PeopleNamesChart.tsx | 39 +++++++++++++++ src/config/routes.ts | 3 ++ src/handlers/api/analytics.handler.ts | 14 +++++- src/handlers/api/asset.handler.ts | 2 +- src/pages/analytics/exif.tsx | 11 +++++ .../api/analytics/statistics/album-photos.ts | 43 +++++++++++++++++ .../api/analytics/statistics/geo-photos.ts | 47 +++++++++++++++++++ .../api/analytics/statistics/people-names.ts | 45 ++++++++++++++++++ src/pages/assets/empty-videos.tsx | 6 +-- src/schema/index.ts | 1 + 12 files changed, 284 insertions(+), 5 deletions(-) create mode 100644 src/components/analytics/exif/AlbumPhotosChart.tsx create mode 100644 src/components/analytics/exif/GeoPhotosChart.tsx create mode 100644 src/components/analytics/exif/PeopleNamesChart.tsx create mode 100644 src/pages/api/analytics/statistics/album-photos.ts create mode 100644 src/pages/api/analytics/statistics/geo-photos.ts create mode 100644 src/pages/api/analytics/statistics/people-names.ts diff --git a/src/components/analytics/exif/AlbumPhotosChart.tsx b/src/components/analytics/exif/AlbumPhotosChart.tsx new file mode 100644 index 0000000..08ffca6 --- /dev/null +++ b/src/components/analytics/exif/AlbumPhotosChart.tsx @@ -0,0 +1,39 @@ +import { Card } from "@/components/ui/card"; +import PieChart, { IPieChartData } from "@/components/ui/pie-chart"; +import { getAlbumPhotosStatistics } from "@/handlers/api/analytics.handler"; +import React, { useEffect, useState } from "react"; + +export default function AlbumPhotosChart() { + const [chartData, setChartData] = useState([]); + const [loading, setLoading] = useState(false); + const [errorMessage, setErrorMessage] = useState(null); + + const fetchData = async () => { + setLoading(true); + try { + const data = await getAlbumPhotosStatistics(); + setChartData(data); + } catch (error: any) { + setErrorMessage(error?.message || "Failed to fetch album photos data"); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + fetchData(); + }, []); + + return ( + + + + ); +} diff --git a/src/components/analytics/exif/GeoPhotosChart.tsx b/src/components/analytics/exif/GeoPhotosChart.tsx new file mode 100644 index 0000000..5d7f15f --- /dev/null +++ b/src/components/analytics/exif/GeoPhotosChart.tsx @@ -0,0 +1,39 @@ +import { Card } from "@/components/ui/card"; +import PieChart, { IPieChartData } from "@/components/ui/pie-chart"; +import { getGeoPhotosStatistics } from "@/handlers/api/analytics.handler"; +import React, { useEffect, useState } from "react"; + +export default function GeoPhotosChart() { + const [chartData, setChartData] = useState([]); + const [loading, setLoading] = useState(false); + const [errorMessage, setErrorMessage] = useState(null); + + const fetchData = async () => { + setLoading(true); + try { + const data = await getGeoPhotosStatistics(); + setChartData(data); + } catch (error: any) { + setErrorMessage(error?.message || "Failed to fetch geo photos data"); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + fetchData(); + }, []); + + return ( + + + + ); +} diff --git a/src/components/analytics/exif/PeopleNamesChart.tsx b/src/components/analytics/exif/PeopleNamesChart.tsx new file mode 100644 index 0000000..e90d9bf --- /dev/null +++ b/src/components/analytics/exif/PeopleNamesChart.tsx @@ -0,0 +1,39 @@ +import { Card } from "@/components/ui/card"; +import PieChart, { IPieChartData } from "@/components/ui/pie-chart"; +import { getPeopleNamesStatistics } from "@/handlers/api/analytics.handler"; +import React, { useEffect, useState } from "react"; + +export default function PeopleNamesChart() { + const [chartData, setChartData] = useState([]); + const [loading, setLoading] = useState(false); + const [errorMessage, setErrorMessage] = useState(null); + + const fetchData = async () => { + setLoading(true); + try { + const data = await getPeopleNamesStatistics(); + setChartData(data); + } catch (error: any) { + setErrorMessage(error?.message || "Failed to fetch people names data"); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + fetchData(); + }, []); + + return ( + + + + ); +} diff --git a/src/config/routes.ts b/src/config/routes.ts index b58dfef..bf04144 100644 --- a/src/config/routes.ts +++ b/src/config/routes.ts @@ -59,6 +59,9 @@ export const SEARCH_PLACES_PATH = BASE_PROXY_ENDPOINT + "/search/places"; export const ASSET_STATISTICS = BASE_PROXY_ENDPOINT + "/assets/statistics"; export const LIVE_PHOTO_STATISTICS = BASE_API_ENDPOINT + "/analytics/statistics/livephoto"; export const HEATMAP_DATA = BASE_API_ENDPOINT + "/analytics/statistics/heatmap"; +export const PEOPLE_NAMES_STATISTICS = BASE_API_ENDPOINT + "/analytics/statistics/people-names"; +export const GEO_PHOTOS_STATISTICS = BASE_API_ENDPOINT + "/analytics/statistics/geo-photos"; +export const ALBUM_PHOTOS_STATISTICS = BASE_API_ENDPOINT + "/analytics/statistics/album-photos"; // Common export const GET_FILTERS = BASE_API_ENDPOINT + "/filters/asset-filters"; diff --git a/src/handlers/api/analytics.handler.ts b/src/handlers/api/analytics.handler.ts index 6649906..605792e 100644 --- a/src/handlers/api/analytics.handler.ts +++ b/src/handlers/api/analytics.handler.ts @@ -1,4 +1,4 @@ -import { ASSET_STATISTICS, EXIF_DISTRIBUTION_PATH, HEATMAP_DATA, LIVE_PHOTO_STATISTICS } from "@/config/routes"; +import { ASSET_STATISTICS, EXIF_DISTRIBUTION_PATH, HEATMAP_DATA, LIVE_PHOTO_STATISTICS, PEOPLE_NAMES_STATISTICS, GEO_PHOTOS_STATISTICS, ALBUM_PHOTOS_STATISTICS } from "@/config/routes"; import API from "@/lib/api"; export type ISupportedEXIFColumns = @@ -18,4 +18,16 @@ export const getLivePhotoStatistics = async () => { export const getHeatMapData = async () => { return API.get(HEATMAP_DATA); +} + +export const getPeopleNamesStatistics = async () => { + return API.get(PEOPLE_NAMES_STATISTICS); +} + +export const getGeoPhotosStatistics = async () => { + return API.get(GEO_PHOTOS_STATISTICS); +} + +export const getAlbumPhotosStatistics = async () => { + return API.get(ALBUM_PHOTOS_STATISTICS); } \ No newline at end of file diff --git a/src/handlers/api/asset.handler.ts b/src/handlers/api/asset.handler.ts index 85667a5..31f3101 100644 --- a/src/handlers/api/asset.handler.ts +++ b/src/handlers/api/asset.handler.ts @@ -72,7 +72,7 @@ export const getAssetGeoHeatmap = async (filters: IHeatMapParams) => { return API.get(ASSET_GEO_HEATMAP_PATH, filters); } -export const deleteAssets = async (ids: string[]) => { +export const deleteAssets = async (ids: string[], options: { force?: boolean } = { force: true }) => { return API.delete(UPDATE_ASSETS_PATH, { ids, force: true }); } diff --git a/src/pages/analytics/exif.tsx b/src/pages/analytics/exif.tsx index 23da1c4..edc9073 100644 --- a/src/pages/analytics/exif.tsx +++ b/src/pages/analytics/exif.tsx @@ -11,6 +11,9 @@ import { CardTitle, } from "@/components/ui/card"; import AssetHeatMap from "@/components/analytics/exif/AssetHeatMap"; +import PeopleNamesChart from "@/components/analytics/exif/PeopleNamesChart"; +import GeoPhotosChart from "@/components/analytics/exif/GeoPhotosChart"; +import AlbumPhotosChart from "@/components/analytics/exif/AlbumPhotosChart"; import { useEffect, useState } from "react"; import { getAssetStatistics, getLivePhotoStatistics } from "@/handlers/api/analytics.handler"; import { humanizeBytes } from "@/helpers/string.helper"; @@ -130,6 +133,14 @@ export default function ExifDataAnalytics() { + + {/* New Analytics Charts */} +
+ + + +
+
{exifCharts.map((chart) => ( { - return deleteAssets(contextState.selectedIds).then(() => { + const handleDelete = (force?: boolean) => { + return deleteAssets(contextState.selectedIds, { force }).then(() => { const newAssets = contextState.assets.filter((a) => !contextState.selectedIds.includes(a.id)); setAssets(newAssets) contextState.updateContext({ @@ -264,7 +264,7 @@ export default function EmptyVideosPage() { handleDelete(true)} disabled={contextState.selectedIds.length === 0} >