From 7a201c3b885c5d23bf0fd0f3da32379a49b30c38 Mon Sep 17 00:00:00 2001 From: Nic Eigel Date: Sun, 14 Jan 2024 15:18:12 +0100 Subject: adding auditor-backoffice-ui --- .../deposit_confirmations/list/List.stories.tsx | 61 +++ .../instance/deposit_confirmations/list/Table.tsx | 496 +++++++++++++++++++++ .../instance/deposit_confirmations/list/index.tsx | 151 +++++++ 3 files changed, 708 insertions(+) create mode 100644 packages/auditor-backoffice-ui/src/paths/instance/deposit_confirmations/list/List.stories.tsx create mode 100644 packages/auditor-backoffice-ui/src/paths/instance/deposit_confirmations/list/Table.tsx create mode 100644 packages/auditor-backoffice-ui/src/paths/instance/deposit_confirmations/list/index.tsx (limited to 'packages/auditor-backoffice-ui/src/paths/instance/deposit_confirmations/list') diff --git a/packages/auditor-backoffice-ui/src/paths/instance/deposit_confirmations/list/List.stories.tsx b/packages/auditor-backoffice-ui/src/paths/instance/deposit_confirmations/list/List.stories.tsx new file mode 100644 index 000000000..c2c4d548c --- /dev/null +++ b/packages/auditor-backoffice-ui/src/paths/instance/deposit_confirmations/list/List.stories.tsx @@ -0,0 +1,61 @@ +/* + 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 + */ + +/** + * + * @author Sebastian Javier Marchano (sebasjm) + */ + +import { h, VNode, FunctionalComponent } from "preact"; +import { CardTable as TestedComponent } from "./Table.js"; + +export default { + title: "Pages/Product/List", + component: TestedComponent, + argTypes: { + onCreate: { action: "onCreate" }, + onSelect: { action: "onSelect" }, + onDelete: { action: "onDelete" }, + onUpdate: { action: "onUpdate" }, + }, +}; + +function createExample( + Component: FunctionalComponent, + props: Partial, +) { + const r = (args: any) => ; + r.args = props; + return r; +} + +export const Example = createExample(TestedComponent, { + instances: [ + { + id: "orderid", + description: "description1", + description_i18n: {} as any, + image: "", + price: "TESTKUDOS:10", + taxes: [], + total_lost: 10, + total_sold: 5, + total_stock: 15, + unit: "bar", + address: {}, + }, + ], +}); diff --git a/packages/auditor-backoffice-ui/src/paths/instance/deposit_confirmations/list/Table.tsx b/packages/auditor-backoffice-ui/src/paths/instance/deposit_confirmations/list/Table.tsx new file mode 100644 index 000000000..ffd1f12e5 --- /dev/null +++ b/packages/auditor-backoffice-ui/src/paths/instance/deposit_confirmations/list/Table.tsx @@ -0,0 +1,496 @@ +/* + 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 + */ + +/** + * + * @author Sebastian Javier Marchano (sebasjm) + */ + +import { Amounts } from "@gnu-taler/taler-util"; +import { useTranslationContext } from "@gnu-taler/web-util/browser"; +import { format } from "date-fns"; +import { ComponentChildren, Fragment, h, VNode } from "preact"; +import { StateUpdater, useState } from "preact/hooks"; +import emptyImage from "../../../../assets/empty.png"; +import { + FormErrors, + FormProvider, +} from "../../../../components/form/FormProvider.js"; +import { InputCurrency } from "../../../../components/form/InputCurrency.js"; +import { InputNumber } from "../../../../components/form/InputNumber.js"; +import { AuditorBackend, WithId } from "../../../../declaration.js"; +import { dateFormatForSettings, useSettings } from "../../../../hooks/useSettings.js"; + +type Entity = AuditorBackend.DepositConfirmation.DepositConfirmationDetail & WithId; + +interface Props { + instances: Entity[]; + onDelete: (id: Entity) => void; + onSelect: (depositConfirmation: Entity) => void; + onUpdate: ( + id: string, + data: AuditorBackend.DepositConfirmation.DepositConfirmationDetail, + ) => Promise; + onCreate: () => void; + selected?: boolean; +} + +export function CardTable({ + instances, + onCreate, + onSelect, + onUpdate, + onDelete, +}: Props): VNode { + const [rowSelection, rowSelectionHandler] = useState( + undefined, + ); + const { i18n } = useTranslationContext(); + return ( +
+
+

+ + + + Deposit Confirmations +

+
+ + + +
+
+
+
+
+ {instances.length > 0 ? ( + + ) : ( + + )} + + + + + ); +} +interface TableProps { + rowSelection: string | undefined; + instances: Entity[]; + onSelect: (id: Entity) => void; + onUpdate: ( + id: string, + data: AuditorBackend.DepositConfirmation.DepositConfirmationDetail, + ) => Promise; + onDelete: (serial_id: Entity) => void; + rowSelectionHandler: StateUpdater; +} + +function Table({ + rowSelection, + rowSelectionHandler, + instances, + onSelect, + onUpdate, + onDelete, +}: TableProps): VNode { + const { i18n } = useTranslationContext(); + const [settings] = useSettings(); + return ( +
+
+ + + + + + + + + + + + + {instances.map((i) => { + const restStockInfo = !i.next_restock + ? "" + : i.next_restock.t_s === "never" + ? "never" + : `restock at ${format( + new Date(i.next_restock.t_s * 1000), + dateFormatForSettings(settings), + )}`; + let stockInfo: ComponentChildren = ""; + if (i.total_stock < 0) { + stockInfo = "infinite"; + } else { + const totalStock = i.total_stock - i.total_lost - i.total_sold; + stockInfo = ( + + ); + } + + const isFree = Amounts.isZero(Amounts.parseOrThrow(i.price)); + + return ( + + + + + + + + + + + + {rowSelection === i.id && ( + + + + )} + + ); + })} + +
+ Image + + Description + + Price per unit + + Taxes + + Sales + + Stock + + Sold + +
+ rowSelection !== i.id && rowSelectionHandler(i.id) + } + style={{ cursor: "pointer" }} + > + + + rowSelection !== i.id && rowSelectionHandler(i.id) + } + style={{ cursor: "pointer" }} + > + {i.description.length > 30 ? i.description.substring(0, 30) + "..." : i.description} + + rowSelection !== i.id && rowSelectionHandler(i.id) + } + style={{ cursor: "pointer" }} + > + {isFree ? i18n.str`free` : `${i.price} / ${i.unit}`} + + rowSelection !== i.id && rowSelectionHandler(i.id) + } + style={{ cursor: "pointer" }} + > + {sum(i.taxes)} + + rowSelection !== i.id && rowSelectionHandler(i.id) + } + style={{ cursor: "pointer" }} + > + {difference(i.price, sum(i.taxes))} + + rowSelection !== i.id && rowSelectionHandler(i.id) + } + style={{ cursor: "pointer" }} + > + {stockInfo} + + rowSelection !== i.id && rowSelectionHandler(i.id) + } + style={{ cursor: "pointer" }} + > + + + {i.total_sold} {i.unit} + + +
+ + + + + + +
+
+ + onUpdate(i.id, prod).then((r) => + rowSelectionHandler(undefined), + ) + } + onCancel={() => rowSelectionHandler(undefined)} + /> +
+
+ ); +} + +interface FastProductUpdateFormProps { + product: Entity; + onUpdate: ( + data: MerchantBackend.Products.ProductPatchDetail, + ) => Promise; + onCancel: () => void; +} +interface FastProductUpdate { + incoming: number; + lost: number; + price: string; +} +interface UpdatePrice { + price: string; +} + +function FastProductWithInfiniteStockUpdateForm({ + product, + onUpdate, + onCancel, +}: FastProductUpdateFormProps) { + const [value, valueHandler] = useState({ price: product.price }); + const { i18n } = useTranslationContext(); + + return ( + + + name="added" + object={value} + valueHandler={valueHandler as any} + > + + name="price" + label={i18n.str`Price`} + tooltip={i18n.str`update the product with new price`} + /> + + +
+ +
+ + +
+
+ + + + +
+
+
+ ); +} + +function FastProductWithManagedStockUpdateForm({ + product, + onUpdate, + onCancel, +}: FastProductUpdateFormProps) { + const [value, valueHandler] = useState({ + incoming: 0, + lost: 0, + price: product.price, + }); + + const currentStock = + product.total_stock - product.total_sold - product.total_lost; + + const errors: FormErrors = { + lost: + currentStock + value.incoming < value.lost + ? `lost cannot be greater that current + incoming (max ${currentStock + value.incoming + })` + : undefined, + }; + + const hasErrors = Object.keys(errors).some( + (k) => (errors as any)[k] !== undefined, + ); + const { i18n } = useTranslationContext(); + + return ( + + + name="added" + errors={errors} + object={value} + valueHandler={valueHandler as any} + > + + name="incoming" + label={i18n.str`Incoming`} + tooltip={i18n.str`add more elements to the inventory`} + /> + + name="lost" + label={i18n.str`Lost`} + tooltip={i18n.str`report elements lost in the inventory`} + /> + + name="price" + label={i18n.str`Price`} + tooltip={i18n.str`new price for the product`} + /> + + +
+ + + + +
+
+ ); +} + +function FastProductUpdateForm(props: FastProductUpdateFormProps) { + return props.product.total_stock === -1 ? ( + + ) : ( + + ); +} + +function EmptyTable(): VNode { + const { i18n } = useTranslationContext(); + return ( +
+

+ + + +

+

+ + There is no products yet, add more pressing the + sign + +

+
+ ); +} + +function difference(price: string, tax: number) { + if (!tax) return price; + const ps = price.split(":"); + const p = parseInt(ps[1], 10); + ps[1] = `${p - tax}`; + return ps.join(":"); +} +function sum(taxes: MerchantBackend.Tax[]) { + return taxes.reduce((p, c) => p + parseInt(c.tax.split(":")[1], 10), 0); +} diff --git a/packages/auditor-backoffice-ui/src/paths/instance/deposit_confirmations/list/index.tsx b/packages/auditor-backoffice-ui/src/paths/instance/deposit_confirmations/list/index.tsx new file mode 100644 index 000000000..dccb3ef25 --- /dev/null +++ b/packages/auditor-backoffice-ui/src/paths/instance/deposit_confirmations/list/index.tsx @@ -0,0 +1,151 @@ +/* + This file is part of GNU Taler + (C) 2021-2024 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 + */ + +/** + * + * @author Sebastian Javier Marchano (sebasjm) + * @author Nic Eigel + */ + +import { + ErrorType, + HttpError, + useTranslationContext, +} from "@gnu-taler/web-util/browser"; +import { h, VNode } from "preact"; +import { useState } from "preact/hooks"; +import { Loading } from "../../../../components/exception/loading.js"; +import { NotificationCard } from "../../../../components/menu/index.js"; +import { AuditorBackend, WithId } from "../../../../declaration.js"; +import { + useDepositConfirmation, + useDepositConfirmationAPI, +} from "../../../../hooks/deposit_confirmations.js"; +import { Notification } from "../../../../utils/types.js"; +import { CardTable } from "./Table.js"; +import { HttpStatusCode } from "@gnu-taler/taler-util"; +import { ConfirmModal, DeleteModal } from "../../../../components/modal/index.js"; +import { JumpToElementById } from "../../../../components/form/JumpToElementById.js"; + +interface Props { + onUnauthorized: () => VNode; + onNotFound: () => VNode; + onCreate: () => void; + onSelect: (id: string) => void; + onLoadError: (e: HttpError) => VNode; +} +export default function DepositConfirmationList({ + onUnauthorized, + onLoadError, + onCreate, + onSelect, + onNotFound, +}: Props): VNode { + const result = useDepositConfirmation(); + const { deleteDepositConfirmation, updateDepositConfirmation, getDepositConfirmation } = useDepositConfirmationAPI(); + const [deleting, setDeleting] = + useState(null); + const [notif, setNotif] = useState(undefined); + + const { i18n } = useTranslationContext(); + + if (result.loading) return ; + if (!result.ok) { + if ( + result.type === ErrorType.CLIENT && + result.status === HttpStatusCode.Unauthorized + ) + return onUnauthorized(); + if ( + result.type === ErrorType.CLIENT && + result.status === HttpStatusCode.NotFound + ) + return onNotFound(); + return onLoadError(result); + } + + return ( +
+ + + + + + updateDepositConfirmation(id, prod) + .then(() => + setNotif({ + message: i18n.str`deposit_confirmation updated successfully`, + type: "SUCCESS", + }), + ) + .catch((error) => + setNotif({ + message: i18n.str`could not update the deposit_confirmation`, + type: "ERROR", + description: error.message, + }), + ) + } + onSelect={(depositConfirmation) => onSelect(depositConfirmation.id)} + onDelete={(depositConfirmation : AuditorBackend.DepositConfirmation.DepositConfirmationDetail & WithId) => + setDeleting(depositConfirmation) + } + /> + + {deleting && ( + setDeleting(null)} + onConfirm={async (): Promise => { + try { + await deleteDepositConfirmation(deleting.serial_id); + setNotif({ + message: i18n.str`Deposit-confirmation "${deleting.serial_id}" (ID: ${deleting.serial_id}) has been deleted`, + type: "SUCCESS", + }); + } catch (error) { + setNotif({ + message: i18n.str`Failed to delete deposit-confirmation`, + type: "ERROR", + description: error instanceof Error ? error.message : undefined, + }); + } + setDeleting(null); + }} + > +

+ If you delete the deposit-confirmation (ID:{" "} + {deleting.serial_id}), the stock and related information will be lost +

+

+ Deleting a deposit-confirmation cannot be undone. +

+
+ )} +
+ ); +} -- cgit v1.2.3