Moved the Select/Filter controls into the photo list area and added the missing “Select” translation

This commit is contained in:
Codex Agent
2025-12-28 22:49:30 +01:00
parent 9d367512c5
commit 795e37ee12
3 changed files with 54 additions and 27 deletions

View File

@@ -178,6 +178,7 @@
"all": "Alle",
"loadMore": "Mehr laden",
"processing": "Verarbeite …",
"select": "Auswählen",
"close": "Schließen",
"reset": "Zurücksetzen"
},

View File

@@ -174,6 +174,7 @@
"all": "All",
"loadMore": "Load more",
"processing": "Processing…",
"select": "Select",
"close": "Close",
"reset": "Reset"
},

View File

@@ -98,6 +98,11 @@ export default function MobileEventPhotosPage() {
const surface = String(theme.surface?.val ?? '#ffffff');
const backdrop = String(theme.gray12?.val ?? '#0f172a');
const basePhotosPath = slug ? adminPath(`/mobile/events/${slug}/photos`) : adminPath('/mobile/events');
const photoQuery = React.useMemo(() => {
return new URLSearchParams(location.search).get('photo');
}, [location.search]);
const sourcePhotoParam = photoQuery ?? photoIdParam ?? null;
const lightboxIndex = React.useMemo(() => {
if (lightboxId === null) {
return -1;
@@ -106,12 +111,12 @@ export default function MobileEventPhotosPage() {
}, [photos, lightboxId]);
const lightbox = lightboxIndex >= 0 ? photos[lightboxIndex] : null;
const parsedPhotoId = React.useMemo(() => {
if (!photoIdParam) {
if (!sourcePhotoParam) {
return null;
}
const parsed = Number(photoIdParam);
const parsed = Number(sourcePhotoParam);
return Number.isFinite(parsed) ? parsed : null;
}, [photoIdParam]);
}, [sourcePhotoParam]);
React.useEffect(() => {
if (lightboxId !== null && lightboxIndex === -1 && !loading && pendingPhotoId !== lightboxId) {
@@ -144,7 +149,8 @@ export default function MobileEventPhotosPage() {
}, [lightbox]);
React.useEffect(() => {
if (!photoIdParam) {
if (!sourcePhotoParam) {
setLightboxId(null);
setPendingPhotoId(null);
return;
}
@@ -156,7 +162,7 @@ export default function MobileEventPhotosPage() {
setLightboxId(parsedPhotoId);
setPendingPhotoId(parsedPhotoId);
}, [parsedPhotoId, photoIdParam]);
}, [parsedPhotoId, sourcePhotoParam]);
React.useEffect(() => {
if (slugParam && activeEvent?.slug !== slugParam) {
@@ -333,9 +339,26 @@ export default function MobileEventPhotosPage() {
}
}, [online, syncQueuedActions]);
const setLightboxWithUrl = React.useCallback((photoId: number | null) => {
setLightboxId(photoId);
}, []);
const setLightboxWithUrl = React.useCallback(
(photoId: number | null) => {
setLightboxId(photoId);
if (typeof window === 'undefined' || !slug) {
return;
}
const params = new URLSearchParams(window.location.search);
if (photoId) {
params.set('photo', String(photoId));
} else {
params.delete('photo');
}
const nextSearch = params.toString();
const nextPath = `${basePhotosPath}${nextSearch ? `?${nextSearch}` : ''}`;
if (`${window.location.pathname}${window.location.search}` !== nextPath) {
window.history.replaceState(null, '', nextPath);
}
},
[basePhotosPath, slug],
);
const handleModerationAction = React.useCallback(
async (action: PhotoModerationAction['action'], photo: TenantPhoto) => {
@@ -641,23 +664,6 @@ export default function MobileEventPhotosPage() {
onBack={back}
headerActions={
<XStack space="$3">
<HeaderActionButton
onPress={() => {
if (selectionMode) {
clearSelection();
} else {
setSelectionMode(true);
}
}}
ariaLabel={selectionMode ? t('common.done', 'Done') : t('common.select', 'Select')}
>
<Text fontSize="$xs" fontWeight="800" color={text}>
{selectionMode ? t('common.done', 'Done') : t('common.select', 'Select')}
</Text>
</HeaderActionButton>
<HeaderActionButton onPress={() => setShowFilters(true)} ariaLabel={t('mobilePhotos.filtersTitle', 'Filter')}>
<Filter size={18} color={text} />
</HeaderActionButton>
<HeaderActionButton onPress={() => load()} ariaLabel={t('common.refresh', 'Refresh')}>
<RefreshCcw size={18} color={text} />
</HeaderActionButton>
@@ -707,6 +713,27 @@ export default function MobileEventPhotosPage() {
</MobileCard>
) : null}
<XStack space="$2">
<CTAButton
label={selectionMode ? t('common.done', 'Done') : t('common.select', 'Select')}
tone={selectionMode ? 'solid' : 'ghost'}
fullWidth={false}
onPress={() => {
if (selectionMode) {
clearSelection();
} else {
setSelectionMode(true);
}
}}
/>
<CTAButton
label={t('mobilePhotos.filtersTitle', 'Filter')}
tone="ghost"
fullWidth={false}
onPress={() => setShowFilters(true)}
/>
</XStack>
<MobileInput
type="search"
value={search}
@@ -808,7 +835,6 @@ export default function MobileEventPhotosPage() {
position="relative"
>
<motion.img
layoutId={`photo-${photo.id}`}
src={photo.thumbnail_url ?? photo.url ?? undefined}
alt={photo.caption ?? 'Photo'}
loading="lazy"
@@ -973,7 +999,6 @@ export default function MobileEventPhotosPage() {
style={{ touchAction: 'none' }}
>
<motion.img
layoutId={`photo-${lightbox.id}`}
src={lightboxImageSrc ?? undefined}
alt={lightbox.caption ?? 'Photo'}
loading="eager"