Added Notifications screen and logic for Records.
This commit is contained in:
1 parent
72ec7472c5
commit
697d72f379
13 files changed
+2255
-145
No files matched your search
@@ -4,6 +4,7 @@ import path from 'node:path'
|
||||
import {
|
||||
fetchEntries,
|
||||
fetchTravellers,
|
||||
createTraveller,
|
||||
createEntry,
|
||||
updateEntry,
|
||||
deleteEntry,
|
||||
@@ -11,6 +12,11 @@ import {
|
||||
fetchVaccineRecords,
|
||||
saveVaccineRecord,
|
||||
updateVaccineRecord,
|
||||
fetchNotifications,
|
||||
updateNotification,
|
||||
markNotificationRead,
|
||||
markAllNotificationsRead,
|
||||
archiveNotification,
|
||||
} from './pocketbase.js'
|
||||
import { JUVONNO_API_URL, JUVONNO_API_KEY } from './juvonno-config.js'
|
||||
|
||||
@@ -104,6 +110,9 @@ ipcMain.handle('pb:fetch-entries', async () => {
|
||||
ipcMain.handle('pb:fetch-travellers', async () => {
|
||||
return await fetchTravellers()
|
||||
})
|
||||
ipcMain.handle('pb:create-traveller', async (_event, data) => {
|
||||
return await createTraveller(data)
|
||||
})
|
||||
ipcMain.handle('pb:create-entry', async (_event, trip) => {
|
||||
return await createEntry(trip)
|
||||
})
|
||||
@@ -130,6 +139,26 @@ ipcMain.handle('pb:update-vaccine-record', async (_event, id, recordData) => {
|
||||
return await updateVaccineRecord(id, recordData)
|
||||
})
|
||||
|
||||
ipcMain.handle('pb:fetch-notifications', async (_event, options) => {
|
||||
return await fetchNotifications(options)
|
||||
})
|
||||
|
||||
ipcMain.handle('pb:update-notification', async (_event, id, data) => {
|
||||
return await updateNotification(id, data)
|
||||
})
|
||||
|
||||
ipcMain.handle('pb:mark-notification-read', async (_event, id, isRead) => {
|
||||
return await markNotificationRead(id, isRead)
|
||||
})
|
||||
|
||||
ipcMain.handle('pb:mark-all-notifications-read', async () => {
|
||||
return await markAllNotificationsRead()
|
||||
})
|
||||
|
||||
ipcMain.handle('pb:archive-notification', async (_event, id, isArchived) => {
|
||||
return await archiveNotification(id, isArchived)
|
||||
})
|
||||
|
||||
// ─── Health Risk Audit IPC Handler (Node-side to avoid CORS) ────────────────
|
||||
ipcMain.handle('audit:run-health-audit', async (_event, payload) => {
|
||||
const AUDIT_WEBHOOK_URL = 'https://n8n.wirediv.dev/webhook/travel-health-audit'
|
||||
|
||||
+126
-2
@@ -5,6 +5,7 @@ const TRAVELLERS_COLLECTION = 'travellers'
|
||||
const TRIPS_COLLECTION = 'trips'
|
||||
const VACCINE_RECORDS_COLLECTION = 'records'
|
||||
const VACCINES_COLLECTION = 'vaccines'
|
||||
const NOTIFICATIONS_COLLECTION = 'notifications'
|
||||
|
||||
|
||||
let pb = null
|
||||
@@ -46,8 +47,6 @@ export async function fetchVaccines() {
|
||||
total_doses: r.total_doses ?? 1,
|
||||
dose_intervals: r.dose_intervals ?? [],
|
||||
min_days_before_travel: r.min_days_before_travel ?? 0,
|
||||
allows_grace_period: r.allows_grace_period ?? false,
|
||||
has_accelerated_schedule: r.has_accelerated_schedule ?? false,
|
||||
requires_icvp_certificate: r.requires_icvp_certificate ?? false,
|
||||
created: r.created,
|
||||
updated: r.updated,
|
||||
@@ -224,6 +223,47 @@ export async function fetchTravellers() {
|
||||
return list.map(parseTraveller)
|
||||
}
|
||||
|
||||
export async function createTraveller(data) {
|
||||
await ensureAuth()
|
||||
const client = getClient()
|
||||
|
||||
const emrStr = data.emrID !== undefined && data.emrID !== null ? String(data.emrID).trim() : ''
|
||||
const nameStr = data.name ? String(data.name).trim() : ''
|
||||
|
||||
if (!nameStr) {
|
||||
throw new Error('Traveller name is required.')
|
||||
}
|
||||
|
||||
// If already exists with this emr_id, return existing
|
||||
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) {
|
||||
return {
|
||||
...parseTraveller(list.items[0]),
|
||||
isExisting: true,
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn(`[pocketbase] Lookup existing traveller with emr_id "${emrStr}":`, e.message)
|
||||
}
|
||||
}
|
||||
|
||||
const travellerPayload = {
|
||||
emr_id: emrStr,
|
||||
name: nameStr,
|
||||
}
|
||||
const record = await client.collection(TRAVELLERS_COLLECTION).create(travellerPayload)
|
||||
return {
|
||||
...parseTraveller(record),
|
||||
isExisting: false,
|
||||
}
|
||||
}
|
||||
|
||||
export async function createEntry(trip) {
|
||||
await ensureAuth()
|
||||
const client = getClient()
|
||||
@@ -376,6 +416,90 @@ export async function deleteEntry(id) {
|
||||
})
|
||||
}
|
||||
|
||||
// ─── Notifications Helpers ──────────────────────────────────────────────────
|
||||
|
||||
function parseNotification(r) {
|
||||
let message = r.message ?? ''
|
||||
if (typeof message === 'string') {
|
||||
const trimmed = message.trim()
|
||||
if ((trimmed.startsWith('"') && trimmed.endsWith('"')) || (trimmed.startsWith("'") && trimmed.endsWith("'"))) {
|
||||
try {
|
||||
message = JSON.parse(trimmed)
|
||||
} catch {
|
||||
message = trimmed.slice(1, -1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: r.id,
|
||||
message,
|
||||
module: r.module ?? 'records',
|
||||
is_read: Boolean(r.is_read),
|
||||
is_archived: Boolean(r.is_archived),
|
||||
meta: r.meta ?? null,
|
||||
application: r.application ?? '',
|
||||
created: r.created,
|
||||
updated: r.updated,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches all notifications, sorted by created desc by default.
|
||||
* Optional query options: { filter, sort }
|
||||
*/
|
||||
export async function fetchNotifications(options = {}) {
|
||||
await ensureAuth()
|
||||
const client = getClient()
|
||||
const queryParams = {
|
||||
sort: options.sort || '-created',
|
||||
requestKey: null,
|
||||
}
|
||||
if (options.filter) {
|
||||
queryParams.filter = options.filter
|
||||
}
|
||||
const list = await client.collection(NOTIFICATIONS_COLLECTION).getFullList(queryParams)
|
||||
return list.map(parseNotification)
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a notification record by id (e.g. { is_read: true }, { is_archived: true }).
|
||||
*/
|
||||
export async function updateNotification(id, data) {
|
||||
await ensureAuth()
|
||||
const client = getClient()
|
||||
const record = await client.collection(NOTIFICATIONS_COLLECTION).update(id, data)
|
||||
return parseNotification(record)
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method to mark a notification as read or unread.
|
||||
*/
|
||||
export async function markNotificationRead(id, isRead = true) {
|
||||
return await updateNotification(id, { is_read: isRead })
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks all unread notifications as read.
|
||||
*/
|
||||
export async function markAllNotificationsRead() {
|
||||
await ensureAuth()
|
||||
const client = getClient()
|
||||
const unreadList = await client.collection(NOTIFICATIONS_COLLECTION).getFullList({
|
||||
filter: 'is_read = false',
|
||||
requestKey: null,
|
||||
})
|
||||
const updated = await Promise.all(
|
||||
unreadList.map((item) =>
|
||||
client.collection(NOTIFICATIONS_COLLECTION).update(item.id, { is_read: true })
|
||||
)
|
||||
)
|
||||
return updated.map(parseNotification)
|
||||
}
|
||||
|
||||
/**
|
||||
* Archives or unarchives a notification.
|
||||
*/
|
||||
export async function archiveNotification(id, isArchived = true) {
|
||||
return await updateNotification(id, { is_archived: isArchived })
|
||||
}
|
||||
Reference in new issue
Block a user