Updated Participant search and delete. Filtered import.
This commit is contained in:
parent
219acb62d6
commit
64ce97c74d
3 changed files with 30 additions and 9 deletions
|
|
@ -53,7 +53,7 @@
|
||||||
<strong style="color:var(--c-text)">Supported formats:</strong><br>
|
<strong style="color:var(--c-text)">Supported formats:</strong><br>
|
||||||
<strong>CrowdWork / ticketing platform:</strong> columns <code>Patron Name</code>, <code>Patron Email</code>, <code>Tier Name</code>, <code>Order Number</code><br>
|
<strong>CrowdWork / ticketing platform:</strong> columns <code>Patron Name</code>, <code>Patron Email</code>, <code>Tier Name</code>, <code>Order Number</code><br>
|
||||||
<strong>Generic:</strong> columns <code>name</code>, <code>email</code>, <code>ticket_id</code>, <code>ticket_type</code>, <code>note</code><br>
|
<strong>Generic:</strong> columns <code>name</code>, <code>email</code>, <code>ticket_id</code>, <code>ticket_type</code>, <code>note</code><br>
|
||||||
Duplicate names are skipped.
|
Duplicate tickets (same source + external ID) are skipped. Participants are matched or created by email.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="submit" class="btn btn-primary" disabled={!file || importing}>
|
<button type="submit" class="btn btn-primary" disabled={!file || importing}>
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,8 @@
|
||||||
.filter(p => {
|
.filter(p => {
|
||||||
if (!s) return true
|
if (!s) return true
|
||||||
return p.preferred_name?.toLowerCase().includes(s) ||
|
return p.preferred_name?.toLowerCase().includes(s) ||
|
||||||
p.email?.toLowerCase().includes(s)
|
p.email?.toLowerCase().includes(s) ||
|
||||||
|
p.phone?.toLowerCase().includes(s)
|
||||||
})
|
})
|
||||||
.sort((a, b) => (a.preferred_name || a.email).localeCompare(b.preferred_name || b.email))
|
.sort((a, b) => (a.preferred_name || a.email).localeCompare(b.preferred_name || b.email))
|
||||||
})
|
})
|
||||||
|
|
@ -177,6 +178,18 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function deleteParticipant(id) {
|
||||||
|
if (!confirm('Permanently delete this participant and all their records?')) return
|
||||||
|
error = ''
|
||||||
|
try {
|
||||||
|
await api.participants.delete(id)
|
||||||
|
await db.participants.delete(id)
|
||||||
|
editId = null
|
||||||
|
} catch (err) {
|
||||||
|
error = err.message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function addTicket(e, participantId) {
|
async function addTicket(e, participantId) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
addingTicket = true; error = ''
|
addingTicket = true; error = ''
|
||||||
|
|
@ -327,6 +340,8 @@
|
||||||
<div class="actions" style="margin-top:0.5rem">
|
<div class="actions" style="margin-top:0.5rem">
|
||||||
<button type="submit" class="btn btn-primary btn-sm" disabled={saving}>{saving ? 'Saving…' : 'Save'}</button>
|
<button type="submit" class="btn btn-primary btn-sm" disabled={saving}>{saving ? 'Saving…' : 'Save'}</button>
|
||||||
<button type="button" class="btn btn-ghost btn-sm" onclick={() => editId = null}>Cancel</button>
|
<button type="button" class="btn btn-ghost btn-sm" onclick={() => editId = null}>Cancel</button>
|
||||||
|
<span class="spacer"></span>
|
||||||
|
<button type="button" class="btn btn-danger btn-sm" onclick={() => deleteParticipant(editId)}>Delete</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</td>
|
</td>
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@
|
||||||
let adding = $state(false)
|
let adding = $state(false)
|
||||||
let newName = $state('')
|
let newName = $state('')
|
||||||
let newEmail = $state('')
|
let newEmail = $state('')
|
||||||
let newPhone = $state('')
|
|
||||||
let newDeptID = $state('')
|
let newDeptID = $state('')
|
||||||
let newIsLead = $state(false)
|
let newIsLead = $state(false)
|
||||||
let newNote = $state('')
|
let newNote = $state('')
|
||||||
|
|
@ -25,6 +24,7 @@
|
||||||
const allVolunteers = liveQuery(() =>
|
const allVolunteers = liveQuery(() =>
|
||||||
db.volunteers.filter(v => !v.deleted_at).toArray()
|
db.volunteers.filter(v => !v.deleted_at).toArray()
|
||||||
)
|
)
|
||||||
|
const allParticipants = liveQuery(() => db.participants.toArray())
|
||||||
const allDepts = liveQuery(() =>
|
const allDepts = liveQuery(() =>
|
||||||
db.departments.filter(d => !d.deleted_at).toArray()
|
db.departments.filter(d => !d.deleted_at).toArray()
|
||||||
.then(arr => arr.sort((a, b) => a.name.localeCompare(b.name)))
|
.then(arr => arr.sort((a, b) => a.name.localeCompare(b.name)))
|
||||||
|
|
@ -62,7 +62,6 @@
|
||||||
const data = {
|
const data = {
|
||||||
name: newName,
|
name: newName,
|
||||||
email: newEmail,
|
email: newEmail,
|
||||||
phone: newPhone,
|
|
||||||
is_lead: newIsLead,
|
is_lead: newIsLead,
|
||||||
note: newNote,
|
note: newNote,
|
||||||
}
|
}
|
||||||
|
|
@ -70,7 +69,7 @@
|
||||||
const v = await api.volunteers.create(data)
|
const v = await api.volunteers.create(data)
|
||||||
await db.volunteers.put(v)
|
await db.volunteers.put(v)
|
||||||
showAdd = false
|
showAdd = false
|
||||||
newName = newEmail = newPhone = newNote = ''
|
newName = newEmail = newNote = ''
|
||||||
newDeptID = ''
|
newDeptID = ''
|
||||||
newIsLead = false
|
newIsLead = false
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
@ -93,6 +92,10 @@
|
||||||
function deptFor(id) {
|
function deptFor(id) {
|
||||||
return ($allDepts ?? []).find(d => d.id === id)
|
return ($allDepts ?? []).find(d => d.id === id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function participantFor(id) {
|
||||||
|
return ($allParticipants ?? []).find(p => p.id === id) ?? null
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="page">
|
<div class="page">
|
||||||
|
|
@ -121,10 +124,6 @@
|
||||||
<label for="v-email">Email</label>
|
<label for="v-email">Email</label>
|
||||||
<input id="v-email" type="email" bind:value={newEmail} placeholder="email@example.com" />
|
<input id="v-email" type="email" bind:value={newEmail} placeholder="email@example.com" />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
|
||||||
<label for="v-phone">Phone</label>
|
|
||||||
<input id="v-phone" bind:value={newPhone} placeholder="Optional" />
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="v-dept">Department</label>
|
<label for="v-dept">Department</label>
|
||||||
<select id="v-dept" bind:value={newDeptID}>
|
<select id="v-dept" bind:value={newDeptID}>
|
||||||
|
|
@ -195,12 +194,19 @@
|
||||||
<tbody>
|
<tbody>
|
||||||
{#each filtered as v (v.id)}
|
{#each filtered as v (v.id)}
|
||||||
{@const dept = deptFor(v.department_id)}
|
{@const dept = deptFor(v.department_id)}
|
||||||
|
{@const participant = participantFor(v.participant_id)}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<strong>{v.name}</strong>
|
<strong>{v.name}</strong>
|
||||||
{#if v.is_lead}
|
{#if v.is_lead}
|
||||||
<span class="badge badge-lead" style="margin-left:0.4rem">Lead</span>
|
<span class="badge badge-lead" style="margin-left:0.4rem">Lead</span>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if !v.participant_id}
|
||||||
|
<span class="badge badge-unchecked" style="margin-left:0.4rem" title="Not linked to a participant — no ticket record">No ticket</span>
|
||||||
|
{/if}
|
||||||
|
{#if v.email}
|
||||||
|
<div class="text-muted" style="font-size:0.78rem">{v.email}</div>
|
||||||
|
{/if}
|
||||||
{#if v.note}
|
{#if v.note}
|
||||||
<div class="text-muted" style="font-size:0.78rem">{v.note}</div>
|
<div class="text-muted" style="font-size:0.78rem">{v.note}</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue