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="card ~neutral @low flex flex-col gap-2 flex-1">
|
||||||
<div class="flex flex-row gap-2">
|
<div class="flex flex-row gap-2">
|
||||||
<label class="w-1/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>
|
<span class="button ~neutral @high supra full-width center">{{ .strings.inviteDuration }}</span>
|
||||||
</label>
|
</label>
|
||||||
<label class="w-1/2">
|
<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>
|
<span class="button ~neutral @low supra full-width center">{{ .strings.userExpiry }}</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@@ -900,7 +900,52 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="tab-statistics" class="flex flex-col gap-4 unfocused">
|
<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>
|
</div>
|
||||||
<div id="tab-settings" class="flex flex-col gap-4 unfocused">
|
<div id="tab-settings" class="flex flex-col gap-4 unfocused">
|
||||||
|
|||||||
@@ -57,6 +57,8 @@
|
|||||||
"unlink": "Unlink Account",
|
"unlink": "Unlink Account",
|
||||||
"deleted": "Deleted",
|
"deleted": "Deleted",
|
||||||
"disabled": "Disabled",
|
"disabled": "Disabled",
|
||||||
|
"query": "Query",
|
||||||
|
"run": "Run",
|
||||||
"sendPWR": "Send Password Reset",
|
"sendPWR": "Send Password Reset",
|
||||||
"noResultsFound": "No Results Found",
|
"noResultsFound": "No Results Found",
|
||||||
"noResultsFoundLocally": "Only loaded records were searched. You can load more, or perform the search over all records on the server.",
|
"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 { _get, _post, _delete, toClipboard, toggleLoader, toDateString } from "../modules/common.js";
|
||||||
import { DiscordUser, newDiscordSearch } from "../modules/discord.js";
|
import { DiscordUser, newDiscordSearch } from "../modules/discord.js";
|
||||||
import { reloadProfileNames } from "../modules/profiles.js";
|
import { reloadProfileNames } from "../modules/profiles.js";
|
||||||
|
import { SimpleRadioTabs } from "./ui.js";
|
||||||
|
|
||||||
declare var window: GlobalWindow;
|
declare var window: GlobalWindow;
|
||||||
|
|
||||||
@@ -604,10 +605,10 @@ export class createInvite {
|
|||||||
private _userHours = document.getElementById("user-hours") as HTMLSelectElement;
|
private _userHours = document.getElementById("user-hours") as HTMLSelectElement;
|
||||||
private _userMinutes = document.getElementById("user-minutes") as HTMLSelectElement;
|
private _userMinutes = document.getElementById("user-minutes") as HTMLSelectElement;
|
||||||
|
|
||||||
private _invDurationButton = document.getElementById('radio-inv-duration') as HTMLInputElement;
|
private _durationTabs = new SimpleRadioTabs(
|
||||||
private _userExpiryButton = document.getElementById('radio-user-expiry') as HTMLInputElement;
|
[document.getElementById("inv-duration"), document.getElementById("user-expiry")],
|
||||||
private _invDuration = document.getElementById('inv-duration');
|
"radio-duration"
|
||||||
private _userExpiry = document.getElementById('user-expiry');
|
);
|
||||||
|
|
||||||
private _sendToDiscord: (passData: string) => void;
|
private _sendToDiscord: (passData: string) => void;
|
||||||
|
|
||||||
@@ -849,30 +850,8 @@ export class createInvite {
|
|||||||
this.uses = 1;
|
this.uses = 1;
|
||||||
this.label = "";
|
this.label = "";
|
||||||
|
|
||||||
const checkDuration = () => {
|
// Select the first tab by default (inv duration)
|
||||||
const invSpan = this._invDurationButton.nextElementSibling as HTMLSpanElement;
|
this._durationTabs.select(0);
|
||||||
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;
|
|
||||||
|
|
||||||
this._days.onchange = this._checkDurationValidity;
|
this._days.onchange = this._checkDurationValidity;
|
||||||
this._months.onchange = this._checkDurationValidity;
|
this._months.onchange = this._checkDurationValidity;
|
||||||
|
|||||||
@@ -33,17 +33,21 @@ export interface QueryType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface SearchConfiguration {
|
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 };
|
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;
|
setVisibility: (items: string[], visible: boolean, appendedItems: boolean) => void;
|
||||||
onSearchCallback: (newItems: boolean, loadAll: boolean, callback?: (resp: paginatedDTO) => void) => void;
|
onSearchCallback: (newItems: boolean, loadAll: boolean, callback?: (resp: paginatedDTO) => void) => void;
|
||||||
searchServer: (params: PaginatedReqDTO, newSearch: boolean) => 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"));
|
if (this._c.sortingByButton) sortingBy = !(this._c.sortingByButton.classList.contains("hidden"));
|
||||||
const hasFilters = this._c.filterArea.textContent != "";
|
const hasFilters = this._c.filterArea.textContent != "";
|
||||||
if (sortingBy || hasFilters) {
|
if (sortingBy || hasFilters) {
|
||||||
this._c.searchOptionsHeader.classList.remove("hidden");
|
this._c.searchOptionsHeader?.classList.remove("hidden");
|
||||||
} else {
|
} else {
|
||||||
this._c.searchOptionsHeader.classList.add("hidden");
|
this._c.searchOptionsHeader?.classList.add("hidden");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -576,14 +580,14 @@ export class Search {
|
|||||||
|
|
||||||
setNotFoundPanelVisibility = (visible: boolean) => {
|
setNotFoundPanelVisibility = (visible: boolean) => {
|
||||||
if (this._inServerSearch || !this.inSearch) {
|
if (this._inServerSearch || !this.inSearch) {
|
||||||
this._c.notFoundLocallyText.classList.add("unfocused");
|
this._c.notFoundLocallyText?.classList.add("unfocused");
|
||||||
} else if (this.inSearch) {
|
} else if (this.inSearch) {
|
||||||
this._c.notFoundLocallyText.classList.remove("unfocused");
|
this._c.notFoundLocallyText?.classList.remove("unfocused");
|
||||||
}
|
}
|
||||||
if (visible) {
|
if (visible) {
|
||||||
this._c.notFoundPanel.classList.remove("unfocused");
|
this._c.notFoundPanel?.classList.remove("unfocused");
|
||||||
} else {
|
} else {
|
||||||
this._c.notFoundPanel.classList.add("unfocused");
|
this._c.notFoundPanel?.classList.add("unfocused");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ import {
|
|||||||
TransformComponent
|
TransformComponent
|
||||||
} from 'echarts/components';
|
} from 'echarts/components';
|
||||||
import { CanvasRenderer } from 'echarts/renderers';
|
import { CanvasRenderer } from 'echarts/renderers';
|
||||||
|
import { AccountsQueries } from "./accounts";
|
||||||
|
import { SimpleRadioTabs } from "./ui";
|
||||||
|
|
||||||
echarts.use([
|
echarts.use([
|
||||||
BarChart,
|
BarChart,
|
||||||
@@ -328,5 +330,32 @@ export class StatsPanel {
|
|||||||
|
|
||||||
this._cards = new Map<string, StatCard>();
|
this._cards = new Map<string, StatCard>();
|
||||||
this._order = [];
|
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) {
|
myGenericNumber.add = function (x, y) {
|
||||||
return 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