bananas-commerce-admin 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -0
- package/dist/cjs/Admin.js +47 -0
- package/dist/cjs/App.js +49 -0
- package/dist/cjs/api.js +225 -0
- package/dist/cjs/components/Branding.js +41 -0
- package/dist/cjs/components/Hamburger.js +40 -0
- package/dist/cjs/components/Link.js +21 -0
- package/dist/cjs/components/Logo.js +25 -0
- package/dist/cjs/components/NavBar.js +101 -0
- package/dist/cjs/components/NavBarItem.js +42 -0
- package/dist/cjs/components/NavBarRoutes.js +47 -0
- package/dist/cjs/components/ProgressBar.js +14 -0
- package/dist/cjs/components/User.js +71 -0
- package/dist/cjs/containers/Content.js +16 -0
- package/dist/cjs/containers/ErrorScreen.js +35 -0
- package/dist/cjs/containers/LoadingScreen.js +84 -0
- package/dist/cjs/containers/PageErrorBoundary.js +43 -0
- package/dist/cjs/containers/PageLoader.js +123 -0
- package/dist/cjs/contexts/ApiContext.js +105 -0
- package/dist/cjs/contexts/I18nContext.js +109 -0
- package/dist/cjs/contexts/RouterContext.js +99 -0
- package/dist/cjs/contexts/UserContext.js +144 -0
- package/dist/cjs/extensions/bananas/components/PasswordChangeForm.js +85 -0
- package/dist/cjs/extensions/bananas/index.js +54 -0
- package/dist/cjs/extensions/bananas/pages/me/list.js +20 -0
- package/dist/cjs/extensions/pos/components/PurchaseRow.js +32 -0
- package/dist/cjs/extensions/pos/components/ReceiptCard.js +86 -0
- package/dist/cjs/extensions/pos/components/ReceiptLine.js +29 -0
- package/dist/cjs/extensions/pos/index.js +22 -0
- package/dist/cjs/extensions/pos/pages/purchase/detail.js +13 -0
- package/dist/cjs/extensions/pos/pages/purchase/list.js +34 -0
- package/dist/cjs/extensions/pos/types/purchase.js +2 -0
- package/dist/cjs/extensions/pos/types/receipt.js +2 -0
- package/dist/cjs/forms/LoginForm.js +63 -0
- package/dist/cjs/hooks/useAsyncError.js +15 -0
- package/dist/cjs/hooks/useLocalStorage.js +47 -0
- package/dist/cjs/index.js +40 -0
- package/dist/cjs/pages/DashboardPage.js +10 -0
- package/dist/cjs/pages/LoginPage.js +31 -0
- package/dist/cjs/router/Router.js +35 -0
- package/dist/cjs/router/routes.js +57 -0
- package/dist/cjs/types/index.js +2 -0
- package/dist/cjs/util/get_cookie.js +10 -0
- package/dist/cjs/util/index.js +62 -0
- package/dist/cjs/util/select_styles.js +38 -0
- package/dist/esm/Admin.js +42 -0
- package/dist/esm/App.js +44 -0
- package/dist/esm/api.js +219 -0
- package/dist/esm/components/Branding.js +36 -0
- package/dist/esm/components/Hamburger.js +35 -0
- package/dist/esm/components/Link.js +16 -0
- package/dist/esm/components/Logo.js +20 -0
- package/dist/esm/components/NavBar.js +73 -0
- package/dist/esm/components/NavBarItem.js +37 -0
- package/dist/esm/components/NavBarRoutes.js +42 -0
- package/dist/esm/components/ProgressBar.js +9 -0
- package/dist/esm/components/User.js +66 -0
- package/dist/esm/containers/Content.js +11 -0
- package/dist/esm/containers/ErrorScreen.js +30 -0
- package/dist/esm/containers/LoadingScreen.js +79 -0
- package/dist/esm/containers/PageErrorBoundary.js +38 -0
- package/dist/esm/containers/PageLoader.js +117 -0
- package/dist/esm/contexts/ApiContext.js +77 -0
- package/dist/esm/contexts/I18nContext.js +77 -0
- package/dist/esm/contexts/RouterContext.js +71 -0
- package/dist/esm/contexts/UserContext.js +113 -0
- package/dist/esm/extensions/bananas/components/PasswordChangeForm.js +80 -0
- package/dist/esm/extensions/bananas/index.js +48 -0
- package/dist/esm/extensions/bananas/pages/me/list.js +15 -0
- package/dist/esm/extensions/pos/components/PurchaseRow.js +25 -0
- package/dist/esm/extensions/pos/components/ReceiptCard.js +56 -0
- package/dist/esm/extensions/pos/components/ReceiptLine.js +22 -0
- package/dist/esm/extensions/pos/index.js +16 -0
- package/dist/esm/extensions/pos/pages/purchase/detail.js +8 -0
- package/dist/esm/extensions/pos/pages/purchase/list.js +29 -0
- package/dist/esm/extensions/pos/types/purchase.js +1 -0
- package/dist/esm/extensions/pos/types/receipt.js +1 -0
- package/dist/esm/forms/LoginForm.js +58 -0
- package/dist/esm/hooks/useAsyncError.js +9 -0
- package/dist/esm/hooks/useLocalStorage.js +41 -0
- package/dist/esm/index.js +14 -0
- package/dist/esm/pages/DashboardPage.js +5 -0
- package/dist/esm/pages/LoginPage.js +26 -0
- package/dist/esm/router/Router.js +28 -0
- package/dist/esm/router/routes.js +49 -0
- package/dist/esm/types/index.js +1 -0
- package/dist/esm/util/get_cookie.js +6 -0
- package/dist/esm/util/index.js +54 -0
- package/dist/esm/util/select_styles.js +34 -0
- package/dist/types/Admin.d.ts +13 -0
- package/dist/types/App.d.ts +13 -0
- package/dist/types/api.d.ts +20 -0
- package/dist/types/components/Branding.d.ts +14 -0
- package/dist/types/components/Hamburger.d.ts +8 -0
- package/dist/types/components/Link.d.ts +9 -0
- package/dist/types/components/Logo.d.ts +7 -0
- package/dist/types/components/NavBar.d.ts +11 -0
- package/dist/types/components/NavBarItem.d.ts +12 -0
- package/dist/types/components/NavBarRoutes.d.ts +7 -0
- package/dist/types/components/ProgressBar.d.ts +7 -0
- package/dist/types/components/User.d.ts +824 -0
- package/dist/types/containers/Content.d.ts +3 -0
- package/dist/types/containers/ErrorScreen.d.ts +7 -0
- package/dist/types/containers/LoadingScreen.d.ts +50 -0
- package/dist/types/containers/PageErrorBoundary.d.ts +16 -0
- package/dist/types/containers/PageLoader.d.ts +13 -0
- package/dist/types/contexts/ApiContext.d.ts +12 -0
- package/dist/types/contexts/I18nContext.d.ts +10 -0
- package/dist/types/contexts/RouterContext.d.ts +24 -0
- package/dist/types/contexts/UserContext.d.ts +20 -0
- package/dist/types/extensions/bananas/components/PasswordChangeForm.d.ts +3 -0
- package/dist/types/extensions/bananas/index.d.ts +2 -0
- package/dist/types/extensions/bananas/pages/me/list.d.ts +6 -0
- package/dist/types/extensions/pos/components/PurchaseRow.d.ts +7 -0
- package/dist/types/extensions/pos/components/ReceiptCard.d.ts +7 -0
- package/dist/types/extensions/pos/components/ReceiptLine.d.ts +7 -0
- package/dist/types/extensions/pos/index.d.ts +2 -0
- package/dist/types/extensions/pos/pages/purchase/detail.d.ts +7 -0
- package/dist/types/extensions/pos/pages/purchase/list.d.ts +9 -0
- package/dist/types/extensions/pos/types/purchase.d.ts +18 -0
- package/dist/types/extensions/pos/types/receipt.d.ts +34 -0
- package/dist/types/forms/LoginForm.d.ts +3 -0
- package/dist/types/hooks/useAsyncError.d.ts +1 -0
- package/dist/types/hooks/useLocalStorage.d.ts +2 -0
- package/dist/types/index.d.ts +14 -0
- package/dist/types/pages/DashboardPage.d.ts +5 -0
- package/dist/types/pages/LoginPage.d.ts +9 -0
- package/dist/types/router/Router.d.ts +13 -0
- package/dist/types/router/routes.d.ts +20 -0
- package/dist/types/types/index.d.ts +6 -0
- package/dist/types/util/get_cookie.d.ts +1 -0
- package/dist/types/util/index.d.ts +8 -0
- package/dist/types/util/select_styles.d.ts +3 -0
- package/example/Dockerfile +27 -0
- package/example/docker-compose.yml +7 -0
- package/example/index.html +13 -0
- package/example/index.tsx +21 -0
- package/example/package-lock.json +13167 -0
- package/example/package.json +52 -0
- package/example/pages/.gitkeep +0 -0
- package/example/webpack.config.js +67 -0
- package/package.json +52 -0
- package/src/Admin.tsx +94 -0
- package/src/App.tsx +43 -0
- package/src/api.ts +202 -0
- package/src/components/Branding.tsx +79 -0
- package/src/components/Hamburger.tsx +41 -0
- package/src/components/Link.tsx +23 -0
- package/src/components/Logo.tsx +57 -0
- package/src/components/NavBar.tsx +115 -0
- package/src/components/NavBarItem.tsx +73 -0
- package/src/components/NavBarRoutes.tsx +67 -0
- package/src/components/ProgressBar.tsx +30 -0
- package/src/components/User.tsx +97 -0
- package/src/containers/Content.tsx +18 -0
- package/src/containers/ErrorScreen.tsx +64 -0
- package/src/containers/LoadingScreen.tsx +135 -0
- package/src/containers/PageErrorBoundary.tsx +32 -0
- package/src/containers/PageLoader.tsx +64 -0
- package/src/contexts/ApiContext.tsx +55 -0
- package/src/contexts/I18nContext.tsx +55 -0
- package/src/contexts/RouterContext.tsx +127 -0
- package/src/contexts/UserContext.tsx +99 -0
- package/src/extensions/bananas/components/PasswordChangeForm.tsx +138 -0
- package/src/extensions/bananas/index.ts +14 -0
- package/src/extensions/bananas/pages/me/list.tsx +31 -0
- package/src/extensions/pos/components/PurchaseRow.tsx +42 -0
- package/src/extensions/pos/components/ReceiptCard.tsx +101 -0
- package/src/extensions/pos/components/ReceiptLine.tsx +51 -0
- package/src/extensions/pos/index.tsx +20 -0
- package/src/extensions/pos/pages/purchase/detail.tsx +22 -0
- package/src/extensions/pos/pages/purchase/list.tsx +56 -0
- package/src/extensions/pos/types/purchase.ts +20 -0
- package/src/extensions/pos/types/receipt.ts +36 -0
- package/src/forms/LoginForm.tsx +99 -0
- package/src/hooks/useAsyncError.ts +13 -0
- package/src/hooks/useLocalStorage.ts +42 -0
- package/src/index.ts +18 -0
- package/src/pages/DashboardPage.tsx +9 -0
- package/src/pages/LoginPage.tsx +50 -0
- package/src/router/Router.tsx +63 -0
- package/src/router/routes.ts +67 -0
- package/src/types/index.ts +4 -0
- package/src/types/swagger-client.d.ts +1 -0
- package/src/util/get_cookie.ts +6 -0
- package/src/util/index.ts +63 -0
- package/src/util/select_styles.ts +29 -0
- package/tsconfig.json +38 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import Card from "@mui/material/Card";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { Receipt } from "../types/receipt";
|
|
4
|
+
import CardHeader from "@mui/material/CardHeader";
|
|
5
|
+
import List from "@mui/material/List";
|
|
6
|
+
import { ReceiptLine } from "./ReceiptLine";
|
|
7
|
+
import Divider from "@mui/material/Divider";
|
|
8
|
+
import CardContent from "@mui/material/CardContent";
|
|
9
|
+
import TableContainer from "@mui/material/TableContainer";
|
|
10
|
+
import Table from "@mui/material/Table";
|
|
11
|
+
import TableBody from "@mui/material/TableBody";
|
|
12
|
+
import TableRow from "@mui/material/TableRow";
|
|
13
|
+
import TableCell, { tableCellClasses } from "@mui/material/TableCell";
|
|
14
|
+
import { useTheme } from "@mui/material/styles";
|
|
15
|
+
|
|
16
|
+
interface ReceiptCardProps {
|
|
17
|
+
receipt: Receipt;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface ReceiptPriceRowProps {
|
|
21
|
+
title: string;
|
|
22
|
+
price: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const ReceiptPriceRow: React.FC<ReceiptPriceRowProps> = ({ title, price }) => {
|
|
26
|
+
return (
|
|
27
|
+
<TableRow>
|
|
28
|
+
<TableCell align="right" sx={{ width: "75%" }}>
|
|
29
|
+
<strong>
|
|
30
|
+
{title}
|
|
31
|
+
</strong>
|
|
32
|
+
</TableCell>
|
|
33
|
+
<TableCell align="right">
|
|
34
|
+
{`${price} SEK`}
|
|
35
|
+
</TableCell>
|
|
36
|
+
</TableRow>
|
|
37
|
+
);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const ReceiptCard: React.FC<ReceiptCardProps> = ({
|
|
41
|
+
receipt,
|
|
42
|
+
}) => {
|
|
43
|
+
const theme = useTheme();
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<Card sx={{ width: 550 }}>
|
|
47
|
+
<CardHeader title={"Receipt"} />
|
|
48
|
+
<CardContent>
|
|
49
|
+
<Divider />
|
|
50
|
+
<List
|
|
51
|
+
sx={{
|
|
52
|
+
width: "100%",
|
|
53
|
+
}}
|
|
54
|
+
>
|
|
55
|
+
{receipt.lines.map((line) => (
|
|
56
|
+
<ReceiptLine key={line.reference} line={line} />
|
|
57
|
+
)).reduce(
|
|
58
|
+
(
|
|
59
|
+
prev,
|
|
60
|
+
curr,
|
|
61
|
+
index,
|
|
62
|
+
) => [...prev, <Divider key={index} light />, curr],
|
|
63
|
+
[] as JSX.Element[],
|
|
64
|
+
)
|
|
65
|
+
.slice(1)}
|
|
66
|
+
</List>
|
|
67
|
+
<Divider />
|
|
68
|
+
<TableContainer
|
|
69
|
+
sx={{
|
|
70
|
+
paddingTop: theme.spacing(2),
|
|
71
|
+
paddingX: theme.spacing(4),
|
|
72
|
+
}}
|
|
73
|
+
>
|
|
74
|
+
<Table
|
|
75
|
+
padding="none"
|
|
76
|
+
sx={{
|
|
77
|
+
[`& .${tableCellClasses.root}`]: {
|
|
78
|
+
borderBottom: "none",
|
|
79
|
+
},
|
|
80
|
+
}}
|
|
81
|
+
>
|
|
82
|
+
<TableBody sx={{ border: "none" }}>
|
|
83
|
+
<ReceiptPriceRow
|
|
84
|
+
title="Discount"
|
|
85
|
+
price={receipt.total_discount_amount}
|
|
86
|
+
/>
|
|
87
|
+
<ReceiptPriceRow
|
|
88
|
+
title="Tax"
|
|
89
|
+
price={receipt.total_tax_amount}
|
|
90
|
+
/>
|
|
91
|
+
<ReceiptPriceRow
|
|
92
|
+
title="Total"
|
|
93
|
+
price={receipt.total_amount}
|
|
94
|
+
/>
|
|
95
|
+
</TableBody>
|
|
96
|
+
</Table>
|
|
97
|
+
</TableContainer>
|
|
98
|
+
</CardContent>
|
|
99
|
+
</Card>
|
|
100
|
+
);
|
|
101
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { ReceiptLine as ReceiptLineType } from "../types/receipt";
|
|
3
|
+
import ListItem from "@mui/material/ListItem";
|
|
4
|
+
import ListItemText from "@mui/material/ListItemText";
|
|
5
|
+
import Typography from "@mui/material/Typography";
|
|
6
|
+
import Tooltip from "@mui/material/Tooltip";
|
|
7
|
+
|
|
8
|
+
interface ReceiptLineProps {
|
|
9
|
+
line: ReceiptLineType;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const ReceiptLine: React.FC<ReceiptLineProps> = ({
|
|
13
|
+
line,
|
|
14
|
+
}) => {
|
|
15
|
+
return (
|
|
16
|
+
<ListItem
|
|
17
|
+
sx={{ display: "flex", flexDirection: "line", alignItems: "baseline" }}
|
|
18
|
+
>
|
|
19
|
+
<ListItemText
|
|
20
|
+
sx={{ width: "50%" }}
|
|
21
|
+
primary={line.title}
|
|
22
|
+
secondary={line.reference}
|
|
23
|
+
/>
|
|
24
|
+
<Tooltip title="Quantity">
|
|
25
|
+
<ListItemText
|
|
26
|
+
sx={{ width: "20%" }}
|
|
27
|
+
primary={`${line.quantity} st`}
|
|
28
|
+
/>
|
|
29
|
+
</Tooltip>
|
|
30
|
+
<ListItemText
|
|
31
|
+
disableTypography
|
|
32
|
+
sx={{ width: "30%" }}
|
|
33
|
+
primary={
|
|
34
|
+
<Tooltip title="Price">
|
|
35
|
+
<Typography>{`${line.total_amount} SEK`}</Typography>
|
|
36
|
+
</Tooltip>
|
|
37
|
+
}
|
|
38
|
+
secondary={
|
|
39
|
+
<>
|
|
40
|
+
<Tooltip title="Tax">
|
|
41
|
+
<Typography>{line.total_tax_amount} SEK</Typography>
|
|
42
|
+
</Tooltip>
|
|
43
|
+
<Tooltip title="Discount">
|
|
44
|
+
<Typography>{line.total_discount_amount} SEK</Typography>
|
|
45
|
+
</Tooltip>
|
|
46
|
+
</>
|
|
47
|
+
}
|
|
48
|
+
/>
|
|
49
|
+
</ListItem>
|
|
50
|
+
);
|
|
51
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { RouterExtension } from "../../router/Router";
|
|
2
|
+
import { PageComponent } from "../../types";
|
|
3
|
+
import PurchaseDetailPage from "./pages/purchase/detail";
|
|
4
|
+
import PurchaseListPage from "./pages/purchase/list";
|
|
5
|
+
|
|
6
|
+
export const posRouterExtension: RouterExtension = {
|
|
7
|
+
app: "pos",
|
|
8
|
+
pages: (route) => {
|
|
9
|
+
if (route.view === "purchase") {
|
|
10
|
+
switch (route.action) {
|
|
11
|
+
case "list":
|
|
12
|
+
return PurchaseListPage as PageComponent;
|
|
13
|
+
case "detail":
|
|
14
|
+
return PurchaseDetailPage as PageComponent;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
throw new Error("Unknown pos page");
|
|
19
|
+
},
|
|
20
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { PurchaseDetail } from "../../types/purchase";
|
|
3
|
+
import { ReceiptCard } from "../../components/ReceiptCard";
|
|
4
|
+
import Content from "../../../../containers/Content";
|
|
5
|
+
|
|
6
|
+
interface PurchaseDetailPageProps {
|
|
7
|
+
data: PurchaseDetail;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const PurchaseDetailPage: React.FC<PurchaseDetailPageProps> = (
|
|
11
|
+
{ data },
|
|
12
|
+
) => {
|
|
13
|
+
return (
|
|
14
|
+
<Content>
|
|
15
|
+
{data?.receipts.map((receipt, i) => (
|
|
16
|
+
<ReceiptCard key={i} receipt={receipt} />
|
|
17
|
+
))}
|
|
18
|
+
</Content>
|
|
19
|
+
);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export default PurchaseDetailPage;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import Tab from "@mui/material/Tab";
|
|
3
|
+
import TableContainer from "@mui/material/TableContainer";
|
|
4
|
+
import Table from "@mui/material/Table";
|
|
5
|
+
import TableHead from "@mui/material/TableHead";
|
|
6
|
+
import TableBody from "@mui/material/TableBody";
|
|
7
|
+
import TableCell from "@mui/material/TableCell";
|
|
8
|
+
import Box from "@mui/material/Box";
|
|
9
|
+
import TabPanel from "@mui/lab/TabPanel";
|
|
10
|
+
import { PurchaseRow } from "../../components/PurchaseRow";
|
|
11
|
+
import TabList from "@mui/lab/TabList";
|
|
12
|
+
import TabContext from "@mui/lab/TabContext";
|
|
13
|
+
import { Purchase } from "../../types/purchase";
|
|
14
|
+
import Paper from "@mui/material/Paper";
|
|
15
|
+
import TableRow from "@mui/material/TableRow";
|
|
16
|
+
import Content from "../../../../containers/Content";
|
|
17
|
+
|
|
18
|
+
interface PurchaseListPageProps {
|
|
19
|
+
data: {
|
|
20
|
+
results: Purchase[];
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const PurchaseListPage: React.FC<PurchaseListPageProps> = ({ data }) => {
|
|
25
|
+
return (
|
|
26
|
+
<Content>
|
|
27
|
+
<Box sx={{ width: "100%" }}>
|
|
28
|
+
<TableContainer component={Paper}>
|
|
29
|
+
<Table>
|
|
30
|
+
<TableHead>
|
|
31
|
+
<TableRow>
|
|
32
|
+
<TableCell>Purchase#</TableCell>
|
|
33
|
+
<TableCell>Kund</TableCell>
|
|
34
|
+
<TableCell>Adress</TableCell>
|
|
35
|
+
<TableCell>Ordervärde</TableCell>
|
|
36
|
+
<TableCell>Betalstatus</TableCell>
|
|
37
|
+
<TableCell>Orderdatum</TableCell>
|
|
38
|
+
<TableCell>Leveransdatum</TableCell>
|
|
39
|
+
</TableRow>
|
|
40
|
+
</TableHead>
|
|
41
|
+
<TableBody>
|
|
42
|
+
{data?.results.map((purchase) => (
|
|
43
|
+
<PurchaseRow
|
|
44
|
+
key={purchase.number}
|
|
45
|
+
purchase={purchase}
|
|
46
|
+
/>
|
|
47
|
+
))}
|
|
48
|
+
</TableBody>
|
|
49
|
+
</Table>
|
|
50
|
+
</TableContainer>
|
|
51
|
+
</Box>
|
|
52
|
+
</Content>
|
|
53
|
+
);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export default PurchaseListPage;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Receipt } from "./receipt";
|
|
2
|
+
|
|
3
|
+
export interface Purchase {
|
|
4
|
+
number: string;
|
|
5
|
+
checkout_session?: string;
|
|
6
|
+
main_payment_identifier?: string;
|
|
7
|
+
date_initiated: string;
|
|
8
|
+
date_confirmed: string;
|
|
9
|
+
currency?: string;
|
|
10
|
+
site_code: string;
|
|
11
|
+
locale: string;
|
|
12
|
+
country_code: string;
|
|
13
|
+
email: string;
|
|
14
|
+
phone?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface PurchaseDetail {
|
|
18
|
+
purchase: Purchase;
|
|
19
|
+
receipts: Receipt[];
|
|
20
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export interface Receipt {
|
|
2
|
+
number: string;
|
|
3
|
+
total_amount: string;
|
|
4
|
+
total_discount_amount: string;
|
|
5
|
+
total_tax_amount: string;
|
|
6
|
+
date_settled?: string;
|
|
7
|
+
/**
|
|
8
|
+
* | | Intention |
|
|
9
|
+
* | - | ------------ |
|
|
10
|
+
* | 1 | payment |
|
|
11
|
+
* | 2 | cancellation |
|
|
12
|
+
* | 3 | refund |
|
|
13
|
+
*/
|
|
14
|
+
intention: 1 | 2 | 3;
|
|
15
|
+
lines: ReceiptLine[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ReceiptLine {
|
|
19
|
+
reference: string;
|
|
20
|
+
title: string;
|
|
21
|
+
line_number: number;
|
|
22
|
+
item_type: string;
|
|
23
|
+
quantity: number;
|
|
24
|
+
unit_price: string;
|
|
25
|
+
total_amount: string;
|
|
26
|
+
total_discount_amount: string;
|
|
27
|
+
discounts: ReceiptLineDiscount[];
|
|
28
|
+
tax_rate: string;
|
|
29
|
+
tax_code: string;
|
|
30
|
+
total_tax_amount: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface ReceiptLineDiscount {
|
|
34
|
+
title: string;
|
|
35
|
+
amount: string;
|
|
36
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { useSnackbar } from "notistack";
|
|
2
|
+
import React from "react";
|
|
3
|
+
|
|
4
|
+
import Box from "@mui/material/Box";
|
|
5
|
+
import Button from "@mui/material/Button";
|
|
6
|
+
import DialogActions from "@mui/material/DialogActions";
|
|
7
|
+
import DialogContent from "@mui/material/DialogContent";
|
|
8
|
+
import Stack from "@mui/material/Stack";
|
|
9
|
+
import { useTheme } from "@mui/material/styles";
|
|
10
|
+
import TextField from "@mui/material/TextField";
|
|
11
|
+
|
|
12
|
+
import { useApi } from "../contexts/ApiContext";
|
|
13
|
+
import { useI18n } from "../contexts/I18nContext";
|
|
14
|
+
import { useUser } from "../contexts/UserContext";
|
|
15
|
+
import LoadingButton from "@mui/lab/LoadingButton";
|
|
16
|
+
|
|
17
|
+
const LoginForm: React.FC = () => {
|
|
18
|
+
const theme = useTheme();
|
|
19
|
+
const { enqueueSnackbar } = useSnackbar();
|
|
20
|
+
const { t } = useI18n();
|
|
21
|
+
const { login } = useUser();
|
|
22
|
+
const api = useApi();
|
|
23
|
+
|
|
24
|
+
const [username, setUsername] = React.useState("");
|
|
25
|
+
const [password, setPassword] = React.useState("");
|
|
26
|
+
const [loading, setLoading] = React.useState(false);
|
|
27
|
+
const operation = api.operations["bananas.login:create"];
|
|
28
|
+
const { properties } = operation.request.body.schema;
|
|
29
|
+
|
|
30
|
+
const handleSubmit = (event: React.FormEvent) => {
|
|
31
|
+
event.preventDefault();
|
|
32
|
+
setLoading(true);
|
|
33
|
+
login(username, password).then((user) => {
|
|
34
|
+
if (user !== null) {
|
|
35
|
+
enqueueSnackbar(`${t("Welcome,")} ${user.full_name}`, {
|
|
36
|
+
variant: "success",
|
|
37
|
+
});
|
|
38
|
+
} else {
|
|
39
|
+
enqueueSnackbar(t("Unable to log in with provided credentials."), {
|
|
40
|
+
variant: "error",
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}).finally(() => setLoading(false));
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<Box component="form" onSubmit={handleSubmit}>
|
|
48
|
+
<DialogContent>
|
|
49
|
+
<Stack spacing={1}>
|
|
50
|
+
<TextField
|
|
51
|
+
autoFocus
|
|
52
|
+
fullWidth
|
|
53
|
+
label={properties.username.title}
|
|
54
|
+
name="username"
|
|
55
|
+
type="text"
|
|
56
|
+
onChange={(e) => {
|
|
57
|
+
setUsername(e.target.value);
|
|
58
|
+
}}
|
|
59
|
+
color={"secondary"}
|
|
60
|
+
inputProps={{ "aria-label": "Username" }}
|
|
61
|
+
/>
|
|
62
|
+
<TextField
|
|
63
|
+
fullWidth
|
|
64
|
+
label={properties.password.title}
|
|
65
|
+
name="password"
|
|
66
|
+
type="password"
|
|
67
|
+
onChange={(e) => {
|
|
68
|
+
setPassword(e.target.value);
|
|
69
|
+
}}
|
|
70
|
+
color="secondary"
|
|
71
|
+
inputProps={{ "aria-label": "Password" }}
|
|
72
|
+
/>
|
|
73
|
+
</Stack>
|
|
74
|
+
</DialogContent>
|
|
75
|
+
<DialogActions
|
|
76
|
+
sx={{
|
|
77
|
+
borderTop: `1px solid ${theme.palette.divider}`,
|
|
78
|
+
margin: 0,
|
|
79
|
+
padding: theme.spacing(1),
|
|
80
|
+
}}
|
|
81
|
+
>
|
|
82
|
+
<LoadingButton
|
|
83
|
+
sx={{
|
|
84
|
+
margin: "auto",
|
|
85
|
+
}}
|
|
86
|
+
variant="contained"
|
|
87
|
+
type="submit"
|
|
88
|
+
color="secondary"
|
|
89
|
+
aria-label="login"
|
|
90
|
+
loading={loading}
|
|
91
|
+
>
|
|
92
|
+
{t("Log in")}
|
|
93
|
+
</LoadingButton>
|
|
94
|
+
</DialogActions>
|
|
95
|
+
</Box>
|
|
96
|
+
);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
export default LoginForm;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
export default function useLocalStorage<T>(key: string, defaultValue: T) {
|
|
4
|
+
const [state, setState] = React.useState<T>(getState(key) ?? defaultValue);
|
|
5
|
+
const [mounted, setMounted] = React.useState(false);
|
|
6
|
+
React.useEffect(() => {
|
|
7
|
+
if (mounted) return;
|
|
8
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
9
|
+
if (localStorage) {
|
|
10
|
+
const value = getState<T>(key);
|
|
11
|
+
if (value) setState(value);
|
|
12
|
+
setMounted(true);
|
|
13
|
+
}
|
|
14
|
+
}, [key, mounted]);
|
|
15
|
+
|
|
16
|
+
React.useEffect(() => {
|
|
17
|
+
if (mounted) setLocalState(key, state);
|
|
18
|
+
}, [state, key, mounted]);
|
|
19
|
+
|
|
20
|
+
return [state, setState] as const;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function getState<T>(key: string): T | null {
|
|
24
|
+
return tryJSONParse<T>(localStorage.getItem(buildStorageKey(key)));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function setLocalState<T>(key: string, state: T) {
|
|
28
|
+
localStorage.setItem(buildStorageKey(key), JSON.stringify(state));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function buildStorageKey(key: string) {
|
|
32
|
+
return `__useLocalStorage_${key}`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function tryJSONParse<T>(json: string | null | undefined): T | null {
|
|
36
|
+
if (!json) return null;
|
|
37
|
+
try {
|
|
38
|
+
return JSON.parse(json) as T;
|
|
39
|
+
} catch (e) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export { default as ProgressBar } from "./components/ProgressBar";
|
|
2
|
+
export { default as Content } from "./containers/Content";
|
|
3
|
+
export { default as Logo } from "./components/Logo";
|
|
4
|
+
export { default as Link } from "./components/Link";
|
|
5
|
+
|
|
6
|
+
export * from "./contexts/ApiContext";
|
|
7
|
+
export * from "./contexts/I18nContext";
|
|
8
|
+
export * from "./contexts/RouterContext";
|
|
9
|
+
export * from "./contexts/UserContext";
|
|
10
|
+
|
|
11
|
+
export * from "./containers/LoadingScreen";
|
|
12
|
+
|
|
13
|
+
export { default as Admin } from "./Admin";
|
|
14
|
+
export { default as App } from "./App";
|
|
15
|
+
export * from "./api";
|
|
16
|
+
|
|
17
|
+
export * from "./extensions/bananas";
|
|
18
|
+
export * from "./extensions/pos";
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Dialog, DialogTitle, Typography } from "@mui/material";
|
|
3
|
+
import { useTheme } from "@mui/material/styles";
|
|
4
|
+
import LoginForm from "../forms/LoginForm";
|
|
5
|
+
import Logo from "../components/Logo";
|
|
6
|
+
import { LogoType } from "../types";
|
|
7
|
+
|
|
8
|
+
interface LoginPageProps {
|
|
9
|
+
title?: string;
|
|
10
|
+
logo?: LogoType;
|
|
11
|
+
form?: React.ElementType;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const LoginPage: React.FC<LoginPageProps> = (
|
|
15
|
+
{ title, logo, form: Form },
|
|
16
|
+
) => {
|
|
17
|
+
const theme = useTheme();
|
|
18
|
+
Form ??= LoginForm;
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<Dialog open sx={{ "> * > *": { width: "100%" } }}>
|
|
22
|
+
<DialogTitle
|
|
23
|
+
sx={{
|
|
24
|
+
borderBottom: `1px solid ${theme.palette.divider}`,
|
|
25
|
+
margin: 0,
|
|
26
|
+
backgroundColor: theme.palette.primary.main,
|
|
27
|
+
padding: theme.spacing(2),
|
|
28
|
+
textAlign: "center",
|
|
29
|
+
alignItems: "middle",
|
|
30
|
+
justifyContent: "center",
|
|
31
|
+
display: "flex",
|
|
32
|
+
}}
|
|
33
|
+
>
|
|
34
|
+
{logo ? <Logo src={logo} /> : (
|
|
35
|
+
<Typography
|
|
36
|
+
sx={{
|
|
37
|
+
color: theme.palette.primary.contrastText,
|
|
38
|
+
fontWeight: "bold",
|
|
39
|
+
}}
|
|
40
|
+
>
|
|
41
|
+
{title}
|
|
42
|
+
</Typography>
|
|
43
|
+
)}
|
|
44
|
+
</DialogTitle>
|
|
45
|
+
<Form />
|
|
46
|
+
</Dialog>
|
|
47
|
+
);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export default LoginPage;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import Box from "@mui/material/Box";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { Route, Routes } from "react-router-dom";
|
|
4
|
+
|
|
5
|
+
import { RouteInfo, useRouter } from "../contexts/RouterContext";
|
|
6
|
+
import DashboardPage from "../pages/DashboardPage";
|
|
7
|
+
import PageLoader from "../containers/PageLoader";
|
|
8
|
+
import { PageComponent } from "../types";
|
|
9
|
+
import PageErrorBoundary from "../containers/PageErrorBoundary";
|
|
10
|
+
import ErrorScreen from "../containers/ErrorScreen";
|
|
11
|
+
|
|
12
|
+
export interface RouterExtension {
|
|
13
|
+
app: string;
|
|
14
|
+
pages: (route: RouteInfo) => PageComponent | Promise<PageComponent>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface RouterProps {
|
|
18
|
+
dashboard?: React.ComponentType;
|
|
19
|
+
extensions?: RouterExtension[];
|
|
20
|
+
pages: (route: RouteInfo) => PageComponent | Promise<PageComponent>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const Router: React.FC<RouterProps> = (
|
|
24
|
+
{ dashboard, extensions, pages },
|
|
25
|
+
) => {
|
|
26
|
+
extensions ??= Array.isArray(extensions) ? extensions : [];
|
|
27
|
+
|
|
28
|
+
const { routes } = useRouter();
|
|
29
|
+
const Dashboard = dashboard ?? DashboardPage;
|
|
30
|
+
|
|
31
|
+
const extensionsMap = new Map(
|
|
32
|
+
extensions.map(({ app, pages }) => [app, pages]),
|
|
33
|
+
);
|
|
34
|
+
const pageRoutes = routes.map((route) => {
|
|
35
|
+
const pageLoader = extensionsMap.get(route.app)!;
|
|
36
|
+
const Element = () => (
|
|
37
|
+
<PageErrorBoundary errorPage={ErrorScreen}>
|
|
38
|
+
<PageLoader
|
|
39
|
+
route={route}
|
|
40
|
+
page={pageLoader(route)}
|
|
41
|
+
/>
|
|
42
|
+
</PageErrorBoundary>
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<Route
|
|
47
|
+
key={route.path}
|
|
48
|
+
path={route.path}
|
|
49
|
+
element={<Element />}
|
|
50
|
+
/>
|
|
51
|
+
);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<Box component="main" sx={{ flexGrow: 1, p: 3 }}>
|
|
56
|
+
<Routes>
|
|
57
|
+
<Route index element={<Dashboard />} />
|
|
58
|
+
|
|
59
|
+
{pageRoutes}
|
|
60
|
+
</Routes>
|
|
61
|
+
</Box>
|
|
62
|
+
);
|
|
63
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { capitalize, nthIndexOf } from "../util";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Parses an `operationId` returning app, view and action
|
|
5
|
+
*
|
|
6
|
+
* ## Examples
|
|
7
|
+
*
|
|
8
|
+
* `bananas.me:list` -> `{ app: "bananas", view: "me", action: "list" }`
|
|
9
|
+
*
|
|
10
|
+
* `bananas.login:create` -> `{ app: "bananas", view: "login", action: "create" }`
|
|
11
|
+
*
|
|
12
|
+
* `bananas.i18n:list` -> `{ app: "bananas", view: "i18n", action: "list" }`
|
|
13
|
+
*/
|
|
14
|
+
export function parseOperationId(
|
|
15
|
+
operationId: string,
|
|
16
|
+
): { app: string; view: string; action: string } | undefined {
|
|
17
|
+
const match = operationId.match(/^(\w+)\.(\w+):(\w+)$/);
|
|
18
|
+
|
|
19
|
+
if (match !== null) {
|
|
20
|
+
return {
|
|
21
|
+
app: match[1],
|
|
22
|
+
view: match[2],
|
|
23
|
+
action: match[3],
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function isNavigation(tags: string[]): boolean {
|
|
31
|
+
return tags.includes("navigation");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function getPath(
|
|
35
|
+
endpoint: string,
|
|
36
|
+
method: string,
|
|
37
|
+
action: string | undefined,
|
|
38
|
+
): string {
|
|
39
|
+
return (method.toLowerCase() === "get"
|
|
40
|
+
? endpoint
|
|
41
|
+
: `${endpoint}${action ?? ""}/`).replace(/{([^}]*)}/, ":$1");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function getBasePath(path: string): string {
|
|
45
|
+
return path.substring(0, (nthIndexOf(path, "/", 2, 1) + 1) || undefined);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function getPage(
|
|
49
|
+
path: string,
|
|
50
|
+
action: string | undefined,
|
|
51
|
+
): string {
|
|
52
|
+
const basePath = getBasePath(path);
|
|
53
|
+
const relativeBasePath = basePath.slice(1);
|
|
54
|
+
return `${relativeBasePath}${action}`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function getAppLabel(tags: string[]): string {
|
|
58
|
+
return tags.filter((tag) => tag.startsWith("app:"))[0]?.split(":")[1];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function getView(id: string): string {
|
|
62
|
+
return id.substring(id.indexOf(".") + 1, id.indexOf(":"));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function getTitle(view: string, summary?: string): string {
|
|
66
|
+
return (summary ?? (view && capitalize(view.replace("_", " "))));
|
|
67
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
declare module "swagger-client";
|