basic POS

This commit is contained in:
core 2023-06-17 23:32:02 -04:00
parent b58358b37a
commit 43b98d6d0d
Signed by: core
GPG Key ID: FDBF740DADDCEECF
10 changed files with 2012 additions and 17 deletions

View File

@ -13,6 +13,7 @@
"smui-theme-dark": "smui-theme compile static/smui-dark.css -i src/theme/dark" "smui-theme-dark": "smui-theme compile static/smui-dark.css -i src/theme/dark"
}, },
"devDependencies": { "devDependencies": {
"@material/typography": "^14.0.0",
"@smui/button": "^7.0.0-beta.8", "@smui/button": "^7.0.0-beta.8",
"@smui/card": "^7.0.0-beta.8", "@smui/card": "^7.0.0-beta.8",
"@smui/circular-progress": "^7.0.0-beta.8", "@smui/circular-progress": "^7.0.0-beta.8",
@ -23,6 +24,7 @@
"@smui/paper": "^7.0.0-beta.8", "@smui/paper": "^7.0.0-beta.8",
"@smui/snackbar": "^7.0.0-beta.8", "@smui/snackbar": "^7.0.0-beta.8",
"@smui/textfield": "^7.0.0-beta.8", "@smui/textfield": "^7.0.0-beta.8",
"@smui/tooltip": "^7.0.0-beta.8",
"@smui/touch-target": "^7.0.0-beta.8", "@smui/touch-target": "^7.0.0-beta.8",
"@sveltejs/adapter-auto": "^2.0.0", "@sveltejs/adapter-auto": "^2.0.0",
"@sveltejs/kit": "^1.5.0", "@sveltejs/kit": "^1.5.0",
@ -33,5 +35,6 @@
"typescript": "^5.0.0", "typescript": "^5.0.0",
"vite": "^4.3.0" "vite": "^4.3.0"
}, },
"type": "module" "type": "module",
"dependencies": {}
} }

View File

@ -7,19 +7,19 @@
<div class="header"> <div class="header">
<Wrapper> <Wrapper>
<Button on:click={() => {window.location.href = "/"}} disabled={selected === "pos"}> <Button on:click={() => {window.location.href = "/"}} disabled={selected === "pos"} class={selected === "pos" ? "underline" : ""}>
<Label>PoS</Label> <Label>PoS</Label>
</Button> </Button>
</Wrapper> </Wrapper>
<Wrapper> <Wrapper>
<Button on:click={() => {window.location.href = "/manage/products"}} disabled={selected === "manage/products"}> <Button on:click={() => {window.location.href = "/manage/products"}} disabled={selected === "manage/products"} class={selected === "manage/products" ? "underline" : ""}>
<Label>Manage Products</Label> <Label>Manage Products</Label>
</Button> </Button>
</Wrapper> </Wrapper>
<Wrapper> <Wrapper>
<Button on:click={() => {window.location.href = "/manage/env"}} disabled={selected === "manage/env"}> <Button on:click={() => {window.location.href = "/manage/env"}} disabled={selected === "manage/env"} class={selected === "manage/env" ? "underline" : ""}>
<Label>Manage Env</Label> <Label>Manage Env</Label>
</Button> </Button>
</Wrapper> </Wrapper>
@ -28,6 +28,9 @@
<style> <style>
.header { .header {
width: 100%; width: 100%;
}
* :global(.underline) {
text-decoration: underline;
} }
</style> </style>

View File

@ -0,0 +1,61 @@
<script>
import { onMount } from 'svelte';
export let codeValue;
export let squareSize;
let qrcode;
let loaded = false;
onMount(() => {
let script = document.createElement('script');
script.src = "https://cdn.rawgit.com/davidshimjs/qrcodejs/gh-pages/qrcode.min.js"
document.head.append(script);
script.onload = function() {
qrcode = new QRCode("qrcode", {
text: codeValue,
width: squareSize,
height: squareSize,
colorDark : "#000000",
colorLight : "#ffffff",
correctLevel : QRCode.CorrectLevel.H
});
loaded = true;
};
});
$: codeValue, reloadQrcode();
function reloadQrcode() {
if (!loaded) {
setTimeout(reloadQrcode, 200);
return;
}
document.getElementById("qrcode").innerHTML = "";
qrcode = new QRCode("qrcode", {
text: codeValue,
width: squareSize,
height: squareSize,
colorDark : "#000000",
colorLight : "#ffffff",
correctLevel : QRCode.CorrectLevel.H
});
}
</script>
<style>
#qrcode {
width:20px;
height:200x;
margin-top:15px;
}
</style>
<div id="qrcode"></div>

