Add outdated photos page

This commit is contained in:
Sam Becker 2024-06-16 13:30:52 -05:00
parent 4d448fb0bb
commit 6b262b46de
7 changed files with 89 additions and 15 deletions

View File

@ -5,15 +5,18 @@ import {
getUniqueTagsCached,
} from '@/photo/cache';
import {
PATH_ADMIN_OUTDATED,
PATH_ADMIN_PHOTOS,
PATH_ADMIN_TAGS,
PATH_ADMIN_UPLOADS,
} from '@/site/paths';
import AdminNavClient from './AdminNavClient';
import { OUTDATED_THRESHOLD } from '@/photo';
export default async function AdminNav() {
const [
countPhotos,
countPhotosOutdated,
countUploads,
countTags,
mostRecentPhotoUpdateTime,
@ -21,6 +24,12 @@ export default async function AdminNav() {
getPhotosMetaCached({ hidden: 'include' })
.then(({ count }) => count)
.catch(() => 0),
getPhotosMetaCached({
hidden: 'include',
takenBefore: OUTDATED_THRESHOLD,
})
.then(({ count }) => count)
.catch(() => 0),
getStorageUploadUrlsNoStore()
.then(urls => urls.length)
.catch(e => {
@ -31,28 +40,33 @@ export default async function AdminNav() {
getPhotosMostRecentUpdateCached().catch(() => undefined),
]);
const navItemPhotos = {
// Photos
const items = [{
label: 'Photos',
href: PATH_ADMIN_PHOTOS,
count: countPhotos,
};
}];
const navItemUploads = {
// Outdated Photos
if (countPhotosOutdated > 0) { items.push({
label: 'Outdated',
href: PATH_ADMIN_OUTDATED,
count: countPhotosOutdated,
}); }
// Uploads
if (countUploads > 0) { items.push({
label: 'Uploads',
href: PATH_ADMIN_UPLOADS,
count: countUploads,
};
}); }
const navItemTags = {
// Tags
if (countTags > 0) { items.push({
label: 'Tags',
href: PATH_ADMIN_TAGS,
count: countTags,
};
const items = [navItemPhotos];
if (countUploads > 0) { items.push(navItemUploads); }
if (countTags > 0) { items.push(navItemTags); }
}); }
return (
<AdminNavClient {...{ items, mostRecentPhotoUpdateTime }} />

View File

@ -0,0 +1,53 @@
import SiteGrid from '@/components/SiteGrid';
import { AI_TEXT_GENERATION_ENABLED } from '@/site/config';
import { getPhotos } from '@/photo/db/query';
import AdminPhotosTable from '@/admin/AdminPhotosTable';
import { OUTDATED_THRESHOLD } from '@/photo';
import LoaderButton from '@/components/primitives/LoaderButton';
import IconGrSync from '@/site/IconGrSync';
import Banner from '@/components/Banner';
const UPDATE_BATCH_SIZE = 5;
export default async function AdminPhotosPage() {
const photos = await getPhotos({
hidden: 'include',
sortBy: 'createdAtAsc',
takenBefore: OUTDATED_THRESHOLD,
limit: 1_000,
}).catch(() => []);
return (
<SiteGrid
contentMain={
<div className="space-y-4">
<Banner>
<div className="space-y-1.5">
These photos {'('}uploaded before
{' '}
{new Date(OUTDATED_THRESHOLD).toLocaleDateString()}{')'}
{' '}
may have: missing EXIF fields, inaccurate blur data,
{' '}
undesired privacy settings,
{' '}
and missing AI-generated text.
</div>
</Banner>
<LoaderButton
icon={<IconGrSync className="translate-y-[1px]" />}
hideTextOnMobile={false}
className="primary"
>
Sync oldest {UPDATE_BATCH_SIZE} photos
</LoaderButton>
<div className="space-y-4">
<AdminPhotosTable
photos={photos}
hasAiTextGeneration={AI_TEXT_GENERATION_ENABLED}
/>
</div>
</div>}
/>
);
}

View File

@ -27,8 +27,8 @@ export default function Banner({
>
<div className="flex items-center gap-2.5">
{icon ?? <IoInformationCircleOutline
size={17}
className="translate-y-[1px]"
size={18}
className="translate-y-[1px] shrink-0"
/>}
{children}
</div>

View File

@ -1,3 +1,5 @@
'use client';
import Spinner, { SpinnerColor } from '@/components/Spinner';
import { clsx } from 'clsx/lite';
import { ButtonHTMLAttributes, ReactNode } from 'react';
@ -42,7 +44,7 @@ export default function LoaderButton(props: {
: ['h-9']),
styleAs === 'link' && 'hover:text-dim',
styleAs === 'link-without-hover' && 'hover:text-main',
'inline-flex items-center gap-2 self-start',
'inline-flex items-center gap-2 self-start whitespace-nowrap',
className,
)}
disabled={isLoading || disabled}

View File

@ -8,7 +8,7 @@ export const GENERATE_STATIC_PARAMS_LIMIT = 1000;
export const PHOTO_DEFAULT_LIMIT = 100;
export type GetPhotosOptions = {
sortBy?: 'createdAt' | 'takenAt' | 'priority'
sortBy?: 'createdAt' | 'createdAtAsc' | 'takenAt' | 'priority'
limit?: number
offset?: number
query?: string
@ -106,6 +106,8 @@ export const getOrderByFromOptions = (options: GetPhotosOptions) => {
switch (sortBy) {
case 'createdAt':
return 'ORDER BY created_at DESC';
case 'createdAtAsc':
return 'ORDER BY created_at ASC';
case 'takenAt':
return 'ORDER BY taken_at DESC';
case 'priority':

View File

@ -14,6 +14,8 @@ import camelcaseKeys from 'camelcase-keys';
import { isBefore } from 'date-fns';
import type { Metadata } from 'next';
export const OUTDATED_THRESHOLD = new Date('2024-06-01');
// INFINITE SCROLL: LARGE PHOTOS
export const INFINITE_SCROLL_LARGE_PHOTO_INITIAL =
process.env.NODE_ENV === 'development' ? 2 : 12;

View File

@ -30,6 +30,7 @@ const PATH_FOCAL_LENGTH_DYNAMIC = `${PREFIX_FOCAL_LENGTH}/[focal]`;
// Admin paths
export const PATH_ADMIN_PHOTOS = `${PATH_ADMIN}/photos`;
export const PATH_ADMIN_OUTDATED = `${PATH_ADMIN}/outdated`;
export const PATH_ADMIN_UPLOADS = `${PATH_ADMIN}/uploads`;
export const PATH_ADMIN_TAGS = `${PATH_ADMIN}/tags`;
export const PATH_ADMIN_CONFIGURATION = `${PATH_ADMIN}/configuration`;