diff --git a/frontend/src/ts/modals/quote-filter.ts b/frontend/src/ts/modals/quote-filter.ts new file mode 100644 index 000000000..d83fccce5 --- /dev/null +++ b/frontend/src/ts/modals/quote-filter.ts @@ -0,0 +1,51 @@ +import { SimpleModal } from "../utils/simple-modal"; + +export let minFilterLength: number = 0; +export let maxFilterLength: number = 0; +export let removeCustom: boolean = false; + +export function setRemoveCustom(value: boolean): void { + removeCustom = value; +} + +function refresh(): void { + const refreshEvent = new CustomEvent("refresh"); + document.dispatchEvent(refreshEvent); +} + +export const quoteFilterModal = new SimpleModal({ + id: "quoteFilter", + title: "Enter minimum and maximum values", + inputs: [ + { + placeholder: "1", + type: "number", + }, + { + placeholder: "100", + type: "number", + }, + ], + buttonText: "save", + execFn: async (_thisPopup, min, max) => { + const minNum = parseInt(min, 10); + const maxNum = parseInt(max, 10); + if (isNaN(minNum) || isNaN(maxNum)) { + return { + status: 0, + message: "Invalid min/max values", + }; + } + + minFilterLength = minNum; + maxFilterLength = maxNum; + refresh(); + + let message: string = "saved custom filter"; + return { status: 1, message }; + }, + afterClickAway: () => { + setRemoveCustom(true); + refresh(); + }, +}); diff --git a/frontend/src/ts/modals/quote-search.ts b/frontend/src/ts/modals/quote-search.ts index 12d02d3ee..33f371384 100644 --- a/frontend/src/ts/modals/quote-search.ts +++ b/frontend/src/ts/modals/quote-search.ts @@ -4,6 +4,7 @@ import * as ManualRestart from "../test/manual-restart-tracker"; import * as Notifications from "../elements/notifications"; import * as QuoteSubmitPopup from "./quote-submit"; import * as QuoteApprovePopup from "./quote-approve"; +import * as QuoteFilterPopup from "./quote-filter"; import * as QuoteReportModal from "./quote-report"; import { buildSearchService, @@ -26,6 +27,7 @@ const searchServiceCache: Record> = {}; const pageSize = 100; let currentPageNumber = 1; +let usingCustomLength = true; function getSearchService( language: string, @@ -45,16 +47,65 @@ function getSearchService( function applyQuoteLengthFilter(quotes: Quote[]): Quote[] { if (!modal.isOpen()) return []; - const quoteLengthFilterValue = $( - "#quoteSearchModal .quoteLengthFilter", - ).val() as string[]; + const quoteLengthDropdown = $("#quoteSearchModal .quoteLengthFilter"); + const quoteLengthFilterValue = quoteLengthDropdown.val() as string[]; + if (quoteLengthFilterValue.length === 0) { + usingCustomLength = true; return quotes; } const quoteLengthFilter = new Set( quoteLengthFilterValue.map((filterValue) => parseInt(filterValue, 10)), ); + + const customFilterIndex = quoteLengthFilterValue.indexOf("4"); + + if (customFilterIndex !== -1) { + if (QuoteFilterPopup.removeCustom) { + QuoteFilterPopup.setRemoveCustom(false); + const selectElement = quoteLengthDropdown.get(0) as + | HTMLSelectElement + | null + | undefined; + + if (!selectElement) { + return quotes; + } + + //@ts-expect-error SlimSelect adds slim to the element + const ss = selectElement.slim as SlimSelect | undefined; + + if (ss !== undefined) { + const currentSelected = ss.getSelected(); + + // remove custom selection + const customIndex = currentSelected.indexOf("4"); + if (customIndex > -1) { + currentSelected.splice(customIndex, 1); + } + + ss.setSelected(currentSelected); + } + } else { + if (usingCustomLength) { + QuoteFilterPopup.quoteFilterModal.show(undefined, {}); + usingCustomLength = false; + } else { + const filteredQuotes = quotes.filter( + (quote) => + (quote.length >= QuoteFilterPopup.minFilterLength && + quote.length <= QuoteFilterPopup.maxFilterLength) || + quoteLengthFilter.has(quote.group), + ); + + return filteredQuotes; + } + } + } else { + usingCustomLength = true; + } + const filteredQuotes = quotes.filter((quote) => quoteLengthFilter.has(quote.group), ); @@ -281,6 +332,10 @@ export async function show(showOptions?: ShowOptions): Promise { text: "thicc", value: "3", }, + { + text: "custom", + value: "4", + }, ], }); }, @@ -437,6 +492,13 @@ async function setup(modalEl: HTMLElement): Promise { currentPageNumber--; void updateResults(searchText); }); + + document?.addEventListener("refresh", () => { + const searchText = ( + document.getElementById("searchBox") as HTMLInputElement + ).value; + void updateResults(searchText); + }); } async function cleanup(): Promise { diff --git a/frontend/src/ts/utils/simple-modal.ts b/frontend/src/ts/utils/simple-modal.ts index b1452dadf..633644676 100644 --- a/frontend/src/ts/utils/simple-modal.ts +++ b/frontend/src/ts/utils/simple-modal.ts @@ -111,6 +111,7 @@ type SimpleModalOptions = { onlineOnly?: boolean; hideCallsExec?: boolean; showLabels?: boolean; + afterClickAway?: () => void; }; export class SimpleModal { @@ -131,6 +132,7 @@ export class SimpleModal { onlineOnly: boolean; hideCallsExec: boolean; showLabels: boolean; + afterClickAway: (() => void) | undefined; constructor(options: SimpleModalOptions) { this.parameters = []; this.id = options.id; @@ -149,6 +151,7 @@ export class SimpleModal { this.onlineOnly = options.onlineOnly ?? false; this.hideCallsExec = options.hideCallsExec ?? false; this.showLabels = options.showLabels ?? false; + this.afterClickAway = options.afterClickAway; } reset(): void { this.element.innerHTML = ` @@ -480,6 +483,7 @@ const modal = new AnimatedModal({ hide(); }, customWrapperClickHandler: (e): void => { + activePopup?.afterClickAway?.(); hide(); }, });