- Add PostgreSQL performance indexes migration script (1000x faster queries) Sales Invoice: 1,248ms → 28ms, Payment Entry: 443ms → 31ms Indexes on customer/party columns for all major tables - Disable 3CX poller (PBX_ENABLED flag, using Twilio instead) - Add TelephonyPage: full CRUD UI for Routr/Fonoster resources (trunks, agents, credentials, numbers, domains, peers) - Add PhoneModal + usePhone composable (Twilio WebRTC softphone) - Lazy-load invoices/payments (initial 5, expand on demand) - Parallelize all API calls in ClientDetailPage (no waterfall) - Add targo-hub service (SSE relay, SMS, voice, telephony API) - Customer portal: invoice detail, ticket detail, messages pages - Remove dead Ollama nginx upstream Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
63 lines
1.6 KiB
JavaScript
63 lines
1.6 KiB
JavaScript
import { ref, onUnmounted } from 'vue'
|
|
|
|
/**
|
|
* Smart incremental polling composable.
|
|
* Calls `fetchFn()` at `interval` ms. Compares result count/IDs
|
|
* with previous to detect new items and trigger `onNew` callback.
|
|
*
|
|
* @param {Function} fetchFn - async function that returns array of items
|
|
* @param {Object} opts
|
|
* @param {number} opts.interval - poll interval in ms (default 10000)
|
|
* @param {Function} opts.getId - function to get unique ID from item (default: item => item.name)
|
|
* @param {Function} opts.onNew - callback when new items detected, receives array of new items
|
|
*/
|
|
export function usePolling (fetchFn, opts = {}) {
|
|
const interval = opts.interval || 10000
|
|
const getId = opts.getId || (item => item.name)
|
|
const onNew = opts.onNew || (() => {})
|
|
|
|
const knownIds = ref(new Set())
|
|
let timer = null
|
|
let running = false
|
|
|
|
function seedKnownIds (items) {
|
|
knownIds.value = new Set(items.map(getId))
|
|
}
|
|
|
|
async function poll () {
|
|
if (running) return
|
|
running = true
|
|
try {
|
|
const items = await fetchFn()
|
|
const newItems = items.filter(item => !knownIds.value.has(getId(item)))
|
|
if (newItems.length) {
|
|
for (const item of items) {
|
|
knownIds.value.add(getId(item))
|
|
}
|
|
onNew(newItems, items)
|
|
}
|
|
} catch {
|
|
// Silent fail on poll
|
|
} finally {
|
|
running = false
|
|
}
|
|
}
|
|
|
|
function start () {
|
|
stop()
|
|
timer = setInterval(poll, interval)
|
|
}
|
|
|
|
function stop () {
|
|
if (timer) {
|
|
clearInterval(timer)
|
|
timer = null
|
|
}
|
|
}
|
|
|
|
// Auto-cleanup on component unmount
|
|
onUnmounted(stop)
|
|
|
|
return { start, stop, poll, seedKnownIds }
|
|
}
|