/* 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 { MerchantBackend, WithId } from "../../../../declaration.js"; import { dateFormatForSettings, useSettings } from "../../../../hooks/useSettings.js"; type Entity = MerchantBackend.Products.ProductDetail & WithId; interface Props { instances: Entity[]; onDelete: (id: Entity) => void; onSelect: (product: Entity) => void; onUpdate: ( id: string, data: MerchantBackend.Products.ProductPatchDetail, ) => Promise; onCreate: () => void; selected?: boolean; } export function CardTable({ instances, onCreate, onSelect, onUpdate, onDelete, }: Props): VNode { const [rowSelection, rowSelectionHandler] = useState( undefined, ); const { i18n } = useTranslationContext(); return (

Inventory

{instances.length > 0 ? ( ) : ( )} ); } interface TableProps { rowSelection: string | undefined; instances: Entity[]; onSelect: (id: Entity) => void; onUpdate: ( id: string, data: MerchantBackend.Products.ProductPatchDetail, ) => Promise; onDelete: (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); }