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()])