Refactored styles.
This commit is contained in:
parent
d73a74965d
commit
d64e93674e
9 changed files with 64 additions and 45 deletions
|
|
@ -37,6 +37,7 @@
|
||||||
history.pushState(null, '', path)
|
history.pushState(null, '', path)
|
||||||
route = path
|
route = path
|
||||||
mobileNavOpen = false
|
mobileNavOpen = false
|
||||||
|
window.scrollTo(0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function checkVersion() {
|
async function checkVersion() {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { db } from './db.js'
|
import { db, clearSession } from './db.js'
|
||||||
|
|
||||||
async function getToken() {
|
async function getToken() {
|
||||||
const session = await db.session.get(1)
|
const session = await db.session.get(1)
|
||||||
|
|
@ -17,7 +17,7 @@ export async function apiFetch(path, options = {}) {
|
||||||
|
|
||||||
const res = await fetch(path, { ...options, headers })
|
const res = await fetch(path, { ...options, headers })
|
||||||
if (res.status === 401) {
|
if (res.status === 401) {
|
||||||
await db.session.clear()
|
await clearSession()
|
||||||
window.location.pathname = '/login'
|
window.location.pathname = '/login'
|
||||||
throw new Error('unauthorized')
|
throw new Error('unauthorized')
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -111,6 +111,7 @@ input[type="date"], input[type="time"], input[type="datetime-local"] { -webkit-a
|
||||||
input:focus, select:focus, textarea:focus { outline: none; border-color: var(--c-accent); }
|
input:focus, select:focus, textarea:focus { outline: none; border-color: var(--c-accent); }
|
||||||
input::placeholder { color: var(--c-muted); }
|
input::placeholder { color: var(--c-muted); }
|
||||||
.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
|
.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
|
||||||
|
.form-grid-3 { display: grid; grid-template-columns: 1fr 1fr auto; gap: 1rem; align-items: end; }
|
||||||
.form-grid .full { grid-column: 1 / -1; }
|
.form-grid .full { grid-column: 1 / -1; }
|
||||||
.checkbox-label { display: inline-flex; align-items: center; gap: 0.5rem; font-size: 0.875rem; cursor: pointer; }
|
.checkbox-label { display: inline-flex; align-items: center; gap: 0.5rem; font-size: 0.875rem; cursor: pointer; }
|
||||||
.checkbox-label-sm { display: inline-flex; align-items: center; gap: 0.3rem; font-size: 0.82rem; cursor: pointer; color: var(--c-text); }
|
.checkbox-label-sm { display: inline-flex; align-items: center; gap: 0.3rem; font-size: 0.82rem; cursor: pointer; color: var(--c-text); }
|
||||||
|
|
@ -137,8 +138,8 @@ tr:hover td { background: rgba(255,255,255,0.02); }
|
||||||
padding: 0.18rem 0.55rem; border-radius: 99px;
|
padding: 0.18rem 0.55rem; border-radius: 99px;
|
||||||
font-size: 0.72rem; font-weight: 600;
|
font-size: 0.72rem; font-weight: 600;
|
||||||
text-transform: uppercase; letter-spacing: 0.04em;
|
text-transform: uppercase; letter-spacing: 0.04em;
|
||||||
margin-left: 0.3rem;
|
|
||||||
}
|
}
|
||||||
|
* + .badge { margin-left: 0.3rem; }
|
||||||
.badge-checked { background: rgba(34,197,94,0.15); color: var(--c-success); }
|
.badge-checked { background: rgba(34,197,94,0.15); color: var(--c-success); }
|
||||||
.badge-confirmed { background: rgba(99,102,241,0.15); color: var(--c-accent-h); }
|
.badge-confirmed { background: rgba(99,102,241,0.15); color: var(--c-accent-h); }
|
||||||
.badge-registered { background: rgba(14,165,233,0.15); color: #0ea5e9; }
|
.badge-registered { background: rgba(14,165,233,0.15); color: #0ea5e9; }
|
||||||
|
|
@ -246,5 +247,5 @@ tr:hover td { background: rgba(255,255,255,0.02); }
|
||||||
|
|
||||||
/* Forms — 16px prevents iOS auto-zoom on focus */
|
/* Forms — 16px prevents iOS auto-zoom on focus */
|
||||||
input, select, textarea { font-size: 16px; }
|
input, select, textarea { font-size: 16px; }
|
||||||
.form-grid { grid-template-columns: 1fr !important; }
|
.form-grid, .form-grid-3 { grid-template-columns: 1fr !important; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -101,7 +101,7 @@
|
||||||
{#if showAdd && canCreate}
|
{#if showAdd && canCreate}
|
||||||
<div class="card" style="margin-bottom:1.5rem">
|
<div class="card" style="margin-bottom:1.5rem">
|
||||||
<form onsubmit={addDept}>
|
<form onsubmit={addDept}>
|
||||||
<div class="form-grid" style="display:grid;grid-template-columns:1fr 1fr auto;gap:1rem;align-items:end">
|
<div class="form-grid-3">
|
||||||
<div class="form-group" style="margin-bottom:0">
|
<div class="form-group" style="margin-bottom:0">
|
||||||
<label for="d-name">Name *</label>
|
<label for="d-name">Name *</label>
|
||||||
<input id="d-name" bind:value={newName} required placeholder="e.g. Security" />
|
<input id="d-name" bind:value={newName} required placeholder="e.g. Security" />
|
||||||
|
|
@ -112,7 +112,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" style="margin-bottom:0">
|
<div class="form-group" style="margin-bottom:0">
|
||||||
<label for="d-color">Color</label>
|
<label for="d-color">Color</label>
|
||||||
<input id="d-color" type="color" bind:value={newColor} style="width:60px;padding:0.2rem;height:2.3rem;cursor:pointer" />
|
<input id="d-color" type="color" bind:value={newColor} class="color-input" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="actions" style="margin-top:1rem">
|
<div class="actions" style="margin-top:1rem">
|
||||||
|
|
@ -191,6 +191,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
.color-input { width: 60px; padding: 0.2rem; height: 2.3rem; cursor: pointer; }
|
||||||
@media (max-width: 640px) {
|
@media (max-width: 640px) {
|
||||||
.td-name { width: 100%; }
|
.td-name { width: 100%; }
|
||||||
.td-desc { width: 100%; }
|
.td-desc { width: 100%; }
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,13 @@
|
||||||
import { api } from '../api.js'
|
import { api } from '../api.js'
|
||||||
import { saveSession } from '../db.js'
|
import { saveSession } from '../db.js'
|
||||||
|
|
||||||
let { onlogin, error: initialError = '' } = $props()
|
let { onlogin, error: externalError = '' } = $props()
|
||||||
|
|
||||||
let email = $state('')
|
let email = $state('')
|
||||||
let password = $state('')
|
let password = $state('')
|
||||||
let error = $state(initialError)
|
let error = $state('')
|
||||||
|
|
||||||
|
$effect(() => { if (externalError) error = externalError })
|
||||||
let loading = $state(false)
|
let loading = $state(false)
|
||||||
let ssoEnabled = $state(false)
|
let ssoEnabled = $state(false)
|
||||||
let ssoLoading = $state(false)
|
let ssoLoading = $state(false)
|
||||||
|
|
|
||||||
|
|
@ -384,6 +384,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{#if canManage}
|
||||||
<div class="board-shift-actions">
|
<div class="board-shift-actions">
|
||||||
<button class="btn btn-ghost btn-sm" onclick={() => startEdit(shift)}>Edit</button>
|
<button class="btn btn-ghost btn-sm" onclick={() => startEdit(shift)}>Edit</button>
|
||||||
<button class="btn btn-ghost btn-sm" title="Move up"
|
<button class="btn btn-ghost btn-sm" title="Move up"
|
||||||
|
|
@ -392,6 +393,7 @@
|
||||||
onclick={() => reorder(shift.id, 1, rows)}>↓</button>
|
onclick={() => reorder(shift.id, 1, rows)}>↓</button>
|
||||||
<button class="btn btn-danger btn-sm" onclick={() => deleteShift(shift)}>Delete</button>
|
<button class="btn btn-danger btn-sm" onclick={() => deleteShift(shift)}>Delete</button>
|
||||||
</div>
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Assigned volunteers -->
|
<!-- Assigned volunteers -->
|
||||||
|
|
@ -406,13 +408,14 @@
|
||||||
{#if checkConflict(volunteer.id, shift.id, $allVolunteerShifts ?? [], $allShifts ?? [])}
|
{#if checkConflict(volunteer.id, shift.id, $allVolunteerShifts ?? [], $allShifts ?? [])}
|
||||||
<span title="Scheduling conflict" style="color:var(--c-warn)">⚠</span>
|
<span title="Scheduling conflict" style="color:var(--c-warn)">⚠</span>
|
||||||
{/if}
|
{/if}
|
||||||
<button class="board-vol-remove" onclick={() => unassign(shift.id, volunteer.id)} title="Remove">×</button>
|
{#if canManage}<button class="board-vol-remove" onclick={() => unassign(shift.id, volunteer.id)} title="Remove">×</button>{/if}
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<!-- Assign volunteer -->
|
<!-- Assign volunteer -->
|
||||||
|
{#if canManage}
|
||||||
{#if assigningShiftID === shift.id}
|
{#if assigningShiftID === shift.id}
|
||||||
<div class="board-assign-row">
|
<div class="board-assign-row">
|
||||||
<select bind:value={assignVolID} style="width:auto">
|
<select bind:value={assignVolID} style="width:auto">
|
||||||
|
|
@ -436,6 +439,7 @@
|
||||||
<button class="board-add-vol" onclick={() => startAssign(shift.id)}>+ Assign volunteer</button>
|
<button class="board-add-vol" onclick={() => startAssign(shift.id)}>+ Assign volunteer</button>
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
{/each}
|
{/each}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
let saving = $state(false)
|
let saving = $state(false)
|
||||||
let savingEvent = $state(false)
|
let savingEvent = $state(false)
|
||||||
let testing = $state(false)
|
let testing = $state(false)
|
||||||
|
let resetting = $state(false)
|
||||||
let error = $state('')
|
let error = $state('')
|
||||||
let success = $state('')
|
let success = $state('')
|
||||||
|
|
||||||
|
|
@ -130,7 +131,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
async function resetModel(label, fn) {
|
async function resetModel(label, fn) {
|
||||||
|
if (resetting) return
|
||||||
if (!confirm(`PERMANENTLY DELETE all ${label}? This cannot be undone.`)) return
|
if (!confirm(`PERMANENTLY DELETE all ${label}? This cannot be undone.`)) return
|
||||||
|
resetting = true
|
||||||
error = ''
|
error = ''
|
||||||
success = ''
|
success = ''
|
||||||
try {
|
try {
|
||||||
|
|
@ -138,6 +141,8 @@
|
||||||
success = `Deleted ${result.deleted} ${label}.`
|
success = `Deleted ${result.deleted} ${label}.`
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
error = err.message
|
error = err.message
|
||||||
|
} finally {
|
||||||
|
resetting = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -332,19 +337,19 @@
|
||||||
Permanently delete all records of a given type. This cannot be undone.
|
Permanently delete all records of a given type. This cannot be undone.
|
||||||
</p>
|
</p>
|
||||||
<div style="display:flex;flex-wrap:wrap;gap:0.5rem">
|
<div style="display:flex;flex-wrap:wrap;gap:0.5rem">
|
||||||
<button class="btn btn-danger" onclick={() => resetModel('tickets', api.settings.resetTickets)}>
|
<button class="btn btn-danger" disabled={resetting} onclick={() => resetModel('tickets', api.settings.resetTickets)}>
|
||||||
Delete All Tickets
|
Delete All Tickets
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-danger" onclick={() => resetModel('volunteers', api.settings.resetVolunteers)}>
|
<button class="btn btn-danger" disabled={resetting} onclick={() => resetModel('volunteers', api.settings.resetVolunteers)}>
|
||||||
Delete All Volunteers
|
Delete All Volunteers
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-danger" onclick={() => resetModel('shifts', api.settings.resetShifts)}>
|
<button class="btn btn-danger" disabled={resetting} onclick={() => resetModel('shifts', api.settings.resetShifts)}>
|
||||||
Delete All Shifts
|
Delete All Shifts
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-danger" onclick={() => resetModel('departments', api.settings.resetDepartments)}>
|
<button class="btn btn-danger" disabled={resetting} onclick={() => resetModel('departments', api.settings.resetDepartments)}>
|
||||||
Delete All Departments
|
Delete All Departments
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-danger" onclick={() => resetModel('volunteer shift assignments', api.settings.resetVolunteerShifts)}>
|
<button class="btn btn-danger" disabled={resetting} onclick={() => resetModel('volunteer shift assignments', api.settings.resetVolunteerShifts)}>
|
||||||
Delete All Shift Assignments
|
Delete All Shift Assignments
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -149,7 +149,7 @@
|
||||||
{#if showAdd}
|
{#if showAdd}
|
||||||
<div class="card" style="margin-bottom:1.5rem">
|
<div class="card" style="margin-bottom:1.5rem">
|
||||||
<form onsubmit={addUser}>
|
<form onsubmit={addUser}>
|
||||||
<div class="form-grid" style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:1rem">
|
<div class="form-grid-3">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="u-email">Email *</label>
|
<label for="u-email">Email *</label>
|
||||||
<input id="u-email" type="email" bind:value={newEmail} required placeholder="email@example.com" autocomplete="off" />
|
<input id="u-email" type="email" bind:value={newEmail} required placeholder="email@example.com" autocomplete="off" />
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@
|
||||||
let editIsLead = $state(false)
|
let editIsLead = $state(false)
|
||||||
let editNote = $state('')
|
let editNote = $state('')
|
||||||
let saving = $state(false)
|
let saving = $state(false)
|
||||||
|
let confirmingID = $state(null)
|
||||||
|
|
||||||
const roles = $derived(session?.user?.roles ?? [])
|
const roles = $derived(session?.user?.roles ?? [])
|
||||||
function hasRole(...allowed) { return roles.some(r => allowed.includes(r)) }
|
function hasRole(...allowed) { return roles.some(r => allowed.includes(r)) }
|
||||||
|
|
@ -76,11 +77,15 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
async function confirmVolunteer(v) {
|
async function confirmVolunteer(v) {
|
||||||
|
if (confirmingID) return
|
||||||
|
confirmingID = v.id
|
||||||
try {
|
try {
|
||||||
const updated = await api.volunteers.confirm(v.id)
|
const updated = await api.volunteers.confirm(v.id)
|
||||||
await db.volunteers.put(updated)
|
await db.volunteers.put(updated)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
error = err.message
|
error = err.message
|
||||||
|
} finally {
|
||||||
|
confirmingID = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -349,7 +354,7 @@
|
||||||
{#if canManage}
|
{#if canManage}
|
||||||
<td class="td-actions">
|
<td class="td-actions">
|
||||||
{#if canConfirm && v.email_confirmed && !v.confirmed}
|
{#if canConfirm && v.email_confirmed && !v.confirmed}
|
||||||
<button class="btn btn-primary btn-sm" onclick={() => confirmVolunteer(v)}>Confirm</button>
|
<button class="btn btn-primary btn-sm" onclick={() => confirmVolunteer(v)} disabled={confirmingID === v.id}>Confirm</button>
|
||||||
{/if}
|
{/if}
|
||||||
<button class="btn btn-ghost btn-sm" onclick={() => startEdit(v)}>Edit</button>
|
<button class="btn btn-ghost btn-sm" onclick={() => startEdit(v)}>Edit</button>
|
||||||
<button class="btn btn-danger btn-sm" onclick={() => deleteVolunteer(v)}>Delete</button>
|
<button class="btn btn-danger btn-sm" onclick={() => deleteVolunteer(v)}>Delete</button>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue