Combined Schedule and Shifts views.
This commit is contained in:
parent
f30b84aa3a
commit
0df93e1886
6 changed files with 123 additions and 244 deletions
|
|
@ -10,12 +10,23 @@
|
|||
let editShift = $state({})
|
||||
let saving = $state(false)
|
||||
|
||||
// Add shift form
|
||||
let showAdd = $state(false)
|
||||
let adding = $state(false)
|
||||
let newDeptID = $state('')
|
||||
let newName = $state('')
|
||||
let newDay = $state('')
|
||||
let newStart = $state('')
|
||||
let newEnd = $state('')
|
||||
let newCapacity = $state(0)
|
||||
|
||||
// For volunteer assignment dropdown
|
||||
let assigningShiftID = $state(null)
|
||||
let assignVolID = $state(0)
|
||||
let assigning = $state(false)
|
||||
|
||||
const role = $derived(session?.user?.role ?? '')
|
||||
const canManage = $derived(['admin', 'coordinator', 'volunteer_lead'].includes(role))
|
||||
const myDeptIDs = $derived(session?.user?.department_ids ?? [])
|
||||
|
||||
const allDepts = liveQuery(() =>
|
||||
|
|
@ -194,6 +205,48 @@
|
|||
}
|
||||
}
|
||||
|
||||
async function addShift(e) {
|
||||
e.preventDefault()
|
||||
if (!newDeptID) return
|
||||
adding = true
|
||||
error = ''
|
||||
try {
|
||||
const s = await api.shifts.create({
|
||||
department_id: parseInt(newDeptID),
|
||||
name: newName,
|
||||
day: newDay,
|
||||
start_time: newStart,
|
||||
end_time: newEnd,
|
||||
capacity: parseInt(newCapacity) || 0,
|
||||
})
|
||||
await db.shifts.put(s)
|
||||
showAdd = false
|
||||
newName = newDay = newStart = newEnd = ''
|
||||
newDeptID = ''
|
||||
newCapacity = 0
|
||||
} catch (err) {
|
||||
error = err.message
|
||||
} finally {
|
||||
adding = false
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteShift(s) {
|
||||
if (!confirm(`Delete shift "${s.name}"?`)) return
|
||||
try {
|
||||
await api.shifts.delete(s.id)
|
||||
await db.shifts.delete(s.id)
|
||||
} catch (err) {
|
||||
error = err.message
|
||||
}
|
||||
}
|
||||
|
||||
function formatDay(d) {
|
||||
if (!d) return ''
|
||||
const dt = new Date(d + 'T00:00:00')
|
||||
return dt.toLocaleDateString(undefined, { weekday: 'short', month: 'short', day: 'numeric' })
|
||||
}
|
||||
|
||||
function fmt(t) {
|
||||
if (!t) return ''
|
||||
const [h, m] = t.split(':').map(Number)
|
||||
|
|
@ -204,17 +257,66 @@
|
|||
|
||||
<div class="page">
|
||||
<div class="page-header">
|
||||
<h1 class="page-title">Schedule Board</h1>
|
||||
<h1 class="page-title">Schedule</h1>
|
||||
{#if canManage}
|
||||
<div class="actions">
|
||||
<button class="btn btn-primary btn-sm" onclick={() => showAdd = !showAdd}>+ Add shift</button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if error}
|
||||
<div class="alert alert-error">{error}</div>
|
||||
{/if}
|
||||
|
||||
{#if ($allShifts ?? []).length === 0}
|
||||
{#if showAdd && canManage}
|
||||
<div class="card" style="margin-bottom:1.5rem">
|
||||
<form onsubmit={addShift}>
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:1rem">
|
||||
<div class="form-group">
|
||||
<label for="s-dept">Department *</label>
|
||||
<select id="s-dept" bind:value={newDeptID} required>
|
||||
<option value="">Select department…</option>
|
||||
{#each visibleDepts as d}
|
||||
<option value={String(d.id)}>{d.name}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="s-name">Shift name *</label>
|
||||
<input id="s-name" bind:value={newName} required placeholder="e.g. Gate Morning" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="s-day">Day *</label>
|
||||
<input id="s-day" type="date" bind:value={newDay} required />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="s-cap">Capacity <span class="text-muted">(0 = unlimited)</span></label>
|
||||
<input id="s-cap" type="number" min="0" bind:value={newCapacity} />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="s-start">Start time *</label>
|
||||
<input id="s-start" type="time" bind:value={newStart} required />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="s-end">End time *</label>
|
||||
<input id="s-end" type="time" bind:value={newEnd} required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button type="submit" class="btn btn-primary" disabled={adding}>
|
||||
{adding ? 'Adding…' : 'Add shift'}
|
||||
</button>
|
||||
<button type="button" class="btn btn-ghost" onclick={() => showAdd = false}>Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if ($allShifts ?? []).length === 0 && !showAdd}
|
||||
<div class="empty">
|
||||
<strong>No shifts yet</strong>
|
||||
<p>Create shifts in the Shifts page first.</p>
|
||||
<p>Add shifts to schedule your volunteers.</p>
|
||||
</div>
|
||||
{:else}
|
||||
{#each board as { dept, days }}
|
||||
|
|
@ -226,7 +328,7 @@
|
|||
</div>
|
||||
|
||||
{#each days as [day, rows]}
|
||||
<div class="board-day-label">{day}</div>
|
||||
<div class="board-day-label">{formatDay(day)}</div>
|
||||
|
||||
{#each rows as { shift, assigned, hasConflict }, i}
|
||||
<div class="board-shift {hasConflict ? 'board-shift-conflict' : ''}">
|
||||
|
|
@ -285,6 +387,7 @@
|
|||
onclick={() => reorder(shift.id, -1, rows)}>↑</button>
|
||||
<button class="btn btn-ghost btn-sm" title="Move down"
|
||||
onclick={() => reorder(shift.id, 1, rows)}>↓</button>
|
||||
<button class="btn btn-danger btn-sm" onclick={() => deleteShift(shift)}>Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue