summaryrefslogtreecommitdiff
path: root/packages/merchant-backoffice-ui/src/paths/instance/products/list/Table.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'packages/merchant-backoffice-ui/src/paths/instance/products/list/Table.tsx')
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/products/list/Table.tsx190
1 files changed, 111 insertions, 79 deletions
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/products/list/Table.tsx b/packages/merchant-backoffice-ui/src/paths/instance/products/list/Table.tsx
index 81acb9876..9d5701fa7 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/products/list/Table.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/products/list/Table.tsx
@@ -1,6 +1,6 @@
/*
This file is part of GNU Taler
- (C) 2021 Taler Systems S.A.
+ (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
@@ -19,21 +19,21 @@
* @author Sebastian Javier Marchano (sebasjm)
*/
+import { AmountString, Amounts, TalerMerchantApi } 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 { ComponentChildren, Fragment, VNode, h } from "preact";
import { StateUpdater, useState } from "preact/hooks";
+import emptyImage from "../../../../assets/empty.png";
import {
- FormProvider,
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 emptyImage from "../../../../assets/empty.png";
-import { Translate, useTranslator } from "../../../../i18n/index.js";
-import { Amounts } from "@gnu-taler/taler-util";
+import { dateFormatForSettings, usePreference } from "../../../../hooks/preference.js";
-type Entity = MerchantBackend.Products.ProductDetail & WithId;
+type Entity = TalerMerchantApi.ProductDetail & WithId;
interface Props {
instances: Entity[];
@@ -41,10 +41,12 @@ interface Props {
onSelect: (product: Entity) => void;
onUpdate: (
id: string,
- data: MerchantBackend.Products.ProductPatchDetail
+ data: TalerMerchantApi.ProductPatchDetail,
) => Promise<void>;
onCreate: () => void;
selected?: boolean;
+ onLoadMoreBefore?: () => void;
+ onLoadMoreAfter?: () => void;
}
export function CardTable({
@@ -53,11 +55,13 @@ export function CardTable({
onSelect,
onUpdate,
onDelete,
+ onLoadMoreAfter,
+ onLoadMoreBefore
}: Props): VNode {
const [rowSelection, rowSelectionHandler] = useState<string | undefined>(
- undefined
+ undefined,
);
- const i18n = useTranslator();
+ const { i18n } = useTranslationContext();
return (
<div class="card has-table">
<header class="card-header">
@@ -65,12 +69,12 @@ export function CardTable({
<span class="icon">
<i class="mdi mdi-shopping" />
</span>
- <Translate>Products</Translate>
+ <i18n.Translate>Inventory</i18n.Translate>
</p>
<div class="card-header-icon" aria-label="more options">
<span
class="has-tooltip-left"
- data-tooltip={i18n`add product to inventory`}
+ data-tooltip={i18n.str`add product to inventory`}
>
<button class="button is-info" type="button" onClick={onCreate}>
<span class="icon is-small">
@@ -89,6 +93,8 @@ export function CardTable({
onSelect={onSelect}
onDelete={onDelete}
onUpdate={onUpdate}
+ onLoadMoreAfter={onLoadMoreAfter}
+ onLoadMoreBefore={onLoadMoreBefore}
rowSelection={rowSelection}
rowSelectionHandler={rowSelectionHandler}
/>
@@ -107,10 +113,12 @@ interface TableProps {
onSelect: (id: Entity) => void;
onUpdate: (
id: string,
- data: MerchantBackend.Products.ProductPatchDetail
+ data: TalerMerchantApi.ProductPatchDetail,
) => Promise<void>;
onDelete: (id: Entity) => void;
rowSelectionHandler: StateUpdater<string | undefined>;
+ onLoadMoreBefore?: () => void;
+ onLoadMoreAfter?: () => void;
}
function Table({
@@ -120,33 +128,41 @@ function Table({
onSelect,
onUpdate,
onDelete,
+ onLoadMoreAfter,
+ onLoadMoreBefore
}: TableProps): VNode {
- const i18n = useTranslator();
+ const { i18n } = useTranslationContext();
+ const [settings] = usePreference();
return (
<div class="table-container">
+ {onLoadMoreBefore && (
+ <button class="button is-fullwidth" onClick={onLoadMoreBefore}>
+ <i18n.Translate>load first page</i18n.Translate>
+ </button>
+ )}
<table class="table is-fullwidth is-striped is-hoverable is-fullwidth">
<thead>
<tr>
<th>
- <Translate>Image</Translate>
+ <i18n.Translate>Image</i18n.Translate>
</th>
<th>
- <Translate>Description</Translate>
+ <i18n.Translate>Description</i18n.Translate>
</th>
<th>
- <Translate>Sell</Translate>
+ <i18n.Translate>Price per unit</i18n.Translate>
</th>
<th>
- <Translate>Taxes</Translate>
+ <i18n.Translate>Taxes</i18n.Translate>
</th>
<th>
- <Translate>Profit</Translate>
+ <i18n.Translate>Sales</i18n.Translate>
</th>
<th>
- <Translate>Stock</Translate>
+ <i18n.Translate>Stock</i18n.Translate>
</th>
<th>
- <Translate>Sold</Translate>
+ <i18n.Translate>Sold</i18n.Translate>
</th>
<th />
</tr>
@@ -156,10 +172,10 @@ function Table({
const restStockInfo = !i.next_restock
? ""
: i.next_restock.t_s === "never"
- ? "never"
- : `restock at ${format(
+ ? "never"
+ : `restock at ${format(
new Date(i.next_restock.t_s * 1000),
- "yyyy/MM/dd"
+ dateFormatForSettings(settings),
)}`;
let stockInfo: ComponentChildren = "";
if (i.total_stock < 0) {
@@ -188,18 +204,21 @@ function Table({
src={i.image ? i.image : emptyImage}
style={{
border: "solid black 1px",
- width: 100,
- height: 100,
+ maxHeight: "2em",
+ width: "auto",
+ height: "auto",
}}
/>
</td>
<td
+ class="has-tooltip-right"
+ data-tooltip={i.description}
onClick={() =>
rowSelection !== i.id && rowSelectionHandler(i.id)
}
style={{ cursor: "pointer" }}
>
- {i.description}
+ {i.description.length > 30 ? i.description.substring(0, 30) + "..." : i.description}
</td>
<td
onClick={() =>
@@ -207,7 +226,7 @@ function Table({
}
style={{ cursor: "pointer" }}
>
- {isFree ? i18n`free` : `${i.price} / ${i.unit}`}
+ {isFree ? i18n.str`free` : `${i.price} / ${i.unit}`}
</td>
<td
onClick={() =>
@@ -239,32 +258,35 @@ function Table({
}
style={{ cursor: "pointer" }}
>
- {i.total_sold} {i.unit}
+ <span style={{ "whiteSpace": "nowrap" }}>
+
+ {i.total_sold} {i.unit}
+ </span>
</td>
<td class="is-actions-cell right-sticky">
<div class="buttons is-right">
<span
class="has-tooltip-bottom"
- data-tooltip={i18n`go to product update page`}
+ data-tooltip={i18n.str`go to product update page`}
>
<button
class="button is-small is-success "
type="button"
onClick={(): void => onSelect(i)}
>
- <Translate>Update</Translate>
+ <i18n.Translate>Update</i18n.Translate>
</button>
</span>
<span
class="has-tooltip-left"
- data-tooltip={i18n`remove this product from the database`}
+ data-tooltip={i18n.str`remove this product from the database`}
>
<button
class="button is-small is-danger"
type="button"
onClick={(): void => onDelete(i)}
>
- <Translate>Delete</Translate>
+ <i18n.Translate>Delete</i18n.Translate>
</button>
</span>
</div>
@@ -276,8 +298,8 @@ function Table({
<FastProductUpdateForm
product={i}
onUpdate={(prod) =>
- onUpdate(i.id, prod).then((r) =>
- rowSelectionHandler(undefined)
+ onUpdate(i.id, prod).then(() =>
+ rowSelectionHandler(undefined),
)
}
onCancel={() => rowSelectionHandler(undefined)}
@@ -290,6 +312,13 @@ function Table({
})}
</tbody>
</table>
+ {onLoadMoreAfter && (
+ <button class="button is-fullwidth"
+ data-tooltip={i18n.str`load more products after the last one`}
+ onClick={onLoadMoreAfter}>
+ <i18n.Translate>load next page</i18n.Translate>
+ </button>
+ )}
</div>
);
}
@@ -297,7 +326,7 @@ function Table({
interface FastProductUpdateFormProps {
product: Entity;
onUpdate: (
- data: MerchantBackend.Products.ProductPatchDetail
+ data: TalerMerchantApi.ProductPatchDetail,
) => Promise<void>;
onCancel: () => void;
}
@@ -316,7 +345,7 @@ function FastProductWithInfiniteStockUpdateForm({
onCancel,
}: FastProductUpdateFormProps) {
const [value, valueHandler] = useState<UpdatePrice>({ price: product.price });
- const i18n = useTranslator();
+ const { i18n } = useTranslationContext();
return (
<Fragment>
@@ -327,31 +356,34 @@ function FastProductWithInfiniteStockUpdateForm({
>
<InputCurrency<FastProductUpdate>
name="price"
- label={i18n`Price`}
- tooltip={i18n`update the product with new price`}
+ label={i18n.str`Price`}
+ tooltip={i18n.str`update the product with new price`}
/>
</FormProvider>
- <div class="buttons is-right mt-5">
- <button class="button" onClick={onCancel}>
- <Translate>Cancel</Translate>
- </button>
- <span
- class="has-tooltip-left"
- data-tooltip={i18n`update product with new price`}
- >
- <button
- class="button is-info"
- onClick={() =>
- onUpdate({
- ...product,
- price: value.price,
- })
- }
- >
- <Translate>Confirm</Translate>
+ <div class="buttons is-expanded">
+
+ <div class="buttons is-right mt-5">
+ <button class="button" onClick={onCancel}>
+ <i18n.Translate>Cancel</i18n.Translate>
</button>
- </span>
+ <span
+ class="has-tooltip-left"
+ data-tooltip={i18n.str`update product with new price`}
+ >
+ <button
+ class="button is-info"
+ onClick={() =>
+ onUpdate({
+ ...product,
+ price: value.price as AmountString,
+ })
+ }
+ >
+ <i18n.Translate>Confirm update</i18n.Translate>
+ </button>
+ </span>
+ </div>
</div>
</Fragment>
);
@@ -374,16 +406,15 @@ function FastProductWithManagedStockUpdateForm({
const errors: FormErrors<FastProductUpdate> = {
lost:
currentStock + value.incoming < value.lost
- ? `lost cannot be greater that current + incoming (max ${
- currentStock + value.incoming
- })`
+ ? `lost cannot be greater that current + incoming (max ${currentStock + value.incoming
+ })`
: undefined,
};
const hasErrors = Object.keys(errors).some(
- (k) => (errors as any)[k] !== undefined
+ (k) => (errors as Record<string,unknown>)[k] !== undefined,
);
- const i18n = useTranslator();
+ const { i18n } = useTranslationContext();
return (
<Fragment>
@@ -395,31 +426,31 @@ function FastProductWithManagedStockUpdateForm({
>
<InputNumber<FastProductUpdate>
name="incoming"
- label={i18n`Incoming`}
- tooltip={i18n`add more elements to the inventory`}
+ label={i18n.str`Incoming`}
+ tooltip={i18n.str`add more elements to the inventory`}
/>
<InputNumber<FastProductUpdate>
name="lost"
- label={i18n`Lost`}
- tooltip={i18n`report elements lost in the inventory`}
+ label={i18n.str`Lost`}
+ tooltip={i18n.str`report elements lost in the inventory`}
/>
<InputCurrency<FastProductUpdate>
name="price"
- label={i18n`Price`}
- tooltip={i18n`new price for the product`}
+ label={i18n.str`Price`}
+ tooltip={i18n.str`new price for the product`}
/>
</FormProvider>
<div class="buttons is-right mt-5">
<button class="button" onClick={onCancel}>
- <Translate>Cancel</Translate>
+ <i18n.Translate>Cancel</i18n.Translate>
</button>
<span
class="has-tooltip-left"
data-tooltip={
hasErrors
- ? i18n`the are value with errors`
- : i18n`update product with new stock and price`
+ ? i18n.str`the are value with errors`
+ : i18n.str`update product with new stock and price`
}
>
<button
@@ -430,11 +461,11 @@ function FastProductWithManagedStockUpdateForm({
...product,
total_stock: product.total_stock + value.incoming,
total_lost: product.total_lost + value.lost,
- price: value.price,
+ price: value.price as AmountString,
})
}
>
- <Translate>Confirm</Translate>
+ <i18n.Translate>Confirm</i18n.Translate>
</button>
</span>
</div>
@@ -451,17 +482,18 @@ function FastProductUpdateForm(props: FastProductUpdateFormProps) {
}
function EmptyTable(): VNode {
+ const { i18n } = useTranslationContext();
return (
<div class="content has-text-grey has-text-centered">
<p>
<span class="icon is-large">
- <i class="mdi mdi-emoticon-sad mdi-48px" />
+ <i class="mdi mdi-magnify mdi-48px" />
</span>
</p>
<p>
- <Translate>
+ <i18n.Translate>
There is no products yet, add more pressing the + sign
- </Translate>
+ </i18n.Translate>
</p>
</div>
);
@@ -474,6 +506,6 @@ function difference(price: string, tax: number) {
ps[1] = `${p - tax}`;
return ps.join(":");
}
-function sum(taxes: MerchantBackend.Tax[]) {
+function sum(taxes: TalerMerchantApi.Tax[]) {
return taxes.reduce((p, c) => p + parseInt(c.tax.split(":")[1], 10), 0);
}