Allows for editing Hunt details
This commit is contained in:
@@ -35,6 +35,9 @@ export const apiGetAllHunts = (status?: 'UNSTARTED' | 'ONGOING' | 'CLOSED') =>
|
|||||||
export const apiCreateHunt = (title: string, startDateTime: string, endDateTime: string) =>
|
export const apiCreateHunt = (title: string, startDateTime: string, endDateTime: string) =>
|
||||||
client.post<HuntResponse>('/hunt', { title, startDateTime, endDateTime })
|
client.post<HuntResponse>('/hunt', { title, startDateTime, endDateTime })
|
||||||
|
|
||||||
|
export const apiUpdateHunt = (huntId: string, title: string, startDateTime: string, endDateTime: string, isTerminated: boolean) =>
|
||||||
|
client.patch<HuntResponse>(`/hunt/${huntId}`, { title, startDateTime, endDateTime, isTerminated })
|
||||||
|
|
||||||
// ── Hunter ────────────────────────────────────────────────────────────────────
|
// ── Hunter ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
export const apiGetOngoingHunts = () =>
|
export const apiGetOngoingHunts = () =>
|
||||||
|
|||||||
@@ -4,9 +4,11 @@
|
|||||||
let {
|
let {
|
||||||
value = $bindable(''),
|
value = $bindable(''),
|
||||||
defaultTimeIndex = 36,
|
defaultTimeIndex = 36,
|
||||||
|
initialValue = '',
|
||||||
}: {
|
}: {
|
||||||
value?: string
|
value?: string
|
||||||
defaultTimeIndex?: number
|
defaultTimeIndex?: number
|
||||||
|
initialValue?: string
|
||||||
} = $props()
|
} = $props()
|
||||||
|
|
||||||
const months = [
|
const months = [
|
||||||
@@ -25,11 +27,19 @@
|
|||||||
return { value: i, label: `${displayHour}:${minute.toString().padStart(2, '0')} ${ampm}` }
|
return { value: i, label: `${displayHour}:${minute.toString().padStart(2, '0')} ${ampm}` }
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const seed = untrack(() => {
|
||||||
|
if (initialValue) {
|
||||||
|
const d = new Date(initialValue)
|
||||||
|
return { month: d.getMonth(), day: d.getDate(), year: d.getFullYear(), timeIndex: d.getHours() * 4 + Math.round(d.getMinutes() / 15) }
|
||||||
|
}
|
||||||
const today = new Date()
|
const today = new Date()
|
||||||
let month = $state(today.getMonth())
|
return { month: today.getMonth(), day: today.getDate(), year: today.getFullYear(), timeIndex: defaultTimeIndex }
|
||||||
let day = $state(today.getDate())
|
})
|
||||||
let year = $state(today.getFullYear())
|
|
||||||
let timeIndex = $state(untrack(() => defaultTimeIndex))
|
let month = $state(seed.month)
|
||||||
|
let day = $state(seed.day)
|
||||||
|
let year = $state(seed.year)
|
||||||
|
let timeIndex = $state(seed.timeIndex)
|
||||||
|
|
||||||
const daysInMonth = $derived(new Date(year, month + 1, 0).getDate())
|
const daysInMonth = $derived(new Date(year, month + 1, 0).getDate())
|
||||||
|
|
||||||
|
|||||||
@@ -8,11 +8,13 @@
|
|||||||
apiGetHunt,
|
apiGetHunt,
|
||||||
apiGetItems,
|
apiGetItems,
|
||||||
apiListTeams,
|
apiListTeams,
|
||||||
|
apiUpdateHunt,
|
||||||
apiUpdateItem
|
apiUpdateItem
|
||||||
} from '../../lib/api/index'
|
} from '../../lib/api/index'
|
||||||
import type {HuntResponse, ItemResponse, TeamResponse} from '../../lib/api/types'
|
import type {HuntResponse, ItemResponse, TeamResponse} from '../../lib/api/types'
|
||||||
import StatusBadge from '../../lib/components/StatusBadge.svelte'
|
import StatusBadge from '../../lib/components/StatusBadge.svelte'
|
||||||
import LoadingSpinner from '../../lib/components/LoadingSpinner.svelte'
|
import LoadingSpinner from '../../lib/components/LoadingSpinner.svelte'
|
||||||
|
import DateTimePicker from '../../lib/components/DateTimePicker.svelte'
|
||||||
|
|
||||||
let { params }: { params: { huntId: string } } = $props()
|
let { params }: { params: { huntId: string } } = $props()
|
||||||
|
|
||||||
@@ -34,6 +36,40 @@
|
|||||||
let newTeamName = $state('')
|
let newTeamName = $state('')
|
||||||
let addingTeam = $state(false)
|
let addingTeam = $state(false)
|
||||||
|
|
||||||
|
let editingHunt = $state(false)
|
||||||
|
let editTitle = $state('')
|
||||||
|
let editStart = $state('')
|
||||||
|
let editEnd = $state('')
|
||||||
|
let editTerminated = $state(false)
|
||||||
|
let savingHunt = $state(false)
|
||||||
|
|
||||||
|
function startEditHunt() {
|
||||||
|
if (!hunt) return
|
||||||
|
editTitle = hunt.title
|
||||||
|
editStart = hunt.startDateTime
|
||||||
|
editEnd = hunt.endDateTime
|
||||||
|
editTerminated = hunt.isTerminated
|
||||||
|
editingHunt = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancelEditHunt() {
|
||||||
|
editingHunt = false
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveHunt() {
|
||||||
|
if (!hunt || !editTitle.trim() || !editStart || !editEnd) return
|
||||||
|
savingHunt = true
|
||||||
|
error = ''
|
||||||
|
try {
|
||||||
|
hunt = await apiUpdateHunt(params.huntId, editTitle.trim(), editStart, editEnd, editTerminated)
|
||||||
|
editingHunt = false
|
||||||
|
} catch (e: unknown) {
|
||||||
|
error = e instanceof Error ? e.message : 'Failed to update hunt'
|
||||||
|
} finally {
|
||||||
|
savingHunt = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let editingItemId = $state<string | null>(null)
|
let editingItemId = $state<string | null>(null)
|
||||||
let editName = $state('')
|
let editName = $state('')
|
||||||
let editPoints = $state(0)
|
let editPoints = $state(0)
|
||||||
@@ -127,18 +163,67 @@
|
|||||||
<div class="p-4 pb-6">
|
<div class="p-4 pb-6">
|
||||||
<div class="flex items-center gap-3 mb-4">
|
<div class="flex items-center gap-3 mb-4">
|
||||||
<button class="btn btn-ghost btn-sm btn-circle" onclick={() => push('/admin')}>←</button>
|
<button class="btn btn-ghost btn-sm btn-circle" onclick={() => push('/admin')}>←</button>
|
||||||
<div>
|
<div class="flex-1 min-w-0">
|
||||||
{#if hunt}
|
{#if hunt}
|
||||||
<h1 class="text-lg font-bold leading-tight">{hunt.title}</h1>
|
<h1 class="text-lg font-bold leading-tight truncate">{hunt.title}</h1>
|
||||||
<StatusBadge status={huntStatus(hunt)} />
|
<StatusBadge status={huntStatus(hunt)} />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
{#if hunt && !editingHunt}
|
||||||
|
<button class="btn btn-outline btn-sm shrink-0" onclick={startEditHunt}>Edit Hunt</button>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{#if editingHunt && hunt}
|
||||||
|
<div class="bg-base-200 rounded-2xl p-4 mb-4 flex flex-col gap-4">
|
||||||
|
<h2 class="font-bold text-sm uppercase tracking-wide text-base-content/50">Edit Hunt</h2>
|
||||||
|
{#if error}
|
||||||
|
<div class="alert alert-error text-sm">{error}</div>
|
||||||
|
{/if}
|
||||||
|
<label class="form-control">
|
||||||
|
<span class="label-text text-xs font-medium mb-1">Title</span>
|
||||||
|
<input type="text" class="input input-bordered input-sm" bind:value={editTitle} disabled={savingHunt} required />
|
||||||
|
</label>
|
||||||
|
<fieldset class="form-control">
|
||||||
|
<legend class="label-text text-xs font-medium mb-1">Start</legend>
|
||||||
|
<DateTimePicker bind:value={editStart} initialValue={editStart} />
|
||||||
|
</fieldset>
|
||||||
|
<fieldset class="form-control">
|
||||||
|
<legend class="label-text text-xs font-medium mb-1">End</legend>
|
||||||
|
<DateTimePicker bind:value={editEnd} initialValue={editEnd} />
|
||||||
|
</fieldset>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="flex items-center justify-between p-4 rounded-xl border-2 transition-all {editTerminated ? 'border-error bg-error/10' : 'border-base-300 bg-base-100'}"
|
||||||
|
onclick={() => editTerminated = !editTerminated}
|
||||||
|
disabled={savingHunt}
|
||||||
|
>
|
||||||
|
<div class="text-left">
|
||||||
|
<p class="font-semibold {editTerminated ? 'text-error' : 'text-base-content'}">Terminated</p>
|
||||||
|
<p class="text-xs text-base-content/50">Permanently closes the hunt for all hunters</p>
|
||||||
|
</div>
|
||||||
|
<div class="w-6 h-6 rounded-full border-2 flex items-center justify-center shrink-0 {editTerminated ? 'border-error bg-error' : 'border-base-300'}">
|
||||||
|
{#if editTerminated}
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-error-content" viewBox="0 0 20 20" fill="currentColor">
|
||||||
|
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 00-1.414 0L8 12.586 4.707 9.293a1 1 0 00-1.414 1.414l4 4a1 1 0 001.414 0l8-8a1 1 0 000-1.414z" clip-rule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<button class="btn btn-primary btn-sm flex-1" onclick={saveHunt} disabled={savingHunt}>
|
||||||
|
{#if savingHunt}<span class="loading loading-spinner loading-xs"></span>{/if}
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-ghost btn-sm" onclick={cancelEditHunt} disabled={savingHunt}>Cancel</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if loading}
|
{#if loading}
|
||||||
<LoadingSpinner />
|
<LoadingSpinner />
|
||||||
{:else}
|
{:else}
|
||||||
{#if error}
|
{#if error && !editingHunt}
|
||||||
<div class="alert alert-error mb-4 text-sm">{error}</div>
|
<div class="alert alert-error mb-4 text-sm">{error}</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user