mirror of
https://github.com/hrfee/jfa-go.git
synced 2026-01-18 16:47:42 +01:00
stats: add unfinished query builder
not gonna go any further. This is an unnecessary feature which could just be a wiki page (and it will).
This commit is contained in:
@@ -566,11 +566,11 @@
|
||||
<div class="card ~neutral @low flex flex-col gap-2 flex-1">
|
||||
<div class="flex flex-row gap-2">
|
||||
<label class="w-1/2">
|
||||
<input type="radio" name="duration" class="unfocused" id="radio-inv-duration" checked>
|
||||
<input type="radio" name="radio-duration" class="unfocused" checked>
|
||||
<span class="button ~neutral @high supra full-width center">{{ .strings.inviteDuration }}</span>
|
||||
</label>
|
||||
<label class="w-1/2">
|
||||
<input type="radio" name="duration" class="unfocused" id="radio-user-expiry">
|
||||
<input type="radio" name="radio-duration" class="unfocused">
|
||||
<span class="button ~neutral @low supra full-width center">{{ .strings.userExpiry }}</span>
|
||||
</label>
|
||||
</div>
|
||||
@@ -900,7 +900,52 @@
|
||||
</div>
|
||||
</div>
|
||||
<div id="tab-statistics" class="flex flex-col gap-4 unfocused">
|
||||
<div class="card @low dark:~d_neutral mb-4" id="statistics-container">
|
||||
<div class="card @low dark:~d_neutral">
|
||||
<div class="card @low dark:~d_neutral flex flex-col gap-2">
|
||||
<div class="flex flex-row gap-2">
|
||||
<label class="w-full">
|
||||
<input type="radio" name="statistics-query-type" class="hidden" id="radio-statistics-accounts" checked>
|
||||
<span class="button ~neutral w-full center @high">{{ .strings.accounts }}</span>
|
||||
</label>
|
||||
<label class="w-full">
|
||||
<input type="radio" name="statistics-query-type" class="hidden" id="radio-statistics-activity">
|
||||
<span class="button ~neutral w-full center @low">{{ .strings.activity }}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div id="statistics-query-tab-accounts">
|
||||
<div class="flex flex-col align-middle gap-2">
|
||||
<div class="flex flex-row align-middle w-full gap-2">
|
||||
<input type="search" class="field ~neutral @low input search mr-2" placeholder="{{ .strings.query }}">
|
||||
<span class="button ~neutral @low center inside-input rounded-s-none statistics-search-clear" aria-label="{{ .strings.clearSearch }}" text="{{ .strings.clearSearch }}"><i class="ri-close-line"></i></span>
|
||||
<button class="button ~info @low statistics-query-execute" aria-label="{{ .strings.run }}"><i class="ri-refresh-line"></i></button>
|
||||
</div>
|
||||
<div class="flex flex-row gap-2 flex-wrap">
|
||||
<div class="statistics-sort-by-field"></div>
|
||||
<span class="flex flex-row gap-2 flex-wrap statistics-filter-area"></span>
|
||||
</div>
|
||||
<div class="card ~neutral @low statistics-filter-list">
|
||||
<p class="supra pb-2">{{ .strings.filters }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="statistics-query-tab-activity">
|
||||
<div class="flex flex-col align-middle gap-2">
|
||||
<div class="flex flex-row align-middle w-full gap-2">
|
||||
<input type="search" class="field ~neutral @low input search mr-2" placeholder="{{ .strings.query }}">
|
||||
<span class="button ~neutral @low center inside-input rounded-s-none statistics-search-clear" aria-label="{{ .strings.clearSearch }}" text="{{ .strings.clearSearch }}"><i class="ri-close-line"></i></span>
|
||||
<button class="button ~info @low statistics-query-execute" aria-label="{{ .strings.run }}"><i class="ri-refresh-line"></i></button>
|
||||
</div>
|
||||
<div class="flex flex-row gap-2 flex-wrap">
|
||||
<div class="statistics-sort-by-field"></div>
|
||||
<span class="flex flex-row gap-2 flex-wrap statistics-filter-area"></span>
|
||||
</div>
|
||||
<div class="card ~neutral @low statistics-filter-list">
|
||||
<p class="supra pb-2">{{ .strings.filters }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="statistics-container"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="tab-settings" class="flex flex-col gap-4 unfocused">
|
||||
|
||||
@@ -57,6 +57,8 @@
|
||||
"unlink": "Unlink Account",
|
||||
"deleted": "Deleted",
|
||||
"disabled": "Disabled",
|
||||
"query": "Query",
|
||||
"run": "Run",
|
||||
"sendPWR": "Send Password Reset",
|
||||
"noResultsFound": "No Results Found",
|
||||
"noResultsFoundLocally": "Only loaded records were searched. You can load more, or perform the search over all records on the server.",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { _get, _post, _delete, toClipboard, toggleLoader, toDateString } from "../modules/common.js";
|
||||
import { DiscordUser, newDiscordSearch } from "../modules/discord.js";
|
||||
import { reloadProfileNames } from "../modules/profiles.js";
|
||||
import { SimpleRadioTabs } from "./ui.js";
|
||||
|
||||
declare var window: GlobalWindow;
|
||||
|
||||
@@ -604,10 +605,10 @@ export class createInvite {
|
||||
private _userHours = document.getElementById("user-hours") as HTMLSelectElement;
|
||||
private _userMinutes = document.getElementById("user-minutes") as HTMLSelectElement;
|
||||
|
||||
private _invDurationButton = document.getElementById('radio-inv-duration') as HTMLInputElement;
|
||||
private _userExpiryButton = document.getElementById('radio-user-expiry') as HTMLInputElement;
|
||||
private _invDuration = document.getElementById('inv-duration');
|
||||
private _userExpiry = document.getElementById('user-expiry');
|
||||
private _durationTabs = new SimpleRadioTabs(
|
||||
[document.getElementById("inv-duration"), document.getElementById("user-expiry")],
|
||||
"radio-duration"
|
||||
);
|
||||
|
||||
private _sendToDiscord: (passData: string) => void;
|
||||
|
||||
@@ -849,30 +850,8 @@ export class createInvite {
|
||||
this.uses = 1;
|
||||
this.label = "";
|
||||
|
||||
const checkDuration = () => {
|
||||
const invSpan = this._invDurationButton.nextElementSibling as HTMLSpanElement;
|
||||
const userSpan = this._userExpiryButton.nextElementSibling as HTMLSpanElement;
|
||||
if (this._invDurationButton.checked) {
|
||||
this._invDuration.classList.remove("unfocused");
|
||||
this._userExpiry.classList.add("unfocused");
|
||||
invSpan.classList.add("@high");
|
||||
invSpan.classList.remove("@low");
|
||||
userSpan.classList.add("@low");
|
||||
userSpan.classList.remove("@high");
|
||||
} else if (this._userExpiryButton.checked) {
|
||||
this._userExpiry.classList.remove("unfocused");
|
||||
this._invDuration.classList.add("unfocused");
|
||||
invSpan.classList.add("@low");
|
||||
invSpan.classList.remove("@high");
|
||||
userSpan.classList.add("@high");
|
||||
userSpan.classList.remove("@low");
|
||||
}
|
||||
};
|
||||
|
||||
this._userExpiryButton.checked = false;
|
||||
this._invDurationButton.checked = true;
|
||||
this._userExpiryButton.onchange = checkDuration;
|
||||
this._invDurationButton.onchange = checkDuration;
|
||||
// Select the first tab by default (inv duration)
|
||||
this._durationTabs.select(0);
|
||||
|
||||
this._days.onchange = this._checkDurationValidity;
|
||||
this._months.onchange = this._checkDurationValidity;
|
||||
|
||||
@@ -33,17 +33,21 @@ export interface QueryType {
|
||||
}
|
||||
|
||||
export interface SearchConfiguration {
|
||||
filterArea: HTMLElement;
|
||||
sortingByButton?: HTMLButtonElement;
|
||||
searchOptionsHeader: HTMLElement;
|
||||
notFoundPanel: HTMLElement;
|
||||
notFoundLocallyText: HTMLElement;
|
||||
notFoundCallback?: (notFound: boolean) => void;
|
||||
filterList: HTMLElement;
|
||||
clearSearchButtonSelector: string;
|
||||
serverSearchButtonSelector: string;
|
||||
search: HTMLInputElement;
|
||||
queries: { [field: string]: QueryType };
|
||||
filterArea: HTMLElement;
|
||||
filterList: HTMLElement;
|
||||
search: HTMLInputElement;
|
||||
clearSearchButtonSelector?: string;
|
||||
serverSearchButtonSelector?: string;
|
||||
// Optionally supply a button for a single type of sorting.
|
||||
sortingByButton?: HTMLButtonElement;
|
||||
searchOptionsHeader?: HTMLElement;
|
||||
|
||||
notFoundPanel?: HTMLElement;
|
||||
notFoundLocallyText?: HTMLElement;
|
||||
notFoundCallback?: (notFound: boolean) => void;
|
||||
|
||||
// Function for showing/hiding search items.
|
||||
setVisibility: (items: string[], visible: boolean, appendedItems: boolean) => void;
|
||||
onSearchCallback: (newItems: boolean, loadAll: boolean, callback?: (resp: paginatedDTO) => void) => void;
|
||||
searchServer: (params: PaginatedReqDTO, newSearch: boolean) => void;
|
||||
@@ -521,9 +525,9 @@ export class Search {
|
||||
if (this._c.sortingByButton) sortingBy = !(this._c.sortingByButton.classList.contains("hidden"));
|
||||
const hasFilters = this._c.filterArea.textContent != "";
|
||||
if (sortingBy || hasFilters) {
|
||||
this._c.searchOptionsHeader.classList.remove("hidden");
|
||||
this._c.searchOptionsHeader?.classList.remove("hidden");
|
||||
} else {
|
||||
this._c.searchOptionsHeader.classList.add("hidden");
|
||||
this._c.searchOptionsHeader?.classList.add("hidden");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -576,14 +580,14 @@ export class Search {
|
||||
|
||||
setNotFoundPanelVisibility = (visible: boolean) => {
|
||||
if (this._inServerSearch || !this.inSearch) {
|
||||
this._c.notFoundLocallyText.classList.add("unfocused");
|
||||
this._c.notFoundLocallyText?.classList.add("unfocused");
|
||||
} else if (this.inSearch) {
|
||||
this._c.notFoundLocallyText.classList.remove("unfocused");
|
||||
this._c.notFoundLocallyText?.classList.remove("unfocused");
|
||||
}
|
||||
if (visible) {
|
||||
this._c.notFoundPanel.classList.remove("unfocused");
|
||||
this._c.notFoundPanel?.classList.remove("unfocused");
|
||||
} else {
|
||||
this._c.notFoundPanel.classList.add("unfocused");
|
||||
this._c.notFoundPanel?.classList.add("unfocused");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,8 @@ import {
|
||||
TransformComponent
|
||||
} from 'echarts/components';
|
||||
import { CanvasRenderer } from 'echarts/renderers';
|
||||
import { AccountsQueries } from "./accounts";
|
||||
import { SimpleRadioTabs } from "./ui";
|
||||
|
||||
echarts.use([
|
||||
BarChart,
|
||||
@@ -328,5 +330,32 @@ export class StatsPanel {
|
||||
|
||||
this._cards = new Map<string, StatCard>();
|
||||
this._order = [];
|
||||
|
||||
const queryTypeTabs = new SimpleRadioTabs(
|
||||
[document.getElementById("statistics-query-tab-accounts"), document.getElementById("statistics-query-tab-activity")],
|
||||
"statistics-query-type"
|
||||
);
|
||||
|
||||
queryTypeTabs.select(0);
|
||||
|
||||
const areas = [document.getElementById("statistics-query-tab-accounts"), document.getElementById("statistics-query-tab-activity")];
|
||||
const querySets = [AccountsQueries(), ActivityQueries()];
|
||||
|
||||
for (let i = 0; i < 2; i++) {
|
||||
let search = new Search({
|
||||
queries: querySets[i],
|
||||
filterArea: areas[i].getElementsByClassName("statistics-filter-area")[0] as HTMLElement,
|
||||
filterList: areas[i].getElementsByClassName("statistics-filter-list")[0] as HTMLElement,
|
||||
search: areas[i].getElementsByClassName("search")[0] as HTMLInputElement,
|
||||
|
||||
setVisibility: () => {},
|
||||
onSearchCallback: () => {},
|
||||
searchServer: () => {},
|
||||
clearServerSearch: () => {},
|
||||
});
|
||||
search.generateFilterList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -100,3 +100,52 @@ myGenericNumber.zeroValue = 0;
|
||||
myGenericNumber.add = function (x, y) {
|
||||
return x + y;
|
||||
};*/
|
||||
|
||||
// Simple radio tabs, when each "button" is a label containing an input[type="radio"] and some element[class="button"].
|
||||
export class SimpleRadioTabs {
|
||||
tabs: Array<HTMLElement>;
|
||||
radios: Array<HTMLInputElement>;
|
||||
|
||||
|
||||
// Pass nothing, or a list of the tab container elements and either the "name" field used on the radios, of a list of them.
|
||||
constructor(tabs?: Array<HTMLElement>, radios?: Array<HTMLInputElement>|string) {
|
||||
this.tabs = tabs || new Array<HTMLElement>();
|
||||
|
||||
if (radios) {
|
||||
if (typeof radios === "string") {
|
||||
this.radios = Array.from(document.querySelectorAll(`input[name=${radios}]`)) as Array<HTMLInputElement>;
|
||||
} else {
|
||||
this.radios = radios as Array<HTMLInputElement>;
|
||||
}
|
||||
this.radios.forEach((radio) => { radio.onchange = this.onChange });
|
||||
}
|
||||
}
|
||||
|
||||
onChange = () => {
|
||||
for (let i = 0; i < this.radios.length; i++) {
|
||||
const buttonEl = this.radios[i].nextElementSibling;
|
||||
if (this.radios[i].checked) {
|
||||
buttonEl.classList.add("@high");
|
||||
buttonEl.classList.remove("@low");
|
||||
this.tabs[i].classList.remove("unfocused");
|
||||
} else {
|
||||
buttonEl.classList.add("@low");
|
||||
buttonEl.classList.remove("@high");
|
||||
this.tabs[i].classList.add("unfocused");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
select(i: number) {
|
||||
for (let j = 0; j < this.radios.length; j++) {
|
||||
this.radios[j].checked = i == j;
|
||||
}
|
||||
this.onChange();
|
||||
}
|
||||
|
||||
push(tab: HTMLElement, radio: HTMLInputElement) {
|
||||
this.tabs.push(tab);
|
||||
radio.onchange = this.onChange;
|
||||
this.radios.push(radio);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user