Add outdated photos page
This commit is contained in:
parent
4d448fb0bb
commit
6b262b46de
@ -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 }} />
|
||||
|
||||
53
src/app/admin/outdated/page.tsx
Normal file
53
src/app/admin/outdated/page.tsx
Normal 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>}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -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>
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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':
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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`;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user