basic POS
This commit is contained in:
parent
b58358b37a
commit
43b98d6d0d
|
@ -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>
|
|
@ -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>
|
|
@ -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
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 New Issue