Could not load duplicate groups: {(error as any)?.message ?? 'unknown error'}
)
}
if (groups.length === 0) {
return (
{data?.total_groups} group{data?.total_groups === 1 ? '' : 's'} ·{' '}
{data?.total_members} photo{data?.total_members === 1 ? '' : 's'}.
Click "Keep best" to auto-discard all but the highest-resolution
copy of each group. Cmd+Z to undo.
{groups.map((group, idx) => (
discardMutation.mutate(discardIds)}
onPreviewMember={(memberId) => openPreview(memberId, allMemberIds)}
onSelectMember={(memberId) => selectPhoto(memberId)}
selectedPhotos={selectedPhotos}
isPending={discardMutation.isPending}
// Hand the column-measurement ref to the first section only
// — every section's grid uses the same auto-fill rule so any
// one is representative of the rendered column count.
gridRef={idx === 0 ? sampleGridRef : undefined}
/>
))}
)
}
interface DuplicateGroupSectionProps {
group: DuplicateGroup
onKeepBest: (discardIds: string[]) => void
onPreviewMember: (memberId: string) => void
onSelectMember: (memberId: string) => void
selectedPhotos: string[]
isPending: boolean
/** Optional callback ref attached to this section's grid container.
* Used by DuplicatesView to measure the rendered column count for
* ↑/↓ keyboard navigation. Only the first section gets one. */
gridRef?: (el: HTMLDivElement | null) => void
}
function DuplicateGroupSection({
group,
onKeepBest,
onPreviewMember,
onSelectMember,
selectedPhotos,
isPending,
gridRef,
}: DuplicateGroupSectionProps) {
// Auto-pick "best" copy: highest pixel count, ties broken by file_size,
// then earliest taken_at, then id for determinism. This is just the
// default — the user can override it by clicking the crown button on
// any other thumbnail (see `manualBestId`).
const autoBest = useMemo(() => pickBestMember(group.members), [group.members])
// When the user clicks "make this the best" on a non-default thumb,
// we override the auto-pick. Local to the section so different groups
// remember independent overrides; resets if the group itself changes.
const [manualBestId, setManualBestId] = useState