diff --git a/app/admin/recipes/[recipe]/edit/page.tsx b/app/admin/recipes/[recipe]/edit/page.tsx new file mode 100644 index 00000000..5cdfc425 --- /dev/null +++ b/app/admin/recipes/[recipe]/edit/page.tsx @@ -0,0 +1,48 @@ +import AdminChildPage from '@/components/AdminChildPage'; +import { redirect } from 'next/navigation'; +import { getPhotosCached } from '@/photo/cache'; +import TagForm from '@/tag/TagForm'; +import { PATH_ADMIN, PATH_ADMIN_TAGS, pathForTag } from '@/app/paths'; +import PhotoLightbox from '@/photo/PhotoLightbox'; +import { getPhotosMeta } from '@/photo/db/query'; +import AdminTagBadge from '@/admin/AdminTagBadge'; + +const MAX_PHOTO_TO_SHOW = 6; + +interface Props { + params: Promise<{ tag: string }> +} + +export default async function PhotoPageEdit({ + params, +}: Props) { + const { tag: tagFromParams } = await params; + + const tag = decodeURIComponent(tagFromParams); + + const [ + { count }, + photos, + ] = await Promise.all([ + getPhotosMeta({ tag }), + getPhotosCached({ tag, limit: MAX_PHOTO_TO_SHOW }), + ]); + + if (count === 0) { redirect(PATH_ADMIN); } + + return ( + } + > + + + + + ); +}; diff --git a/app/admin/recipes/page.tsx b/app/admin/recipes/page.tsx new file mode 100644 index 00000000..c70d03e7 --- /dev/null +++ b/app/admin/recipes/page.tsx @@ -0,0 +1,18 @@ +import AdminTagTable from '@/admin/AdminTagTable'; +import SiteGrid from '@/components/SiteGrid'; +import { getUniqueTagsHiddenCached } from '@/photo/cache'; + +export default async function AdminTagsPage() { + const tags = await getUniqueTagsHiddenCached().catch(() => []); + + return ( + +
+ +
+ } + /> + ); +} diff --git a/src/admin/AdminAppMenu.tsx b/src/admin/AdminAppMenu.tsx index 6f64b0cd..6d4880e8 100644 --- a/src/admin/AdminAppMenu.tsx +++ b/src/admin/AdminAppMenu.tsx @@ -5,6 +5,7 @@ import { PATH_ADMIN_CONFIGURATION, PATH_ADMIN_INSIGHTS, PATH_ADMIN_PHOTOS, + PATH_ADMIN_RECIPES, PATH_ADMIN_TAGS, PATH_ADMIN_UPLOADS, PATH_GRID_INFERRED, @@ -13,7 +14,7 @@ import { useAppState } from '@/state/AppState'; import { ImCheckboxUnchecked } from 'react-icons/im'; import { IoArrowDown, IoArrowUp, IoCloseSharp } from 'react-icons/io5'; import { clsx } from 'clsx/lite'; -import { TbPhoto } from 'react-icons/tb'; +import { TbChecklist, TbPhoto } from 'react-icons/tb'; import { FiTag } from 'react-icons/fi'; import { BiLockAlt } from 'react-icons/bi'; import AdminAppInfoIcon from './AdminAppInfoIcon'; @@ -37,6 +38,7 @@ export default function AdminAppMenu({ photosCountTotal = 0, uploadsCount = 0, tagsCount = 0, + recipesCount = 0, selectedPhotoIds, startUpload, setSelectedPhotoIds, @@ -103,6 +105,18 @@ export default function AdminAppMenu({ }); } + if (recipesCount) { + items.push({ + label: 'Manage Recipes', + annotation: `${recipesCount}`, + icon: , + href: PATH_ADMIN_RECIPES, + }); + } + if (photosCountTotal) { items.push({ label: isSelecting diff --git a/src/admin/AdminNav.tsx b/src/admin/AdminNav.tsx index eecda0aa..671b1fd6 100644 --- a/src/admin/AdminNav.tsx +++ b/src/admin/AdminNav.tsx @@ -2,10 +2,12 @@ import { getStorageUploadUrlsNoStore } from '@/platforms/storage/cache'; import { getPhotosMetaCached, getPhotosMostRecentUpdateCached, + getUniqueRecipesCached, getUniqueTagsCached, } from '@/photo/cache'; import { PATH_ADMIN_PHOTOS, + PATH_ADMIN_RECIPES, PATH_ADMIN_TAGS, PATH_ADMIN_UPLOADS, } from '@/app/paths'; @@ -14,21 +16,24 @@ import AdminNavClient from './AdminNavClient'; export default async function AdminNav() { const [ countPhotos, - countTags, countUploads, + countTags, + countRecipes, mostRecentPhotoUpdateTime, ] = await Promise.all([ getPhotosMetaCached({ hidden: 'include' }) .then(({ count }) => count) .catch(() => 0), - getUniqueTagsCached().then(tags => tags.length) - .catch(() => 0), getStorageUploadUrlsNoStore() .then(urls => urls.length) .catch(e => { console.error(`Error getting blob upload urls: ${e}`); return 0; }), + getUniqueTagsCached().then(tags => tags.length) + .catch(() => 0), + getUniqueRecipesCached().then(recipes => recipes.length) + .catch(() => 0), getPhotosMostRecentUpdateCached().catch(() => undefined), ]); @@ -55,6 +60,13 @@ export default async function AdminNav() { count: countTags, }); } + // Recipes + if (countRecipes > 0) { items.push({ + label: 'Recipes', + href: PATH_ADMIN_RECIPES, + count: countRecipes, + }); } + return ( >; @@ -17,8 +21,9 @@ export const getAdminDataAction = async () => const [ photosCount, photosCountHidden, - tagsCount, uploadsCount, + tagsCount, + recipesCount, insightsIndicatorStatus, ] = await Promise.all([ getPhotosMeta() @@ -27,15 +32,18 @@ export const getAdminDataAction = async () => getPhotosMeta({ hidden: 'only' }) .then(({ count }) => count) .catch(() => 0), - getUniqueTags() - .then(tags => tags.length) - .catch(() => 0), getStorageUploadUrlsNoStore() .then(urls => urls.length) .catch(e => { console.error(`Error getting blob upload urls: ${e}`); return 0; }), + getUniqueTags() + .then(tags => tags.length) + .catch(() => 0), + getUniqueRecipes() + .then(recipes => recipes.length) + .catch(() => 0), getInsightsIndicatorStatus(), ]); @@ -50,8 +58,9 @@ export const getAdminDataAction = async () => photosCount, photosCountHidden, photosCountTotal, - tagsCount, uploadsCount, + tagsCount, + recipesCount, insightsIndicatorStatus, }; }); diff --git a/src/app/paths.ts b/src/app/paths.ts index 8490f53b..77939527 100644 --- a/src/app/paths.ts +++ b/src/app/paths.ts @@ -41,6 +41,7 @@ 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_RECIPES = `${PATH_ADMIN}/recipes`; export const PATH_ADMIN_CONFIGURATION = `${PATH_ADMIN}/configuration`; export const PATH_ADMIN_INSIGHTS = `${PATH_ADMIN}/insights`; export const PATH_ADMIN_BASELINE = `${PATH_ADMIN}/baseline`;