basic POS
This commit is contained in:
parent
b58358b37a
commit
43b98d6d0d
10 changed files with 2012 additions and 17 deletions
frontend
|
@ -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": {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
61
frontend/src/lib/components/QRJS.svelte
Normal file
61
frontend/src/lib/components/QRJS.svelte
Normal 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>
|
||||||
|
|
|
@ -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>
|
|
@ -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>
|
||||||
|
@ -309,4 +336,27 @@
|
||||||
<ButtonLabel>I'm sure</ButtonLabel>
|
<ButtonLabel>I'm sure</ButtonLabel>
|
||||||
</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>
|
8
frontend/src/routes/manage/products/style.scss
Normal file
8
frontend/src/routes/manage/products/style.scss
Normal 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
1717
frontend/yarn-error.log
Normal file
File diff suppressed because it is too large
Load diff
|
@ -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"
|
||||||
|
|
Loading…
Add table
Reference in a new issue