Split Travellers data into Trips; Added 'Risk Audit' screen; Added follow-up checks; connected 'Trips' to Juvonno.
This commit is contained in:
+228
-64
@@ -1,10 +1,25 @@
|
||||
import PocketBase from 'pocketbase'
|
||||
import { POCKETBASE_URL, PB_ADMIN_EMAIL, PB_ADMIN_PASSWORD } from './pocketbase-config.js'
|
||||
|
||||
const COLLECTION = 'travellers'
|
||||
const TRAVELLERS_COLLECTION = 'travellers'
|
||||
const TRIPS_COLLECTION = 'trips'
|
||||
const VACCINE_RECORDS_COLLECTION = 'records'
|
||||
const VACCINES_COLLECTION = 'vaccines'
|
||||
|
||||
|
||||
let pb = null
|
||||
let authPromise = null
|
||||
|
||||
function getClient() {
|
||||
if (!pb) {
|
||||
pb = new PocketBase(POCKETBASE_URL)
|
||||
// Main process may receive overlapping IPC calls (e.g. React Strict Mode).
|
||||
// Default auto-cancel would abort duplicate in-flight PocketBase requests.
|
||||
pb.autoCancellation(false)
|
||||
}
|
||||
return pb
|
||||
}
|
||||
|
||||
function parseVaccineRecord(r) {
|
||||
return {
|
||||
id: r.id,
|
||||
@@ -48,15 +63,17 @@ export async function fetchVaccineRecords() {
|
||||
export async function saveVaccineRecord(data) {
|
||||
await ensureAuth()
|
||||
const client = getClient()
|
||||
const travellerId = data.traveller_id
|
||||
const travellerId = data.traveller_id || data.travellerId || data.traveller
|
||||
|
||||
let existing = null
|
||||
try {
|
||||
const list = await client.collection(VACCINE_RECORDS_COLLECTION).getList(1, 50, {
|
||||
filter: `traveller_id = "${travellerId}"`
|
||||
})
|
||||
if (list.items && list.items.length > 0) {
|
||||
existing = list.items[0]
|
||||
if (travellerId) {
|
||||
const list = await client.collection(VACCINE_RECORDS_COLLECTION).getList(1, 50, {
|
||||
filter: `traveller_id = "${travellerId}"`
|
||||
})
|
||||
if (list.items && list.items.length > 0) {
|
||||
existing = list.items[0]
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore filter error
|
||||
@@ -69,52 +86,76 @@ export async function saveVaccineRecord(data) {
|
||||
timelines: data.timelines || data.vaccineTimelines || data.vaccine_timelines || [],
|
||||
}
|
||||
|
||||
let record
|
||||
if (existing) {
|
||||
const record = await client.collection(VACCINE_RECORDS_COLLECTION).update(existing.id, payload)
|
||||
return parseVaccineRecord(record)
|
||||
record = await client.collection(VACCINE_RECORDS_COLLECTION).update(existing.id, payload)
|
||||
} else {
|
||||
const record = await client.collection(VACCINE_RECORDS_COLLECTION).create(payload)
|
||||
return parseVaccineRecord(record)
|
||||
record = await client.collection(VACCINE_RECORDS_COLLECTION).create(payload)
|
||||
}
|
||||
|
||||
// Also link record.id into travellers collection record_id
|
||||
if (travellerId && record?.id) {
|
||||
try {
|
||||
await client.collection(TRAVELLERS_COLLECTION).update(travellerId, {
|
||||
record_id: record.id,
|
||||
})
|
||||
} catch (e) {
|
||||
console.warn(`[pocketbase] Could not link record_id to traveller ${travellerId}:`, e.message)
|
||||
}
|
||||
}
|
||||
|
||||
return parseVaccineRecord(record)
|
||||
}
|
||||
|
||||
export async function updateVaccineRecord(id, data) {
|
||||
await ensureAuth()
|
||||
const travellerId = data.traveller_id
|
||||
const client = getClient()
|
||||
const travellerId = data.traveller_id || data.travellerId || data.traveller
|
||||
const payload = {
|
||||
traveller_id: travellerId,
|
||||
traveller_name: data.travellerName || data.traveller_name || '',
|
||||
emr_id: data.emrID || data.emr_id || null,
|
||||
timelines: data.timelines || data.vaccineTimelines || data.vaccine_timelines || [],
|
||||
}
|
||||
const record = await getClient().collection(VACCINE_RECORDS_COLLECTION).update(id, payload)
|
||||
const record = await client.collection(VACCINE_RECORDS_COLLECTION).update(id, payload)
|
||||
|
||||
if (travellerId && record?.id) {
|
||||
try {
|
||||
await client.collection(TRAVELLERS_COLLECTION).update(travellerId, {
|
||||
record_id: record.id,
|
||||
})
|
||||
} catch (e) {
|
||||
console.warn(`[pocketbase] Could not link record_id to traveller ${travellerId}:`, e.message)
|
||||
}
|
||||
}
|
||||
|
||||
return parseVaccineRecord(record)
|
||||
}
|
||||
|
||||
|
||||
let pb = null
|
||||
let authPromise = null
|
||||
|
||||
function getClient() {
|
||||
if (!pb) {
|
||||
pb = new PocketBase(POCKETBASE_URL)
|
||||
// Main process may receive overlapping IPC calls (e.g. React Strict Mode).
|
||||
// Default auto-cancel would abort duplicate in-flight PocketBase requests.
|
||||
pb.autoCancellation(false)
|
||||
}
|
||||
return pb
|
||||
}
|
||||
|
||||
function parseEntry(r) {
|
||||
const traveller = r.expand?.traveller_id
|
||||
? (Array.isArray(r.expand.traveller_id) ? r.expand.traveller_id[0] : r.expand.traveller_id)
|
||||
: null
|
||||
|
||||
const travellerId = typeof r.traveller_id === 'string'
|
||||
? r.traveller_id
|
||||
: (Array.isArray(r.traveller_id) ? r.traveller_id[0] : (traveller?.id || ''))
|
||||
|
||||
return {
|
||||
id: r.id,
|
||||
recordID: r.record_id ?? r.recordID ?? '',
|
||||
emrID: r.emr_id ?? '',
|
||||
name: r.name ?? '',
|
||||
location: r.location ?? '',
|
||||
tripId: r.id,
|
||||
travellerId: travellerId,
|
||||
name: traveller?.name ?? r.name ?? '',
|
||||
emrID: traveller?.emr_id ?? r.emr_id ?? '',
|
||||
recordID: traveller?.record_id ?? r.record_id ?? r.recordID ?? '',
|
||||
location: r.locations ?? r.location ?? '',
|
||||
locations: r.locations ?? r.location ?? '',
|
||||
countryISO: r.country_iso ?? '',
|
||||
countriesISO: r.countries_iso ?? [],
|
||||
status: r.status ?? '',
|
||||
followUpStatus: r.follow_up_status ?? [],
|
||||
isAtRisk: r.is_at_risk ?? false,
|
||||
archived: r.archived ?? false,
|
||||
departure: r.departure ?? '',
|
||||
arrival: r.arrival ?? '',
|
||||
created: r.created,
|
||||
@@ -145,61 +186,184 @@ export async function ensureAuth() {
|
||||
|
||||
export async function fetchEntries() {
|
||||
await ensureAuth()
|
||||
const list = await getClient().collection(COLLECTION).getFullList({ sort: '-created' })
|
||||
const list = await getClient().collection(TRIPS_COLLECTION).getFullList({
|
||||
sort: '-created',
|
||||
filter: 'archived != true',
|
||||
expand: 'traveller_id',
|
||||
})
|
||||
return list.map(parseEntry)
|
||||
}
|
||||
|
||||
function parseTraveller(r) {
|
||||
return {
|
||||
id: r.id,
|
||||
travellerId: r.id,
|
||||
name: r.name ?? '',
|
||||
emrID: r.emr_id ?? '',
|
||||
recordID: r.record_id ?? '',
|
||||
created: r.created,
|
||||
updated: r.updated,
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchTravellers() {
|
||||
await ensureAuth()
|
||||
const list = await getClient().collection(TRAVELLERS_COLLECTION).getFullList({
|
||||
sort: 'name',
|
||||
})
|
||||
return list.map(parseTraveller)
|
||||
}
|
||||
|
||||
export async function createEntry(trip) {
|
||||
await ensureAuth()
|
||||
const payload = {
|
||||
name: trip.name,
|
||||
location: trip.location,
|
||||
countries_iso: trip.countriesISO,
|
||||
departure: trip.departure,
|
||||
arrival: trip.arrival,
|
||||
const client = getClient()
|
||||
|
||||
let traveller = null
|
||||
|
||||
// 1. Validate mandatory emrID and search existing traveller by emr_id (string)
|
||||
const emrStr = trip.emrID !== undefined && trip.emrID !== null ? String(trip.emrID).trim() : ''
|
||||
if (!emrStr) {
|
||||
throw new Error('EMR ID is required to create a trip.')
|
||||
}
|
||||
if (trip.status !== undefined) {
|
||||
payload.status = trip.status
|
||||
|
||||
try {
|
||||
const filter = client.filter
|
||||
? client.filter('emr_id = {:emrId}', { emrId: emrStr })
|
||||
: `emr_id = "${emrStr.replace(/"/g, '\\"')}"`
|
||||
|
||||
const list = await client.collection(TRAVELLERS_COLLECTION).getList(1, 1, { filter })
|
||||
if (list.items && list.items.length > 0) {
|
||||
traveller = list.items[0]
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn(`[pocketbase] Failed looking up traveller with emr_id "${emrStr}":`, e.message)
|
||||
}
|
||||
if (trip.recordID || trip.record_id) {
|
||||
payload.record_id = trip.recordID || trip.record_id
|
||||
|
||||
// 2. If no traveller found with this emr_id, create a new record in travellers collection
|
||||
if (!traveller) {
|
||||
const travellerPayload = {
|
||||
emr_id: emrStr,
|
||||
name: trip.name ? trip.name.trim() : '',
|
||||
}
|
||||
traveller = await client.collection(TRAVELLERS_COLLECTION).create(travellerPayload)
|
||||
}
|
||||
if (trip.emrID !== undefined && trip.emrID !== null && trip.emrID !== '') {
|
||||
const emrNum = Number(trip.emrID)
|
||||
payload.emr_id = !isNaN(emrNum) ? emrNum : trip.emrID
|
||||
|
||||
// 3. Create the trip record in trips collection linked to the traveller
|
||||
const tripPayload = {
|
||||
traveller_id: traveller.id,
|
||||
locations: trip.location ?? trip.locations ?? '',
|
||||
countries_iso: trip.countriesISO ?? trip.countries_iso ?? [],
|
||||
departure: trip.departure ?? '',
|
||||
arrival: trip.arrival ?? '',
|
||||
follow_up_status: trip.followUpStatus ?? trip.follow_up_status ?? [],
|
||||
is_at_risk: trip.isAtRisk ?? trip.is_at_risk ?? false,
|
||||
}
|
||||
const record = await getClient().collection(COLLECTION).create(payload)
|
||||
|
||||
const record = await client.collection(TRIPS_COLLECTION).create(tripPayload, {
|
||||
expand: 'traveller_id',
|
||||
})
|
||||
|
||||
// Ensure expand is attached for parseEntry
|
||||
if (!record.expand || !record.expand.traveller_id) {
|
||||
record.expand = { ...record.expand, traveller_id: traveller }
|
||||
}
|
||||
|
||||
return parseEntry(record)
|
||||
}
|
||||
|
||||
export async function updateEntry(id, trip) {
|
||||
await ensureAuth()
|
||||
const payload = {
|
||||
name: trip.name,
|
||||
location: trip.location,
|
||||
countries_iso: trip.countriesISO,
|
||||
departure: trip.departure,
|
||||
arrival: trip.arrival,
|
||||
const client = getClient()
|
||||
|
||||
// 1. Fetch current trip to identify the linked traveller
|
||||
let existingTrip = null
|
||||
try {
|
||||
existingTrip = await client.collection(TRIPS_COLLECTION).getOne(id, {
|
||||
expand: 'traveller_id',
|
||||
})
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
if (trip.status !== undefined) {
|
||||
payload.status = trip.status
|
||||
|
||||
// 2. Update trip in trips collection
|
||||
const tripPayload = {}
|
||||
if (trip.location !== undefined || trip.locations !== undefined) {
|
||||
tripPayload.locations = trip.locations ?? trip.location ?? ''
|
||||
}
|
||||
if (trip.recordID !== undefined || trip.record_id !== undefined) {
|
||||
payload.record_id = trip.recordID || trip.record_id || null
|
||||
if (trip.countriesISO !== undefined || trip.countries_iso !== undefined) {
|
||||
tripPayload.countries_iso = trip.countriesISO ?? trip.countries_iso ?? []
|
||||
}
|
||||
if (trip.emrID !== undefined && trip.emrID !== null && trip.emrID !== '') {
|
||||
const emrNum = Number(trip.emrID)
|
||||
payload.emr_id = !isNaN(emrNum) ? emrNum : trip.emrID
|
||||
} else if (trip.emrID === '') {
|
||||
payload.emr_id = null
|
||||
if (trip.departure !== undefined) {
|
||||
tripPayload.departure = trip.departure
|
||||
}
|
||||
const record = await getClient().collection(COLLECTION).update(id, payload)
|
||||
return parseEntry(record)
|
||||
if (trip.arrival !== undefined) {
|
||||
tripPayload.arrival = trip.arrival
|
||||
}
|
||||
if (trip.followUpStatus !== undefined || trip.follow_up_status !== undefined) {
|
||||
tripPayload.follow_up_status = trip.followUpStatus ?? trip.follow_up_status ?? []
|
||||
}
|
||||
if (trip.isAtRisk !== undefined || trip.is_at_risk !== undefined) {
|
||||
tripPayload.is_at_risk = trip.isAtRisk ?? trip.is_at_risk ?? false
|
||||
}
|
||||
|
||||
const updatedTrip = await client.collection(TRIPS_COLLECTION).update(id, tripPayload)
|
||||
|
||||
// 3. Update associated traveller record if traveller fields changed
|
||||
const emrStr = trip.emrID !== undefined && trip.emrID !== null ? String(trip.emrID).trim() : ''
|
||||
let travellerId = trip.travellerId || trip.traveller_id || existingTrip?.traveller_id || existingTrip?.expand?.traveller_id?.id
|
||||
let updatedTraveller = existingTrip?.expand?.traveller_id || null
|
||||
|
||||
// If emrID was provided, check if it matches an existing traveller to re-link
|
||||
if (emrStr) {
|
||||
try {
|
||||
const filter = client.filter
|
||||
? client.filter('emr_id = {:emrId}', { emrId: emrStr })
|
||||
: `emr_id = "${emrStr.replace(/"/g, '\\"')}"`
|
||||
|
||||
const list = await client.collection(TRAVELLERS_COLLECTION).getList(1, 1, { filter })
|
||||
if (list.items && list.items.length > 0) {
|
||||
const matched = list.items[0]
|
||||
if (matched.id !== travellerId) {
|
||||
travellerId = matched.id
|
||||
updatedTraveller = matched
|
||||
await client.collection(TRIPS_COLLECTION).update(id, { traveller_id: travellerId })
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn(`[pocketbase] Failed looking up traveller with emr_id "${emrStr}" in updateEntry:`, e.message)
|
||||
}
|
||||
}
|
||||
|
||||
if (travellerId) {
|
||||
const travellerPayload = {}
|
||||
if (trip.name !== undefined) {
|
||||
travellerPayload.name = typeof trip.name === 'string' ? trip.name.trim() : trip.name
|
||||
}
|
||||
if (emrStr) {
|
||||
travellerPayload.emr_id = emrStr
|
||||
}
|
||||
if (trip.recordID !== undefined || trip.record_id !== undefined) {
|
||||
travellerPayload.record_id = trip.recordID || trip.record_id || null
|
||||
}
|
||||
|
||||
if (Object.keys(travellerPayload).length > 0) {
|
||||
try {
|
||||
updatedTraveller = await client.collection(TRAVELLERS_COLLECTION).update(travellerId, travellerPayload)
|
||||
} catch (e) {
|
||||
console.warn(`[pocketbase] Could not update traveller ${travellerId}:`, e.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updatedTrip.expand = { ...updatedTrip.expand, traveller_id: updatedTraveller }
|
||||
return parseEntry(updatedTrip)
|
||||
}
|
||||
|
||||
export async function deleteEntry(id) {
|
||||
await ensureAuth()
|
||||
await getClient().collection(COLLECTION).delete(id)
|
||||
return await getClient().collection(TRIPS_COLLECTION).update(id, {
|
||||
archived: true,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user