diff options
Diffstat (limited to 'packages/auditor-backoffice-ui/src/components/form/InputStock.tsx')
-rw-r--r-- | packages/auditor-backoffice-ui/src/components/form/InputStock.tsx | 224 |
1 files changed, 224 insertions, 0 deletions
diff --git a/packages/auditor-backoffice-ui/src/components/form/InputStock.tsx b/packages/auditor-backoffice-ui/src/components/form/InputStock.tsx new file mode 100644 index 000000000..1d18685c5 --- /dev/null +++ b/packages/auditor-backoffice-ui/src/components/form/InputStock.tsx @@ -0,0 +1,224 @@ +/* + This file is part of GNU Taler + (C) 2021-2023 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +/** + * + * @author Sebastian Javier Marchano (sebasjm) + */ +import { useTranslationContext } from "@gnu-taler/web-util/browser"; +import { Fragment, h } from "preact"; +import { useLayoutEffect, useState } from "preact/hooks"; +import { MerchantBackend, Timestamp } from "../../declaration.js"; +import { FormErrors, FormProvider } from "./FormProvider.js"; +import { InputDate } from "./InputDate.js"; +import { InputGroup } from "./InputGroup.js"; +import { InputLocation } from "./InputLocation.js"; +import { InputNumber } from "./InputNumber.js"; +import { InputProps, useField } from "./useField.js"; + +export interface Props<T> extends InputProps<T> { + alreadyExist?: boolean; +} + +type Entity = Stock; + +export interface Stock { + current: number; + lost: number; + sold: number; + address?: MerchantBackend.Location; + nextRestock?: Timestamp; +} + +interface StockDelta { + incoming: number; + lost: number; +} + +export function InputStock<T>({ + name, + tooltip, + label, + alreadyExist, +}: Props<keyof T>) { + const { error, value, onChange } = useField<T>(name); + + const [errors, setErrors] = useState<FormErrors<Entity>>({}); + + const [formValue, valueHandler] = useState<Partial<Entity>>(value); + const [addedStock, setAddedStock] = useState<StockDelta>({ + incoming: 0, + lost: 0, + }); + const { i18n } = useTranslationContext(); + + useLayoutEffect(() => { + if (!formValue) { + onChange(undefined as any); + } else { + onChange({ + ...formValue, + current: (formValue?.current || 0) + addedStock.incoming, + lost: (formValue?.lost || 0) + addedStock.lost, + } as any); + } + }, [formValue, addedStock]); + + if (!formValue) { + return ( + <Fragment> + <div class="field is-horizontal"> + <div class="field-label is-normal"> + <label class="label"> + {label} + {tooltip && ( + <span class="icon has-tooltip-right" data-tooltip={tooltip}> + <i class="mdi mdi-information" /> + </span> + )} + </label> + </div> + <div class="field-body is-flex-grow-3"> + <div class="field has-addons"> + {!alreadyExist ? ( + <button + class="button" + data-tooltip={i18n.str`click here to configure the stock of the product, leave it as is and the backend will not control stock`} + onClick={(): void => { + valueHandler({ + current: 0, + lost: 0, + sold: 0, + } as Stock as any); + }} + > + <span> + <i18n.Translate>Manage stock</i18n.Translate> + </span> + </button> + ) : ( + <button + class="button" + data-tooltip={i18n.str`this product has been configured without stock control`} + disabled + > + <span> + <i18n.Translate>Infinite</i18n.Translate> + </span> + </button> + )} + </div> + </div> + </div> + </Fragment> + ); + } + + const currentStock = + (formValue.current || 0) - (formValue.lost || 0) - (formValue.sold || 0); + + const stockAddedErrors: FormErrors<typeof addedStock> = { + lost: + currentStock + addedStock.incoming < addedStock.lost + ? i18n.str`lost cannot be greater than current and incoming (max ${ + currentStock + addedStock.incoming + })` + : undefined, + }; + + // const stockUpdateDescription = stockAddedErrors.lost ? '' : ( + // !!addedStock.incoming || !!addedStock.lost ? + // i18n.str`current stock will change from ${currentStock} to ${currentStock + addedStock.incoming - addedStock.lost}` : + // i18n.str`current stock will stay at ${currentStock}` + // ) + + return ( + <Fragment> + <div class="card"> + <header class="card-header"> + <p class="card-header-title"> + {label} + {tooltip && ( + <span class="icon" data-tooltip={tooltip}> + <i class="mdi mdi-information" /> + </span> + )} + </p> + </header> + <div class="card-content"> + <FormProvider<Entity> + name="stock" + errors={errors} + object={formValue} + valueHandler={valueHandler} + > + {alreadyExist ? ( + <Fragment> + <FormProvider + name="added" + errors={stockAddedErrors} + object={addedStock} + valueHandler={setAddedStock as any} + > + <InputNumber name="incoming" label={i18n.str`Incoming`} /> + <InputNumber name="lost" label={i18n.str`Lost`} /> + </FormProvider> + + {/* <div class="field is-horizontal"> + <div class="field-label is-normal" /> + <div class="field-body is-flex-grow-3"> + <div class="field"> + {stockUpdateDescription} + </div> + </div> + </div> */} + </Fragment> + ) : ( + <InputNumber<Entity> + name="current" + label={i18n.str`Current`} + side={ + <button + class="button is-danger" + data-tooltip={i18n.str`remove stock control for this product`} + onClick={(): void => { + valueHandler(undefined as any); + }} + > + <span> + <i18n.Translate>without stock</i18n.Translate> + </span> + </button> + } + /> + )} + + <InputDate<Entity> + name="nextRestock" + label={i18n.str`Next restock`} + withTimestampSupport + /> + + <InputGroup<Entity> name="address" label={i18n.str`Warehouse address`}> + <InputLocation name="address" /> + </InputGroup> + </FormProvider> + </div> + </div> + </Fragment> + ); +} +// ( |