View File

@ -1,5 +1,133 @@
<script lang="ts"> <script lang="ts">
import Header from "$lib/components/Header.svelte"; import Header from "$lib/components/Header.svelte";
import {serverUrl} from "$lib/stores/ServerStore";
import Snackbar, {Label as SnackbarLabel} from "@smui/snackbar";
import {onMount} from "svelte";
import Button, {Label as ButtonLabel, Group as ButtonGroup} from "@smui/button";
import DataTable, { Head, Body, Row, Cell } from '@smui/data-table';
import LinearProgress from "@smui/linear-progress";
let products = [];
let productsCounter = {};
let failureSnackbar: Snackbar;
let failureSnackbarText = "";
let loaded;
const formatter = new Intl.NumberFormat('en-US', {style: 'currency', currency: 'USD'});
// load products from the backend
async function loadProducts() {
loaded = false;
try {
let resp = await fetch(`${$serverUrl}/products`);
if (resp.ok) {
let json = await resp.json();
console.log(json);
products = json.products;
for (let i = 0; i < products.length; i++) {
productsCounter[products[i].id] = 0;
}
loaded = true;
} else {
console.error(await resp.text());
failureSnackbarText = "There was an error loading the products list. Check the browser console for more information.";
failureSnackbar.open();
return;
}
} catch (e) {
console.error(e);
failureSnackbarText = "There was an error loading the products list. Check the browser console for more information.";
failureSnackbar.open();
return;
}
}
let total;
$: productsCounter, reTotalPrice();
function reTotalPrice() {
total = 0.0;
for (let i = 0; i < products.length; i++) {
total += productsCounter[products[i].id] * products[i].price_usd;
}
}
onMount(() => {
loadProducts();
})
</script> </script>
<Header selected="pos" /> <Header selected="pos" />
<Snackbar bind:this={failureSnackbar}>
<SnackbarLabel>{failureSnackbarText}</SnackbarLabel>
</Snackbar>
<DataTable table$aria-label="Product list" style="width: 100%;">
<Head>
<Row>
<Cell numeric>ID</Cell>
<Cell style="width: 70%;">Name</Cell>
<Cell>Price Ea</Cell>
<Cell>Stock Remaining</Cell>
<Cell>In Order</Cell>
<Cell>Order Price</Cell>
<Cell>
Actions
</Cell>
</Row>
</Head>
<Body>
{#each products as product (product.id)}
<Row>
<Cell numeric>{product.id}</Cell>
<Cell>{product.name}</Cell>
<Cell>{formatter.format(product.price_usd)}</Cell>
<Cell>{product.stock}</Cell>
<Cell>{productsCounter[product.id]}</Cell>
<Cell>{formatter.format(productsCounter[product.id] * product.price_usd)}</Cell>
<Cell>
<ButtonGroup>
<Button variant="outlined" on:click={() => {productsCounter[product.id]++}}>
<ButtonLabel>+</ButtonLabel>
</Button>
<Button variant="outlined" on:click={() => {if (productsCounter[product.id] >= 1) {productsCounter[product.id]--}}}>
<ButtonLabel>-</ButtonLabel>
</Button>
<Button variant="outlined" on:click={() => {productsCounter[product.id] = 0}}>
<ButtonLabel>Clear</ButtonLabel>
</Button>
</ButtonGroup>
</Cell>
</Row>
{/each}
<Row>
<Cell numeric></Cell>
<Cell></Cell>
<Cell></Cell>
<Cell></Cell>
<Cell><b>Total</b></Cell>
<Cell>
{formatter.format(total)}
</Cell>
<Cell>
<ButtonGroup>
<Button variant="outlined">
<ButtonLabel>Cash In</ButtonLabel>
</Button>
<Button variant="outlined">
<ButtonLabel>Stripe</ButtonLabel>
</Button>
<Button variant="unelevated" on:click={() => {for (let i = 0; i < products.length; i++) {productsCounter[products[i].id] = 0}}}>
<ButtonLabel>Clear All</ButtonLabel>
</Button>
</ButtonGroup>
</Cell>
</Row>
</Body>
<LinearProgress indeterminate bind:closed={loaded} aria-label="Data is being loaded..." slot="progress" />
</DataTable>

View File

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import Header from "$lib/components/Header.svelte"; import Header from "$lib/components/Header.svelte";
import QRCode from "$lib/components/QRJS.svelte";
import DataTable, { Head, Body, Row, Cell } from '@smui/data-table'; import DataTable, { Head, Body, Row, Cell } from '@smui/data-table';
import IconButton from "@smui/icon-button"; import IconButton from "@smui/icon-button";
import LinearProgress from '@smui/linear-progress'; import LinearProgress from '@smui/linear-progress';
@ -9,8 +9,11 @@
import {serverUrl} from "$lib/stores/ServerStore"; import {serverUrl} from "$lib/stores/ServerStore";
import Textfield from "@smui/textfield"; import Textfield from "@smui/textfield";
import HelperText from "@smui/textfield/helper-text"; import HelperText from "@smui/textfield/helper-text";
import Button, {Label as ButtonLabel} from "@smui/button"; import Button, {Label as ButtonLabel, Icon as ButtonIcon} from "@smui/button";
import Dialog, {Actions, Title as DialogTitle, Content as DialogContent} from "@smui/dialog"; import Dialog, {Actions, Title as DialogTitle, Content as DialogContent, Header as DialogHeader} from "@smui/dialog";
import Tooltip, { Wrapper as TooltipWrapper } from "@smui/tooltip";
import "./style.scss";
let loaded = false; let loaded = false;
let products = []; let products = [];
@ -202,10 +205,24 @@
} }
} }
let qrcodeOpen = false;
let qrcodeContent = "";
function qrcodeProduct(id) {
qrcodeContent = `LMSPOS-${id}`;
qrcodeOpen = true;
}
</script> </script>
<Header selected="manage/products"></Header> <Header selected="manage/products"></Header>
<div class="titlebar">
<Button class="btn" on:click={createProduct} variant="outlined">
<ButtonLabel>Create Product</ButtonLabel>
<ButtonIcon class="material-icons">add</ButtonIcon>
</Button>
</div>
<DataTable table$aria-label="Product list" style="width: 100%;"> <DataTable table$aria-label="Product list" style="width: 100%;">
<Head> <Head>
<Row> <Row>
@ -226,8 +243,20 @@
<Cell>{formatter.format(product.price_usd)}</Cell> <Cell>{formatter.format(product.price_usd)}</Cell>
<Cell>{product.stock}</Cell> <Cell>{product.stock}</Cell>
<Cell> <Cell>
<IconButton class="material-icons" on:click={() => {editProduct(product)}}>edit</IconButton> <TooltipWrapper>
<IconButton style="color: #f44336;" class="material-icons" on:click={() => {deleteProduct(product.id)}}>delete</IconButton> <IconButton class="material-icons" on:click={() => {editProduct(product)}}>edit</IconButton>
<Tooltip>Edit product</Tooltip>
</TooltipWrapper>
<TooltipWrapper>
<IconButton style="color: #f44336;" class="material-icons" on:click={() => {deleteProduct(product.id)}}>delete</IconButton>
<Tooltip>Delete product</Tooltip>
</TooltipWrapper>
<TooltipWrapper>
<IconButton class="material-icons" on:click={() => {qrcodeProduct(product.id)}}>qr_code_scanner</IconButton>
<Tooltip>Show QR code</Tooltip>
</TooltipWrapper>
</Cell> </Cell>
</Row> </Row>
{/each} {/each}
@ -236,9 +265,7 @@
<LinearProgress indeterminate bind:closed={loaded} aria-label="Data is being loaded..." slot="progress" /> <LinearProgress indeterminate bind:closed={loaded} aria-label="Data is being loaded..." slot="progress" />
</DataTable> </DataTable>
<Button on:click={createProduct}>
<ButtonLabel>Create Product</ButtonLabel>
</Button>
<Snackbar bind:this={failureSnackbar}> <Snackbar bind:this={failureSnackbar}>
<SnackbarLabel>{failureSnackbarText}</SnackbarLabel> <SnackbarLabel>{failureSnackbarText}</SnackbarLabel>
@ -310,3 +337,26 @@
</Button> </Button>
</Actions> </Actions>
</Dialog> </Dialog>
<Dialog bind:open={qrcodeOpen} aria-labelledby="qrcode-title" aria-describedby="qrcode-content">
<DialogHeader>
<DialogTitle id="qrcode-title">QRCode</DialogTitle>
</DialogHeader>
<DialogContent id="qrcode-content">
<QRCode codeValue={qrcodeContent} squareSize="230" />
</DialogContent>
<Actions>
<Button>
<ButtonLabel>Close</ButtonLabel>
</Button>
</Actions>
</Dialog>
<style>
.titlebar {
width: 100%;
}
.btn {
float: right;
}
</style>

