fix: clean up single user page
Signed-off-by: Varun Raj <varun@skcript.com>
This commit is contained in:
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user