Add outdated photos page
This commit is contained in:
parent
4d448fb0bb
commit
6b262b46de
@ -5,15 +5,18 @@ import {
|
|||||||
getUniqueTagsCached,
|
getUniqueTagsCached,
|
||||||
} from '@/photo/cache';
|
} from '@/photo/cache';
|
||||||
import {
|
import {
|
||||||
|
PATH_ADMIN_OUTDATED,
|
||||||
PATH_ADMIN_PHOTOS,
|
PATH_ADMIN_PHOTOS,
|
||||||
PATH_ADMIN_TAGS,
|
PATH_ADMIN_TAGS,
|
||||||
PATH_ADMIN_UPLOADS,
|
PATH_ADMIN_UPLOADS,
|
||||||
} from '@/site/paths';
|
} from '@/site/paths';
|
||||||
import AdminNavClient from './AdminNavClient';
|
import AdminNavClient from './AdminNavClient';
|
||||||
|
import { OUTDATED_THRESHOLD } from '@/photo';
|
||||||
|
|
||||||
export default async function AdminNav() {
|
export default async function AdminNav() {
|
||||||
const [
|
const [
|
||||||
countPhotos,
|
countPhotos,
|
||||||
|
countPhotosOutdated,
|
||||||
countUploads,
|
countUploads,
|
||||||
countTags,
|
countTags,
|
||||||
mostRecentPhotoUpdateTime,
|
mostRecentPhotoUpdateTime,
|
||||||
@ -21,6 +24,12 @@ export default async function AdminNav() {
|
|||||||
getPhotosMetaCached({ hidden: 'include' })
|
getPhotosMetaCached({ hidden: 'include' })
|
||||||
.then(({ count }) => count)
|
.then(({ count }) => count)
|
||||||
.catch(() => 0),
|
.catch(() => 0),
|
||||||
|
getPhotosMetaCached({
|
||||||
|
hidden: 'include',
|
||||||
|
takenBefore: OUTDATED_THRESHOLD,
|
||||||
|
})
|
||||||
|
.then(({ count }) => count)
|
||||||
|
.catch(() => 0),
|
||||||
getStorageUploadUrlsNoStore()
|
getStorageUploadUrlsNoStore()
|
||||||
.then(urls => urls.length)
|
.then(urls => urls.length)
|
||||||
.catch(e => {
|
.catch(e => {
|
||||||
@ -31,28 +40,33 @@ export default async function AdminNav() {
|
|||||||
getPhotosMostRecentUpdateCached().catch(() => undefined),
|
getPhotosMostRecentUpdateCached().catch(() => undefined),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const navItemPhotos = {
|
// Photos
|
||||||
|
const items = [{
|
||||||
label: 'Photos',
|
label: 'Photos',
|
||||||
href: PATH_ADMIN_PHOTOS,
|
href: PATH_ADMIN_PHOTOS,
|
||||||
count: countPhotos,
|
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',
|
label: 'Uploads',
|
||||||
href: PATH_ADMIN_UPLOADS,
|
href: PATH_ADMIN_UPLOADS,
|
||||||
count: countUploads,
|
count: countUploads,
|
||||||
};
|
}); }
|
||||||
|
|
||||||
const navItemTags = {
|
// Tags
|
||||||
|
if (countTags > 0) { items.push({
|
||||||
label: 'Tags',
|
label: 'Tags',
|
||||||
href: PATH_ADMIN_TAGS,
|
href: PATH_ADMIN_TAGS,
|
||||||
count: countTags,
|
count: countTags,
|
||||||
};
|
}); }
|
||||||
|
|
||||||
const items = [navItemPhotos];
|
|
||||||
|
|
||||||
if (countUploads > 0) { items.push(navItemUploads); }
|
|
||||||
if (countTags > 0) { items.push(navItemTags); }
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AdminNavClient {...{ items, mostRecentPhotoUpdateTime }} />
|
<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">
|
<div className="flex items-center gap-2.5">
|
||||||
{icon ?? <IoInformationCircleOutline
|
{icon ?? <IoInformationCircleOutline
|
||||||
size={17}
|
size={18}
|
||||||
className="translate-y-[1px]"
|
className="translate-y-[1px] shrink-0"
|
||||||
/>}
|
/>}
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
import Spinner, { SpinnerColor } from '@/components/Spinner';
|
import Spinner, { SpinnerColor } from '@/components/Spinner';
|
||||||
import { clsx } from 'clsx/lite';
|
import { clsx } from 'clsx/lite';
|
||||||
import { ButtonHTMLAttributes, ReactNode } from 'react';
|
import { ButtonHTMLAttributes, ReactNode } from 'react';
|
||||||
@ -42,7 +44,7 @@ export default function LoaderButton(props: {
|
|||||||
: ['h-9']),
|
: ['h-9']),
|
||||||
styleAs === 'link' && 'hover:text-dim',
|
styleAs === 'link' && 'hover:text-dim',
|
||||||
styleAs === 'link-without-hover' && 'hover:text-main',
|
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,
|
className,
|
||||||
)}
|
)}
|
||||||
disabled={isLoading || disabled}
|
disabled={isLoading || disabled}
|
||||||
|
|||||||
@ -8,7 +8,7 @@ export const GENERATE_STATIC_PARAMS_LIMIT = 1000;
|
|||||||
export const PHOTO_DEFAULT_LIMIT = 100;
|
export const PHOTO_DEFAULT_LIMIT = 100;
|
||||||
|
|
||||||
export type GetPhotosOptions = {
|
export type GetPhotosOptions = {
|
||||||
sortBy?: 'createdAt' | 'takenAt' | 'priority'
|
sortBy?: 'createdAt' | 'createdAtAsc' | 'takenAt' | 'priority'
|
||||||
limit?: number
|
limit?: number
|
||||||
offset?: number
|
offset?: number
|
||||||
query?: string
|
query?: string
|
||||||
@ -106,6 +106,8 @@ export const getOrderByFromOptions = (options: GetPhotosOptions) => {
|
|||||||
switch (sortBy) {
|
switch (sortBy) {
|
||||||
case 'createdAt':
|
case 'createdAt':
|
||||||
return 'ORDER BY created_at DESC';
|
return 'ORDER BY created_at DESC';
|
||||||
|
case 'createdAtAsc':
|
||||||
|
return 'ORDER BY created_at ASC';
|
||||||
case 'takenAt':
|
case 'takenAt':
|
||||||
return 'ORDER BY taken_at DESC';
|
return 'ORDER BY taken_at DESC';
|
||||||
case 'priority':
|
case 'priority':
|
||||||
|
|||||||
@ -14,6 +14,8 @@ import camelcaseKeys from 'camelcase-keys';
|
|||||||
import { isBefore } from 'date-fns';
|
import { isBefore } from 'date-fns';
|
||||||
import type { Metadata } from 'next';
|
import type { Metadata } from 'next';
|
||||||
|
|
||||||
|
export const OUTDATED_THRESHOLD = new Date('2024-06-01');
|
||||||
|
|
||||||
// INFINITE SCROLL: LARGE PHOTOS
|
// INFINITE SCROLL: LARGE PHOTOS
|
||||||
export const INFINITE_SCROLL_LARGE_PHOTO_INITIAL =
|
export const INFINITE_SCROLL_LARGE_PHOTO_INITIAL =
|
||||||
process.env.NODE_ENV === 'development' ? 2 : 12;
|
process.env.NODE_ENV === 'development' ? 2 : 12;
|
||||||
|
|||||||
@ -30,6 +30,7 @@ const PATH_FOCAL_LENGTH_DYNAMIC = `${PREFIX_FOCAL_LENGTH}/[focal]`;
|
|||||||
|
|
||||||
// Admin paths
|
// Admin paths
|
||||||
export const PATH_ADMIN_PHOTOS = `${PATH_ADMIN}/photos`;
|
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_UPLOADS = `${PATH_ADMIN}/uploads`;
|
||||||
export const PATH_ADMIN_TAGS = `${PATH_ADMIN}/tags`;
|
export const PATH_ADMIN_TAGS = `${PATH_ADMIN}/tags`;
|
||||||
export const PATH_ADMIN_CONFIGURATION = `${PATH_ADMIN}/configuration`;
|
export const PATH_ADMIN_CONFIGURATION = `${PATH_ADMIN}/configuration`;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user