200 lines
5.1 KiB
JavaScript
200 lines
5.1 KiB
JavaScript
import { computed, ref } from 'vue'
|
|
|
|
import { fetchApprovalExpenseClaims, fetchArchivedExpenseClaims, fetchExpenseClaims } from '../services/reimbursements.js'
|
|
import {
|
|
DOCUMENT_VIEWED_KEYS_CHANGE_EVENT,
|
|
countNewDocuments,
|
|
readViewedDocumentKeys,
|
|
resolveDocumentNewKey
|
|
} from '../utils/documentCenterNewState.js'
|
|
import { mapExpenseClaimToRequest } from './useRequests.js'
|
|
|
|
const SOURCE_PRIORITY = {
|
|
owned: 1,
|
|
approval: 2,
|
|
archive: 3
|
|
}
|
|
|
|
const documentRows = ref([])
|
|
const viewedDocumentKeys = ref(readViewedDocumentKeys())
|
|
const loading = ref(false)
|
|
const INBOX_CACHE_TTL_MS = 30000
|
|
let refreshTimer = null
|
|
let refreshPromise = null
|
|
let lastRefreshAt = 0
|
|
let viewedKeysListenerAttached = false
|
|
|
|
function normalizeClaimText(...values) {
|
|
for (const value of values) {
|
|
const normalized = String(value || '').trim()
|
|
if (normalized) {
|
|
return normalized
|
|
}
|
|
}
|
|
|
|
return ''
|
|
}
|
|
|
|
function buildDocumentInboxRow(claim, source) {
|
|
const request = mapExpenseClaimToRequest(claim)
|
|
const claimId = normalizeClaimText(request.claimId, request.id, claim?.id, claim?.claim_id)
|
|
const documentNo = normalizeClaimText(request.documentNo, request.claimNo, request.id, claim?.claim_no)
|
|
const documentKey = normalizeClaimText(claimId, documentNo)
|
|
|
|
return documentKey
|
|
? {
|
|
source,
|
|
claimId: claimId || documentKey,
|
|
documentNo,
|
|
documentKey: `${source}:${documentKey}`
|
|
}
|
|
: null
|
|
}
|
|
|
|
function sourcePriority(row) {
|
|
return SOURCE_PRIORITY[row?.source] || 0
|
|
}
|
|
|
|
function mergeNonArchivedRows(rows) {
|
|
const rowMap = new Map()
|
|
|
|
rows.filter(Boolean).forEach((row) => {
|
|
const key = normalizeClaimText(row.claimId, row.documentNo, row.documentKey)
|
|
if (!key) {
|
|
return
|
|
}
|
|
|
|
const current = rowMap.get(key)
|
|
if (!current || sourcePriority(row) >= sourcePriority(current)) {
|
|
rowMap.set(key, row)
|
|
}
|
|
})
|
|
|
|
return Array.from(rowMap.values())
|
|
}
|
|
|
|
function uniqueRowsByNewKey(rows) {
|
|
const seenKeys = new Set()
|
|
|
|
return rows.filter((row) => {
|
|
const key = resolveDocumentNewKey(row)
|
|
if (!key || seenKeys.has(key)) {
|
|
return false
|
|
}
|
|
|
|
seenKeys.add(key)
|
|
return true
|
|
})
|
|
}
|
|
|
|
function mapClaimsToRows(claims, source) {
|
|
return Array.isArray(claims)
|
|
? claims.map((claim) => buildDocumentInboxRow(claim, source)).filter(Boolean)
|
|
: []
|
|
}
|
|
|
|
export function buildDocumentInboxRows({ ownedClaims = [], approvalClaims = [], archivedClaims = [] } = {}) {
|
|
const ownedRows = mapClaimsToRows(ownedClaims, 'owned')
|
|
const approvalRows = mapClaimsToRows(approvalClaims, 'approval')
|
|
const archiveRows = mapClaimsToRows(archivedClaims, 'archive')
|
|
|
|
return uniqueRowsByNewKey([
|
|
...mergeNonArchivedRows([...ownedRows, ...approvalRows]),
|
|
...archiveRows
|
|
])
|
|
}
|
|
|
|
function refreshViewedDocumentKeys() {
|
|
viewedDocumentKeys.value = readViewedDocumentKeys()
|
|
}
|
|
|
|
function attachViewedKeysListener() {
|
|
if (typeof window === 'undefined' || viewedKeysListenerAttached) {
|
|
return
|
|
}
|
|
|
|
window.addEventListener(DOCUMENT_VIEWED_KEYS_CHANGE_EVENT, refreshViewedDocumentKeys)
|
|
viewedKeysListenerAttached = true
|
|
}
|
|
|
|
async function readClaimList(fetcher) {
|
|
const result = await fetcher()
|
|
return Array.isArray(result) ? result : []
|
|
}
|
|
|
|
export function useDocumentCenterInbox() {
|
|
attachViewedKeysListener()
|
|
|
|
const unreadCount = computed(() => countNewDocuments(documentRows.value, viewedDocumentKeys.value))
|
|
const hasUnread = computed(() => unreadCount.value > 0)
|
|
|
|
async function refreshDocumentInbox(options = {}) {
|
|
const force = Boolean(options.force)
|
|
const now = Date.now()
|
|
|
|
if (refreshPromise) {
|
|
return refreshPromise
|
|
}
|
|
|
|
if (!force && lastRefreshAt && now - lastRefreshAt < INBOX_CACHE_TTL_MS) {
|
|
refreshViewedDocumentKeys()
|
|
return documentRows.value
|
|
}
|
|
|
|
loading.value = true
|
|
|
|
refreshPromise = (async () => {
|
|
const [ownedResult, approvalResult, archiveResult] = await Promise.allSettled([
|
|
readClaimList(fetchExpenseClaims),
|
|
readClaimList(fetchApprovalExpenseClaims),
|
|
readClaimList(fetchArchivedExpenseClaims)
|
|
])
|
|
|
|
documentRows.value = buildDocumentInboxRows({
|
|
ownedClaims: ownedResult.status === 'fulfilled' ? ownedResult.value : [],
|
|
approvalClaims: approvalResult.status === 'fulfilled' ? approvalResult.value : [],
|
|
archivedClaims: archiveResult.status === 'fulfilled' ? archiveResult.value : []
|
|
})
|
|
lastRefreshAt = Date.now()
|
|
refreshViewedDocumentKeys()
|
|
|
|
return documentRows.value
|
|
})()
|
|
|
|
try {
|
|
return await refreshPromise
|
|
} finally {
|
|
loading.value = false
|
|
refreshPromise = null
|
|
}
|
|
}
|
|
|
|
function startDocumentInboxPolling(intervalMs = 120000) {
|
|
stopDocumentInboxPolling()
|
|
|
|
if (typeof window === 'undefined') {
|
|
return
|
|
}
|
|
|
|
refreshTimer = window.setInterval(() => {
|
|
void refreshDocumentInbox()
|
|
}, intervalMs)
|
|
}
|
|
|
|
function stopDocumentInboxPolling() {
|
|
if (refreshTimer && typeof window !== 'undefined') {
|
|
window.clearInterval(refreshTimer)
|
|
refreshTimer = null
|
|
}
|
|
}
|
|
|
|
return {
|
|
hasUnread,
|
|
loading,
|
|
refreshDocumentInbox,
|
|
startDocumentInboxPolling,
|
|
stopDocumentInboxPolling,
|
|
unreadCount
|
|
}
|
|
}
|