feat: added option to filter no name people

Signed-off-by: Varun Raj <varun@skcript.com>
This commit is contained in:
Varun Raj committed 2024-12-01 18:58:32 +05:30
1 parent cbfee2ee40
commit 0597f34662
8 files changed
+45 -26

No files matched your search

+14 -12
View File
@@ -15,10 +15,11 @@ import {
import { Input } from "../ui/input";
import { IPersonListFilters } from "@/handlers/api/people.handler";
import AssetFilter from "../shared/common/AssetFilter";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../ui/select";
export function PeopleFilters() {
const router = useRouter();
const { updateContext, page, maximumAssetCount } = usePeopleFilterContext();
const { updateContext, page, maximumAssetCount, type = "all" } = usePeopleFilterContext();
const handleChange = (data: Partial<IPersonListFilters>) => {
updateContext(data);
@@ -28,6 +29,7 @@ export function PeopleFilters() {
...router.query,
...data,
page: data.page || undefined,
type: data.type || undefined,
},
});
}
@@ -51,17 +53,17 @@ export function PeopleFilters() {
}
}}
/>
<div className="flex items-center gap-1">
<Switch
id="nameLessOnly"
onCheckedChange={(checked) => {
handleChange({ nameLessOnly: checked, page: 1 });
}}
/>
<Label className="text-nowrap" htmlFor="nameLessOnly">
Show only Nameless
</Label>
</div>
<Select value={type} onValueChange={(value) => handleChange({ type: value })}>
<SelectTrigger>
<SelectValue placeholder="Person Type" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All</SelectItem>
<SelectItem value="nameless">Nameless</SelectItem>
<SelectItem value="named">Named</SelectItem>
</SelectContent>
</Select>
<Button
disabled={prevPage < 1}
+1 -2
View File
@@ -8,12 +8,11 @@ type ISortField = "assetCount" | "updatedAt" | "createdAt";
export interface IPersonListFilters {
page: number | string;
perPage?: number;
nameLessOnly?: boolean | string;
minimumAssetCount?: number;
maximumAssetCount?: number;
sort?: ISortField;
sortOrder?: "asc" | "desc";
type?: string;
}
export const listPeople = (filters: IPersonListFilters): Promise<IPeopleListResponse> => {
return API.get(LIST_PEOPLE_PATH, filters).then((response) => {
+5 -1
View File
@@ -14,6 +14,7 @@ import { Select, SelectItem, SelectContent, SelectTrigger, SelectValue } from '@
import { useRouter } from 'next/router'
import { ChevronDownIcon, ChevronUpIcon } from '@radix-ui/react-icons'
import { SortAsc, SortDesc } from 'lucide-react'
import { Input } from '@/components/ui/input'
const SORT_BY_OPTIONS = [
{ value: 'lastPhotoDate', label: 'Last Photo Date' },
@@ -28,6 +29,7 @@ const SORT_BY_OPTIONS = [
export default function AlbumListPage() {
const { exImmichUrl } = useConfig()
const router = useRouter()
const [search, setSearch] = useState('')
const { query, pathname } = router
const { sortBy, sortOrder } = query as { sortBy: string, sortOrder: string }
const [albums, setAlbums] = useState<IAlbum[]>([])
@@ -37,6 +39,7 @@ export default function AlbumListPage() {
const selectedSortBy = useMemo(() => SORT_BY_OPTIONS.find((option) => option.value === sortBy), [sortBy])
const searchedAlbums = useMemo(() => albums.filter((album) => album.albumName.toLowerCase().includes(search.toLowerCase())), [albums, search])
const fetchAlbums = async () => {
setLoading(true)
listAlbums({
@@ -74,7 +77,7 @@ export default function AlbumListPage() {
}
return (
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-4 p-4">
{albums.map((album) => (
{searchedAlbums.map((album) => (
<AlbumThumbnail album={album} key={album.id} onSelect={(checked) => handleSelect(checked, album.id)} />
))}
</div>
@@ -86,6 +89,7 @@ export default function AlbumListPage() {
leftComponent="Manage Albums"
rightComponent={
<div className="flex items-center gap-2">
<Input type="text" placeholder="Search" className="w-48" onChange={(e) => setSearch(e.target.value)} />
<Select
defaultValue={selectedSortBy?.value}
onValueChange={(value) => router.push({
+2 -1
View File
@@ -28,7 +28,7 @@ export default async function handler(
if (!id) {
return res.status(400).json({ error: "Album id is required" });
}
const dbAssets = await db.select({
const dbAssets = await db.selectDistinctOn([assets.id], {
id: assets.id,
deviceId: assets.deviceId,
type: assets.type,
@@ -53,6 +53,7 @@ export default async function handler(
.where(and(
eq(albumsAssetsAssets.albumsId, id),
eq(assets.isVisible, true),
isNotNull(assets.isArchived),
faceId ? eq(assetFaces.personId, faceId) : undefined,
))
.limit(100)
+1 -1
View File
@@ -21,7 +21,7 @@ export default async function handler(
}
const { id } = req.query as { id: string };
const dbAlbums = await db.select({
const dbAlbums = await db.selectDistinctOn([assets.id], {
id: albums.id,
albumName: albums.albumName,
createdAt: albums.createdAt,
+7 -3
View File
@@ -4,7 +4,7 @@ import { db } from "@/config/db";
import { getCurrentUser } from "@/handlers/serverUtils/user.utils";
import { NextApiResponse } from "next";
import { albums } from "@/schema/albums.schema";
import { count, desc, eq, min, max, sql, and, sum } from "drizzle-orm";
import { count, desc, eq, min, max, sql, and, sum, isNotNull } from "drizzle-orm";
import { assets } from "@/schema/assets.schema";
import { albumsAssetsAssets } from "@/schema/albumAssetsAssets.schema";
import { users } from "@/schema/users.schema";
@@ -55,7 +55,7 @@ export default async function handler(
createdAt: albums.createdAt,
updatedAt: albums.updatedAt,
albumThumbnailAssetId: albums.albumThumbnailAssetId,
assetCount: count(assets.id),
assetCount: count(sql<string>`DISTINCT ${assets.id}`),
firstPhotoDate: min(exif.dateTimeOriginal),
lastPhotoDate: max(exif.dateTimeOriginal),
size: sum(exif.fileSizeInByte),
@@ -68,7 +68,11 @@ export default async function handler(
})
.from(albums)
.leftJoin(albumsAssetsAssets, eq(albums.id, albumsAssetsAssets.albumsId))
.leftJoin(assets, eq(albumsAssetsAssets.assetsId, assets.id))
.leftJoin(assets, and(
eq(albumsAssetsAssets.assetsId, assets.id),
eq(assets.isVisible, true),
isNotNull(assets.isArchived),
))
.leftJoin(exif, eq(assets.id, exif.assetId))
.leftJoin(assetFaces, eq(assets.id, assetFaces.assetId))
.leftJoin(person, and(eq(assetFaces.personId, person.id), eq(person.isHidden, false)))
+3 -3
View File
@@ -25,7 +25,7 @@ type ISortField = "assetCount" | "updatedAt" | "createdAt";
interface IQuery {
page: number;
perPage: number;
nameLessOnly: boolean | string;
type: string;
maximumAssetCount: number;
sort: ISortField;
sortOrder: "asc" | "desc";
@@ -38,10 +38,10 @@ export default async function handler(
const {
page = 1,
perPage = 60,
nameLessOnly = false,
maximumAssetCount: maxValue = 1000000,
sort = "assetCount",
sortOrder = "desc",
type = "all",
} = req.query as any as IQuery;
const currentUser = await getCurrentUser(req);
@@ -53,7 +53,7 @@ export default async function handler(
eq(assets.isVisible, true),
eq(assets.isArchived, false),
eq(assets.ownerId, currentUser.id),
stringToBoolean(nameLessOnly) ? eq(person.name, "") : undefined
type === "all" ? undefined : (type === "nameless" ? eq(person.name, "") : ne(person.name, ""))
);
let query = db
+12 -3
View File
@@ -1,5 +1,6 @@
// pages/api/proxy.js
import { db } from "@/config/db";
import { ENV } from "@/config/environment";
import { getCurrentUser } from "@/handlers/serverUtils/user.utils";
import { NextApiRequest, NextApiResponse } from "next";
@@ -19,13 +20,21 @@ export default async function handler(
}
try {
const user = await getCurrentUser(req);
if (!db) {
return res.status(500).json({ message: "Database connection failed" });
}
const user = await getCurrentUser(req);
if (user) return res.status(200).json(user);
return res.status(403).json({ message: "Forbidden" });
} catch (error) {
return res.status(500).json(error);
} catch (error: any) {
return res.status(500).json({
message: error.message ||"Internal Server Error",
error: error,
});
}
}