diff --git a/webapp/src/components/common/fieldEditors/ListFE/index.tsx b/webapp/src/components/common/fieldEditors/ListFE/index.tsx
index d72fa71c81..29f3518e26 100644
--- a/webapp/src/components/common/fieldEditors/ListFE/index.tsx
+++ b/webapp/src/components/common/fieldEditors/ListFE/index.tsx
@@ -42,9 +42,9 @@ import {
import { makeLabel, makeListItems } from "./utils";
interface ListFEProps
{
- defaultValue?: readonly TItem[];
- value?: readonly TItem[];
- options: readonly TOption[];
+ defaultValue?: TItem[];
+ value?: TItem[];
+ options?: TOption[];
label?: string;
getOptionLabel?: (option: TOption) => string;
getValueLabel?: (value: TItem) => string;
@@ -64,7 +64,7 @@ function ListFE(props: ListFEProps) {
value,
defaultValue,
label,
- options,
+ options = [],
getOptionLabel = makeLabel,
getValueLabel = makeLabel,
optionToItem = (option: TOption) => option as unknown as TItem,
diff --git a/webapp/src/hoc/reactHookFormSupport.tsx b/webapp/src/hoc/reactHookFormSupport.tsx
index 282ce5358a..a0aec8f2b2 100644
--- a/webapp/src/hoc/reactHookFormSupport.tsx
+++ b/webapp/src/hoc/reactHookFormSupport.tsx
@@ -67,19 +67,36 @@ export type ReactHookFormSupportProps<
shouldUnregister?: never;
};
+/**
+ * Provides React Hook Form support to a field editor component, enhancing it with form control and validation capabilities.
+ * It integrates custom validation logic, value transformation, and handles form submission state.
+ *
+ * @param options - Configuration options for the hook support.
+ * @param options.preValidate - A function that pre-validates the value before the main validation.
+ * @param options.setValueAs - A function that transforms the value before setting it into the form.
+ * @returns A function that takes a field editor component and returns a new component wrapped with React Hook Form functionality.
+ */
function reactHookFormSupport(
options: ReactHookFormSupport = {},
) {
const { preValidate, setValueAs = R.identity } = options;
/**
- * Wrap in a higher component the specified field editor component
+ * Wraps the provided field editor component with React Hook Form functionality,
+ * applying the specified pre-validation and value transformation logic.
+ *
+ * @param FieldEditor - The field editor component to wrap.
+ * @returns The wrapped component with added React Hook Form support.
*/
function wrapWithReactHookFormSupport<
TProps extends FieldEditorProps,
>(FieldEditor: React.ComponentType) {
/**
- * The wrapper component
+ * The wrapper component that integrates React Hook Form capabilities with the original field editor.
+ * It manages form control registration, handles value changes and blurring with custom logic, and displays validation errors.
+ *
+ * @param props - The props of the field editor, extended with React Hook Form and custom options.
+ * @returns The field editor component wrapped with React Hook Form functionality.
*/
function ReactHookFormSupport<
TFieldValues extends FieldValues = FieldValues,
diff --git a/webapp/src/hooks/useMemoLocked.ts b/webapp/src/hooks/useMemoLocked.ts
index 6af425bbc6..b6e03535f9 100644
--- a/webapp/src/hooks/useMemoLocked.ts
+++ b/webapp/src/hooks/useMemoLocked.ts
@@ -10,6 +10,7 @@ import { useState } from "react";
*/
function useMemoLocked(factory: () => T): T {
+ // eslint-disable-next-line react/hook-use-state
const [state] = useState(factory);
return state;
}
diff --git a/webapp/src/hooks/useNavigateOnCondition.ts b/webapp/src/hooks/useNavigateOnCondition.ts
index 6930e1ecf6..036ce0e72f 100644
--- a/webapp/src/hooks/useNavigateOnCondition.ts
+++ b/webapp/src/hooks/useNavigateOnCondition.ts
@@ -23,18 +23,17 @@ interface UseNavigateOnConditionOptions {
}
/**
- * A React hook for conditional navigation using react-router-dom.
+ * A React hook for conditional navigation using react-router-dom. This hook allows for navigating to a different route
+ * based on custom logic encapsulated in a `shouldNavigate` function. It observes specified dependencies and triggers navigation
+ * when they change if the conditions defined in `shouldNavigate` are met.
*
- * @function
- * @name useNavigateOnCondition
- *
- * @param {Object} options - Configuration options for the hook.
- * @param {DependencyList} options.deps - An array of dependencies that the effect will observe.
- * @param {To} options.to - The target location to navigate to, it could be a route as a string or a relative numeric location.
- * @param {function} [options.shouldNavigate] - An optional function that returns a boolean to determine whether navigation should take place.
+ * @param options - Configuration options for the hook.
+ * @param options.deps - An array of dependencies that the effect will observe.
+ * @param options.to - The target location to navigate to, which can be a route as a string or a relative numeric location.
+ * @param options.shouldNavigate - An optional function that returns a boolean to determine whether navigation should take place. Defaults to a function that always returns true.
*
* @example
- * - Basic usage
+ * Basic usage
* useNavigateOnCondition({
* deps: [someDependency],
* to: '/some-route',
diff --git a/webapp/src/services/api/constants.ts b/webapp/src/services/api/constants.ts
deleted file mode 100644
index 421ab814ed..0000000000
--- a/webapp/src/services/api/constants.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-const API_URL_BASE = "v1";
-const STUDIES_API_URL = `${API_URL_BASE}/studies/{studyId}`;
-
-export const TABLE_MODE_API_URL = `${STUDIES_API_URL}/tablemode`;
diff --git a/webapp/src/services/api/studies/raw/index.ts b/webapp/src/services/api/studies/raw/index.ts
new file mode 100644
index 0000000000..63e46dc31a
--- /dev/null
+++ b/webapp/src/services/api/studies/raw/index.ts
@@ -0,0 +1,13 @@
+import client from "../../client";
+import type { DownloadMatrixParams } from "./types";
+
+export async function downloadMatrix(params: DownloadMatrixParams) {
+ const { studyId, ...rest } = params;
+ const url = `v1/studies/${studyId}/raw/download`;
+ const res = await client.get(url, {
+ params: rest,
+ responseType: "blob",
+ });
+
+ return res.data;
+}
diff --git a/webapp/src/services/api/studies/raw/types.ts b/webapp/src/services/api/studies/raw/types.ts
new file mode 100644
index 0000000000..e524fbdc72
--- /dev/null
+++ b/webapp/src/services/api/studies/raw/types.ts
@@ -0,0 +1,9 @@
+import type { StudyMetadata } from "../../../../common/types";
+
+export interface DownloadMatrixParams {
+ studyId: StudyMetadata["id"];
+ path: string;
+ format?: "tsv" | "xlsx";
+ header?: boolean;
+ index?: boolean;
+}
diff --git a/webapp/src/services/api/studies/tableMode/constants.ts b/webapp/src/services/api/studies/tableMode/constants.ts
index 70526c7484..acef3d51f6 100644
--- a/webapp/src/services/api/studies/tableMode/constants.ts
+++ b/webapp/src/services/api/studies/tableMode/constants.ts
@@ -1,20 +1,30 @@
-const AREA = "area";
-const LINK = "link";
-const CLUSTER = "cluster";
-const RENEWABLE = "renewable";
-const BINDING_CONSTRAINT = "binding constraint";
+const AREA = "areas";
+const LINK = "links";
+const THERMAL = "thermals";
+const RENEWABLE = "renewables";
+const ST_STORAGE = "st-storages";
+const BINDING_CONSTRAINT = "binding-constraints";
export const TABLE_MODE_TYPES = [
AREA,
LINK,
- CLUSTER,
+ THERMAL,
RENEWABLE,
+ ST_STORAGE,
BINDING_CONSTRAINT,
] as const;
+// Deprecated types (breaking change from v2.16.8)
+export const TABLE_MODE_TYPES_ALIASES = {
+ area: AREA,
+ link: LINK,
+ cluster: THERMAL,
+ renewable: RENEWABLE,
+ "binding constraint": BINDING_CONSTRAINT,
+};
+
export const TABLE_MODE_COLUMNS_BY_TYPE = {
[AREA]: [
- // Optimization - Nodal optimization
"nonDispatchablePower",
"dispatchableHydroPower",
"otherDispatchablePower",
@@ -22,10 +32,9 @@ export const TABLE_MODE_COLUMNS_BY_TYPE = {
"spreadUnsuppliedEnergyCost",
"averageSpilledEnergyCost",
"spreadSpilledEnergyCost",
- // Optimization - Filtering
"filterSynthesis",
"filterYearByYear",
- // Adequacy patch
+ // Since v8.3
"adequacyPatchMode",
],
[LINK]: [
@@ -36,38 +45,79 @@ export const TABLE_MODE_COLUMNS_BY_TYPE = {
"assetType",
"linkStyle",
"linkWidth",
+ "comments",
"displayComments",
"filterSynthesis",
"filterYearByYear",
],
- [CLUSTER]: [
+ [THERMAL]: [
"group",
"enabled",
- "mustRun",
"unitCount",
"nominalCapacity",
+ "genTs",
"minStablePower",
- "spinning",
"minUpTime",
"minDownTime",
- "co2",
- "marginalCost",
- "fixedCost",
- "startupCost",
- "marketBidCost",
- "spreadCost",
- "tsGen",
+ "mustRun",
+ "spinning",
"volatilityForced",
"volatilityPlanned",
"lawForced",
"lawPlanned",
+ "marginalCost",
+ "spreadCost",
+ "fixedCost",
+ "startupCost",
+ "marketBidCost",
+ "co2",
+ // Since v8.6
+ "nh3",
+ "so2",
+ "nox",
+ "pm25",
+ "pm5",
+ "pm10",
+ "nmvoc",
+ "op1",
+ "op2",
+ "op3",
+ "op4",
+ "op5",
+ // Since v8.7
+ "costGeneration",
+ "efficiency",
+ "variableOMCost",
],
[RENEWABLE]: [
+ // Since v8.1
"group",
- "tsInterpretation",
"enabled",
+ "tsInterpretation",
"unitCount",
"nominalCapacity",
],
- [BINDING_CONSTRAINT]: ["type", "operator", "enabled"],
+ [ST_STORAGE]: [
+ // Since v8.6
+ "group",
+ "injectionNominalCapacity",
+ "withdrawalNominalCapacity",
+ "reservoirCapacity",
+ "efficiency",
+ "initialLevel",
+ "initialLevelOptim",
+ // Since v8.8
+ "enabled",
+ ],
+ [BINDING_CONSTRAINT]: [
+ "enabled",
+ "timeStep",
+ "operator",
+ "comments",
+ // Since v8.3
+ "filterSynthesis",
+ "filterYearByYear",
+ // Since v8.7
+ "group",
+ ],
} as const;
diff --git a/webapp/src/services/api/studies/tableMode/index.ts b/webapp/src/services/api/studies/tableMode/index.ts
index c6c4db5b80..03915b259e 100644
--- a/webapp/src/services/api/studies/tableMode/index.ts
+++ b/webapp/src/services/api/studies/tableMode/index.ts
@@ -1,35 +1,29 @@
-import { DeepPartial } from "react-hook-form";
-import { StudyMetadata } from "../../../../common/types";
import client from "../../client";
import { format } from "../../../../utils/stringUtils";
-import { TABLE_MODE_API_URL } from "../../constants";
-import type { TableData, TableModeColumnsForType, TableModeType } from "./type";
-import { toColumnApiName } from "./utils";
+import type {
+ GetTableModeParams,
+ SetTableModeParams,
+ TableData,
+ TableModeType,
+} from "./types";
+
+const TABLE_MODE_API_URL = `v1/studies/{studyId}/table-mode/{tableType}`;
export async function getTableMode(
- studyId: StudyMetadata["id"],
- type: T,
- columns: TableModeColumnsForType,
-): Promise {
- const url = format(TABLE_MODE_API_URL, { studyId });
- const res = await client.get(url, {
- params: {
- table_type: type,
- columns: columns.map(toColumnApiName).join(","),
- },
+ params: GetTableModeParams,
+) {
+ const { studyId, tableType, columns } = params;
+ const url = format(TABLE_MODE_API_URL, { studyId, tableType });
+
+ const res = await client.get(url, {
+ params: columns.length > 0 ? { columns: columns.join(",") } : {},
});
+
return res.data;
}
-export function setTableMode(
- studyId: StudyMetadata["id"],
- type: TableModeType,
- data: DeepPartial,
-): Promise {
- const url = format(TABLE_MODE_API_URL, { studyId });
- return client.put(url, data, {
- params: {
- table_type: type,
- },
- });
+export async function setTableMode(params: SetTableModeParams) {
+ const { studyId, tableType, data } = params;
+ const url = format(TABLE_MODE_API_URL, { studyId, tableType });
+ await client.put(url, data);
}
diff --git a/webapp/src/services/api/studies/tableMode/type.ts b/webapp/src/services/api/studies/tableMode/type.ts
deleted file mode 100644
index 71b751d875..0000000000
--- a/webapp/src/services/api/studies/tableMode/type.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import { TABLE_MODE_COLUMNS_BY_TYPE, TABLE_MODE_TYPES } from "./constants";
-
-export type TableModeType = (typeof TABLE_MODE_TYPES)[number];
-
-export type TableModeColumnsForType = Array<
- (typeof TABLE_MODE_COLUMNS_BY_TYPE)[T][number]
->;
-
-export type TableData = Record<
- string,
- Record
->;
diff --git a/webapp/src/services/api/studies/tableMode/types.ts b/webapp/src/services/api/studies/tableMode/types.ts
new file mode 100644
index 0000000000..e20a167e27
--- /dev/null
+++ b/webapp/src/services/api/studies/tableMode/types.ts
@@ -0,0 +1,26 @@
+import { DeepPartial } from "react-hook-form";
+import type { StudyMetadata } from "../../../../common/types";
+import { TABLE_MODE_COLUMNS_BY_TYPE, TABLE_MODE_TYPES } from "./constants";
+
+export type TableModeType = (typeof TABLE_MODE_TYPES)[number];
+
+export type TableModeColumnsForType = Array<
+ (typeof TABLE_MODE_COLUMNS_BY_TYPE)[T][number]
+>;
+
+export type TableData = Record<
+ string,
+ Record
+>;
+
+export interface GetTableModeParams {
+ studyId: StudyMetadata["id"];
+ tableType: T;
+ columns: TableModeColumnsForType;
+}
+
+export interface SetTableModeParams {
+ studyId: StudyMetadata["id"];
+ tableType: TableModeType;
+ data: DeepPartial;
+}
diff --git a/webapp/src/services/api/studies/tableMode/utils.ts b/webapp/src/services/api/studies/tableMode/utils.ts
deleted file mode 100644
index e856785502..0000000000
--- a/webapp/src/services/api/studies/tableMode/utils.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import { snakeCase } from "lodash";
-import { TableModeColumnsForType, TableModeType } from "./type";
-
-export function toColumnApiName(
- column: TableModeColumnsForType[number],
-) {
- if (column === "co2") {
- return "co2";
- }
- return snakeCase(column);
-}
diff --git a/webapp/src/services/api/studydata.ts b/webapp/src/services/api/studydata.ts
index 561d8197fe..a39b55f20b 100644
--- a/webapp/src/services/api/studydata.ts
+++ b/webapp/src/services/api/studydata.ts
@@ -1,15 +1,12 @@
import {
- AllClustersAndLinks,
LinkCreationInfoDTO,
LinkInfoWithUI,
UpdateAreaUi,
} from "../../common/types";
-import { CreateBindingConstraint } from "../../components/App/Singlestudy/Commands/Edition/commandTypes";
import {
- BindingConstFields,
- BindingConstFieldsDTO,
- ConstraintType,
- UpdateBindingConstraint,
+ BindingConstraint,
+ ConstraintTerm,
+ bindingConstraintModelAdapter,
} from "../../components/App/Singlestudy/explore/Modelization/BindingConstraints/BindingConstView/utils";
import { StudyMapNode } from "../../redux/ducks/studyMaps";
import client from "./client";
@@ -18,7 +15,7 @@ export const createArea = async (
uuid: string,
name: string,
): Promise => {
- const res = await client.post(`/v1/studies/${uuid}/areas?uuid=${uuid}`, {
+ const res = await client.post(`/v1/studies/${uuid}/areas`, {
name,
type: "AREA",
});
@@ -29,10 +26,7 @@ export const createLink = async (
uuid: string,
linkCreationInfo: LinkCreationInfoDTO,
): Promise => {
- const res = await client.post(
- `/v1/studies/${uuid}/links?uuid=${uuid}`,
- linkCreationInfo,
- );
+ const res = await client.post(`/v1/studies/${uuid}/links`, linkCreationInfo);
return res.data;
};
@@ -43,7 +37,7 @@ export const updateAreaUI = async (
areaUi: UpdateAreaUi,
): Promise => {
const res = await client.put(
- `/v1/studies/${uuid}/areas/${areaId}/ui?uuid=${uuid}&area_id=${areaId}&layer=${layerId}`,
+ `/v1/studies/${uuid}/areas/${areaId}/ui?layer=${layerId}`,
areaUi,
);
return res.data;
@@ -53,9 +47,7 @@ export const deleteArea = async (
uuid: string,
areaId: string,
): Promise => {
- const res = await client.delete(
- `/v1/studies/${uuid}/areas/${areaId}?uuid=${uuid}&area_id=${areaId}`,
- );
+ const res = await client.delete(`/v1/studies/${uuid}/areas/${areaId}`);
return res.data;
};
@@ -65,89 +57,92 @@ export const deleteLink = async (
areaIdTo: string,
): Promise => {
const res = await client.delete(
- `/v1/studies/${uuid}/links/${areaIdFrom}/${areaIdTo}?uuid=${uuid}&area_from=${areaIdFrom}&area_to=${areaIdTo}`,
+ `/v1/studies/${uuid}/links/${areaIdFrom}/${areaIdTo}`,
);
return res.data;
};
export const updateConstraintTerm = async (
- uuid: string,
- bindingConst: string,
- constraint: Partial,
+ studyId: string,
+ constraintId: string,
+ term: Partial,
): Promise => {
const res = await client.put(
- `/v1/studies/${uuid}/bindingconstraints/${encodeURIComponent(
- bindingConst,
+ `/v1/studies/${studyId}/bindingconstraints/${encodeURIComponent(
+ constraintId,
)}/term`,
- constraint,
+ term,
);
return res.data;
};
-export const addConstraintTerm = async (
- uuid: string,
- bindingConst: string,
- constraint: ConstraintType,
-): Promise => {
+export const createConstraintTerm = async (
+ studyId: string,
+ constraintId: string,
+ term: ConstraintTerm,
+): Promise => {
const res = await client.post(
- `/v1/studies/${uuid}/bindingconstraints/${encodeURIComponent(
- bindingConst,
+ `/v1/studies/${studyId}/bindingconstraints/${encodeURIComponent(
+ constraintId,
)}/term`,
- constraint,
+ term,
);
return res.data;
};
export const deleteConstraintTerm = async (
- uuid: string,
- bindingConst: string,
- termId: ConstraintType["id"],
+ studyId: string,
+ constraintId: string,
+ termId: ConstraintTerm["id"],
): Promise => {
const res = await client.delete(
- `/v1/studies/${uuid}/bindingconstraints/${encodeURIComponent(
- bindingConst,
+ `/v1/studies/${studyId}/bindingconstraints/${encodeURIComponent(
+ constraintId,
)}/term/${encodeURIComponent(termId)}`,
);
return res.data;
};
export const getBindingConstraint = async (
- uuid: string,
- bindingConst: string,
-): Promise => {
+ studyId: string,
+ constraintId: string,
+): Promise => {
const res = await client.get(
- `/v1/studies/${uuid}/bindingconstraints/${encodeURIComponent(
- bindingConst,
+ `/v1/studies/${studyId}/bindingconstraints/${encodeURIComponent(
+ constraintId,
)}`,
);
- return res.data;
+
+ return bindingConstraintModelAdapter(res.data);
};
export const getBindingConstraintList = async (
- uuid: string,
-): Promise => {
- const res = await client.get(`/v1/studies/${uuid}/bindingconstraints`);
+ studyId: string,
+): Promise => {
+ const res = await client.get(`/v1/studies/${studyId}/bindingconstraints`);
return res.data;
};
export const updateBindingConstraint = async (
- uuid: string,
- bindingConst: string,
- data: UpdateBindingConstraint,
+ studyId: string,
+ constraintId: string,
+ data: Omit,
): Promise => {
+ const adaptedData = bindingConstraintModelAdapter(data as BindingConstraint); // TODO fix type
+
const res = await client.put(
- `/v1/studies/${uuid}/bindingconstraints/${encodeURIComponent(
- bindingConst,
+ `/v1/studies/${studyId}/bindingconstraints/${encodeURIComponent(
+ constraintId,
)}`,
- data,
+ adaptedData,
);
return res.data;
};
export const createBindingConstraint = async (
studyId: string,
- data: CreateBindingConstraint,
-): Promise => {
+ data: Partial,
+): Promise => {
const res = await client.post(
`/v1/studies/${studyId}/bindingconstraints`,
data,
@@ -155,13 +150,6 @@ export const createBindingConstraint = async (
return res.data;
};
-export const getClustersAndLinks = async (
- uuid: string,
-): Promise => {
- const res = await client.get(`/v1/studies/${uuid}/linksandclusters`);
- return res.data;
-};
-
interface GetAllLinksParams {
uuid: string;
withUi?: boolean;
@@ -175,10 +163,7 @@ export const getAllLinks = async (
params: T,
): Promise>> => {
const { uuid, withUi } = params;
- const res = await client.get(
- `/v1/studies/${uuid}/links${withUi ? `?with_ui=${withUi}` : ""}`,
- );
+ const withUiStr = withUi ? "with_ui=true" : "";
+ const res = await client.get(`/v1/studies/${uuid}/links?${withUiStr}`);
return res.data;
};
-
-export default {};
diff --git a/webapp/src/services/utils/index.ts b/webapp/src/services/utils/index.ts
index 0386ebef79..f7980ee2bd 100644
--- a/webapp/src/services/utils/index.ts
+++ b/webapp/src/services/utils/index.ts
@@ -155,7 +155,7 @@ export const exportText = (fileData: string, filename: string): void => {
*
* Ex: '820' -> '8.2'
*
- * @param v Version in format '[major][minor]0' (ex: '820').
+ * @param v - Version in format '[major][minor]0' (ex: '820').
* @returns Version in format '[major].[minor]' (ex: '8.2').
*/
export const displayVersionName = (v: string): string => `${v[0]}.${v[1]}`;
@@ -248,7 +248,12 @@ export const sortByName = (list: T[]): T[] => {
};
/**
- * @deprecated This function is deprecated. Please use nameToId instead.
+ * Converts a name string to an ID format.
+ *
+ * @deprecated Please use `nameToId` instead.
+ *
+ * @param name - The string to transform.
+ * @returns The transformed ID string.
*/
export const transformNameToId = (name: string): string => {
let duppl = false;
@@ -290,7 +295,8 @@ export const transformNameToId = (name: string): string => {
* Converts a name string to a valid ID string.
* Replacing any characters that are not alphanumeric or -_,()& with a space,
* trimming the resulting string, and converting it to lowercase.
- * @param name The name string to convert to an ID.
+ *
+ * @param name - The name string to convert to an ID.
* @returns The resulting ID string.
*/
export const nameToId = (name: string): string => {
diff --git a/webapp/src/services/utils/localStorage.ts b/webapp/src/services/utils/localStorage.ts
index fa5c084c56..faa230e17a 100644
--- a/webapp/src/services/utils/localStorage.ts
+++ b/webapp/src/services/utils/localStorage.ts
@@ -4,6 +4,7 @@ import { UserInfo } from "../../common/types";
import { TableTemplate } from "../../components/App/Singlestudy/explore/TableModeList/utils";
import { StudiesSortConf, StudiesState } from "../../redux/ducks/studies";
import { UIState } from "../../redux/ducks/ui";
+import { TABLE_MODE_TYPES_ALIASES } from "../api/studies/tableMode/constants";
export enum StorageKey {
Version = "version",
@@ -46,7 +47,18 @@ function getItem(key: T): TypeFromKey[T] | null {
if (serializedState === null) {
return null;
}
- return JSON.parse(serializedState);
+ const res = JSON.parse(serializedState);
+
+ // Convert deprecated types to new ones (breaking change from v2.16.8)
+ if (key === StorageKey.StudiesModelTableModeTemplates) {
+ return res.map((template: Record) => ({
+ ...template,
+ // @ts-expect-error To ignore error TS2551
+ type: TABLE_MODE_TYPES_ALIASES[template.type] ?? template.type,
+ }));
+ }
+
+ return res;
} catch (err) {
return null;
}
diff --git a/webapp/src/utils/fileUtils.ts b/webapp/src/utils/fileUtils.ts
new file mode 100644
index 0000000000..0233d6f984
--- /dev/null
+++ b/webapp/src/utils/fileUtils.ts
@@ -0,0 +1,13 @@
+/**
+ * Triggers the download of a file with the given data and name.
+ *
+ * @param fileData - The data of the file to be downloaded.
+ * @param fileName - The name of the file to be downloaded.
+ */
+export function downloadFile(fileData: BlobPart, fileName: string) {
+ const link = document.createElement("a");
+ link.href = URL.createObjectURL(new Blob([fileData]));
+ link.download = fileName;
+ link.click();
+ URL.revokeObjectURL(link.href);
+}
diff --git a/webapp/src/utils/fnUtils.ts b/webapp/src/utils/fnUtils.ts
index 155078d711..dd32ac4c97 100644
--- a/webapp/src/utils/fnUtils.ts
+++ b/webapp/src/utils/fnUtils.ts
@@ -1,8 +1,15 @@
/**
- * Use it instead of disabling ESLint rule.
+ * A utility function designed to be used as a placeholder or stub. It can be used in situations where you might
+ * otherwise be tempted to disable an ESLint rule temporarily, such as when you need to pass a function that
+ * does nothing (for example, as a default prop in React components or as a no-operation callback).
+ *
+ * By using this function, you maintain code cleanliness and intention clarity without directly suppressing
+ * linting rules.
+ *
+ * @param args - Accepts any number of arguments of any type, but does nothing with them.
*/
export function voidFn(...args: TArgs) {
- // Do nothing
+ // Intentionally empty, as its purpose is to do nothing.
}
/**
diff --git a/webapp/src/utils/matrixUtils.ts b/webapp/src/utils/matrixUtils.ts
deleted file mode 100644
index c4380f8bcb..0000000000
--- a/webapp/src/utils/matrixUtils.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import { MatrixType } from "../common/types";
-
-export function downloadMatrix(matrixData: MatrixType, fileName: string): void {
- const fileData = matrixData.data.map((row) => row.join("\t")).join("\n");
- const blob = new Blob([fileData], { type: "text/plain" });
- const a = document.createElement("a");
- a.download = fileName;
- a.href = URL.createObjectURL(blob);
- a.click();
- URL.revokeObjectURL(a.href);
-}
diff --git a/webapp/src/utils/stringUtils.ts b/webapp/src/utils/stringUtils.ts
index 1b83ea6b6d..823829fb29 100644
--- a/webapp/src/utils/stringUtils.ts
+++ b/webapp/src/utils/stringUtils.ts
@@ -11,9 +11,14 @@ export const isSearchMatching = R.curry(
);
/**
- * Formats a string with values.
+ * Formats a string by replacing placeholders with specified values.
+ *
+ * @param str - The string containing placeholders in the format `{placeholder}`.
+ * @param values - An object mapping placeholders to their replacement values.
+ * @returns The formatted string with all placeholders replaced by their corresponding values.
+ *
* @example
- * format("Hello {name}", { name: "John" }); // returns "Hello John"
+ * format("Hello {name}", { name: "John" }); // Returns: "Hello John"
*/
export function format(str: string, values: Record): string {
return str.replace(/{([a-zA-Z0-9]+)}/g, (_, key) => values[key]);
diff --git a/webapp/src/utils/studiesUtils.ts b/webapp/src/utils/studiesUtils.ts
index 1fa14e7698..4072e09c51 100644
--- a/webapp/src/utils/studiesUtils.ts
+++ b/webapp/src/utils/studiesUtils.ts
@@ -64,7 +64,9 @@ const tagsPredicate = R.curry(
if (!study.tags || study.tags.length === 0) {
return false;
}
- return R.intersection(study.tags, tags).length > 0;
+ const upperCaseTags = tags.map((tag) => tag.toUpperCase());
+ const upperCaseStudyTags = study.tags.map((tag) => tag.toUpperCase());
+ return R.intersection(upperCaseStudyTags, upperCaseTags).length > 0;
},
);