diff --git a/src/admin/AdminAppMenu.tsx b/src/admin/AdminAppMenu.tsx index ff3cbf2e..318041da 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_PHOTOS_SYNC, PATH_ADMIN_RECIPES, PATH_ADMIN_TAGS, PATH_ADMIN_UPLOADS, @@ -26,6 +27,7 @@ import IconSignOut from '@/components/icons/IconSignOut'; import IconLock from '@/components/icons/IconLock'; import { IoMdCheckboxOutline } from 'react-icons/io'; import Spinner from '@/components/Spinner'; +import IconBroom from '@/components/icons/IconBroom'; export default function AdminAppMenu({ active, @@ -38,6 +40,7 @@ export default function AdminAppMenu({ }) { const { photosCountTotal = 0, + photosCountNeedSync = 0, uploadsCount = 0, tagsCount = 0, recipesCount = 0, @@ -71,7 +74,7 @@ export default function AdminAppMenu({ annotation: `${uploadsCount}`, icon: , href: PATH_ADMIN_UPLOADS, }); @@ -121,7 +124,10 @@ export default function AdminAppMenu({ size={18} className="translate-x-[-1px] translate-y-[0.5px]" /> - : , + : , href: PATH_GRID_INFERRED, action: () => { if (isSelecting) { @@ -136,6 +142,17 @@ export default function AdminAppMenu({ shouldPreventDefault: false, }); } + if (photosCountNeedSync) { + items.push({ + label: 'To Sync', + annotation: `${photosCountNeedSync}`, + icon: , + href: PATH_ADMIN_PHOTOS_SYNC, + }); + } items.push({ label: showAppInsightsLink diff --git a/src/admin/AdminPhotosClient.tsx b/src/admin/AdminPhotosClient.tsx index ac500d94..4e4cff08 100644 --- a/src/admin/AdminPhotosClient.tsx +++ b/src/admin/AdminPhotosClient.tsx @@ -61,7 +61,7 @@ export default function AdminPhotosClient({ />} tooltip={( pluralize(photosCountNeedsSync, 'photo') + - ' needs sync' + ' missing data or AI-generated text' )} className={clsx( 'text-blue-600 dark:text-blue-400', diff --git a/src/admin/actions.ts b/src/admin/actions.ts index 283a69cb..c108be96 100644 --- a/src/admin/actions.ts +++ b/src/admin/actions.ts @@ -7,12 +7,16 @@ import { testDatabaseConnection } from '@/platforms/postgres'; import { testStorageConnection } from '@/platforms/storage'; import { APP_CONFIGURATION } from '@/app/config'; import { getStorageUploadUrlsNoStore } from '@/platforms/storage/cache'; -import { getInsightsIndicatorStatus } from '@/admin/insights/server'; import { getPhotosMeta, getUniqueTags, getUniqueRecipes, + getPhotosInNeedOfSyncCount, } from '@/photo/db/query'; +import { + getGitHubMetaForCurrentApp, + indicatorStatusForSignificantInsights, +} from './insights'; export type AdminData = Awaited>; @@ -21,10 +25,11 @@ export const getAdminDataAction = async () => const [ photosCount, photosCountHidden, + photosCountNeedSync, + codeMeta, uploadsCount, tagsCount, recipesCount, - insightsIndicatorStatus, ] = await Promise.all([ getPhotosMeta() .then(({ count }) => count) @@ -32,6 +37,8 @@ export const getAdminDataAction = async () => getPhotosMeta({ hidden: 'only' }) .then(({ count }) => count) .catch(() => 0), + getPhotosInNeedOfSyncCount(), + getGitHubMetaForCurrentApp(), getStorageUploadUrlsNoStore() .then(urls => urls.length) .catch(e => { @@ -44,9 +51,13 @@ export const getAdminDataAction = async () => getUniqueRecipes() .then(recipes => recipes.length) .catch(() => 0), - getInsightsIndicatorStatus(), ]); + const insightsIndicatorStatus = indicatorStatusForSignificantInsights({ + codeMeta, + photosCountNeedSync, + }); + const photosCountTotal = ( photosCount !== undefined && photosCountHidden !== undefined @@ -57,12 +68,13 @@ export const getAdminDataAction = async () => return { photosCount, photosCountHidden, + photosCountNeedSync, photosCountTotal, uploadsCount, tagsCount, recipesCount, insightsIndicatorStatus, - }; + } as const; }); const scanForError = ( diff --git a/src/admin/insights/AdminAppInsightsClient.tsx b/src/admin/insights/AdminAppInsightsClient.tsx index 550d876e..a62bfb39 100644 --- a/src/admin/insights/AdminAppInsightsClient.tsx +++ b/src/admin/insights/AdminAppInsightsClient.tsx @@ -46,6 +46,7 @@ import IconFocalLength from '@/components/icons/IconFocalLength'; import IconTag from '@/components/icons/IconTag'; import IconPhoto from '@/components/icons/IconPhoto'; import { HiOutlineDocumentText } from 'react-icons/hi'; +import { ReactNode } from 'react'; const DEBUG_COMMIT_SHA = '4cd29ed'; const DEBUG_COMMIT_MESSAGE = 'Long commit message for debugging purposes'; @@ -131,6 +132,13 @@ export default function AdminAppInsightsClient({ {codeMeta?.branch ?? TEMPLATE_REPO_BRANCH} ; + const renderTooltipContent = (content: ReactNode) => + ; + return ( {(codeMeta || debug) && <> @@ -143,11 +151,9 @@ export default function AdminAppInsightsClient({ />} content={<> Could not analyze source code - + {renderTooltipContent( + 'Could not connect to GitHub API. Try refreshing.', + )} } />} {((!codeMeta?.didError && noFork) || debug) && @@ -435,6 +441,9 @@ export default function AdminAppInsightsClient({ )} {' '} to sync + {renderTooltipContent(<> + Missing data or AI‑generated text + )} } expandPath={PATH_ADMIN_PHOTOS_SYNC} />} diff --git a/src/admin/insights/index.ts b/src/admin/insights/index.ts index 7837ea14..59aab3d9 100644 --- a/src/admin/insights/index.ts +++ b/src/admin/insights/index.ts @@ -99,9 +99,17 @@ export const getSignificantInsights = ({ }; }; -export const indicatorStatusForSignificantInsights = ( - insights: Awaited>, -) => { +export const indicatorStatusForSignificantInsights = ({ + codeMeta, + photosCountNeedSync, +}: Parameters[0] & { + photosCountNeedSync: number +}) => { + const insights = getSignificantInsights({ + codeMeta, + photosCountNeedSync, + }); + const { forkBehind, noAiRateLimiting, diff --git a/src/admin/insights/server.ts b/src/admin/insights/server.ts deleted file mode 100644 index 168c167b..00000000 --- a/src/admin/insights/server.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { getPhotosInNeedOfSyncCount } from '@/photo/db/query'; -import { - getSignificantInsights, - indicatorStatusForSignificantInsights, -} from '.'; -import { getGitHubMetaForCurrentApp } from '.'; - -export const getInsightsIndicatorStatus = async () => { - const [ - codeMeta, - photosCountNeedSync, - ] = await Promise.all([ - getGitHubMetaForCurrentApp(), - getPhotosInNeedOfSyncCount(), - ]); - - const significantInsights = getSignificantInsights({ - codeMeta, - photosCountNeedSync, - }); - - return indicatorStatusForSignificantInsights(significantInsights); -}; diff --git a/src/state/AppStateProvider.tsx b/src/state/AppStateProvider.tsx index d5b3290a..832d1324 100644 --- a/src/state/AppStateProvider.tsx +++ b/src/state/AppStateProvider.tsx @@ -151,7 +151,7 @@ export default function AppStateProvider({ if (userEmail) { storeAuthEmailCookie(userEmail); } - }, [userEmail, adminData]); + }, [userEmail]); const registerAdminUpdate = useCallback(() => setAdminUpdateTimes(updates => [...updates, new Date()])