Skip to content

Commit

Permalink
feat(common-ui,clusters-ui): implement optimistic row creation
Browse files Browse the repository at this point in the history
* fix TS mistakes
* update i18n
  • Loading branch information
skamril committed Mar 24, 2024
1 parent 689f737 commit dc55ad7
Show file tree
Hide file tree
Showing 14 changed files with 199 additions and 145 deletions.
3 changes: 2 additions & 1 deletion webapp/public/locales/en/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
"global.error.failedtoretrievejobs": "Failed to retrieve job information",
"global.error.failedtoretrievelogs": "Failed to retrieve job logs",
"global.error.failedtoretrievedownloads": "Failed to retrieve downloads list",
"global.error.createFailed": "Unable to create",
"global.error.deleteFailed": "Unable to delete",
"global.area.add": "Add an area",
"login.error": "Failed to authenticate",
Expand Down Expand Up @@ -115,7 +116,7 @@
"form.submit.inProgress": "The form is being submitted. Are you sure you want to leave the page?",
"form.asyncDefaultValues.error": "Failed to get values",
"form.field.required": "Field required",
"form.field.duplicate": "Value already exists: {{0}}",
"form.field.duplicate": "Value already exists",
"form.field.minLength": "{{0}} character(s) minimum",
"form.field.minValue": "The minimum value is {{0}}",
"form.field.maxValue": "The maximum value is {{0}}",
Expand Down
3 changes: 2 additions & 1 deletion webapp/public/locales/fr/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
"global.error.failedtoretrievejobs": "Échec de la récupération des tâches",
"global.error.failedtoretrievelogs": "Échec de la récupération des logs",
"global.error.failedtoretrievedownloads": "Échec de la récupération des exports",
"global.error.createFailed": "Impossible d'effectuer la création",
"global.error.deleteFailed": "Impossible d'effectuer la suppression",
"global.area.add": "Ajouter une zone",
"login.error": "Échec de l'authentification",
Expand Down Expand Up @@ -115,7 +116,7 @@
"form.submit.inProgress": "Le formulaire est en cours de soumission. Etes-vous sûr de vouloir quitter la page ?",
"form.asyncDefaultValues.error": "Impossible d'obtenir les valeurs",
"form.field.required": "Champ requis",
"form.field.duplicate": "Cette valeur existe déjà: {{0}}",
"form.field.duplicate": "Cette valeur existe déjà",
"form.field.minLength": "{{0}} caractère(s) minimum",
"form.field.minValue": "La valeur minimum est {{0}}",
"form.field.maxValue": "La valeur maximum est {{0}}",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
RENEWABLE_GROUPS,
RenewableCluster,
RenewableClusterWithCapacity,
RenewableGroup,
createRenewableCluster,
deleteRenewableClusters,
getRenewableClusters,
Expand All @@ -16,9 +17,11 @@ import useAppSelector from "../../../../../../../redux/hooks/useAppSelector";
import { getCurrentAreaId } from "../../../../../../../redux/selectors";
import GroupedDataTable from "../../../../../../common/GroupedDataTable";
import {
addCapacity,
capacityAggregationFn,
useClusterDataWithCapacity,
} from "../common/utils";
import { TRow } from "../../../../../../common/GroupedDataTable/types";

function Renewables() {
const { study } = useOutletContext<{ study: StudyMetadata }>();
Expand Down Expand Up @@ -86,8 +89,8 @@ function Renewables() {
),
Cell: ({ row }) => (
<>
{Math.floor(row.original.enabledCapacity ?? 0)} /{" "}
{Math.floor(row.original.installedCapacity ?? 0)}
{Math.floor(row.original.enabledCapacity)} /{" "}
{Math.floor(row.original.installedCapacity)}
</>
),
Footer: () => (
Expand All @@ -105,13 +108,9 @@ function Renewables() {
// Event handlers
////////////////////////////////////////////////////////////////

const handleCreateRow = ({
id,
installedCapacity,
enabledCapacity,
...cluster
}: RenewableClusterWithCapacity) => {
return createRenewableCluster(study.id, areaId, cluster);
const handleCreate = async (values: TRow<RenewableGroup>) => {
const cluster = await createRenewableCluster(study.id, areaId, values);
return addCapacity(cluster);
};

const handleDelete = (rows: RenewableClusterWithCapacity[]) => {
Expand All @@ -132,8 +131,8 @@ function Renewables() {
isLoading={isLoading}
data={clustersWithCapacity}
columns={columns}
groups={RENEWABLE_GROUPS}
onCreate={handleCreateRow}
groups={[...RENEWABLE_GROUPS]}
onCreate={handleCreate}
onDelete={handleDelete}
onNameClick={handleNameClick}
deleteConfirmationMessage={(count) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
StudyMetadata,
} from "../../../../../../../common/types";
import client from "../../../../../../../services/api/client";
import type { PartialExceptFor } from "../../../../../../../utils/tsUtils";

////////////////////////////////////////////////////////////////
// Constants
Expand All @@ -30,8 +31,9 @@ export const TS_INTERPRETATION_OPTIONS = [
// Types
////////////////////////////////////////////////////////////////

export type RenewableGroup = (typeof RENEWABLE_GROUPS)[number];

type TimeSeriesInterpretation = (typeof TS_INTERPRETATION_OPTIONS)[number];
type RenewableGroup = (typeof RENEWABLE_GROUPS)[number];

export interface RenewableFormFields {
name: string;
Expand Down Expand Up @@ -115,12 +117,12 @@ export async function updateRenewableCluster(
);
}

export async function createRenewableCluster(
export function createRenewableCluster(
studyId: StudyMetadata["id"],
areaId: Area["name"],
data: Partial<RenewableCluster>,
): Promise<RenewableClusterWithCapacity> {
return makeRequest<RenewableClusterWithCapacity>(
data: PartialExceptFor<RenewableCluster, "name">,
) {
return makeRequest<RenewableCluster>(
"post",
getClustersUrl(studyId, areaId),
data,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ import {
deleteStorages,
createStorage,
STORAGE_GROUPS,
StorageGroup,
} from "./utils";
import usePromiseWithSnackbarError from "../../../../../../../hooks/usePromiseWithSnackbarError";
import type { TRow } from "../../../../../../common/GroupedDataTable/types";

function Storages() {
const { study } = useOutletContext<{ study: StudyMetadata }>();
Expand Down Expand Up @@ -155,8 +157,8 @@ function Storages() {
// Event handlers
////////////////////////////////////////////////////////////////

const handleCreateRow = ({ id, ...storage }: Storage) => {
return createStorage(study.id, areaId, storage);
const handleCreate = (values: TRow<StorageGroup>) => {
return createStorage(study.id, areaId, values);
};

const handleDelete = (rows: Storage[]) => {
Expand All @@ -177,8 +179,8 @@ function Storages() {
isLoading={isLoading}
data={storages || []}
columns={columns}
groups={STORAGE_GROUPS}
onCreate={handleCreateRow}
groups={[...STORAGE_GROUPS]}
onCreate={handleCreate}
onDelete={handleDelete}
onNameClick={handleNameClick}
deleteConfirmationMessage={(count) =>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { StudyMetadata, Area } from "../../../../../../../common/types";
import client from "../../../../../../../services/api/client";
import type { PartialExceptFor } from "../../../../../../../utils/tsUtils";

////////////////////////////////////////////////////////////////
// Constants
Expand Down Expand Up @@ -87,11 +88,11 @@ export async function updateStorage(
);
}

export async function createStorage(
export function createStorage(
studyId: StudyMetadata["id"],
areaId: Area["name"],
data: Partial<Storage>,
): Promise<Storage> {
data: PartialExceptFor<Storage, "name">,
) {
return makeRequest<Storage>("post", getStoragesUrl(studyId, areaId), data);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,17 @@ import {
ThermalClusterWithCapacity,
THERMAL_GROUPS,
ThermalCluster,
ThermalGroup,
} from "./utils";
import useAppSelector from "../../../../../../../redux/hooks/useAppSelector";
import { getCurrentAreaId } from "../../../../../../../redux/selectors";
import GroupedDataTable from "../../../../../../common/GroupedDataTable";
import {
addCapacity,
capacityAggregationFn,
useClusterDataWithCapacity,
} from "../common/utils";
import { TRow } from "../../../../../../common/GroupedDataTable/types";

function Thermal() {
const { study } = useOutletContext<{ study: StudyMetadata }>();
Expand Down Expand Up @@ -95,8 +98,8 @@ function Thermal() {
),
Cell: ({ row }) => (
<>
{Math.floor(row.original.enabledCapacity ?? 0)} /{" "}
{Math.floor(row.original.installedCapacity ?? 0)}
{Math.floor(row.original.enabledCapacity)} /{" "}
{Math.floor(row.original.installedCapacity)}
</>
),
Footer: () => (
Expand All @@ -119,13 +122,9 @@ function Thermal() {
// Event handlers
////////////////////////////////////////////////////////////////

const handleCreateRow = ({
id,
installedCapacity,
enabledCapacity,
...cluster
}: ThermalClusterWithCapacity) => {
return createThermalCluster(study.id, areaId, cluster);
const handleCreate = async (values: TRow<ThermalGroup>) => {
const cluster = await createThermalCluster(study.id, areaId, values);
return addCapacity(cluster);
};

const handleDelete = (rows: ThermalClusterWithCapacity[]) => {
Expand All @@ -146,8 +145,8 @@ function Thermal() {
isLoading={isLoading}
data={clustersWithCapacity}
columns={columns}
groups={THERMAL_GROUPS}
onCreate={handleCreateRow}
groups={[...THERMAL_GROUPS]}
onCreate={handleCreate}
onDelete={handleDelete}
onNameClick={handleNameClick}
deleteConfirmationMessage={(count) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
StudyMetadata,
} from "../../../../../../../common/types";
import client from "../../../../../../../services/api/client";
import type { PartialExceptFor } from "../../../../../../../utils/tsUtils";

////////////////////////////////////////////////////////////////
// Constants
Expand Down Expand Up @@ -51,7 +52,8 @@ export const TS_LAW_OPTIONS = ["geometric", "uniform"] as const;
// Types
////////////////////////////////////////////////////////////////

type ThermalGroup = (typeof THERMAL_GROUPS)[number];
export type ThermalGroup = (typeof THERMAL_GROUPS)[number];

type LocalTSGenerationBehavior = (typeof TS_GENERATION_OPTIONS)[number];
type TimeSeriesLawOption = (typeof TS_LAW_OPTIONS)[number];

Expand Down Expand Up @@ -143,12 +145,12 @@ export async function updateThermalCluster(
);
}

export async function createThermalCluster(
export function createThermalCluster(
studyId: StudyMetadata["id"],
areaId: Area["name"],
data: Partial<ThermalCluster>,
): Promise<ThermalClusterWithCapacity> {
return makeRequest<ThermalClusterWithCapacity>(
data: PartialExceptFor<ThermalCluster, "name">,
) {
return makeRequest<ThermalCluster>(
"post",
getClustersUrl(studyId, areaId),
data,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,7 @@ export const useClusterDataWithCapacity = <T extends BaseCluster>(
});

const clustersWithCapacity: Array<ClusterWithCapacity<T>> = useMemo(
() =>
clusters?.map((cluster) => {
const { unitCount, nominalCapacity, enabled } = cluster;
const installedCapacity = unitCount * nominalCapacity;
const enabledCapacity = enabled ? installedCapacity : 0;
return { ...cluster, installedCapacity, enabledCapacity };
}) || [],
() => clusters?.map(addCapacity) || [],
[clusters],
);

Expand Down Expand Up @@ -116,3 +110,10 @@ export const useClusterDataWithCapacity = <T extends BaseCluster>(
isLoading,
};
};

export function addCapacity<T extends BaseCluster>(cluster: T) {
const { unitCount, nominalCapacity, enabled } = cluster;
const installedCapacity = unitCount * nominalCapacity;
const enabledCapacity = enabled ? installedCapacity : 0;
return { ...cluster, installedCapacity, enabledCapacity };
}
44 changes: 15 additions & 29 deletions webapp/src/components/common/GroupedDataTable/CreateDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,48 +1,38 @@
import { t } from "i18next";
import AddCircleIcon from "@mui/icons-material/AddCircle";
import FormDialog from "../dialogs/FormDialog";
import StringFE from "../fieldEditors/StringFE";
import Fieldset from "../Fieldset";
import { SubmitHandlerPlus } from "../Form/types";
import SelectFE from "../fieldEditors/SelectFE";
import { nameToId } from "../../../services/utils";
import { TRow } from "./utils";
import { validateString } from "../../../utils/validationUtils";
import type { TRow } from "./types";
import { useTranslation } from "react-i18next";

interface Props<TData extends TRow> {
interface Props {
open: boolean;
onClose: VoidFunction;
onSubmit: (values: TData) => Promise<void>;
groups: string[] | readonly string[];
existingNames: Array<TData["name"]>;
onSubmit: (values: TRow) => Promise<void>;
groups: string[];
existingNames: Array<TRow["name"]>;
}

const defaultValues = {
name: "",
group: "",
};

function CreateDialog<TData extends TRow>({
function CreateDialog({
open,
onClose,
onSubmit,
groups,
existingNames,
}: Props<TData>) {
}: Props) {
const { t } = useTranslation();

////////////////////////////////////////////////////////////////
// Event Handlers
////////////////////////////////////////////////////////////////

const handleSubmit = async ({
values,
}: SubmitHandlerPlus<typeof defaultValues>) => {
await onSubmit({
...values,
id: nameToId(values.name),
name: values.name.trim(),
} as TData);

onClose();
const handleSubmit = ({
values: { name, group },
}: SubmitHandlerPlus<TRow>) => {
return onSubmit({ name: name.trim(), group });
};

////////////////////////////////////////////////////////////////
Expand All @@ -56,7 +46,6 @@ function CreateDialog<TData extends TRow>({
open={open}
onCancel={onClose}
onSubmit={handleSubmit}
config={{ defaultValues }}
>
{({ control }) => (
<Fieldset fullFieldWidth>
Expand All @@ -76,10 +65,7 @@ function CreateDialog<TData extends TRow>({
name="group"
control={control}
options={groups}
required
sx={{
alignSelf: "center",
}}
rules={{ required: t("form.field.required") }}
/>
</Fieldset>
)}
Expand Down
Loading

0 comments on commit dc55ad7

Please sign in to comment.