feat: improve sorting by count

This commit is contained in:
Marco Breiter 2024-05-12 23:45:36 +02:00
parent ac666df4ce
commit c114425844
5 changed files with 80 additions and 38 deletions

View File

@ -22,7 +22,7 @@ test('basic filtering', async ({ page }) => {
await page.getByRole('button', { name: 'Correspondent' }).click()
await page.getByRole('menuitem', { name: 'Test Correspondent 1' }).click()
await page.getByRole('menuitem', { name: 'Correspondent 9' }).click()
await expect(page).toHaveURL(/correspondent__id__in=12,1/)
await expect(page).toHaveURL(/correspondent__id__in=1,12/)
await expect(page.locator('pngx-document-list')).toHaveText(/7 documents/)
await page
.locator('pngx-filter-editor')

View File

@ -4113,7 +4113,7 @@
"time": 0.554,
"request": {
"method": "GET",
"url": "http://localhost:8000/api/documents/?page=1&page_size=50&ordering=-created&truncate_content=true&correspondent__id__in=12,1",
"url": "http://localhost:8000/api/documents/?page=1&page_size=50&ordering=-created&truncate_content=true&correspondent__id__in=1,12",
"httpVersion": "HTTP/1.1",
"cookies": [],
"headers": [
@ -4148,7 +4148,7 @@
},
{
"name": "correspondent__id__in",
"value": "12,1"
"value": "1,12"
}
],
"headersSize": -1,

View File

@ -35,7 +35,7 @@
</div>
@if (selectionModel.items) {
<div class="items" #buttonItems>
@for (item of selectionModel.itemsSorted | filter: filterText; track item; let i = $index) {
@for (item of selectionModel.items | filter: filterText; track item; let i = $index) {
@if (allowSelectNone || item.id) {
<pngx-toggleable-dropdown-button
[item]="item" [hideCount]="hideCount(item)" [state]="selectionModel.get(item.id)" [count]="getUpdatedDocumentCount(item.id)" (toggle)="selectionModel.toggle(item.id)" (exclude)="excludeClicked(item.id)" (click)="setButtonItemIndex(i - 1)" [disabled]="disabled">
@ -45,13 +45,13 @@
</div>
}
@if (editing) {
@if ((selectionModel.itemsSorted | filter: filterText).length === 0 && createRef !== undefined) {
@if ((selectionModel.items | filter: filterText).length === 0 && createRef !== undefined) {
<button class="list-group-item list-group-item-action bg-light" (click)="createClicked()" [disabled]="disabled">
<small class="ms-2"><ng-container i18n>Create</ng-container> "{{filterText}}"</small>
<i-bs width="1.5em" height="1em" name="plus"></i-bs>
</button>
}
@if ((selectionModel.itemsSorted | filter: filterText).length > 0) {
@if ((selectionModel.items | filter: filterText).length > 0) {
<button class="list-group-item list-group-item-action bg-light" (click)="applyClicked()" [disabled]="!modelIsDirty || disabled">
<small class="ms-2" [ngClass]="{'fw-bold': modelIsDirty}" i18n>Apply</small>
<i-bs width="1.5em" height="1em" name="arrow-right"></i-bs>

View File

@ -501,11 +501,11 @@ describe('FilterableDropdownComponent & FilterableDropdownSelectionModel', () =>
component.selectionModel = selectionModel
selectionModel.toggle(items[1].id)
selectionModel.apply()
expect(selectionModel.itemsSorted).toEqual([
expect(component.items).toEqual([
nullItem,
{ id: null, name: 'Null B' },
items[1],
items[0],
items[1],
])
})

View File

@ -44,32 +44,9 @@ export class FilterableDropdownSelectionModel {
items: MatchingModel[] = []
get itemsSorted(): MatchingModel[] {
// TODO: this is getting called very often
return this.items.sort((a, b) => {
if (a.id == null && b.id != null) {
return -1
} else if (a.id != null && b.id == null) {
return 1
} else if (
this.getNonTemporary(a.id) == ToggleableItemState.NotSelected &&
this.getNonTemporary(b.id) != ToggleableItemState.NotSelected
) {
return 1
} else if (
this.getNonTemporary(a.id) != ToggleableItemState.NotSelected &&
this.getNonTemporary(b.id) == ToggleableItemState.NotSelected
) {
return -1
} else {
return a.name.localeCompare(b.name)
}
})
}
private selectionStates = new Map<number, ToggleableItemState>()
private temporarySelectionStates = new Map<number, ToggleableItemState>()
temporarySelectionStates = new Map<number, ToggleableItemState>()
getSelectedItems() {
return this.items.filter(
@ -188,7 +165,7 @@ export class FilterableDropdownSelectionModel {
}
}
private getNonTemporary(id: number) {
getNonTemporary(id: number) {
return this.selectionStates.get(id) || ToggleableItemState.NotSelected
}
@ -335,7 +312,7 @@ export class FilterableDropdownComponent implements OnDestroy, OnInit {
@Input()
set items(items: MatchingModel[]) {
if (items) {
this._selectionModel.items = Array.from(items)
this._selectionModel.items = this.itemsSorted(items)
this._selectionModel.items.unshift({
name: $localize`:Filter drop down element to filter for documents with no correspondent/type/tag assigned:Not assigned`,
id: null,
@ -343,6 +320,65 @@ export class FilterableDropdownComponent implements OnDestroy, OnInit {
}
}
itemsSorted(
items: MatchingModel[],
isEditModeActive: boolean = true
): MatchingModel[] {
const isUnassignedElement = (a) => a.id == null
const getSelectionCount = (a) =>
this._documentCounts?.find((c) => c.id === a.id)?.document_count || 0
enum priority {
NotAssignable = 5,
HasTemporarySelection = 4,
HasSelection = 3,
HasDocuments = 2,
Other = 1,
}
const getPriority = (a): priority => {
if (isUnassignedElement(a)) {
return priority.NotAssignable
} else if (this.selectionModel.temporarySelectionStates.get(a.id)) {
return priority.HasTemporarySelection
} else if (
this.selectionModel.getNonTemporary(a.id) !=
ToggleableItemState.NotSelected
) {
return priority.HasSelection
} else if (getSelectionCount(a) > 0) {
return priority.HasDocuments
} else {
return priority.Other
}
}
return items
.sort((a, b) => {
const aPriority = getPriority(a)
const bPriority = getPriority(b)
if (aPriority != bPriority) {
return aPriority > bPriority ? -1 : 1
} else {
if (aPriority >= priority.HasSelection) {
return getSelectionCount(a) > getSelectionCount(b) ? -1 : 1
} else if (priority.HasDocuments == aPriority) {
return a.document_count > b.document_count ? -1 : 1
} else {
return a.name.localeCompare(b.name)
}
}
})
.filter(
(a: SelectionDataItem) =>
isEditModeActive ||
getSelectionCount(a) ||
isUnassignedElement(a) ||
this.selectionModel.getNonTemporary(a.id) !=
ToggleableItemState.NotSelected ||
this.selectionModel.temporarySelectionStates.get(a.id)
)
}
get items(): MatchingModel[] {
return this._selectionModel.items
}
@ -354,7 +390,7 @@ export class FilterableDropdownComponent implements OnDestroy, OnInit {
set selectionModel(model: FilterableDropdownSelectionModel) {
if (this.selectionModel) {
this.selectionModel.changed.complete()
model.items = this.selectionModel.items
model.items = this.itemsSorted(this.selectionModel.items)
model.manyToOne = this.selectionModel.manyToOne
model.singleSelect = this.editing && !this.selectionModel.manyToOne
}
@ -419,8 +455,14 @@ export class FilterableDropdownComponent implements OnDestroy, OnInit {
: !this.selectionModel.isNoneSelected()
}
_documentCounts: SelectionDataItem[] = []
@Input()
documentCounts: SelectionDataItem[]
set documentCounts(documentCounts: SelectionDataItem[]) {
this._documentCounts = documentCounts
if (documentCounts) {
this.selectionModel.items = this.itemsSorted(this.selectionModel.items)
}
}
@Input()
shortcutKey: string
@ -533,8 +575,8 @@ export class FilterableDropdownComponent implements OnDestroy, OnInit {
}
getUpdatedDocumentCount(id: number) {
if (this.documentCounts) {
return this.documentCounts.find((c) => c.id === id)?.document_count
if (this._documentCounts) {
return this._documentCounts.find((c) => c.id === id)?.document_count
}
}