feat: improve sorting by count
This commit is contained in:
parent
ac666df4ce
commit
c114425844
@ -22,7 +22,7 @@ test('basic filtering', async ({ page }) => {
|
|||||||
await page.getByRole('button', { name: 'Correspondent' }).click()
|
await page.getByRole('button', { name: 'Correspondent' }).click()
|
||||||
await page.getByRole('menuitem', { name: 'Test Correspondent 1' }).click()
|
await page.getByRole('menuitem', { name: 'Test Correspondent 1' }).click()
|
||||||
await page.getByRole('menuitem', { name: 'Correspondent 9' }).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 expect(page.locator('pngx-document-list')).toHaveText(/7 documents/)
|
||||||
await page
|
await page
|
||||||
.locator('pngx-filter-editor')
|
.locator('pngx-filter-editor')
|
||||||
|
@ -4113,7 +4113,7 @@
|
|||||||
"time": 0.554,
|
"time": 0.554,
|
||||||
"request": {
|
"request": {
|
||||||
"method": "GET",
|
"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",
|
"httpVersion": "HTTP/1.1",
|
||||||
"cookies": [],
|
"cookies": [],
|
||||||
"headers": [
|
"headers": [
|
||||||
@ -4148,7 +4148,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "correspondent__id__in",
|
"name": "correspondent__id__in",
|
||||||
"value": "12,1"
|
"value": "1,12"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"headersSize": -1,
|
"headersSize": -1,
|
||||||
|
@ -35,7 +35,7 @@
|
|||||||
</div>
|
</div>
|
||||||
@if (selectionModel.items) {
|
@if (selectionModel.items) {
|
||||||
<div class="items" #buttonItems>
|
<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) {
|
@if (allowSelectNone || item.id) {
|
||||||
<pngx-toggleable-dropdown-button
|
<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">
|
[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>
|
</div>
|
||||||
}
|
}
|
||||||
@if (editing) {
|
@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">
|
<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>
|
<small class="ms-2"><ng-container i18n>Create</ng-container> "{{filterText}}"</small>
|
||||||
<i-bs width="1.5em" height="1em" name="plus"></i-bs>
|
<i-bs width="1.5em" height="1em" name="plus"></i-bs>
|
||||||
</button>
|
</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">
|
<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>
|
<small class="ms-2" [ngClass]="{'fw-bold': modelIsDirty}" i18n>Apply</small>
|
||||||
<i-bs width="1.5em" height="1em" name="arrow-right"></i-bs>
|
<i-bs width="1.5em" height="1em" name="arrow-right"></i-bs>
|
||||||
|
@ -501,11 +501,11 @@ describe('FilterableDropdownComponent & FilterableDropdownSelectionModel', () =>
|
|||||||
component.selectionModel = selectionModel
|
component.selectionModel = selectionModel
|
||||||
selectionModel.toggle(items[1].id)
|
selectionModel.toggle(items[1].id)
|
||||||
selectionModel.apply()
|
selectionModel.apply()
|
||||||
expect(selectionModel.itemsSorted).toEqual([
|
expect(component.items).toEqual([
|
||||||
nullItem,
|
nullItem,
|
||||||
{ id: null, name: 'Null B' },
|
{ id: null, name: 'Null B' },
|
||||||
items[1],
|
|
||||||
items[0],
|
items[0],
|
||||||
|
items[1],
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -44,32 +44,9 @@ export class FilterableDropdownSelectionModel {
|
|||||||
|
|
||||||
items: MatchingModel[] = []
|
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 selectionStates = new Map<number, ToggleableItemState>()
|
||||||
|
|
||||||
private temporarySelectionStates = new Map<number, ToggleableItemState>()
|
temporarySelectionStates = new Map<number, ToggleableItemState>()
|
||||||
|
|
||||||
getSelectedItems() {
|
getSelectedItems() {
|
||||||
return this.items.filter(
|
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
|
return this.selectionStates.get(id) || ToggleableItemState.NotSelected
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -335,7 +312,7 @@ export class FilterableDropdownComponent implements OnDestroy, OnInit {
|
|||||||
@Input()
|
@Input()
|
||||||
set items(items: MatchingModel[]) {
|
set items(items: MatchingModel[]) {
|
||||||
if (items) {
|
if (items) {
|
||||||
this._selectionModel.items = Array.from(items)
|
this._selectionModel.items = this.itemsSorted(items)
|
||||||
this._selectionModel.items.unshift({
|
this._selectionModel.items.unshift({
|
||||||
name: $localize`:Filter drop down element to filter for documents with no correspondent/type/tag assigned:Not assigned`,
|
name: $localize`:Filter drop down element to filter for documents with no correspondent/type/tag assigned:Not assigned`,
|
||||||
id: null,
|
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[] {
|
get items(): MatchingModel[] {
|
||||||
return this._selectionModel.items
|
return this._selectionModel.items
|
||||||
}
|
}
|
||||||
@ -354,7 +390,7 @@ export class FilterableDropdownComponent implements OnDestroy, OnInit {
|
|||||||
set selectionModel(model: FilterableDropdownSelectionModel) {
|
set selectionModel(model: FilterableDropdownSelectionModel) {
|
||||||
if (this.selectionModel) {
|
if (this.selectionModel) {
|
||||||
this.selectionModel.changed.complete()
|
this.selectionModel.changed.complete()
|
||||||
model.items = this.selectionModel.items
|
model.items = this.itemsSorted(this.selectionModel.items)
|
||||||
model.manyToOne = this.selectionModel.manyToOne
|
model.manyToOne = this.selectionModel.manyToOne
|
||||||
model.singleSelect = this.editing && !this.selectionModel.manyToOne
|
model.singleSelect = this.editing && !this.selectionModel.manyToOne
|
||||||
}
|
}
|
||||||
@ -419,8 +455,14 @@ export class FilterableDropdownComponent implements OnDestroy, OnInit {
|
|||||||
: !this.selectionModel.isNoneSelected()
|
: !this.selectionModel.isNoneSelected()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_documentCounts: SelectionDataItem[] = []
|
||||||
@Input()
|
@Input()
|
||||||
documentCounts: SelectionDataItem[]
|
set documentCounts(documentCounts: SelectionDataItem[]) {
|
||||||
|
this._documentCounts = documentCounts
|
||||||
|
if (documentCounts) {
|
||||||
|
this.selectionModel.items = this.itemsSorted(this.selectionModel.items)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
shortcutKey: string
|
shortcutKey: string
|
||||||
@ -533,8 +575,8 @@ export class FilterableDropdownComponent implements OnDestroy, OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getUpdatedDocumentCount(id: number) {
|
getUpdatedDocumentCount(id: number) {
|
||||||
if (this.documentCounts) {
|
if (this._documentCounts) {
|
||||||
return this.documentCounts.find((c) => c.id === id)?.document_count
|
return this._documentCounts.find((c) => c.id === id)?.document_count
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user