View File

@ -0,0 +1,8 @@
@use '@material/typography/mdc-typography';
.btn {
float: right;
display: inline-block;
vertical-align: middle;
margin-bottom: 15px;
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1717
frontend/yarn-error.log Normal file

File diff suppressed because it is too large Load Diff

View File

@ -532,6 +532,22 @@
dependencies: dependencies:
"@material/elevation" "^14.0.0" "@material/elevation" "^14.0.0"
"@material/tooltip@^14.0.0":
version "14.0.0"
resolved "https://registry.yarnpkg.com/@material/tooltip/-/tooltip-14.0.0.tgz#16bc9277bd347e581c0fd23da29ef9ff3a463431"
integrity sha512-rp7sOuVE1hmg4VgBJMnSvtDbSzctL42X7y1yv8ukuu40Sli+H5FT0Zbn351EfjJgQWg/AlXA6+reVXkXje8JzQ==
dependencies:
"@material/animation" "^14.0.0"
"@material/base" "^14.0.0"
"@material/dom" "^14.0.0"
"@material/elevation" "^14.0.0"
"@material/feature-targeting" "^14.0.0"
"@material/rtl" "^14.0.0"
"@material/shape" "^14.0.0"
"@material/theme" "^14.0.0"
"@material/typography" "^14.0.0"
tslib "^2.1.0"
"@material/touch-target@^14.0.0": "@material/touch-target@^14.0.0":
version "14.0.0" version "14.0.0"
resolved "https://registry.yarnpkg.com/@material/touch-target/-/touch-target-14.0.0.tgz#66b0b61ff14975946cdbf6fad6627bcbc025423d" resolved "https://registry.yarnpkg.com/@material/touch-target/-/touch-target-14.0.0.tgz#66b0b61ff14975946cdbf6fad6627bcbc025423d"
@ -812,6 +828,15 @@
"@smui/ripple" "^7.0.0-beta.8" "@smui/ripple" "^7.0.0-beta.8"
svelte2tsx "^0.6.10" svelte2tsx "^0.6.10"
"@smui/tooltip@^7.0.0-beta.8":
version "7.0.0-beta.8"
resolved "https://registry.yarnpkg.com/@smui/tooltip/-/tooltip-7.0.0-beta.8.tgz#bfba7618baadb3b5ab405038f05f3b2c153f347c"
integrity sha512-nECLWPBhEcCBtrLe0HWnykrN+VbNzNaFE/NaqTnkOxGR2RPzqlBsbplQvP76fHASZm3oFHiP3Wo4vmMvbmES+g==
dependencies:
"@material/tooltip" "^14.0.0"
"@smui/common" "^7.0.0-beta.8"
svelte2tsx "^0.6.10"
"@smui/touch-target@^7.0.0-beta.8": "@smui/touch-target@^7.0.0-beta.8":
version "7.0.0-beta.8" version "7.0.0-beta.8"
resolved "https://registry.yarnpkg.com/@smui/touch-target/-/touch-target-7.0.0-beta.8.tgz#ff8e4168168e5fcca3fc37fb846adab3e629c819" resolved "https://registry.yarnpkg.com/@smui/touch-target/-/touch-target-7.0.0-beta.8.tgz#ff8e4168168e5fcca3fc37fb846adab3e629c819"