fix(documents): sort newest rows first
This commit is contained in:
24
web/src/utils/documentCenterSort.js
Normal file
24
web/src/utils/documentCenterSort.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
function normalizeSortTime(value) {
|
||||||
|
const time = Number(value)
|
||||||
|
return Number.isFinite(time) ? time : 0
|
||||||
|
}
|
||||||
|
|
||||||
|
export function compareDocumentRowsByLatestTime(left, right) {
|
||||||
|
const latestDiff = normalizeSortTime(right?.sortTime) - normalizeSortTime(left?.sortTime)
|
||||||
|
if (latestDiff !== 0) {
|
||||||
|
return latestDiff
|
||||||
|
}
|
||||||
|
|
||||||
|
const createdDiff = normalizeSortTime(right?.createdSortTime) - normalizeSortTime(left?.createdSortTime)
|
||||||
|
if (createdDiff !== 0) {
|
||||||
|
return createdDiff
|
||||||
|
}
|
||||||
|
|
||||||
|
const rightKey = String(right?.documentKey || right?.documentNo || '').trim()
|
||||||
|
const leftKey = String(left?.documentKey || left?.documentNo || '').trim()
|
||||||
|
return rightKey.localeCompare(leftKey, 'zh-CN')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sortDocumentRowsByLatestTime(rows) {
|
||||||
|
return (Array.isArray(rows) ? [...rows] : []).sort(compareDocumentRowsByLatestTime)
|
||||||
|
}
|
||||||
@@ -250,6 +250,7 @@ import { useMinimumVisibleState } from '../composables/useMinimumVisibleState.js
|
|||||||
import { mapExpenseClaimToRequest } from '../composables/useRequests.js'
|
import { mapExpenseClaimToRequest } from '../composables/useRequests.js'
|
||||||
import { fetchApprovalExpenseClaims, fetchArchivedExpenseClaims } from '../services/reimbursements.js'
|
import { fetchApprovalExpenseClaims, fetchArchivedExpenseClaims } from '../services/reimbursements.js'
|
||||||
import { countNewDocuments, isNewDocument, markDocumentViewed, markDocumentsViewed, readDocumentScope, readViewedDocumentKeys, writeDocumentScope } from '../utils/documentCenterNewState.js'
|
import { countNewDocuments, isNewDocument, markDocumentViewed, markDocumentsViewed, readDocumentScope, readViewedDocumentKeys, writeDocumentScope } from '../utils/documentCenterNewState.js'
|
||||||
|
import { sortDocumentRowsByLatestTime } from '../utils/documentCenterSort.js'
|
||||||
import { extractDateText, formatDocumentListTime, resolveDocumentSortTime, resolveDocumentStayTimeDisplay } from '../utils/documentCenterTime.js'
|
import { extractDateText, formatDocumentListTime, resolveDocumentSortTime, resolveDocumentStayTimeDisplay } from '../utils/documentCenterTime.js'
|
||||||
import { excludeArchivedDocumentRows, filterApplicationScopeNewRows, isArchivedDocumentRow, prepareApplicationScopeRows } from '../utils/documentCenterRows.js'
|
import { excludeArchivedDocumentRows, filterApplicationScopeNewRows, isArchivedDocumentRow, prepareApplicationScopeRows } from '../utils/documentCenterRows.js'
|
||||||
import { normalizeRequestForUi } from '../utils/requestViewModel.js'
|
import { normalizeRequestForUi } from '../utils/requestViewModel.js'
|
||||||
@@ -535,7 +536,7 @@ const statusFilterLabel = computed(() =>
|
|||||||
const filteredRows = computed(() => {
|
const filteredRows = computed(() => {
|
||||||
const keyword = listKeyword.value.trim().toLowerCase()
|
const keyword = listKeyword.value.trim().toLowerCase()
|
||||||
|
|
||||||
return activeScopeRows.value.filter((row) => {
|
return sortDocumentRowsByLatestTime(activeScopeRows.value.filter((row) => {
|
||||||
const matchesKeyword = !keyword || [
|
const matchesKeyword = !keyword || [
|
||||||
row.documentNo,
|
row.documentNo,
|
||||||
row.documentTypeLabel,
|
row.documentTypeLabel,
|
||||||
@@ -556,7 +557,7 @@ const filteredRows = computed(() => {
|
|||||||
const matchesDateRange = matchesAppliedDateRange(row)
|
const matchesDateRange = matchesAppliedDateRange(row)
|
||||||
|
|
||||||
return matchesKeyword && matchesDocumentType && matchesScene && matchesStatus && matchesDateRange
|
return matchesKeyword && matchesDocumentType && matchesScene && matchesStatus && matchesDateRange
|
||||||
})
|
}))
|
||||||
})
|
})
|
||||||
|
|
||||||
const totalPages = computed(() => Math.max(1, Math.ceil(filteredRows.value.length / pageSize.value)))
|
const totalPages = computed(() => Math.max(1, Math.ceil(filteredRows.value.length / pageSize.value)))
|
||||||
@@ -653,6 +654,8 @@ function buildDocumentRow(request, options = {}) {
|
|||||||
const claimId = normalized.claimId || normalized.id || documentNo
|
const claimId = normalized.claimId || normalized.id || documentNo
|
||||||
const createdAtSource = normalized.createdAt || normalized.submittedAt || normalized.applyTime || normalized.updatedAt
|
const createdAtSource = normalized.createdAt || normalized.submittedAt || normalized.applyTime || normalized.updatedAt
|
||||||
const updatedAtSource = normalized.updatedAt || normalized.submittedAt || normalized.createdAt || normalized.applyTime
|
const updatedAtSource = normalized.updatedAt || normalized.submittedAt || normalized.createdAt || normalized.applyTime
|
||||||
|
const createdSortTime = resolveDocumentSortTime(createdAtSource)
|
||||||
|
const updatedSortTime = resolveDocumentSortTime(updatedAtSource)
|
||||||
const documentTypeCode = normalized.documentTypeCode || DOCUMENT_TYPE_REIMBURSEMENT
|
const documentTypeCode = normalized.documentTypeCode || DOCUMENT_TYPE_REIMBURSEMENT
|
||||||
const documentTypeLabel =
|
const documentTypeLabel =
|
||||||
normalized.documentTypeLabel
|
normalized.documentTypeLabel
|
||||||
@@ -689,7 +692,9 @@ function buildDocumentRow(request, options = {}) {
|
|||||||
? false
|
? false
|
||||||
: isNewDocument({ ...normalized, source: options.source || 'owned', claimId, documentNo }, viewedDocumentKeys.value),
|
: isNewDocument({ ...normalized, source: options.source || 'owned', claimId, documentNo }, viewedDocumentKeys.value),
|
||||||
updatedAtDisplay: formatDocumentListTime(updatedAtSource),
|
updatedAtDisplay: formatDocumentListTime(updatedAtSource),
|
||||||
sortTime: resolveDocumentSortTime(updatedAtSource)
|
createdSortTime,
|
||||||
|
updatedSortTime,
|
||||||
|
sortTime: Math.max(createdSortTime, updatedSortTime)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -751,7 +756,7 @@ function mergeDocumentRows(rows) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return Array.from(rowMap.values()).sort((left, right) => right.sortTime - left.sortTime)
|
return sortDocumentRowsByLatestTime(Array.from(rowMap.values()))
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveSourcePriority(row) {
|
function resolveSourcePriority(row) {
|
||||||
|
|||||||
33
web/tests/document-center-sort.test.mjs
Normal file
33
web/tests/document-center-sort.test.mjs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import assert from 'node:assert/strict'
|
||||||
|
import test from 'node:test'
|
||||||
|
|
||||||
|
import {
|
||||||
|
compareDocumentRowsByLatestTime,
|
||||||
|
sortDocumentRowsByLatestTime
|
||||||
|
} from '../src/utils/documentCenterSort.js'
|
||||||
|
|
||||||
|
test('document center sorts newest document rows first without mutating input', () => {
|
||||||
|
const rows = [
|
||||||
|
{ documentNo: 'AP-001', sortTime: 1000 },
|
||||||
|
{ documentNo: 'AP-003', sortTime: 3000 },
|
||||||
|
{ documentNo: 'AP-002', sortTime: 2000 }
|
||||||
|
]
|
||||||
|
|
||||||
|
const sortedRows = sortDocumentRowsByLatestTime(rows)
|
||||||
|
|
||||||
|
assert.deepEqual(sortedRows.map((row) => row.documentNo), ['AP-003', 'AP-002', 'AP-001'])
|
||||||
|
assert.deepEqual(rows.map((row) => row.documentNo), ['AP-001', 'AP-003', 'AP-002'])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('document center sort falls back to created time and stable document keys', () => {
|
||||||
|
const rows = [
|
||||||
|
{ documentKey: 'owned:AP-001', documentNo: 'AP-001', sortTime: 1000, createdSortTime: 1000 },
|
||||||
|
{ documentKey: 'owned:AP-002', documentNo: 'AP-002', sortTime: 1000, createdSortTime: 2000 },
|
||||||
|
{ documentKey: 'owned:AP-003', documentNo: 'AP-003', sortTime: 1000, createdSortTime: 2000 }
|
||||||
|
]
|
||||||
|
|
||||||
|
const sortedRows = sortDocumentRowsByLatestTime(rows)
|
||||||
|
|
||||||
|
assert.deepEqual(sortedRows.map((row) => row.documentNo), ['AP-003', 'AP-002', 'AP-001'])
|
||||||
|
assert.equal(compareDocumentRowsByLatestTime(rows[1], rows[2]) > 0, true)
|
||||||
|
})
|
||||||
@@ -68,6 +68,7 @@ test('documents center category tabs map to the intended row sources', () => {
|
|||||||
assert.match(documentsCenterView, /excludeArchivedDocumentRows/)
|
assert.match(documentsCenterView, /excludeArchivedDocumentRows/)
|
||||||
assert.match(documentsCenterView, /approvalRows\.value = excludeArchivedDocumentRows/)
|
assert.match(documentsCenterView, /approvalRows\.value = excludeArchivedDocumentRows/)
|
||||||
assert.match(documentsCenterView, /const nonArchivedRows = computed\(\(\) => mergeDocumentRows\(\[\.\.\.ownedRows\.value, \.\.\.approvalRows\.value\]\)\)/)
|
assert.match(documentsCenterView, /const nonArchivedRows = computed\(\(\) => mergeDocumentRows\(\[\.\.\.ownedRows\.value, \.\.\.approvalRows\.value\]\)\)/)
|
||||||
|
assert.match(documentsCenterView, /import \{ sortDocumentRowsByLatestTime \} from '..\/utils\/documentCenterSort\.js'/)
|
||||||
assert.match(documentsCenterView, /activeScopeTab\.value !== DOCUMENT_SCOPE_ARCHIVE && isArchivedDocumentRow\(row\)/)
|
assert.match(documentsCenterView, /activeScopeTab\.value !== DOCUMENT_SCOPE_ARCHIVE && isArchivedDocumentRow\(row\)/)
|
||||||
assert.match(
|
assert.match(
|
||||||
documentsCenterView,
|
documentsCenterView,
|
||||||
@@ -92,6 +93,23 @@ test('documents center category tabs map to the intended row sources', () => {
|
|||||||
assert.match(documentsCenterView, /return nonArchivedRows\.value/)
|
assert.match(documentsCenterView, /return nonArchivedRows\.value/)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('documents center sorts every filtered scope by latest document time before pagination', () => {
|
||||||
|
assert.match(
|
||||||
|
documentsCenterView,
|
||||||
|
/return sortDocumentRowsByLatestTime\(activeScopeRows\.value\.filter\(\(row\) => \{[\s\S]*matchesKeyword && matchesDocumentType && matchesScene && matchesStatus && matchesDateRange[\s\S]*\}\)\)/
|
||||||
|
)
|
||||||
|
assert.match(
|
||||||
|
documentsCenterView,
|
||||||
|
/const createdSortTime = resolveDocumentSortTime\(createdAtSource\)[\s\S]*const updatedSortTime = resolveDocumentSortTime\(updatedAtSource\)/
|
||||||
|
)
|
||||||
|
assert.match(
|
||||||
|
documentsCenterView,
|
||||||
|
/createdSortTime,[\s\S]*updatedSortTime,[\s\S]*sortTime: Math\.max\(createdSortTime, updatedSortTime\)/
|
||||||
|
)
|
||||||
|
assert.match(documentsCenterView, /return sortDocumentRowsByLatestTime\(Array\.from\(rowMap\.values\(\)\)\)/)
|
||||||
|
assert.doesNotMatch(documentsCenterView, /right\.sortTime - left\.sortTime/)
|
||||||
|
})
|
||||||
|
|
||||||
test('documents center preserves application document type from mapped requests', () => {
|
test('documents center preserves application document type from mapped requests', () => {
|
||||||
assert.match(
|
assert.match(
|
||||||
documentsCenterView,
|
documentsCenterView,
|
||||||
|
|||||||
Reference in New Issue
Block a user