basic POS
This commit is contained in:
parent
b58358b37a
commit
43b98d6d0d
10 changed files with 2012 additions and 17 deletions
|
@ -13,6 +13,7 @@
|
|||
"smui-theme-dark": "smui-theme compile static/smui-dark.css -i src/theme/dark"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@material/typography": "^14.0.0",
|
||||
"@smui/button": "^7.0.0-beta.8",
|
||||
"@smui/card": "^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/snackbar": "^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",
|
||||
"@sveltejs/adapter-auto": "^2.0.0",
|
||||
"@sveltejs/kit": "^1.5.0",
|
||||
|
@ -33,5 +35,6 @@
|
|||
"typescript": "^5.0.0",
|
||||
"vite": "^4.3.0"
|
||||
},
|
||||
"type": "module"
|
||||
"type": "module",
|
||||
"dependencies": {}
|
||||
}
|
||||
|
|
|
@ -7,19 +7,19 @@
|
|||
|
||||
<div class="header">
|
||||
<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>
|
||||
</Button>
|
||||
</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>
|
||||
</Button>
|
||||
</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>
|
||||
</Button>
|
||||
</Wrapper>
|
||||
|
@ -28,6 +28,9 @@
|
|||
<style>
|
||||
.header {
|
||||
width: 100%;
|
||||
|
||||
}
|
||||
|
||||
* :global(.underline) {
|
||||
text-decoration: underline;
|
||||
}
|
||||
</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">
|
||||
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>
|
||||
|
||||
<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">
|
||||
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 IconButton from "@smui/icon-button";
|
||||
import LinearProgress from '@smui/linear-progress';
|
||||
|
@ -9,8 +9,11 @@
|
|||
import {serverUrl} from "$lib/stores/ServerStore";
|
||||
import Textfield from "@smui/textfield";
|
||||
import HelperText from "@smui/textfield/helper-text";
|
||||
import Button, {Label as ButtonLabel} from "@smui/button";
|
||||
import Dialog, {Actions, Title as DialogTitle, Content as DialogContent} from "@smui/dialog";
|
||||
import Button, {Label as ButtonLabel, Icon as ButtonIcon} from "@smui/button";
|
||||
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 products = [];
|
||||
|
@ -202,10 +205,24 @@
|
|||
}
|
||||
}
|
||||
|
||||
let qrcodeOpen = false;
|
||||
let qrcodeContent = "";
|
||||
|
||||
function qrcodeProduct(id) {
|
||||
qrcodeContent = `LMSPOS-${id}`;
|
||||
qrcodeOpen = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<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%;">
|
||||
<Head>
|
||||
<Row>
|
||||
|
@ -226,8 +243,20 @@
|
|||
<Cell>{formatter.format(product.price_usd)}</Cell>
|
||||
<Cell>{product.stock}</Cell>
|
||||
<Cell>
|
||||
<IconButton class="material-icons" on:click={() => {editProduct(product)}}>edit</IconButton>
|
||||
<IconButton style="color: #f44336;" class="material-icons" on:click={() => {deleteProduct(product.id)}}>delete</IconButton>
|
||||
<TooltipWrapper>
|
||||
<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>
|
||||
</Row>
|
||||
{/each}
|
||||
|
@ -236,9 +265,7 @@
|
|||
<LinearProgress indeterminate bind:closed={loaded} aria-label="Data is being loaded..." slot="progress" />
|
||||
</DataTable>
|
||||
|
||||
<Button on:click={createProduct}>
|
||||
<ButtonLabel>Create Product</ButtonLabel>
|
||||
</Button>
|
||||
|
||||
|
||||
<Snackbar bind:this={failureSnackbar}>
|
||||
<SnackbarLabel>{failureSnackbarText}</SnackbarLabel>
|
||||
|
@ -309,4 +336,27 @@
|
|||
<ButtonLabel>I'm sure</ButtonLabel>
|
||||
</Button>
|
||||
</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:
|
||||
"@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":
|
||||
version "14.0.0"
|
||||
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"
|
||||
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":
|
||||
version "7.0.0-beta.8"
|
||||
resolved "https://registry.yarnpkg.com/@smui/touch-target/-/touch-target-7.0.0-beta.8.tgz#ff8e4168168e5fcca3fc37fb846adab3e629c819"
|
||||
|
|
Loading…
Reference in a new issue