adapt typescript to strict mode

This commit is contained in:
imperosol
2026-07-13 12:03:38 +02:00
parent 448e67738e
commit ac8a79468e
40 changed files with 386 additions and 242 deletions
@@ -1,7 +1,7 @@
import type { TomOption } from "tom-select/dist/types/types";
import type { escape_html } from "tom-select/dist/types/utils";
import { AjaxSelect } from "#core:core/components/ajax-select-base.ts";
import { registerComponent } from "#core:utils/web-components.ts";
import type { TomOption } from "tom-select/src/types";
import type { escape_html } from "tom-select/src/utils";
import { AjaxSelect } from "#core:core/components/ajax-select-base";
import { registerComponent } from "#core:utils/web-components";
import {
type CounterSchema,
counterSearchCounter,
@@ -28,14 +28,18 @@ export class ProductAjaxSelect extends AjaxSelect {
return [];
}
private getName(item: SimpleProductSchema, sanitize: typeof escape_html): string {
return item.code ? `${sanitize(item.code)} - ${sanitize(item.name)}` : item.name;
}
protected renderOption(item: SimpleProductSchema, sanitize: typeof escape_html) {
return `<div class="select-item">
<span class="select-item-text">${sanitize(item.code)} - ${sanitize(item.name)}</span>
<span class="select-item-text">${this.getName(item, sanitize)}</span>
</div>`;
}
protected renderItem(item: SimpleProductSchema, sanitize: typeof escape_html) {
return `<span>${sanitize(item.code)} - ${sanitize(item.name)}</span>`;
return `<span>${this.getName(item, sanitize)}</span>`;
}
}
@@ -44,7 +48,7 @@ export class ProductTypeAjaxSelect extends AjaxSelect {
protected valueField = "id";
protected labelField = "name";
protected searchField = ["name"];
private productTypes = null as ProductTypeSchema[];
private productTypes = null as ProductTypeSchema[] | null;
protected async search(query: string): Promise<TomOption[]> {
// The production database has a grand total of 26 product types
@@ -52,7 +56,7 @@ export class ProductTypeAjaxSelect extends AjaxSelect {
// Thus, it's appropriate to fetch all product types during first use,
// then to reuse the result again and again.
if (this.productTypes === null) {
this.productTypes = (await producttypeFetchAll()).data || null;
this.productTypes = (await producttypeFetchAll()).data || [];
}
return this.productTypes.filter((t) =>
t.name.toLowerCase().includes(query.toLowerCase()),
@@ -59,7 +59,7 @@ export class CounterProductSelect extends AutoCompleteSelectBase {
return onOptionSelect.call(
this.widget,
evt,
this.widget.getOption(value, true),
this.widget.getOption(value, true) as HTMLElement,
);
},
);
@@ -70,12 +70,15 @@ export class CounterProductSelect extends AutoCompleteSelectBase {
...super.tomSelectSettings(),
openOnFocus: false,
// We make searching on exact code matching a higher priority
// We need to manually set weights or it results on an inconsistent
// We need to manually set weights, or it results on an inconsistent
// behavior between production and development environment
//
// SearchField can be an object array (according to the docs),
// but it is unproperly typed, so we must force-cast to trick TS
searchField: [
{ field: "code", weight: 2 },
{ field: "text", weight: 0.5 },
],
] as unknown as string[],
};
}
}
@@ -1,9 +1,14 @@
import { showSaveFilePicker } from "native-file-system-adapter";
import type TomSelect from "tom-select";
import type { ClubAjaxSelect } from "#club:club/components/ajax-select-index";
import type { NestedKeyOf } from "#core:types/nested-key";
import { paginated } from "#core:utils/api";
import { type PaginatedRequest, paginated } from "#core:utils/api";
import { csv } from "#core:utils/csv";
import { getCurrentUrlParams, History, updateQueryString } from "#core:utils/history";
import type {
CounterAjaxSelect,
ProductTypeAjaxSelect,
} from "#counter:counter/components/ajax-select-index";
import {
type ProductSchema,
type ProductSearchProductsDetailedData,
@@ -51,6 +56,8 @@ const csvColumnTitles = [
gettext("archived"),
];
type ProductStatus = "active" | "archived" | "both";
document.addEventListener("alpine:init", () => {
Alpine.data("productList", () => ({
loading: false,
@@ -59,8 +66,7 @@ document.addEventListener("alpine:init", () => {
/** Total number of elements corresponding to the current query. */
nbPages: 0,
productStatus: "" as "active" | "archived" | "both",
productStatus: "" as ProductStatus,
search: "",
productTypes: [] as string[],
clubs: [] as string[],
@@ -71,16 +77,18 @@ document.addEventListener("alpine:init", () => {
async init() {
const url = getCurrentUrlParams();
this.search = url.get("search") || "";
this.productStatus = url.get("productStatus") ?? "active";
const productTypesWidget = this.$refs.productTypesInput.widget as TomSelect;
this.productStatus = (url.get("productStatus") ?? "active") as ProductStatus;
const productTypesWidget = (this.$refs.productTypesInput as ProductTypeAjaxSelect)
.widget as TomSelect;
productTypesWidget.on("change", (items: string[]) => {
this.productTypes = [...items];
});
const clubsWidget = this.$refs.clubsInput.widget as TomSelect;
const clubsWidget = (this.$refs.clubsInput as ClubAjaxSelect).widget as TomSelect;
clubsWidget.on("change", (items: string[]) => {
this.clubs = [...items];
});
const countersWidget = this.$refs.countersInput.widget as TomSelect;
const countersWidget = (this.$refs.countersInput as CounterAjaxSelect)
.widget as TomSelect;
countersWidget.on("change", (items: string[]) => {
this.counters = [...items];
});
@@ -127,9 +135,9 @@ document.addEventListener("alpine:init", () => {
// biome-ignore lint/style/useNamingConvention: api is in snake_case
is_archived: isArchived,
// biome-ignore lint/style/useNamingConvention: api is in snake_case
product_type: [...this.productTypes],
club: [...this.clubs],
counter: [...this.counters],
product_type: [...this.productTypes.map(Number.parseInt)],
club: [...this.clubs.map(Number.parseInt)],
counter: [...this.counters.map(Number.parseInt)],
},
};
},
@@ -141,6 +149,10 @@ document.addEventListener("alpine:init", () => {
this.loading = true;
const options = this.getQueryParams();
const resp = await productSearchProductsDetailed(options);
if (resp.data === undefined) {
console.error("Product search request failed");
return;
}
this.nbPages = Math.ceil(resp.data.count / defaultPageSize);
this.products = resp.data.results.reduce<GroupedProducts>(
(acc: GroupedProducts, curr: ProductSchema) => {
@@ -172,7 +184,10 @@ document.addEventListener("alpine:init", () => {
// If not, fetch them.
const products: ProductSchema[] =
this.nbPages > 1
? await paginated(productSearchProductsDetailed, this.getQueryParams())
? await paginated(
productSearchProductsDetailed,
this.getQueryParams() as PaginatedRequest,
)
: Object.values<ProductSchema[]>(this.products).flat();
// CSV cannot represent nested data
// so we create a row for each price of each product.
@@ -1,5 +1,5 @@
import Alpine from "alpinejs";
import { AlertMessage } from "#core:utils/alert-message.ts";
import { AlertMessage } from "#core:utils/alert-message";
import { producttypeReorder } from "#openapi";
document.addEventListener("alpine:init", () => {
@@ -22,7 +22,7 @@ document.addEventListener("alpine:init", () => {
const productTypes = this.$refs.productTypes
.childNodes as NodeListOf<HTMLLIElement>;
const getId = (elem: HTMLLIElement) =>
Number.parseInt(elem.getAttribute("x-sort:item"), 10);
Number.parseInt(elem.getAttribute("x-sort:item") as string, 10);
const query =
newPosition === 0
? { above: getId(productTypes.item(1)) }
@@ -32,6 +32,10 @@ document.addEventListener("alpine:init", () => {
path: { type_id: itemId },
query: query,
});
if (response.response === undefined) {
console.error("Product type reordering request failed");
return;
}
this.openAlertMessage(response.response);
this.loading = false;
},