diff --git a/app/Http/Controllers/Api/EventPublicController.php b/app/Http/Controllers/Api/EventPublicController.php index 22b74cb..457fcbd 100644 --- a/app/Http/Controllers/Api/EventPublicController.php +++ b/app/Http/Controllers/Api/EventPublicController.php @@ -2923,7 +2923,7 @@ class EventPublicController extends BaseController $autoApproveUploads = $uploadVisibility === 'immediate'; $controlRoom = Arr::get($eventModel->settings ?? [], 'control_room', []); $controlRoom = is_array($controlRoom) ? $controlRoom : []; - $autoAddApprovedToLiveSetting = (bool) Arr::get($controlRoom, 'auto_add_approved_to_live', false); + $autoAddApprovedToLiveSetting = (bool) Arr::get($controlRoom, 'auto_add_approved_to_live', true); $trustedUploaders = Arr::get($controlRoom, 'trusted_uploaders', []); $forceReviewUploaders = Arr::get($controlRoom, 'force_review_uploaders', []); $autoAddApprovedToLiveDefault = $autoAddApprovedToLiveSetting || $autoApproveUploads; diff --git a/app/Http/Controllers/Api/Tenant/PhotoController.php b/app/Http/Controllers/Api/Tenant/PhotoController.php index 4d979fc..3f575a2 100644 --- a/app/Http/Controllers/Api/Tenant/PhotoController.php +++ b/app/Http/Controllers/Api/Tenant/PhotoController.php @@ -133,7 +133,7 @@ class PhotoController extends Controller $photo->status = $validated['visible'] ? 'approved' : 'hidden'; $photo->save(); - $autoRemoveLiveOnHide = (bool) Arr::get($event->settings ?? [], 'control_room.auto_remove_live_on_hide', false); + $autoRemoveLiveOnHide = (bool) Arr::get($event->settings ?? [], 'control_room.auto_remove_live_on_hide', true); if ($autoRemoveLiveOnHide && ! $validated['visible']) { $photo->rejectForLiveShow($request->user(), 'hidden'); } @@ -537,7 +537,7 @@ class PhotoController extends Controller $photo->update($validated); - $autoRemoveLiveOnHide = (bool) Arr::get($event->settings ?? [], 'control_room.auto_remove_live_on_hide', false); + $autoRemoveLiveOnHide = (bool) Arr::get($event->settings ?? [], 'control_room.auto_remove_live_on_hide', true); if ($autoRemoveLiveOnHide && ($validated['status'] ?? null) === 'rejected') { $photo->rejectForLiveShow($request->user()); } diff --git a/resources/js/admin/i18n/locales/de/management.json b/resources/js/admin/i18n/locales/de/management.json index 7db4ac4..a8a02dd 100644 --- a/resources/js/admin/i18n/locales/de/management.json +++ b/resources/js/admin/i18n/locales/de/management.json @@ -2484,6 +2484,7 @@ }, "automation": { "title": "Automatik", + "sectionTitle": "Einstellungen für Uploads", "autoApproveHighlights": { "label": "Highlights automatisch freigeben", "help": "Wenn du ein ausstehendes Foto highlightest, wird es automatisch freigegeben." diff --git a/resources/js/admin/i18n/locales/en/management.json b/resources/js/admin/i18n/locales/en/management.json index bb4014d..ad694ca 100644 --- a/resources/js/admin/i18n/locales/en/management.json +++ b/resources/js/admin/i18n/locales/en/management.json @@ -2486,6 +2486,7 @@ }, "automation": { "title": "Automation", + "sectionTitle": "Einstellungen für Uploads", "autoApproveHighlights": { "label": "Auto-approve highlights", "help": "When you highlight a pending photo it will be approved automatically." diff --git a/resources/js/admin/mobile/EventControlRoomPage.tsx b/resources/js/admin/mobile/EventControlRoomPage.tsx index c3accdc..a1b8662 100644 --- a/resources/js/admin/mobile/EventControlRoomPage.tsx +++ b/resources/js/admin/mobile/EventControlRoomPage.tsx @@ -1,11 +1,12 @@ import React from 'react'; import { useLocation, useNavigate, useParams } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; -import { Check, Eye, EyeOff, Image as ImageIcon, RefreshCcw, Settings, Sparkles } from 'lucide-react'; +import { Check, ChevronDown, Eye, EyeOff, Image as ImageIcon, RefreshCcw, Settings, Sparkles } from 'lucide-react'; import { YStack, XStack } from '@tamagui/stacks'; import { SizableText as Text } from '@tamagui/text'; import { Pressable } from '@tamagui/react-native-web-lite'; import { Switch } from '@tamagui/switch'; +import { Accordion } from '@tamagui/accordion'; import { MobileShell, HeaderActionButton } from './components/MobileShell'; import { MobileCard, CTAButton, SkeletonCard } from './components/Primitives'; import { MobileField, MobileSelect } from './components/FormControls'; @@ -302,9 +303,9 @@ export default function MobileEventControlRoomPage() { const [liveBusyId, setLiveBusyId] = React.useState(null); const [controlRoomSettings, setControlRoomSettings] = React.useState({ - auto_approve_highlights: false, - auto_add_approved_to_live: false, - auto_remove_live_on_hide: false, + auto_approve_highlights: true, + auto_add_approved_to_live: true, + auto_remove_live_on_hide: true, trusted_uploaders: [], force_review_uploaders: [], }); @@ -463,10 +464,11 @@ export default function MobileEventControlRoomPage() { React.useEffect(() => { const settings = (activeEvent?.settings?.control_room ?? {}) as ControlRoomSettings; + const resolveFlag = (value: boolean | undefined) => (typeof value === 'boolean' ? value : true); setControlRoomSettings({ - auto_approve_highlights: Boolean(settings.auto_approve_highlights), - auto_add_approved_to_live: Boolean(settings.auto_add_approved_to_live), - auto_remove_live_on_hide: Boolean(settings.auto_remove_live_on_hide), + auto_approve_highlights: resolveFlag(settings.auto_approve_highlights), + auto_add_approved_to_live: resolveFlag(settings.auto_add_approved_to_live), + auto_remove_live_on_hide: resolveFlag(settings.auto_remove_live_on_hide), trusted_uploaders: Array.isArray(settings.trusted_uploaders) ? settings.trusted_uploaders : [], force_review_uploaders: Array.isArray(settings.force_review_uploaders) ? settings.force_review_uploaders : [], }); @@ -1018,251 +1020,267 @@ export default function MobileEventControlRoomPage() { - - - {t('controlRoom.automation.title', 'Automation')} - - - - saveControlRoomSettings({ - ...controlRoomSettings, - auto_approve_highlights: Boolean(checked), - }) - } - aria-label={t('controlRoom.automation.autoApproveHighlights.label', 'Auto-approve highlights')} + + + - - - - - { - if (isImmediateUploads) { - return; - } - saveControlRoomSettings({ - ...controlRoomSettings, - auto_add_approved_to_live: Boolean(checked), - }); - }} - aria-label={t('controlRoom.automation.autoAddApproved.label', 'Auto-add approved to Live Show')} - > - - - - - - saveControlRoomSettings({ - ...controlRoomSettings, - auto_remove_live_on_hide: Boolean(checked), - }) - } - aria-label={t('controlRoom.automation.autoRemoveLive.label', 'Auto-remove from Live Show')} - > - - - - - - - - - - {t('controlRoom.automation.uploaders.title', 'Uploader overrides')} - - - {t( - 'controlRoom.automation.uploaders.subtitle', - 'Pick devices from recent uploads to always approve or always review their photos.', - )} - - - - - setTrustedUploaderSelection(event.target.value)} - style={{ flex: 1 }} - > - - {uploaderOptions.map((option) => ( - - ))} - - addUploaderRule('trusted')} - tone="ghost" - fullWidth={false} - disabled={controlRoomSaving || !trustedUploaderSelection} - style={{ width: 100 }} - /> - - {trustedUploaders.length ? ( - - {trustedUploaders.map((rule) => ( - + + {t('controlRoom.automation.sectionTitle', 'Einstellungen für Uploads')} + + + + + + + + + {t('controlRoom.automation.title', 'Automation')} + + - - - {rule.label ?? t('common.anonymous', 'Anonymous')} - - - {formatDeviceId(rule.device_id)} - - - removeUploaderRule('trusted', rule.device_id)} + + saveControlRoomSettings({ + ...controlRoomSettings, + auto_approve_highlights: Boolean(checked), + }) + } + aria-label={t('controlRoom.automation.autoApproveHighlights.label', 'Auto-approve highlights')} > - - {t('controlRoom.automation.uploaders.remove', 'Remove')} - - - - ))} - - ) : ( - - {t('controlRoom.automation.uploaders.empty', 'No overrides yet.')} - - )} - - - - - setForceReviewSelection(event.target.value)} - style={{ flex: 1 }} - > - - {uploaderOptions.map((option) => ( - - ))} - - addUploaderRule('force')} - tone="ghost" - fullWidth={false} - disabled={controlRoomSaving || !forceReviewSelection} - style={{ width: 100 }} - /> - - {forceReviewUploaders.length ? ( - - {forceReviewUploaders.map((rule) => ( - + + + - - - {rule.label ?? t('common.anonymous', 'Anonymous')} - - - {formatDeviceId(rule.device_id)} - - - removeUploaderRule('force', rule.device_id)} - disabled={controlRoomSaving} - aria-label={t('controlRoom.automation.uploaders.remove', 'Remove')} - style={{ - paddingHorizontal: 12, - paddingVertical: 6, - borderRadius: 999, - backgroundColor: withAlpha(danger, 0.12), + { + if (isImmediateUploads) { + return; + } + saveControlRoomSettings({ + ...controlRoomSettings, + auto_add_approved_to_live: Boolean(checked), + }); }} + aria-label={t('controlRoom.automation.autoAddApproved.label', 'Auto-add approved to Live Show')} > - - {t('controlRoom.automation.uploaders.remove', 'Remove')} + + + + + + saveControlRoomSettings({ + ...controlRoomSettings, + auto_remove_live_on_hide: Boolean(checked), + }) + } + aria-label={t('controlRoom.automation.autoRemoveLive.label', 'Auto-remove from Live Show')} + > + + + + + + + + {t('controlRoom.automation.uploaders.title', 'Uploader overrides')} + + + {t( + 'controlRoom.automation.uploaders.subtitle', + 'Pick devices from recent uploads to always approve or always review their photos.', + )} + + + + + setTrustedUploaderSelection(event.target.value)} + > + + {uploaderOptions.map((option) => ( + + ))} + + addUploaderRule('trusted')} + tone="ghost" + disabled={controlRoomSaving || !trustedUploaderSelection} + /> + + {trustedUploaders.length ? ( + + {trustedUploaders.map((rule) => ( + + + + {rule.label ?? t('common.anonymous', 'Anonymous')} + + + {formatDeviceId(rule.device_id)} + + + removeUploaderRule('trusted', rule.device_id)} + disabled={controlRoomSaving} + aria-label={t('controlRoom.automation.uploaders.remove', 'Remove')} + style={{ + paddingHorizontal: 12, + paddingVertical: 6, + borderRadius: 999, + backgroundColor: withAlpha(danger, 0.12), + }} + > + + {t('controlRoom.automation.uploaders.remove', 'Remove')} + + + + ))} + + ) : ( + + {t('controlRoom.automation.uploaders.empty', 'No overrides yet.')} - - - ))} + )} + + + + + setForceReviewSelection(event.target.value)} + > + + {uploaderOptions.map((option) => ( + + ))} + + addUploaderRule('force')} + tone="ghost" + disabled={controlRoomSaving || !forceReviewSelection} + /> + + {forceReviewUploaders.length ? ( + + {forceReviewUploaders.map((rule) => ( + + + + {rule.label ?? t('common.anonymous', 'Anonymous')} + + + {formatDeviceId(rule.device_id)} + + + removeUploaderRule('force', rule.device_id)} + disabled={controlRoomSaving} + aria-label={t('controlRoom.automation.uploaders.remove', 'Remove')} + style={{ + paddingHorizontal: 12, + paddingVertical: 6, + borderRadius: 999, + backgroundColor: withAlpha(danger, 0.12), + }} + > + + {t('controlRoom.automation.uploaders.remove', 'Remove')} + + + + ))} + + ) : ( + + {t('controlRoom.automation.uploaders.empty', 'No overrides yet.')} + + )} + + - ) : ( - - {t('controlRoom.automation.uploaders.empty', 'No overrides yet.')} - - )} - - + + + {activeTab === 'moderation' ? ( diff --git a/resources/js/admin/mobile/__tests__/EventControlRoomPage.test.tsx b/resources/js/admin/mobile/__tests__/EventControlRoomPage.test.tsx index b5c80dd..8de0f4d 100644 --- a/resources/js/admin/mobile/__tests__/EventControlRoomPage.test.tsx +++ b/resources/js/admin/mobile/__tests__/EventControlRoomPage.test.tsx @@ -108,6 +108,17 @@ vi.mock('@tamagui/switch', () => ({ ), })); +vi.mock('@tamagui/accordion', () => ({ + Accordion: Object.assign( + ({ children }: { children: React.ReactNode }) =>
{children}
, + { + Item: ({ children }: { children: React.ReactNode }) =>
{children}
, + Trigger: ({ children }: { children: React.ReactNode }) =>
{children}
, + Content: ({ children }: { children: React.ReactNode }) =>
{children}
, + }, + ), +})); + vi.mock('@tamagui/react-native-web-lite', () => ({ Pressable: ({ children,