Files
immich-folio/lib/admin/proofing-view.ts

99 lines
3.0 KiB
TypeScript
Raw Permalink Normal View History

/**
* What the admin panel sees of a proofing session: the stored record plus the
* album's name and the state, without the Immich lookups repeated per caller.
*/
import { immich, type ImmichAlbum } from '@/lib/immich';
import { imageUrl } from '@/lib/urls';
import {
downloadsRemaining,
sessionState,
type ProofSession,
type ProofState,
} from '@/lib/proofSessions';
export interface AdminProofSession {
id: string;
clientName: string;
albumId: string;
/** Null when Immich no longer has the album (or cannot be reached). */
albumName: string | null;
albumAssetCount: number | null;
alias: string | null;
/** Relative path of the client link. */
link: string;
createdAt: string;
expiresOn: string | null;
download: ProofSession['download'];
downloadLimit: number | null;
downloadsUsed: number;
downloadsRemaining: number | null;
selected: number;
updatedAt: string | null;
submittedAt: string | null;
state: ProofState;
}
export interface AdminProofPick {
assetId: string;
position: number;
fileName: string;
thumbUrl: string;
takenAt?: string;
}
/** Load each album once, tolerating ones that are gone or unreachable. */
export async function loadAlbums(ids: string[]): Promise<Map<string, ImmichAlbum | null>> {
const unique = Array.from(new Set(ids));
const albums = await Promise.all(
unique.map((id) => immich.getProofingAlbum(id).catch(() => null)),
);
return new Map(unique.map((id, i) => [id, albums[i]]));
}
export function toAdminSession(
session: ProofSession,
album: ImmichAlbum | null,
): AdminProofSession {
return {
id: session.id,
clientName: session.clientName,
albumId: session.albumId,
albumName: album?.albumName ?? null,
albumAssetCount: album ? album.assets.length : null,
alias: session.alias ?? null,
link: session.alias ? `/c/${session.alias}` : `/proof/${session.token}`,
createdAt: session.createdAt,
expiresOn: session.expiresOn ?? null,
download: session.download,
downloadLimit: session.downloadLimit ?? null,
downloadsUsed: session.downloadsUsed,
downloadsRemaining: downloadsRemaining(session),
selected: session.selection.length,
updatedAt: session.updatedAt ?? null,
submittedAt: session.submittedAt ?? null,
state: sessionState(session),
};
}
/** The selected photos in album order, with what the export and preview need. */
export function picksFor(session: ProofSession, album: ImmichAlbum | null): AdminProofPick[] {
if (!album) return [];
const chosen = new Set(session.selection);
const media = album.assets.filter((a) => a.type === 'IMAGE' || a.type === 'VIDEO');
const picks: AdminProofPick[] = [];
media.forEach((asset, index) => {
if (!chosen.has(asset.id)) return;
picks.push({
assetId: asset.id,
position: index + 1,
fileName: asset.originalFileName,
2026-10-02 18:14:23 +02:00
thumbUrl: imageUrl(asset, 'thumbnail'),
...(asset.localDateTime || asset.fileCreatedAt
? { takenAt: asset.localDateTime ?? asset.fileCreatedAt }
: {}),
});
});
return picks;
}