fix: clean up single user page

Signed-off-by: Varun Raj <varun@skcript.com>
This commit is contained in:
Varun Raj
2024-11-30 19:37:15 +05:30
parent e0dd1b9e00
commit cbfee2ee40
4 changed files with 92 additions and 24 deletions
+2 -4
View File
@@ -4,11 +4,9 @@ import { Avatar } from "../ui/avatar";
import { updatePerson } from "@/handlers/api/people.handler";
import { PersonMergeDropdown } from "./PersonMergeDropdown";
import PersonBirthdayCell from "./PersonBirthdayCell";
import PersonHideCell from "./PersonHideCell";
import clsx from "clsx";
import Link from "next/link";
import { ENV } from "@/config/environment";
import { ArrowUpRight, Search } from "lucide-react";
import { ArrowUpRight, Info, Search } from "lucide-react";
import { useConfig } from "@/contexts/ConfigContext";
import { useToast } from "../ui/use-toast";
import { Badge } from "../ui/badge";
@@ -107,7 +105,7 @@ export default function PersonItem({ person, onRemove }: IProps) {
className="bg-gray-300 block rounded-lg px-2 py-1 text-sm dark:text-gray-900"
href={`/people/${person.id}`}
>
<Search size={16} />
<Info size={16} />
</Link>
</div>
<div className="absolute top-2 right-2 ">
@@ -0,0 +1,71 @@
import { ASSET_THUMBNAIL_PATH } from '@/config/routes';
import { useConfig } from '@/contexts/ConfigContext';
import { ExternalLink } from 'lucide-react';
import Image from 'next/image';
import Link from 'next/link';
import React, { useMemo } from 'react'
interface PersonAlbumListProps {
albums: {
id: string;
name: string;
thumbnail: string;
assetCount: number;
lastAssetYear: number;
}[]
personId: string;
}
export const PersonAlbum = ({ album, personId }: { album: PersonAlbumListProps['albums'][number], personId: string }) => {
const { exImmichUrl } = useConfig();
return (
<div key={album.id} className="flex flex-col gap-1 w-full bg-gray-100 dark:bg-zinc-800 border border-gray-200 dark:border-zinc-700 rounded-lg p-2">
<div className="relative w-full h-[200px]">
<Image src={ASSET_THUMBNAIL_PATH(album.thumbnail)}
alt={album.name}
width={100}
height={100}
className="rounded-md w-full h-full object-cover" />
</div>
<Link href={`${exImmichUrl}/albums/${album.id}`} target="_blank" className="text-sm font-medium truncate">{album.name}</Link>
<span className="text-sm text-gray-500 truncate">{album.assetCount} Occurences</span>
</div>
)
}
export default function PersonAlbumList({ albums, personId }: PersonAlbumListProps) {
const groupedAlbums = useMemo(() => {
const years: {
label: number;
albums: {
id: string;
name: string;
thumbnail: string;
assetCount: number;
lastAssetYear: number;
}[];
}[] = [];
const uniqueYears = albums.map((album) => album.lastAssetYear).filter((year, index, self) => self.indexOf(year) === index);
uniqueYears.forEach((year) => {
years.push({ label: year, albums: albums.filter((album) => album.lastAssetYear === year) });
});
return years.sort((a, b) => b.label - a.label);
}, [albums]);
return (
<div className='flex flex-col gap-10'>
{groupedAlbums.map((year) => (
<div key={year.label} className="flex flex-col gap-2 w-full">
{/* Country */}
<span className="font-medium text-xl">{year.label}</span>
{/* Cities */}
<div className='grid grid-cols-2 gap-4 md:grid-cols-5 w-full'>
{year.albums.map((album) => (
<PersonAlbum key={album.id} album={album} personId={personId} />
))}
</div>
</div>
))}
</div>
)
}
+8 -1
View File
@@ -30,10 +30,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
name: albums.albumName,
thumbnail: albums.albumThumbnailAssetId,
assetCount: sql<number>`count(${assets.id})`,
lastAssetDate: sql<Date>`max(${exif.dateTimeOriginal})`,
})
.from(albums)
.leftJoin(assetFaces, eq(assetFaces.personId, personRecord.id))
.leftJoin(assets, eq(assets.id, assetFaces.assetId))
.leftJoin(exif, eq(exif.assetId, assets.id))
.leftJoin(albumsAssetsAssets, eq(albumsAssetsAssets.assetsId, assets.id))
.where(eq(albumsAssetsAssets.albumsId, albums.id))
.groupBy(albums.id);
@@ -57,7 +59,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
return res.status(200).json({
...personRecord,
albums: dbPersonAlbums.sort((a, b) => b.assetCount - a.assetCount),
albums: dbPersonAlbums.sort((a, b) => b.assetCount - a.assetCount).map((album) => ({
...album,
lastAssetYear: album.lastAssetDate ? new Date(album.lastAssetDate).getFullYear() : null,
})),
cities: dbPersonCities,
citiesCount: dbPersonCities.length,
countriesCount: dbPersonCities.map((city) => city.country).filter((country, index, self) => self.indexOf(country) === index).length,
});
}
+11 -19
View File
@@ -1,4 +1,5 @@
import PageLayout from '@/components/layouts/PageLayout';
import PersonAlbumList from '@/components/people/info/PersonAlbumList';
import PersonCityList from '@/components/people/info/PersonCityList';
import Header from '@/components/shared/Header';
import { Button } from '@/components/ui/button';
@@ -20,7 +21,10 @@ interface IPersonFullInfo extends IPerson {
name: string;
assetCount: number;
thumbnail: string;
lastAssetYear: number;
}[];
citiesCount: number;
countriesCount: number;
cities: {
city: string;
country: string;
@@ -29,7 +33,6 @@ interface IPersonFullInfo extends IPerson {
}
export default function PersonPage() {
const { exImmichUrl } = useConfig();
const router = useRouter();
const { pathname, query } = router;
const { personId } = router.query as { personId: string };
@@ -56,8 +59,9 @@ export default function PersonPage() {
if (query.tab === "cities") {
return (
<div className="flex flex-col gap-4">
<div className="flex flex-col gap-2">
<div className="flex justify-between items-center gap-2">
<span className="text-lg font-medium">Cities</span>
<span className="text-sm text-gray-500">{person?.citiesCount} Cities, {person?.countriesCount} Countries</span>
</div>
<PersonCityList cities={person?.cities || []} personId={personId} />
</div>
@@ -66,24 +70,12 @@ export default function PersonPage() {
return (
<div className="flex flex-col gap-4">
<div className="flex flex-col gap-2">
<span className="text-sm font-medium">Albums {person?.albums.length}</span>
</div>
<div className="grid grid-cols-2 md:grid-cols-5 gap-4 w-full">
{person?.albums.map((album) => (
<div key={album.id} className="flex flex-col gap-2 w-full">
<div className="relative w-full h-[200px]">
<Image src={ASSET_THUMBNAIL_PATH(album.thumbnail)}
alt={album.name}
width={100}
height={100}
className="rounded-md w-full h-full object-cover" />
</div>
<Link href={`${exImmichUrl}/albums/${album.id}`} target="_blank" className="text-sm font-medium truncate">{album.name}</Link>
<span className="text-sm text-gray-500 truncate">{album.assetCount} Occurences</span>
</div>
))}
<div className="flex justify-between items-center gap-2">
<span className="text-lg font-medium">Albums</span>
<span className="text-sm text-gray-500">{person?.albums.length} Albums</span>
</div>
<PersonAlbumList albums={person?.albums || []} personId={personId} />
</div>
)
}