Makes the photo review process easier

This commit is contained in:
2026-05-18 12:05:31 -05:00
parent 2c614a2d46
commit 6191c6c804

View File

@@ -14,12 +14,13 @@
if (!auth.isAdmin) push('/') if (!auth.isAdmin) push('/')
}) })
type PhotoWithTeam = PhotoResponse & { teamName: string }
let hunt = $state<HuntResponse | null>(null) let hunt = $state<HuntResponse | null>(null)
let teams = $state<TeamResponse[]>([]) let teams = $state<TeamResponse[]>([])
let items = $state<ItemResponse[]>([]) let items = $state<ItemResponse[]>([])
let selectedTeam = $state<TeamResponse | null>(null)
let selectedItem = $state<ItemResponse | null>(null) let selectedItem = $state<ItemResponse | null>(null)
let photos = $state<PhotoResponse[]>([]) let photos = $state<PhotoWithTeam[]>([])
let loading = $state(true) let loading = $state(true)
let photosLoading = $state(false) let photosLoading = $state(false)
let error = $state('') let error = $state('')
@@ -32,29 +33,28 @@
hunt = h hunt = h
teams = t teams = t
items = i items = i
// Pre-select team from query string if provided
const qs = new URLSearchParams(router.querystring ?? '')
const preTeam = qs.get('teamId')
if (preTeam) {
selectedTeam = t.find(x => x.id === preTeam) ?? null
}
}) })
.catch(e => { error = e instanceof Error ? e.message : 'Failed to load' }) .catch(e => { error = e instanceof Error ? e.message : 'Failed to load' })
.finally(() => { loading = false }) .finally(() => { loading = false })
}) })
$effect(() => { $effect(() => {
if (selectedTeam && selectedItem) { if (selectedItem) {
photosLoading = true photosLoading = true
photos = [] photos = []
apiGetItemPhotos(params.huntId, selectedTeam.id, selectedItem.id) Promise.all(
.then(p => { photos = p }) teams.map(team =>
apiGetItemPhotos(params.huntId, team.id, selectedItem!.id)
.then(ps => ps.map(p => ({ ...p, teamName: team.name })))
)
)
.then(results => { photos = results.flat() })
.catch(e => { error = e instanceof Error ? e.message : 'Failed to load photos' }) .catch(e => { error = e instanceof Error ? e.message : 'Failed to load photos' })
.finally(() => { photosLoading = false }) .finally(() => { photosLoading = false })
} }
}) })
async function review(photo: PhotoResponse, status: 'APPROVED' | 'REJECTED') { async function review(photo: PhotoWithTeam, status: 'APPROVED' | 'REJECTED') {
reviewing = photo.id reviewing = photo.id
try { try {
await apiReviewPhoto(photo.id, status) await apiReviewPhoto(photo.id, status)
@@ -82,24 +82,6 @@
{#if loading} {#if loading}
<LoadingSpinner /> <LoadingSpinner />
{:else} {:else}
<!-- Team selector -->
<div class="mb-4">
<p class="text-sm font-medium text-base-content/60 mb-2">Select Team</p>
<div class="flex flex-wrap gap-2">
{#each teams as team}
<button
class="btn btn-sm"
class:btn-primary={selectedTeam?.id === team.id}
class:btn-outline={selectedTeam?.id !== team.id}
onclick={() => { selectedTeam = team; selectedItem = null; photos = [] }}
>
{team.name}
</button>
{/each}
</div>
</div>
{#if selectedTeam}
<!-- Item selector --> <!-- Item selector -->
<div class="mb-4"> <div class="mb-4">
<p class="text-sm font-medium text-base-content/60 mb-2">Select Item</p> <p class="text-sm font-medium text-base-content/60 mb-2">Select Item</p>
@@ -116,9 +98,8 @@
{/each} {/each}
</div> </div>
</div> </div>
{/if}
{#if selectedTeam && selectedItem} {#if selectedItem}
{#if photosLoading} {#if photosLoading}
<LoadingSpinner /> <LoadingSpinner />
{:else if activePhotos.length === 0} {:else if activePhotos.length === 0}
@@ -133,7 +114,7 @@
<div class="p-3"> <div class="p-3">
<div class="flex items-center justify-between mb-3"> <div class="flex items-center justify-between mb-3">
<div> <div>
<p class="font-semibold">{photo.hunterName}</p> <p class="font-semibold">{photo.hunterName} <span class="text-base-content/50 font-normal">· {photo.teamName}</span></p>
<p class="text-xs text-base-content/50"> <p class="text-xs text-base-content/50">
{new Date(photo.photoUploadDateTime).toLocaleString()} {new Date(photo.photoUploadDateTime).toLocaleString()}
</p> </p>