Compare commits
3 Commits
dcf0455772
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5f75b8658a | ||
|
|
222169be74 | ||
|
|
026813d1e0 |
89
package-lock.json
generated
89
package-lock.json
generated
@@ -18,9 +18,11 @@
|
|||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
"html-to-image": "^1.11.11",
|
"html-to-image": "^1.11.11",
|
||||||
|
"html2canvas": "^1.4.1",
|
||||||
"jsqr": "^1.4.0",
|
"jsqr": "^1.4.0",
|
||||||
"qr-scanner": "^1.4.2",
|
"qr-scanner": "^1.4.2",
|
||||||
"qrcode.react": "^3.1.0",
|
"qrcode.react": "^3.1.0",
|
||||||
|
"qs": "^6.14.0",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-apexcharts": "^1.7.0",
|
"react-apexcharts": "^1.7.0",
|
||||||
"react-bootstrap": "^2.10.4",
|
"react-bootstrap": "^2.10.4",
|
||||||
@@ -6951,6 +6953,15 @@
|
|||||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/base64-arraybuffer": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/batch": {
|
"node_modules/batch": {
|
||||||
"version": "0.6.1",
|
"version": "0.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz",
|
||||||
@@ -7051,6 +7062,21 @@
|
|||||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
|
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/body-parser/node_modules/qs": {
|
||||||
|
"version": "6.13.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
|
||||||
|
"integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"side-channel": "^1.0.6"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.6"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/bonjour-service": {
|
"node_modules/bonjour-service": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.3.0.tgz",
|
||||||
@@ -7865,6 +7891,15 @@
|
|||||||
"postcss": "^8.4"
|
"postcss": "^8.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/css-line-break": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"utrie": "^1.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/css-loader": {
|
"node_modules/css-loader": {
|
||||||
"version": "6.11.0",
|
"version": "6.11.0",
|
||||||
"resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.11.0.tgz",
|
"resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.11.0.tgz",
|
||||||
@@ -10015,6 +10050,21 @@
|
|||||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
|
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/express/node_modules/qs": {
|
||||||
|
"version": "6.13.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
|
||||||
|
"integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"side-channel": "^1.0.6"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.6"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fast-deep-equal": {
|
"node_modules/fast-deep-equal": {
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||||
@@ -11137,6 +11187,19 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/html2canvas": {
|
||||||
|
"version": "1.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz",
|
||||||
|
"integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"css-line-break": "^2.1.0",
|
||||||
|
"text-segmentation": "^1.0.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/htmlparser2": {
|
"node_modules/htmlparser2": {
|
||||||
"version": "6.1.0",
|
"version": "6.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz",
|
||||||
@@ -17681,12 +17744,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/qs": {
|
"node_modules/qs": {
|
||||||
"version": "6.13.0",
|
"version": "6.14.0",
|
||||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
|
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
|
||||||
"integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
|
"integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"side-channel": "^1.0.6"
|
"side-channel": "^1.1.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.6"
|
"node": ">=0.6"
|
||||||
@@ -20482,6 +20545,15 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/text-segmentation": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"utrie": "^1.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/text-table": {
|
"node_modules/text-table": {
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
|
||||||
@@ -21024,6 +21096,15 @@
|
|||||||
"node": ">= 0.4.0"
|
"node": ">= 0.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/utrie": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"base64-arraybuffer": "^1.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/uuid": {
|
"node_modules/uuid": {
|
||||||
"version": "8.3.2",
|
"version": "8.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||||
|
|||||||
@@ -14,9 +14,11 @@
|
|||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
"html-to-image": "^1.11.11",
|
"html-to-image": "^1.11.11",
|
||||||
|
"html2canvas": "^1.4.1",
|
||||||
"jsqr": "^1.4.0",
|
"jsqr": "^1.4.0",
|
||||||
"qr-scanner": "^1.4.2",
|
"qr-scanner": "^1.4.2",
|
||||||
"qrcode.react": "^3.1.0",
|
"qrcode.react": "^3.1.0",
|
||||||
|
"qs": "^6.14.0",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-apexcharts": "^1.7.0",
|
"react-apexcharts": "^1.7.0",
|
||||||
"react-bootstrap": "^2.10.4",
|
"react-bootstrap": "^2.10.4",
|
||||||
|
|||||||
10
src/App.js
10
src/App.js
@@ -10,6 +10,7 @@ import {
|
|||||||
} from "react-router-dom";
|
} from "react-router-dom";
|
||||||
import socket from "./services/socketService";
|
import socket from "./services/socketService";
|
||||||
|
|
||||||
|
import Print from "./pages/PrintPage.js";
|
||||||
import Dashboard from "./pages/Dashboard";
|
import Dashboard from "./pages/Dashboard";
|
||||||
import ScanMeja from "./pages/ScanMeja";
|
import ScanMeja from "./pages/ScanMeja";
|
||||||
import CafePage from "./pages/CafePage";
|
import CafePage from "./pages/CafePage";
|
||||||
@@ -45,6 +46,7 @@ import {
|
|||||||
import Modal from "./components/Modal"; // Import your modal component
|
import Modal from "./components/Modal"; // Import your modal component
|
||||||
|
|
||||||
import { requestNotificationPermission } from "./services/notificationService"; // Import the notification service
|
import { requestNotificationPermission } from "./services/notificationService"; // Import the notification service
|
||||||
|
import PrintPage from "./pages/PrintPage.js";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
@@ -674,6 +676,12 @@ function App() {
|
|||||||
<div className="App">
|
<div className="App">
|
||||||
<header className="App-header" id="header">
|
<header className="App-header" id="header">
|
||||||
<Routes>
|
<Routes>
|
||||||
|
<Route
|
||||||
|
path="/:shopIdentifier/print"
|
||||||
|
element={
|
||||||
|
<PrintPage />
|
||||||
|
}
|
||||||
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/"
|
path="/"
|
||||||
element={
|
element={
|
||||||
@@ -811,8 +819,10 @@ function App() {
|
|||||||
welcomePageConfig={shop.welcomePageConfig}
|
welcomePageConfig={shop.welcomePageConfig}
|
||||||
onClose={closeModal}
|
onClose={closeModal}
|
||||||
setModal={setModal}
|
setModal={setModal}
|
||||||
|
setIsModalOpen={setIsModalOpen}
|
||||||
onModalCloseFunction={onModalCloseFunction}
|
onModalCloseFunction={onModalCloseFunction}
|
||||||
onModalYesFunction={onModalYesFunction}
|
onModalYesFunction={onModalYesFunction}
|
||||||
|
shopIdentifier={shopIdentifier}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
|
import dayjs from "dayjs";
|
||||||
import Chart from "react-apexcharts";
|
import Chart from "react-apexcharts";
|
||||||
import styles from "./BarChart.module.css"; // Import the CSS module
|
import styles from "./BarChart.module.css"; // Import the CSS module
|
||||||
|
|
||||||
@@ -7,7 +8,21 @@ const DailyCharts = ({ incomeGraph, transactionGraph, materialGraph, colors, typ
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSelectedIndex(-1);
|
setSelectedIndex(-1);
|
||||||
}, [transactionGraph]);
|
}, [transactionGraph, graphFilter]);
|
||||||
|
|
||||||
|
// Helper function to format numbers to Indonesian Rupiah
|
||||||
|
const formatRupiah = (number) => {
|
||||||
|
if (number === null || number === undefined) {
|
||||||
|
return "Rp 0";
|
||||||
|
}
|
||||||
|
const formatter = new Intl.NumberFormat("id-ID", {
|
||||||
|
style: "currency",
|
||||||
|
currency: "IDR",
|
||||||
|
minimumFractionDigits: 0,
|
||||||
|
maximumFractionDigits: 0,
|
||||||
|
});
|
||||||
|
return formatter.format(number);
|
||||||
|
};
|
||||||
|
|
||||||
const processData = (graphData) => {
|
const processData = (graphData) => {
|
||||||
if (!graphData) return null;
|
if (!graphData) return null;
|
||||||
@@ -22,18 +37,18 @@ const DailyCharts = ({ incomeGraph, transactionGraph, materialGraph, colors, typ
|
|||||||
"18-21",
|
"18-21",
|
||||||
"21-24",
|
"21-24",
|
||||||
];
|
];
|
||||||
console.log(dayData)
|
const sumSold = (transactions) =>
|
||||||
const sumSold = (transactions) =>
|
Array.isArray(transactions) ? transactions.reduce((acc, t) => acc + t.sold, 0) : transactions?.transaction || 0;
|
||||||
Array.isArray(transactions) ? transactions.reduce((acc, t) => acc + t.sold, 0) : transactions.transaction || 0;
|
|
||||||
|
|
||||||
const sumTotal = (transactions) =>
|
const sumTotal = (transactions) =>
|
||||||
Array.isArray(transactions) ? transactions.reduce((acc, t) => acc + t.totalPrice, 0) : transactions.income || 0;
|
Array.isArray(transactions) ? transactions.reduce((acc, t) => acc + t.totalPrice, 0) : transactions?.income || 0;
|
||||||
|
|
||||||
|
const sumOutcome = (transactions) =>
|
||||||
|
Array.isArray(transactions) ? transactions.reduce((acc, t) => acc + (t.materialOutcome || t.price * t.stockDifference), 0) : transactions?.outcome || 0;
|
||||||
|
|
||||||
const sumOutcome = (transactions) =>
|
|
||||||
Array.isArray(transactions) ? transactions.reduce((acc, t) => acc + t.materialOutcome || t.price, 0) : transactions.outcome || 0;
|
|
||||||
|
|
||||||
let seriesData = []
|
let seriesData = []
|
||||||
if (graphFilter == 'transactions') {
|
if (graphFilter === 'transactions') {
|
||||||
seriesData = [
|
seriesData = [
|
||||||
sumSold(dayData?.hour0To3Transactions),
|
sumSold(dayData?.hour0To3Transactions),
|
||||||
sumSold(dayData?.hour3To6Transactions),
|
sumSold(dayData?.hour3To6Transactions),
|
||||||
@@ -45,7 +60,7 @@ const DailyCharts = ({ incomeGraph, transactionGraph, materialGraph, colors, typ
|
|||||||
sumSold(dayData?.hour21To24Transactions),
|
sumSold(dayData?.hour21To24Transactions),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
else if (graphFilter == 'income') {
|
else if (graphFilter === 'income') {
|
||||||
seriesData = [
|
seriesData = [
|
||||||
sumTotal(dayData?.hour0To3Transactions),
|
sumTotal(dayData?.hour0To3Transactions),
|
||||||
sumTotal(dayData?.hour3To6Transactions),
|
sumTotal(dayData?.hour3To6Transactions),
|
||||||
@@ -57,7 +72,7 @@ const DailyCharts = ({ incomeGraph, transactionGraph, materialGraph, colors, typ
|
|||||||
sumTotal(dayData?.hour21To24Transactions),
|
sumTotal(dayData?.hour21To24Transactions),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
else if (graphFilter == 'outcome') {
|
else if (graphFilter === 'outcome') {
|
||||||
seriesData = [
|
seriesData = [
|
||||||
sumOutcome(dayData?.hour0To3MaterialIds),
|
sumOutcome(dayData?.hour0To3MaterialIds),
|
||||||
sumOutcome(dayData?.hour3To6MaterialIds),
|
sumOutcome(dayData?.hour3To6MaterialIds),
|
||||||
@@ -69,23 +84,26 @@ const DailyCharts = ({ incomeGraph, transactionGraph, materialGraph, colors, typ
|
|||||||
sumOutcome(dayData?.hour21To24MaterialIds),
|
sumOutcome(dayData?.hour21To24MaterialIds),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
let totalValue = seriesData.reduce((acc, val) => acc + val, 0);
|
||||||
return {
|
return {
|
||||||
date: new Date(dayData.date).toLocaleDateString(),
|
date: dayData.date,
|
||||||
categories,
|
categories,
|
||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
name: `Transactions on ${new Date(dayData.date).toLocaleDateString()}`,
|
name: graphFilter === 'transactions' ? 'Transaksi' : (graphFilter === 'income' ? 'Pemasukan' : 'Pengeluaran'),
|
||||||
data: seriesData,
|
data: seriesData,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
totalValue, // ⬅️ Tambahkan ini
|
||||||
};
|
};
|
||||||
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const chartData = processData(graphFilter != 'outcome' ? transactionGraph : materialGraph);
|
const chartData = processData(graphFilter !== 'outcome' ? transactionGraph : materialGraph);
|
||||||
|
|
||||||
let globalMax = null;
|
let globalMax = null;
|
||||||
if (chartData)
|
if (chartData) {
|
||||||
globalMax = chartData.reduce(
|
globalMax = chartData.reduce(
|
||||||
(max, data) => {
|
(max, data) => {
|
||||||
const localMax = Math.max(...data.series[0].data);
|
const localMax = Math.max(...data.series[0].data);
|
||||||
@@ -93,24 +111,17 @@ const DailyCharts = ({ incomeGraph, transactionGraph, materialGraph, colors, typ
|
|||||||
},
|
},
|
||||||
0
|
0
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const formatDate = (dateString) => {
|
const formatDate = (dateString) => {
|
||||||
const date = new Date(dateString); // Parse the date string
|
const d = dayjs(dateString, ["YYYY-MM-DD", "YYYY-MM-DDTHH:mm:ssZ"]);
|
||||||
|
return { month: d.format("MMM"), day: d.format("D") };
|
||||||
// Create an array of month names (use the same names you had earlier)
|
|
||||||
const monthNames = [
|
|
||||||
"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
|
|
||||||
];
|
|
||||||
|
|
||||||
// Get the month and day
|
|
||||||
const month = monthNames[date.getMonth()]; // Month is 0-indexed (January = 0)
|
|
||||||
const day = date.getDate(); // Get the day of the month
|
|
||||||
|
|
||||||
return { month, day }; // Return the result
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`${styles.chartItemContainer} ${selectedIndex !== -1 ? styles.expanded : ''}`}>
|
<div className={`${styles.chartItemContainer} ${selectedIndex !== -1 ? styles.expanded : ''}`}>
|
||||||
|
|
||||||
{chartData &&
|
{chartData &&
|
||||||
chartData.map((data, index) => (
|
chartData.map((data, index) => (
|
||||||
<div
|
<div
|
||||||
@@ -128,33 +139,78 @@ const DailyCharts = ({ incomeGraph, transactionGraph, materialGraph, colors, typ
|
|||||||
key={indexx}
|
key={indexx}
|
||||||
className={`${styles.dateSelector} ${index === indexx ? styles.dateSelectorActive : styles.dateSelectorInactive
|
className={`${styles.dateSelector} ${index === indexx ? styles.dateSelectorActive : styles.dateSelectorInactive
|
||||||
}`}
|
}`}
|
||||||
style={{ position: 'relative' }}
|
style={{ position: 'relative', width: 'calc(100% / 7)' }}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
type == 'yesterday' && selectedIndex == -1 || type != 'yesterday' && selectedIndex !== index ? setSelectedIndex(index) : setSelectedIndex(-1)
|
type == 'yesterday' && selectedIndex == -1 || type != 'yesterday' && selectedIndex !== index ? setSelectedIndex(index) : setSelectedIndex(-1)
|
||||||
}
|
}
|
||||||
// style={{ backgroundColor: index === indexx ? colors[index % colors.length] : 'transparent' }}
|
|
||||||
>
|
>
|
||||||
<div style={{ position: 'absolute', bottom: 0, left: '10%', right: '10%', borderBottom: index == indexx ? `2px solid ${colors[index % colors.length]}` : 'none' }}></div>
|
|
||||||
|
<div style={{ position: 'absolute', bottom: '21px', left: '10%', right: '10%', borderBottom: index == indexx ? `2px solid ${colors[index % colors.length]}` : 'none' }}></div>
|
||||||
<div
|
<div
|
||||||
style={{ color: index === indexx ? 'black' : 'transparent' }}>
|
style={{ color: index === indexx ? 'black' : 'transparent' }}>
|
||||||
{indexx !== chartData.length - 1 ? (
|
{indexx !== chartData.length - 1 ? (
|
||||||
<>
|
<p style={{ fontSize: '13px' }}>{day}{" "}
|
||||||
{day}{" "}
|
{(
|
||||||
{(indexx === 0 || (formatDate(chartData[indexx - 1].date).month !== month && type != 'weekly')) && month}
|
indexx === 0 ||
|
||||||
</>
|
(indexx > 0 &&
|
||||||
|
dayjs(chartData[indexx - 1].date).month() !== dayjs(item.date).month() &&
|
||||||
|
type !== "weekly")
|
||||||
|
) && month}
|
||||||
|
</p>
|
||||||
) : (
|
) : (
|
||||||
'Hari ini'
|
<p style={{ fontSize: '13px' }}>
|
||||||
|
{type != 'weekly' ? 'Hari ini' : day + ' ' + month}
|
||||||
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
{index == indexx && <p style={{
|
||||||
|
margin: '7px 0 0 0', fontSize: '9px', color: 'black',
|
||||||
|
position: 'absolute',
|
||||||
|
width: '100%',
|
||||||
|
left: 0,
|
||||||
|
bottom: 6
|
||||||
|
}}>
|
||||||
|
{graphFilter === 'transactions'
|
||||||
|
? chartData[indexx].totalValue
|
||||||
|
: formatRupiah(chartData[indexx].totalValue)}
|
||||||
|
</p>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.chartWrapper}>
|
<div className={styles.chartWrapper}>
|
||||||
<Chart
|
<Chart
|
||||||
options={{
|
options={{
|
||||||
tooltip: { enabled: false },
|
tooltip: {
|
||||||
|
enabled: true,
|
||||||
|
y: {
|
||||||
|
formatter: function (val) {
|
||||||
|
return graphFilter === "transactions"
|
||||||
|
? val
|
||||||
|
: formatRupiah(val);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dataLabels: {
|
||||||
|
enabled: true,
|
||||||
|
formatter: function (val) {
|
||||||
|
if (graphFilter === 'transactions') {
|
||||||
|
return val; // angka biasa
|
||||||
|
} else {
|
||||||
|
return formatRupiah(val); // format Rupiah
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
style: {
|
||||||
|
colors: [(index == chartData.length - 1 || selectedIndex != -1) ? "#000" : "transparent"],
|
||||||
|
fontSize: '7px',
|
||||||
|
},
|
||||||
|
offsetY: -10,
|
||||||
|
background: {
|
||||||
|
enabled: (index == chartData.length - 1 || selectedIndex != -1) ? true : false
|
||||||
|
}
|
||||||
|
},
|
||||||
chart: {
|
chart: {
|
||||||
id: `chart-${index}`,
|
id: `chart-${index}`,
|
||||||
type: "area",
|
type: "area",
|
||||||
@@ -169,7 +225,7 @@ const DailyCharts = ({ incomeGraph, transactionGraph, materialGraph, colors, typ
|
|||||||
categories: data.categories,
|
categories: data.categories,
|
||||||
labels: {
|
labels: {
|
||||||
style: {
|
style: {
|
||||||
colors: index === 0 || index == selectedIndex || selectedIndex == 0 && index == 1 ? "#000" : "transparent",
|
colors: index === 0 || index === selectedIndex || (selectedIndex === 0 && index === 1) ? "#000" : "transparent",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -181,6 +237,9 @@ const DailyCharts = ({ incomeGraph, transactionGraph, materialGraph, colors, typ
|
|||||||
style: {
|
style: {
|
||||||
colors: "transparent",
|
colors: "transparent",
|
||||||
},
|
},
|
||||||
|
formatter: function (val) {
|
||||||
|
return formatRupiah(val);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
grid: {
|
grid: {
|
||||||
@@ -198,7 +257,6 @@ const DailyCharts = ({ incomeGraph, transactionGraph, materialGraph, colors, typ
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ import CreateCoupon from "../pages/CreateCoupon";
|
|||||||
import CheckCoupon from "../pages/CheckCoupon";
|
import CheckCoupon from "../pages/CheckCoupon";
|
||||||
import CreateUserWithCoupon from "../pages/CreateUserWithCoupon";
|
import CreateUserWithCoupon from "../pages/CreateUserWithCoupon";
|
||||||
|
|
||||||
const Modal = ({ user, shop, isOpen, onClose, modalContent, deviceType, setModal, handleMoveToTransaction, depth,welcomePageConfig, onModalCloseFunction, onModalYesFunction }) => {
|
const Modal = ({ user, shop, shopIdentifier, isOpen, onClose, modalContent, deviceType, setModal, setIsModalOpen, handleMoveToTransaction, depth,welcomePageConfig, onModalCloseFunction, onModalYesFunction }) => {
|
||||||
|
|
||||||
const [shopImg, setShopImg] = useState('');
|
const [shopImg, setShopImg] = useState('');
|
||||||
const [updateKey, setUpdateKey] = useState(0);
|
const [updateKey, setUpdateKey] = useState(0);
|
||||||
@@ -88,10 +88,10 @@ const Modal = ({ user, shop, isOpen, onClose, modalContent, deviceType, setModal
|
|||||||
{modalContent === "create_tenant" && <CreateTenant shopId={shop.cafeId} />}
|
{modalContent === "create_tenant" && <CreateTenant shopId={shop.cafeId} />}
|
||||||
{modalContent === "edit_tables" && <TablesPage shop={shop} />}
|
{modalContent === "edit_tables" && <TablesPage shop={shop} />}
|
||||||
{modalContent === "new_transaction" && (
|
{modalContent === "new_transaction" && (
|
||||||
<Transaction propsShopId={shop.cafeId} handleMoveToTransaction={handleMoveToTransaction} depth={depth} shopImg={shopImg} setModal={setModal}/>
|
<Transaction propsShopId={shop.cafeId} setIsModalOpen={setIsModalOpen} cafeIdentityName={shopIdentifier} handleMoveToTransaction={handleMoveToTransaction} depth={depth} shopImg={shopImg} setModal={setModal}/>
|
||||||
)}
|
)}
|
||||||
{modalContent === "transaction_canceled" && (
|
{modalContent === "transaction_canceled" && (
|
||||||
<Transaction propsShopId={shop.cafeId} />
|
<Transaction propsShopId={shop.cafeId} setIsModalOpen={setIsModalOpen} cafeIdentityName={shopIdentifier} />
|
||||||
)}
|
)}
|
||||||
{modalContent === "transaction_pending" && <Transaction_pending deviceType={deviceType} setModal={setModal}/>}
|
{modalContent === "transaction_pending" && <Transaction_pending deviceType={deviceType} setModal={setModal}/>}
|
||||||
{modalContent === "transaction_item" && <Transaction_item />}
|
{modalContent === "transaction_item" && <Transaction_item />}
|
||||||
|
|||||||
@@ -7,182 +7,195 @@ const PeriodCharts = ({ type, graphFilter, aggregatedCurrentReports, aggregatedP
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSelectedIndex(-1);
|
setSelectedIndex(-1);
|
||||||
}, [aggregatedCurrentReports, aggregatedPreviousReports]);
|
}, [aggregatedCurrentReports, aggregatedPreviousReports, graphFilter]);
|
||||||
|
|
||||||
const monthly = ["1 - 7", "8 - 14", "15 - 21", "22 - 28", "29 - 31"];
|
const monthly = ["1 - 7", "8 - 14", "15 - 21", "22 - 28", "29 - 31"];
|
||||||
const yearly = ["Kuartal 1", "Kuartal 2", "Kuartal 3", "Kuartal 4"];
|
const yearly = ["Kuartal 1", "Kuartal 2", "Kuartal 3", "Kuartal 4"];
|
||||||
const cat = type == 'monthly' ? monthly : yearly;
|
const cat = type === "monthly" ? monthly : yearly;
|
||||||
|
|
||||||
|
// Helper Rupiah formatter
|
||||||
|
const formatRupiah = (number) => {
|
||||||
|
if (number === null || number === undefined) return "Rp 0";
|
||||||
|
return new Intl.NumberFormat("id-ID", {
|
||||||
|
style: "currency",
|
||||||
|
currency: "IDR",
|
||||||
|
minimumFractionDigits: 0,
|
||||||
|
maximumFractionDigits: 0,
|
||||||
|
}).format(number);
|
||||||
|
};
|
||||||
|
|
||||||
|
let currentIncomeData,
|
||||||
|
currentOutcomeData,
|
||||||
|
currentTransactionData,
|
||||||
|
previousIncomeData,
|
||||||
|
previousOutcomeData,
|
||||||
|
previousTransactionData = null;
|
||||||
|
|
||||||
// Map the data for the current reports
|
|
||||||
let currentIncomeData, currentOutcomeData, currentTransactionData, previousIncomeData, previousOutcomeData, previousTransactionData = null;
|
|
||||||
if (aggregatedCurrentReports) {
|
if (aggregatedCurrentReports) {
|
||||||
currentIncomeData = aggregatedCurrentReports.map((report) => report.income);
|
currentIncomeData = aggregatedCurrentReports.map((r) => r.income);
|
||||||
currentOutcomeData = aggregatedCurrentReports.map((report) => report.outcome);
|
currentOutcomeData = aggregatedCurrentReports.map((r) => r.outcome);
|
||||||
currentTransactionData = aggregatedCurrentReports.map((report) => report.transactions);
|
currentTransactionData = aggregatedCurrentReports.map((r) => r.transactions);
|
||||||
|
|
||||||
if (type == 'monthly' && currentIncomeData.length === 4) {
|
if (type === "monthly" && currentIncomeData.length === 4) {
|
||||||
currentIncomeData.push(null);
|
currentIncomeData.push(null);
|
||||||
}
|
|
||||||
if (type == 'monthly' && currentOutcomeData.length === 4) {
|
|
||||||
currentOutcomeData.push(null);
|
currentOutcomeData.push(null);
|
||||||
}
|
|
||||||
if (type == 'monthly' && currentTransactionData.length === 4) {
|
|
||||||
currentTransactionData.push(null);
|
currentTransactionData.push(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (aggregatedPreviousReports) {
|
|
||||||
// Map the data for the previous reports
|
|
||||||
previousIncomeData = aggregatedPreviousReports.map((report) => report.income);
|
|
||||||
previousOutcomeData = aggregatedPreviousReports.map((report) => report.outcome);
|
|
||||||
previousTransactionData = aggregatedPreviousReports.map((report) => report.transactions);
|
|
||||||
|
|
||||||
if (type == 'monthly' && previousIncomeData.length === 4) {
|
if (aggregatedPreviousReports) {
|
||||||
|
previousIncomeData = aggregatedPreviousReports.map((r) => r.income);
|
||||||
|
previousOutcomeData = aggregatedPreviousReports.map((r) => r.outcome);
|
||||||
|
previousTransactionData = aggregatedPreviousReports.map((r) => r.transactions);
|
||||||
|
|
||||||
|
if (type === "monthly" && previousIncomeData.length === 4) {
|
||||||
previousIncomeData.push(null);
|
previousIncomeData.push(null);
|
||||||
}
|
|
||||||
if (type == 'monthly' && previousOutcomeData.length === 4) {
|
|
||||||
previousOutcomeData.push(null);
|
previousOutcomeData.push(null);
|
||||||
}
|
|
||||||
if (type == 'monthly' && previousTransactionData.length === 4) {
|
|
||||||
previousTransactionData.push(null);
|
previousTransactionData.push(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let globalMax = null;
|
|
||||||
if (aggregatedCurrentReports || aggregatedPreviousReports) {
|
// cari global max untuk y-axis
|
||||||
// Find the global maximum for the y-axis
|
let globalMax = 0;
|
||||||
globalMax = Math.max(
|
if (aggregatedCurrentReports || aggregatedPreviousReports) {
|
||||||
...(graphFilter === 'income'
|
const dataset =
|
||||||
? [...currentIncomeData, ...previousIncomeData]
|
graphFilter === "income"
|
||||||
: graphFilter === 'outcome'
|
? [...(currentIncomeData || []), ...(previousIncomeData || [])]
|
||||||
? [...currentOutcomeData, ...previousOutcomeData]
|
: graphFilter === "outcome"
|
||||||
: [...currentTransactionData, ...previousTransactionData])
|
? [...(currentOutcomeData || []), ...(previousOutcomeData || [])]
|
||||||
);
|
: [...(currentTransactionData || []), ...(previousTransactionData || [])];
|
||||||
}
|
globalMax = Math.max(...dataset);
|
||||||
|
}
|
||||||
|
|
||||||
|
const getSeries = (isCurrent) => {
|
||||||
|
if (graphFilter === "income")
|
||||||
|
return isCurrent ? currentIncomeData : previousIncomeData;
|
||||||
|
if (graphFilter === "outcome")
|
||||||
|
return isCurrent ? currentOutcomeData : previousOutcomeData;
|
||||||
|
return isCurrent ? currentTransactionData : previousTransactionData;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`${styles.chartItemContainer} ${selectedIndex !== -1 ? styles.expanded : ''}`}>
|
<div
|
||||||
{aggregatedPreviousReports && (
|
className={`${styles.chartItemContainer} ${selectedIndex !== -1 ? styles.expanded : ""
|
||||||
<div className={`${styles.chartItemWrapper} ${selectedIndex !== -1 && selectedIndex !== 0
|
}`}
|
||||||
? styles.chartItemWrapperActive
|
>
|
||||||
: styles.chartItemWrapperInactive
|
{[aggregatedPreviousReports, aggregatedCurrentReports].map(
|
||||||
}`}>
|
(dataset, i) =>
|
||||||
|
dataset && (
|
||||||
<div className={styles.dateSelectorWrapper}>
|
|
||||||
<div className={styles.dateSelector}
|
|
||||||
onClick={() =>
|
|
||||||
selectedIndex === -1 ? setSelectedIndex(0) : setSelectedIndex(-1)
|
|
||||||
}
|
|
||||||
style={{ color: 'black', position: 'relative' }}
|
|
||||||
>
|
|
||||||
<div style={{ position: 'absolute', bottom: 0, left: '10%', right: '10%', borderBottom: `2px solid ${colors[0]}` }}></div>
|
|
||||||
<div>{type == 'monthly' ? 'bulan lalu' : 'tahun lalu'}</div>
|
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
className={`${styles.dateSelector} ${styles.dateSelectorInactive
|
key={i}
|
||||||
|
className={`${styles.chartItemWrapper} ${selectedIndex !== -1 && selectedIndex !== i
|
||||||
|
? styles.chartItemWrapperActive
|
||||||
|
: styles.chartItemWrapperInactive
|
||||||
}`}
|
}`}
|
||||||
|
|
||||||
onClick={() =>
|
|
||||||
selectedIndex === 0 ? setSelectedIndex(-1) : setSelectedIndex(1)
|
|
||||||
}>
|
|
||||||
<div>{type == 'monthly' ? 'bulan ini' : 'tahun ini'}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className={styles.chartWrapper}>
|
|
||||||
<Chart
|
|
||||||
options={{
|
|
||||||
tooltip: { enabled: false },
|
|
||||||
chart: { type: "area", zoom: { enabled: false }, toolbar: { show: false } },
|
|
||||||
xaxis: {
|
|
||||||
categories: cat,
|
|
||||||
axisBorder: {
|
|
||||||
show: false, // Removes the x-axis line
|
|
||||||
},
|
|
||||||
axisTicks: {
|
|
||||||
show: false, // Removes the ticks on the x-axis
|
|
||||||
},
|
|
||||||
labels: {
|
|
||||||
style: {
|
|
||||||
colors: ['black', 'black', 'black', 'black', aggregatedPreviousReports?.length == 4 ? 'transparent' : 'black'],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
yaxis: { max: globalMax, min: 0, labels: {
|
|
||||||
maxWidth: 20, style: { colors: "transparent" } } },
|
|
||||||
grid: { show: false },
|
|
||||||
fill: { opacity: 0.5 },
|
|
||||||
colors: [colors[0]],
|
|
||||||
}}
|
|
||||||
series={[
|
|
||||||
// { name: "Pemasukan", data: previousIncomeData },
|
|
||||||
// { name: "Pengaluaran", data: previousOutcomeData },
|
|
||||||
{ name: "Total transaksi", data: graphFilter == 'income' ? previousIncomeData : graphFilter == 'outcome' ? previousOutcomeData : previousTransactionData },
|
|
||||||
]}
|
|
||||||
type="area"
|
|
||||||
height={200}
|
|
||||||
width="100%"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{aggregatedCurrentReports && (
|
|
||||||
<div className={`${styles.chartItemWrapper} ${selectedIndex !== -1 && selectedIndex !== 1
|
|
||||||
? styles.chartItemWrapperActive
|
|
||||||
: styles.chartItemWrapperInactive
|
|
||||||
}`}>
|
|
||||||
<div className={styles.dateSelectorWrapper}>
|
|
||||||
<div
|
|
||||||
className={`${styles.dateSelector} ${styles.dateSelectorInactive
|
|
||||||
}`}
|
|
||||||
onClick={() =>
|
|
||||||
selectedIndex === 1 ? setSelectedIndex(-1) : setSelectedIndex(0)
|
|
||||||
}>
|
|
||||||
<div>{type == 'monthly' ? 'bulan lalu' : 'tahun lalu'}</div>
|
|
||||||
</div>
|
|
||||||
<div className={styles.dateSelector}
|
|
||||||
onClick={() =>
|
|
||||||
selectedIndex === -1 ? setSelectedIndex(1) : setSelectedIndex(-1)
|
|
||||||
}
|
|
||||||
|
|
||||||
style={{ color: 'black', position: 'relative' }}
|
|
||||||
>
|
>
|
||||||
<div style={{ position: 'absolute', bottom: 0, left: '10%', right: '10%', borderBottom: `2px solid ${colors[1]}` }}></div>
|
<div className={styles.dateSelectorWrapper}>
|
||||||
<div>{type == 'monthly' ? 'bulan ini' : 'tahun ini'}</div>
|
{[0, 1].map((idx) => (
|
||||||
</div>
|
<div
|
||||||
</div>
|
key={idx}
|
||||||
<div className={styles.chartWrapper}>
|
className={`${styles.dateSelector} ${idx === i ? styles.dateSelectorActive : styles.dateSelectorInactive
|
||||||
<Chart
|
}`}
|
||||||
options={{
|
style={{ position: "relative" }}
|
||||||
tooltip: { enabled: false },
|
onClick={() =>
|
||||||
chart: { type: "area", zoom: { enabled: false }, toolbar: { show: false } },
|
selectedIndex == idx ? setSelectedIndex(-1) : setSelectedIndex(idx)
|
||||||
xaxis: {
|
|
||||||
categories: cat,
|
|
||||||
axisBorder: {
|
|
||||||
show: false, // Removes the x-axis line
|
|
||||||
},
|
|
||||||
axisTicks: {
|
|
||||||
show: false, // Removes the ticks on the x-axis
|
|
||||||
},
|
|
||||||
labels: {
|
|
||||||
style: {
|
|
||||||
colors: ['black', 'black', 'black', 'black', aggregatedCurrentReports?.length == 4 ? 'transparent' : 'black'],
|
|
||||||
}
|
}
|
||||||
}
|
>
|
||||||
},
|
{idx === i && (
|
||||||
yaxis: { max: globalMax, min: 0, labels: { maxWidth: 20, style: { colors: "transparent" } } },
|
<div
|
||||||
grid: { show: false },
|
style={{
|
||||||
fill: { opacity: 0.5 },
|
position: "absolute",
|
||||||
colors: [colors[1]],
|
bottom: 0,
|
||||||
}}
|
left: "10%",
|
||||||
series={[
|
right: "10%",
|
||||||
// { name: "Pemasukan", data: currentIncomeData },
|
borderBottom: `2px solid ${colors[i]}`,
|
||||||
// { name: "Pengeluaran", data: currentOutcomeData },
|
}}
|
||||||
{ name: "Total transaksi", data: graphFilter == 'income' ? currentIncomeData : graphFilter == 'outcome' ? currentOutcomeData : currentTransactionData },
|
></div>
|
||||||
]}
|
)}
|
||||||
type="area"
|
<div style={{ color: idx === i ? "black" : "transparent" }}>
|
||||||
height={200}
|
{type === "monthly"
|
||||||
width="100%"
|
? idx === 0
|
||||||
/>
|
? "bulan lalu"
|
||||||
</div>
|
: "bulan ini"
|
||||||
</div>
|
: idx === 0
|
||||||
|
? "tahun lalu"
|
||||||
|
: "tahun ini"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.chartWrapper}>
|
||||||
|
<Chart
|
||||||
|
options={{
|
||||||
|
tooltip: {
|
||||||
|
enabled: true,
|
||||||
|
y: {
|
||||||
|
formatter: (val) =>
|
||||||
|
graphFilter === "transactions" ? val : formatRupiah(val),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dataLabels: {
|
||||||
|
enabled: true,
|
||||||
|
formatter: (val) =>
|
||||||
|
graphFilter === "transactions" ? val : formatRupiah(val),
|
||||||
|
style: {
|
||||||
|
colors: ["#000"], // <- Selalu tampil hitam
|
||||||
|
fontSize: "10px",
|
||||||
|
},
|
||||||
|
offsetY: -10,
|
||||||
|
background: {
|
||||||
|
enabled: true, // <- Selalu tampil background label
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
chart: {
|
||||||
|
type: "area",
|
||||||
|
zoom: { enabled: false },
|
||||||
|
toolbar: { show: false },
|
||||||
|
},
|
||||||
|
xaxis: {
|
||||||
|
categories: cat,
|
||||||
|
labels: {
|
||||||
|
style: {
|
||||||
|
colors: cat.map(() =>
|
||||||
|
i === selectedIndex || selectedIndex === -1 ? "#000" : "transparent"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
yaxis: {
|
||||||
|
max: globalMax,
|
||||||
|
min: 0,
|
||||||
|
labels: {
|
||||||
|
maxWidth: 20,
|
||||||
|
style: { colors: "transparent" },
|
||||||
|
formatter: (val) => formatRupiah(val),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
grid: { show: false },
|
||||||
|
fill: { opacity: 0.5 },
|
||||||
|
colors: [colors[i]],
|
||||||
|
}}
|
||||||
|
series={[
|
||||||
|
{
|
||||||
|
name:
|
||||||
|
graphFilter === "transactions"
|
||||||
|
? "Transaksi"
|
||||||
|
: graphFilter === "income"
|
||||||
|
? "Pemasukan"
|
||||||
|
: "Pengeluaran",
|
||||||
|
data: getSeries(i === 1),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
type="area"
|
||||||
|
height={200}
|
||||||
|
width="100%"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -252,10 +252,9 @@ export const handlePaymentFromClerk = async (
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
// Handle success response
|
const data = await response.json();
|
||||||
console.log("Transaction successful!");
|
console.log("Transaction successful!", data);
|
||||||
// Optionally return response data or handle further actions upon success
|
return data;
|
||||||
return true;
|
|
||||||
} else {
|
} else {
|
||||||
// Handle error response
|
// Handle error response
|
||||||
console.error("Transaction failed:", response.statusText);
|
console.error("Transaction failed:", response.statusText);
|
||||||
|
|||||||
@@ -103,6 +103,17 @@ function CafePage({
|
|||||||
// };
|
// };
|
||||||
const [isTablet, setIsTablet] = useState(window.innerWidth >= 768);
|
const [isTablet, setIsTablet] = useState(window.innerWidth >= 768);
|
||||||
|
|
||||||
|
const [isFullscreen, setIsFullscreen] = useState(!!document.fullscreenElement);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
function fullscreenChangeHandler() {
|
||||||
|
setIsFullscreen(!!document.fullscreenElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener("fullscreenchange", fullscreenChangeHandler);
|
||||||
|
return () => document.removeEventListener("fullscreenchange", fullscreenChangeHandler);
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleResize = () => {
|
const handleResize = () => {
|
||||||
setIsTablet(window.innerWidth >= 768);
|
setIsTablet(window.innerWidth >= 768);
|
||||||
@@ -259,6 +270,70 @@ function CafePage({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
const FullscreenButton = ({ onClick }) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
onClick={onClick}
|
||||||
|
style={{
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
borderRadius: "50%",
|
||||||
|
backgroundColor: "#7272729e",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
cursor: "pointer",
|
||||||
|
userSelect: "none",
|
||||||
|
position: 'fixed',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
zIndex: 1000
|
||||||
|
}}
|
||||||
|
title="Toggle Fullscreen"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
display: "inline-block",
|
||||||
|
transform: "rotate(45deg)",
|
||||||
|
color: "white",
|
||||||
|
fontWeight: "bold",
|
||||||
|
fontSize: 24,
|
||||||
|
lineHeight: 1,
|
||||||
|
userSelect: "none",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const handleFullscreen = () => {
|
||||||
|
const elem = document.documentElement; // fullscreen seluruh halaman
|
||||||
|
|
||||||
|
if (!document.fullscreenElement) {
|
||||||
|
// masuk fullscreen
|
||||||
|
if (elem.requestFullscreen) {
|
||||||
|
elem.requestFullscreen();
|
||||||
|
} else if (elem.mozRequestFullScreen) { /* Firefox */
|
||||||
|
elem.mozRequestFullScreen();
|
||||||
|
} else if (elem.webkitRequestFullscreen) { /* Chrome, Safari & Opera */
|
||||||
|
elem.webkitRequestFullscreen();
|
||||||
|
} else if (elem.msRequestFullscreen) { /* IE/Edge */
|
||||||
|
elem.msRequestFullscreen();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// keluar fullscreen
|
||||||
|
if (document.exitFullscreen) {
|
||||||
|
document.exitFullscreen();
|
||||||
|
} else if (document.mozCancelFullScreen) { /* Firefox */
|
||||||
|
document.mozCancelFullScreen();
|
||||||
|
} else if (document.webkitExitFullscreen) { /* Chrome, Safari and Opera */
|
||||||
|
document.webkitExitFullscreen();
|
||||||
|
} else if (document.msExitFullscreen) { /* IE/Edge */
|
||||||
|
document.msExitFullscreen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (loading)
|
if (loading)
|
||||||
return (
|
return (
|
||||||
@@ -297,6 +372,7 @@ function CafePage({
|
|||||||
)}
|
)}
|
||||||
<div style={{ width: isTablet ? "60%" : "100%" }}>
|
<div style={{ width: isTablet ? "60%" : "100%" }}>
|
||||||
<div className="App-header">
|
<div className="App-header">
|
||||||
|
{isTablet && !isFullscreen && <FullscreenButton onClick={handleFullscreen} />}
|
||||||
<Header
|
<Header
|
||||||
HeaderText={"Menu"}
|
HeaderText={"Menu"}
|
||||||
showProfile={true}
|
showProfile={true}
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ export default function Invoice({
|
|||||||
if (lastTransaction?.payment_type == "paylater")
|
if (lastTransaction?.payment_type == "paylater")
|
||||||
methods.isOpenBillAvailable = false;
|
methods.isOpenBillAvailable = false;
|
||||||
setPaymentMethods(methods);
|
setPaymentMethods(methods);
|
||||||
} catch (err) {}
|
} catch (err) { }
|
||||||
};
|
};
|
||||||
|
|
||||||
if (shopId) {
|
if (shopId) {
|
||||||
@@ -275,6 +275,75 @@ export default function Invoice({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
const handlePrint = (transaction) => {
|
||||||
|
console.log(transaction)
|
||||||
|
const formatWaktu = (() => {
|
||||||
|
const date = transaction?.createdAt
|
||||||
|
? new Date(transaction.createdAt)
|
||||||
|
: new Date(); // UTC now
|
||||||
|
const tanggal = date.toLocaleDateString("id-ID");
|
||||||
|
const jam = date.toLocaleTimeString("id-ID", {
|
||||||
|
hour: "2-digit",
|
||||||
|
minute: "2-digit",
|
||||||
|
hour12: false,
|
||||||
|
});
|
||||||
|
return `${tanggal} ${jam}`;
|
||||||
|
})();
|
||||||
|
|
||||||
|
|
||||||
|
const itemsStr = transaction.DetailedTransactions.map((dt) => {
|
||||||
|
const name =
|
||||||
|
dt.Item.name.length > 11
|
||||||
|
? dt.Item.name.slice(0, 11)
|
||||||
|
: dt.Item.name.padEnd(11);
|
||||||
|
const qty = dt.qty.toString().padStart(3);
|
||||||
|
const total = formatRupiah(dt.qty * (dt.promoPrice || dt.price)).padStart(15);
|
||||||
|
return `${name} ${qty} ${total}`;
|
||||||
|
}).join("\n");
|
||||||
|
|
||||||
|
const totalHarga = transaction.DetailedTransactions.reduce((acc, dt) => {
|
||||||
|
return acc + dt.qty * (dt.promoPrice || dt.price);
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
const totalStr = `Total: ${formatRupiah(totalHarga)}`;
|
||||||
|
|
||||||
|
const receiptText = ` CAFE HOREE
|
||||||
|
Jl. Ahmad Yani No. 12, Kediri
|
||||||
|
Telp: 0812-1617-6963
|
||||||
|
|
||||||
|
==============================
|
||||||
|
Tanggal : ${formatWaktu}
|
||||||
|
Bayar : ${transaction.payment_type}
|
||||||
|
------------------------------
|
||||||
|
Item Qty Total
|
||||||
|
------------------------------
|
||||||
|
${itemsStr}
|
||||||
|
${totalStr}
|
||||||
|
==============================
|
||||||
|
Terima kasih atas kunjungannya!
|
||||||
|
~~
|
||||||
|
supported by kedaimaster.com
|
||||||
|
|
||||||
|
|
||||||
|
\n\n\n\n\n`;
|
||||||
|
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("content", receiptText);
|
||||||
|
params.append("encode_format", "UTF-8");
|
||||||
|
|
||||||
|
const printUrl = `btprinter://print?${params.toString()}`;
|
||||||
|
|
||||||
|
window.location.href = printUrl;
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatRupiah = (value) => {
|
||||||
|
if (typeof value !== "number") return value;
|
||||||
|
return value.toLocaleString("id-ID", {
|
||||||
|
style: "currency",
|
||||||
|
currency: "IDR",
|
||||||
|
minimumFractionDigits: 0,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const handlePay = async (orderMethod) => {
|
const handlePay = async (orderMethod) => {
|
||||||
setIsPaymentLoading(true);
|
setIsPaymentLoading(true);
|
||||||
@@ -300,9 +369,16 @@ export default function Invoice({
|
|||||||
tableNumber,
|
tableNumber,
|
||||||
textareaRef.current.value
|
textareaRef.current.value
|
||||||
);
|
);
|
||||||
if (pay) window.location.reload();
|
if (pay) {
|
||||||
|
handlePrint(pay.transaction);
|
||||||
} else if (deviceType == "guestSide") {
|
localStorage.removeItem("cart");
|
||||||
|
localStorage.removeItem("lastTransaction");
|
||||||
|
setCartItems([]);
|
||||||
|
setTotalPrice(0);
|
||||||
|
window.dispatchEvent(new Event("localStorageUpdated"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (deviceType == "guestSide") {
|
||||||
const pay = await handlePaymentFromGuestSide(
|
const pay = await handlePaymentFromGuestSide(
|
||||||
shopId,
|
shopId,
|
||||||
email,
|
email,
|
||||||
@@ -429,15 +505,15 @@ export default function Invoice({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{!isTablet &&
|
{!isTablet &&
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
width="30"
|
width="30"
|
||||||
height="30"
|
height="30"
|
||||||
viewBox="0 0 512 512"
|
viewBox="0 0 512 512"
|
||||||
>
|
>
|
||||||
<path d="M48,256c0,114.87,93.13,208,208,208s208-93.13,208-208S370.87,48,256,48,48,141.13,48,256Zm212.65-91.36a16,16,0,0,1,.09,22.63L208.42,240H342a16,16,0,0,1,0,32H208.42l52.32,52.73A16,16,0,1,1,238,347.27l-79.39-80a16,16,0,0,1,0-22.54l79.39-80A16,16,0,0,1,260.65,164.64Z" />
|
<path d="M48,256c0,114.87,93.13,208,208,208s208-93.13,208-208S370.87,48,256,48,48,141.13,48,256Zm212.65-91.36a16,16,0,0,1,.09,22.63L208.42,240H342a16,16,0,0,1,0,32H208.42l52.32,52.73A16,16,0,1,1,238,347.27l-79.39-80a16,16,0,0,1,0-22.54l79.39-80A16,16,0,0,1,260.65,164.64Z" />
|
||||||
</svg>
|
</svg>
|
||||||
}
|
}
|
||||||
Keranjang
|
Keranjang
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -452,7 +528,7 @@ export default function Invoice({
|
|||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div style={{ width: isTablet ? "30%":"50%" }}>
|
<div style={{ width: isTablet ? "30%" : "50%" }}>
|
||||||
<svg viewBox="0 0 32 32" style={{ fill: "#8F8787" }}>
|
<svg viewBox="0 0 32 32" style={{ fill: "#8F8787" }}>
|
||||||
<path d="M9.79175 24.75C8.09591 24.75 6.72383 26.1375 6.72383 27.8333C6.72383 29.5292 8.09591 30.9167 9.79175 30.9167C11.4876 30.9167 12.8751 29.5292 12.8751 27.8333C12.8751 26.1375 11.4876 24.75 9.79175 24.75ZM0.541748 0.0833435V3.16668H3.62508L9.17508 14.8679L7.09383 18.645C6.84717 19.0767 6.70842 19.5854 6.70842 20.125C6.70842 21.8208 8.09591 23.2083 9.79175 23.2083H28.2917V20.125H10.4392C10.2234 20.125 10.0538 19.9554 10.0538 19.7396L10.1001 19.5546L11.4876 17.0417H22.973C24.1292 17.0417 25.1467 16.4096 25.6709 15.4538L31.1901 5.44834C31.3134 5.23251 31.3751 4.97043 31.3751 4.70834C31.3751 3.86043 30.6813 3.16668 29.8334 3.16668H7.03217L5.583 0.0833435H0.541748ZM25.2084 24.75C23.5126 24.75 22.1405 26.1375 22.1405 27.8333C22.1405 29.5292 23.5126 30.9167 25.2084 30.9167C26.9042 30.9167 28.2917 29.5292 28.2917 27.8333C28.2917 26.1375 26.9042 24.75 25.2084 24.75Z" />
|
<path d="M9.79175 24.75C8.09591 24.75 6.72383 26.1375 6.72383 27.8333C6.72383 29.5292 8.09591 30.9167 9.79175 30.9167C11.4876 30.9167 12.8751 29.5292 12.8751 27.8333C12.8751 26.1375 11.4876 24.75 9.79175 24.75ZM0.541748 0.0833435V3.16668H3.62508L9.17508 14.8679L7.09383 18.645C6.84717 19.0767 6.70842 19.5854 6.70842 20.125C6.70842 21.8208 8.09591 23.2083 9.79175 23.2083H28.2917V20.125H10.4392C10.2234 20.125 10.0538 19.9554 10.0538 19.7396L10.1001 19.5546L11.4876 17.0417H22.973C24.1292 17.0417 25.1467 16.4096 25.6709 15.4538L31.1901 5.44834C31.3134 5.23251 31.3751 4.97043 31.3751 4.70834C31.3751 3.86043 30.6813 3.16668 29.8334 3.16668H7.03217L5.583 0.0833435H0.541748ZM25.2084 24.75C23.5126 24.75 22.1405 26.1375 22.1405 27.8333C22.1405 29.5292 23.5126 30.9167 25.2084 30.9167C26.9042 30.9167 28.2917 29.5292 28.2917 27.8333C28.2917 26.1375 26.9042 24.75 25.2084 24.75Z" />
|
||||||
</svg>
|
</svg>
|
||||||
@@ -671,7 +747,7 @@ export default function Invoice({
|
|||||||
return (
|
return (
|
||||||
total +
|
total +
|
||||||
(transaction.promoPrice == 0 ||
|
(transaction.promoPrice == 0 ||
|
||||||
transaction.promoPrice == null
|
transaction.promoPrice == null
|
||||||
? transaction.price * transaction.qty
|
? transaction.price * transaction.qty
|
||||||
: transaction.promoPrice * transaction.qty)
|
: transaction.promoPrice * transaction.qty)
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,62 +1,92 @@
|
|||||||
import React from "react";
|
import React, { useState } from "react";
|
||||||
|
|
||||||
const CircularDiagram = ({ segments }) => {
|
const HorizontalBarDiagram = ({ segments, width = 300 }) => {
|
||||||
const radius = 70; // Radius of the circle
|
const [showAll, setShowAll] = useState(false);
|
||||||
const strokeWidth = 20; // Width of each portion
|
|
||||||
const circumference = 2 * Math.PI * (radius - strokeWidth / 2);
|
|
||||||
|
|
||||||
let startOffset = -63; // Initial offset for each segment
|
const barHeight = 20; // tinggi tiap bar
|
||||||
|
const gap = 12; // jarak antar bar
|
||||||
|
const total = segments.reduce((sum, seg) => sum + seg.value, 0);
|
||||||
|
|
||||||
|
// tentukan data yang ditampilkan
|
||||||
|
const visibleSegments = showAll ? segments : segments.slice(0, 5);
|
||||||
|
const height = visibleSegments.length * (barHeight + gap);
|
||||||
|
|
||||||
const svgStyles = {
|
|
||||||
display: "block",
|
|
||||||
margin: "0 auto",
|
|
||||||
};
|
|
||||||
console.log(segments)
|
|
||||||
return (
|
return (
|
||||||
<svg
|
<div style={{ textAlign: "center" }}>
|
||||||
width={radius * 2}
|
<svg
|
||||||
height={radius * 2}
|
width={width}
|
||||||
viewBox={`0 0 ${radius * 2} ${radius * 2}`}
|
height={height}
|
||||||
style={svgStyles}
|
style={{ display: "block", margin: "0 auto" }}
|
||||||
>
|
>
|
||||||
<circle
|
{visibleSegments.map((segment, index) => {
|
||||||
cx={radius}
|
const { name, value, color, unit} = segment;
|
||||||
cy={radius}
|
const percentage = total > 0 ? ((value / total) * 100).toFixed(1) : 0;
|
||||||
r={radius - strokeWidth / 2}
|
const barWidth = (width * percentage) / 100;
|
||||||
stroke="#eee"
|
|
||||||
strokeWidth={strokeWidth}
|
|
||||||
fill="none"
|
|
||||||
/>
|
|
||||||
{segments.map((segment, index) => {
|
|
||||||
const { percentage, color } = segment;
|
|
||||||
console.log(percentage)
|
|
||||||
let p = percentage;
|
|
||||||
if(p == 'Infinity' || isNaN(p)) p = 0;
|
|
||||||
const segmentLength = (circumference * p) / 100;
|
|
||||||
const strokeDashoffset = circumference - startOffset;
|
|
||||||
|
|
||||||
startOffset += segmentLength;
|
return (
|
||||||
|
<g
|
||||||
|
key={index}
|
||||||
|
transform={`translate(0, ${index * (barHeight + gap)})`}
|
||||||
|
>
|
||||||
|
{/* Background bar */}
|
||||||
|
<rect
|
||||||
|
x={0}
|
||||||
|
y={0}
|
||||||
|
width={width}
|
||||||
|
height={barHeight}
|
||||||
|
fill="#eee"
|
||||||
|
rx={8}
|
||||||
|
ry={8}
|
||||||
|
/>
|
||||||
|
|
||||||
return (
|
{/* Filled bar */}
|
||||||
<circle
|
<rect
|
||||||
key={index}
|
x={0}
|
||||||
cx={radius}
|
y={0}
|
||||||
cy={radius}
|
width={barWidth}
|
||||||
r={radius - strokeWidth / 2}
|
height={barHeight}
|
||||||
stroke={color}
|
fill={color}
|
||||||
strokeWidth={strokeWidth}
|
rx={8}
|
||||||
fill="none"
|
ry={8}
|
||||||
strokeDasharray={`${segmentLength} ${
|
/>
|
||||||
circumference - segmentLength
|
|
||||||
}`}
|
{/* Name + Value + Percentage di dalam bar */}
|
||||||
strokeDashoffset={strokeDashoffset}
|
<text
|
||||||
strokeLinecap="round" // Rounds the edges of each segment
|
x={width - 8} // dekat ujung kanan
|
||||||
transform={`rotate(-90 ${radius} ${radius})`}
|
y={barHeight / 2}
|
||||||
/>
|
dy=".35em"
|
||||||
);
|
textAnchor="end"
|
||||||
})}
|
fill="black"
|
||||||
</svg>
|
fontSize="11"
|
||||||
|
fontWeight="bold"
|
||||||
|
style={{ pointerEvents: "none", textTransform: "capitalize" }}
|
||||||
|
>
|
||||||
|
{unit ? (name + ' ( ' + value +' '+ unit+')') : (name + ' ' + value +' ('+ percentage + '%)')}
|
||||||
|
</text>
|
||||||
|
</g>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
{/* Tombol lihat lebih banyak / lebih sedikit */}
|
||||||
|
{segments.length > 5 && (
|
||||||
|
<button
|
||||||
|
onClick={() => setShowAll(!showAll)}
|
||||||
|
style={{
|
||||||
|
marginTop: "8px",
|
||||||
|
padding: "6px 12px",
|
||||||
|
fontSize: "12px",
|
||||||
|
border: "1px solid #ccc",
|
||||||
|
borderRadius: "6px",
|
||||||
|
background: "#f9f9f9",
|
||||||
|
cursor: "pointer",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{showAll ? "Lihat lebih sedikit" : "Lihat lebih banyak"}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default CircularDiagram;
|
export default HorizontalBarDiagram;
|
||||||
|
|||||||
@@ -234,7 +234,7 @@ const LinktreePage = ({ user, setModal }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{user && user.roleId < 2 ? (
|
{getLocalStorage("auth") ? (
|
||||||
<div>
|
<div>
|
||||||
<div className={styles.header}>
|
<div className={styles.header}>
|
||||||
|
|
||||||
|
|||||||
@@ -180,7 +180,7 @@ const SetPaymentQr = ({ cafeId }) => {
|
|||||||
setLatestMutation(latestMutation);
|
setLatestMutation(latestMutation);
|
||||||
setCurrentQuantity(latestMutation.newStock);
|
setCurrentQuantity(latestMutation.newStock);
|
||||||
setCurrentPrice(formatCurrency(latestMutation.priceAtp));
|
setCurrentPrice(formatCurrency(latestMutation.priceAtp));
|
||||||
setNewPrice(formatCurrency(latestMutation.priceAtp));
|
setNewPrice(formatCurrency(latestMutation.priceAtp) || 0);
|
||||||
} else {
|
} else {
|
||||||
setCurrentQuantity(0); // Default value if no mutations exist
|
setCurrentQuantity(0); // Default value if no mutations exist
|
||||||
setLatestMutation({ newStock: 0 });
|
setLatestMutation({ newStock: 0 });
|
||||||
@@ -195,12 +195,12 @@ const SetPaymentQr = ({ cafeId }) => {
|
|||||||
|
|
||||||
const handleUpdateStock = async () => {
|
const handleUpdateStock = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
console.log('aaa')
|
||||||
try {
|
try {
|
||||||
const newprice = convertToInteger(newPrice)
|
|
||||||
const newStock = currentQuantity + quantityChange;
|
const newStock = currentQuantity + quantityChange;
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("newStock", newStock);
|
formData.append("newStock", newStock);
|
||||||
formData.append("priceAtp", newprice);
|
formData.append("priceAtp", newPrice);
|
||||||
formData.append("reason", "Stock update");
|
formData.append("reason", "Stock update");
|
||||||
|
|
||||||
await createMaterialMutation(materials[selectedMaterialIndex].materialId, formData);
|
await createMaterialMutation(materials[selectedMaterialIndex].materialId, formData);
|
||||||
|
|||||||
102
src/pages/PrintPage.js
Normal file
102
src/pages/PrintPage.js
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
import React, { useState, useMemo } from 'react';
|
||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
|
import qs from 'qs';
|
||||||
|
import '../print.css';
|
||||||
|
|
||||||
|
export default function PrintPage() {
|
||||||
|
const location = useLocation();
|
||||||
|
const [orientation, setOrientation] = useState('portrait');
|
||||||
|
|
||||||
|
const data = useMemo(() => {
|
||||||
|
try {
|
||||||
|
const query = qs.parse(location.search, { ignoreQueryPrefix: true });
|
||||||
|
return JSON.parse(query.data);
|
||||||
|
} catch (err) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}, [location.search]);
|
||||||
|
|
||||||
|
if (!data) return <div>Invalid data</div>;
|
||||||
|
|
||||||
|
const formatWaktu = (() => {
|
||||||
|
const date = new Date(data.date);
|
||||||
|
const tanggal = date.toLocaleDateString('id-ID');
|
||||||
|
const jam = date.toLocaleTimeString('id-ID', {
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
hour12: false,
|
||||||
|
});
|
||||||
|
return `${tanggal} ${jam}`;
|
||||||
|
})();
|
||||||
|
|
||||||
|
const itemsStr = data.items.map((item) => {
|
||||||
|
const name = item.name.length > 11 ? item.name.slice(0, 11) : item.name.padEnd(11);
|
||||||
|
const qty = item.qty.toString().padStart(2);
|
||||||
|
const total = (item.qty * item.price).toString().padStart(6);
|
||||||
|
return `${name} ${qty} ${total}`;
|
||||||
|
}).join('\n');
|
||||||
|
|
||||||
|
const getReceiptText = () => {
|
||||||
|
return (
|
||||||
|
` CAFE HOREE
|
||||||
|
Jl. Merdeka No. 123, Jakarta
|
||||||
|
Telp: (021) 12345678
|
||||||
|
|
||||||
|
==============================
|
||||||
|
Tanggal : ${formatWaktu}
|
||||||
|
Kasir : ${data.cashier || 'UNKNOWN'}
|
||||||
|
Bayar : ${data.payment_type}
|
||||||
|
------------------------------
|
||||||
|
Item Q Total
|
||||||
|
------------------------------
|
||||||
|
${itemsStr}
|
||||||
|
------------------------------
|
||||||
|
Terima kasih atas kunjungan Anda!
|
||||||
|
~ Cafe Horee ~
|
||||||
|
www.kedaimaster.com`
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePrintBluetooth = () => {
|
||||||
|
const content = getReceiptText();
|
||||||
|
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("content", content);
|
||||||
|
params.append("encode_format", "UTF-8");
|
||||||
|
|
||||||
|
// Optional: jika ingin spesifik printer Bluetooth
|
||||||
|
// params.append("device_address", "00:11:22:33:44:55");
|
||||||
|
|
||||||
|
const printUrl = `btprinter://print?${params.toString()}`;
|
||||||
|
window.location.href = printUrl;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`print-test ${orientation}`}>
|
||||||
|
<div className="controls">
|
||||||
|
<h1>CAFE HOREE - Mode {orientation.charAt(0).toUpperCase() + orientation.slice(1)}</h1>
|
||||||
|
<div className="orientation-selector">
|
||||||
|
<button
|
||||||
|
className={orientation === 'portrait' ? 'active' : ''}
|
||||||
|
onClick={() => setOrientation('portrait')}
|
||||||
|
>
|
||||||
|
Portrait
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className={orientation === 'landscape' ? 'active' : ''}
|
||||||
|
onClick={() => setOrientation('landscape')}
|
||||||
|
>
|
||||||
|
Landscape
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<button className="print-button" onClick={handlePrintBluetooth}>
|
||||||
|
🖨️ Print ke Bluetooth
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<pre className="print-area">
|
||||||
|
{getReceiptText()}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -55,6 +55,9 @@ const RoundedRectangle = ({
|
|||||||
? "rgb(85 85 85)"
|
? "rgb(85 85 85)"
|
||||||
: !isChildren && !children && backgroundColor,
|
: !isChildren && !children && backgroundColor,
|
||||||
color: loading ? "transparent" : color,
|
color: loading ? "transparent" : color,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between'
|
||||||
};
|
};
|
||||||
|
|
||||||
const valueAndPercentageContainerStyle = {
|
const valueAndPercentageContainerStyle = {
|
||||||
@@ -91,19 +94,17 @@ const RoundedRectangle = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={containerStyle} onClick={onClick}>
|
<div style={containerStyle} onClick={onClick}>
|
||||||
<div style={titleStyle}>{title}</div>
|
<div style={titleStyle}>
|
||||||
|
{title}
|
||||||
|
<div style={percentageStyle}>
|
||||||
|
{loading ? "" : percentage}
|
||||||
|
{percentage !== undefined && !loading && "%"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{!children && (
|
{!children && (
|
||||||
<div style={valueAndPercentageContainerStyle}>
|
<div style={valueAndPercentageContainerStyle}>
|
||||||
<div style={valueStyle}>{loading ? "Loading..." : value}</div>
|
<div style={valueStyle}>{loading ? "Loading..." : value}</div>
|
||||||
<div style={percentageStyle}>
|
|
||||||
{loading ? "" : percentage}
|
|
||||||
{percentage !== undefined && !loading && "%"}
|
|
||||||
{percentage !== undefined && !loading && (
|
|
||||||
<span style={arrowStyle}>
|
|
||||||
{percentage > 0 ? "↗" : percentage === 0 ? "-" : "↘"}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{children && <div>{children}</div>} {/* Properly render children */}
|
{children && <div>{children}</div>} {/* Properly render children */}
|
||||||
@@ -118,7 +119,7 @@ const App = ({ forCafe = true, cafeId = -1,
|
|||||||
const [selectedCafeId, setSelectedCafeId] = useState(cafeId);
|
const [selectedCafeId, setSelectedCafeId] = useState(cafeId);
|
||||||
const [analytics, setAnalytics] = useState({});
|
const [analytics, setAnalytics] = useState({});
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [filter, setFilter] = useState("monthly");
|
const [filter, setFilter] = useState("yesterday");
|
||||||
const [circularFilter, setCircularFilter] = useState("item");
|
const [circularFilter, setCircularFilter] = useState("item");
|
||||||
const [graphFilter, setGraphFilter] = useState("income");
|
const [graphFilter, setGraphFilter] = useState("income");
|
||||||
|
|
||||||
@@ -195,102 +196,84 @@ const App = ({ forCafe = true, cafeId = -1,
|
|||||||
// Define a color palette or generate colors dynamically
|
// Define a color palette or generate colors dynamically
|
||||||
const colorPalette = colors;
|
const colorPalette = colors;
|
||||||
|
|
||||||
// Ensure that each segment gets a unique color
|
|
||||||
let colorIndex = 0;
|
let colorIndex = 0;
|
||||||
|
|
||||||
console.log(filteredItems)
|
// Segment penjualan item
|
||||||
let segments = (selectedCafeId == 0 || selectedCafeId == -1) ? filteredItems.flatMap((cafe) => {
|
let segments =
|
||||||
const cafeItems = cafe.report?.itemSales || [];
|
selectedCafeId == 0 || selectedCafeId == -1
|
||||||
console.log(cafeItems); // Log all items for the cafe
|
? filteredItems.flatMap((cafe) => {
|
||||||
|
const cafeItems = cafe.report?.itemSales || [];
|
||||||
|
return cafeItems.map((item) => {
|
||||||
|
const color = colorPalette[colorIndex % colorPalette.length];
|
||||||
|
colorIndex++;
|
||||||
|
return {
|
||||||
|
name: item.itemName,
|
||||||
|
value: item.sold,
|
||||||
|
color,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
})
|
||||||
|
: filteredItems.map((item) => {
|
||||||
|
const color = colorPalette[colorIndex % colorPalette.length];
|
||||||
|
colorIndex++;
|
||||||
|
return {
|
||||||
|
name: item.itemName,
|
||||||
|
value: item.sold,
|
||||||
|
color,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
return cafeItems.map((item, index) => {
|
// Urutkan descending berdasarkan value
|
||||||
const percentage = totalSoldAcrossAllCafes > 0
|
segments.sort((a, b) => b.value - a.value);
|
||||||
? ((item.sold / totalSoldAcrossAllCafes) * 100).toFixed(2)
|
|
||||||
: 0;
|
|
||||||
|
|
||||||
console.log(`${item.itemName}: ${(percentage)}%`); // Log item name and percentage
|
// Reset color index untuk material
|
||||||
|
colorIndex = 0;
|
||||||
|
|
||||||
// Assign a unique color from the color palette
|
// Segment pengeluaran material
|
||||||
const color = colorPalette[colorIndex % colorPalette.length]; // Use modulo to cycle through colors
|
let materialSegments =
|
||||||
|
selectedCafeId == 0 || selectedCafeId == -1
|
||||||
|
? filteredItems.flatMap((cafe) => {
|
||||||
|
const cafeItems = cafe.report?.materialSpend || [];
|
||||||
|
return cafeItems.map((item) => {
|
||||||
|
const color = colorPalette[colorIndex % colorPalette.length];
|
||||||
|
colorIndex++;
|
||||||
|
return {
|
||||||
|
name: item.materialName,
|
||||||
|
value: item.spend,
|
||||||
|
unit: item.unit,
|
||||||
|
color,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
})
|
||||||
|
: filteredItems.map((item) => {
|
||||||
|
const color = colorPalette[colorIndex % colorPalette.length];
|
||||||
|
colorIndex++;
|
||||||
|
return {
|
||||||
|
name: item.materialName,
|
||||||
|
value: item.spend,
|
||||||
|
unit: item.unit,
|
||||||
|
|
||||||
colorIndex++; // Increment to ensure a new color for the next item
|
color,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
// Urutkan descending berdasarkan value
|
||||||
itemName: item.itemName,
|
materialSegments.sort((a, b) => b.value - a.value);
|
||||||
sold: item.sold,
|
|
||||||
percentage: percentage,
|
|
||||||
color: color,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}) : filteredItems.map((item, index) => {
|
|
||||||
const color = colorPalette[colorIndex % colorPalette.length]; // Use modulo to cycle through colors
|
|
||||||
colorIndex++; // Increment to ensure a new color for the next item
|
|
||||||
return {
|
|
||||||
itemName: item.itemName,
|
|
||||||
percentage: item.percentage,
|
|
||||||
color: color,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
segments.sort((a, b) => b.sold - a.sold);
|
|
||||||
|
|
||||||
|
|
||||||
let materialSegments = (selectedCafeId == 0 || selectedCafeId == -1) ? filteredItems.flatMap((cafe) => {
|
|
||||||
const cafeItems = cafe.report?.materialSpend || [];
|
|
||||||
console.log(cafeItems); // Log all items for the cafe
|
|
||||||
|
|
||||||
return cafeItems.map((item, index) => {
|
|
||||||
const percentage = totalSpendAcrossAllCafes > 0
|
|
||||||
? ((item.spend / totalSpendAcrossAllCafes) * 100).toFixed(2)
|
|
||||||
: 0;
|
|
||||||
|
|
||||||
console.log(`${item.materialName}: ${(percentage)}%`); // Log item name and percentage
|
|
||||||
|
|
||||||
// Assign a unique color from the color palette
|
|
||||||
const color = colorPalette[colorIndex % colorPalette.length]; // Use modulo to cycle through colors
|
|
||||||
|
|
||||||
colorIndex++; // Increment to ensure a new color for the next item
|
|
||||||
|
|
||||||
return {
|
|
||||||
itemName: item.materialName,
|
|
||||||
sold: item.spend,
|
|
||||||
percentage: percentage,
|
|
||||||
color: color,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}) : filteredItems.map((item, index) => {
|
|
||||||
const color = colorPalette[colorIndex % colorPalette.length]; // Use modulo to cycle through colors
|
|
||||||
colorIndex++; // Increment to ensure a new color for the next item
|
|
||||||
return {
|
|
||||||
itemName: item.materialName,
|
|
||||||
percentage: item.percentage,
|
|
||||||
color: color,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
materialSegments.sort((a, b) => b.spend - a.spend);
|
|
||||||
|
|
||||||
|
|
||||||
console.log(selectedCafeId)
|
console.log(selectedCafeId)
|
||||||
console.log(segments)
|
console.log(segments)
|
||||||
|
|
||||||
const formatIncome = (amount) => {
|
const formatIncome = (amount) => {
|
||||||
if (amount >= 1_000_000_000) {
|
if (amount == null) return "0";
|
||||||
// Format for billions
|
|
||||||
const billions = amount / 1_000_000_000;
|
const formatter = new Intl.NumberFormat("id-ID", {
|
||||||
return billions.toFixed(0) + "m"; // No decimal places for billions
|
style: "currency",
|
||||||
} else if (amount >= 1_000_000) {
|
currency: "IDR",
|
||||||
// Format for millions
|
minimumFractionDigits: 0,
|
||||||
const millions = amount / 1_000_000;
|
});
|
||||||
return millions.toFixed(2).replace(/\.00$/, "") + "jt"; // Two decimal places, remove trailing '.00'
|
|
||||||
} else if (amount >= 1_000) {
|
return formatter.format(amount);
|
||||||
// Format for thousands
|
|
||||||
const thousands = amount / 1_000;
|
|
||||||
return thousands.toFixed(1).replace(/\.0$/, "") + "k"; // One decimal place, remove trailing '.0'
|
|
||||||
} else {
|
|
||||||
// Less than a thousand
|
|
||||||
if (amount != null) return amount.toString();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function roundToInteger(num) {
|
function roundToInteger(num) {
|
||||||
@@ -478,7 +461,7 @@ const App = ({ forCafe = true, cafeId = -1,
|
|||||||
{forCafe && <div style={{ marginTop: '49px', marginRight: '10px', display: 'flex', flexWrap: 'nowrap', alignItems: 'center', fontSize: '25px' }} onClick={handleClose}><svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 512 512"><path d="M48,256c0,114.87,93.13,208,208,208s208-93.13,208-208S370.87,48,256,48,48,141.13,48,256Zm212.65-91.36a16,16,0,0,1,.09,22.63L208.42,240H342a16,16,0,0,1,0,32H208.42l52.32,52.73A16,16,0,1,1,238,347.27l-79.39-80a16,16,0,0,1,0-22.54l79.39-80A16,16,0,0,1,260.65,164.64Z" /></svg>Laporan</div>}
|
{forCafe && <div style={{ marginTop: '49px', marginRight: '10px', display: 'flex', flexWrap: 'nowrap', alignItems: 'center', fontSize: '25px' }} onClick={handleClose}><svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 512 512"><path d="M48,256c0,114.87,93.13,208,208,208s208-93.13,208-208S370.87,48,256,48,48,141.13,48,256Zm212.65-91.36a16,16,0,0,1,.09,22.63L208.42,240H342a16,16,0,0,1,0,32H208.42l52.32,52.73A16,16,0,1,1,238,347.27l-79.39-80a16,16,0,0,1,0-22.54l79.39-80A16,16,0,0,1,260.65,164.64Z" /></svg>Laporan</div>}
|
||||||
<div style={{ marginTop: '10px' }}>
|
<div style={{ marginTop: '10px' }}>
|
||||||
{!forCafe &&
|
{!forCafe &&
|
||||||
<div className={styles.dateSelectorWrapper} style={{ fontSize: '12px' }}>
|
<div className={styles.dateSelectorWrapper} style={{ fontSize: '16px', textTransform: 'uppercase' }}>
|
||||||
{texts.map((item, indexx) => {
|
{texts.map((item, indexx) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -531,7 +514,7 @@ const App = ({ forCafe = true, cafeId = -1,
|
|||||||
<RoundedRectangle
|
<RoundedRectangle
|
||||||
title="Pendapatan"
|
title="Pendapatan"
|
||||||
fontSize="12px"
|
fontSize="12px"
|
||||||
value={!loading && "Rp" + formatIncome(analytics?.currentTotals?.income)}
|
value={!loading && formatIncome(analytics?.currentTotals?.income)}
|
||||||
percentage={roundToInteger(analytics?.growth?.incomeGrowth)}
|
percentage={roundToInteger(analytics?.growth?.incomeGrowth)}
|
||||||
invert={false}
|
invert={false}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
@@ -543,7 +526,7 @@ const App = ({ forCafe = true, cafeId = -1,
|
|||||||
<RoundedRectangle
|
<RoundedRectangle
|
||||||
title="Pengeluaran"
|
title="Pengeluaran"
|
||||||
fontSize="12px"
|
fontSize="12px"
|
||||||
value={!loading && "Rp" + formatIncome(analytics?.currentTotals?.outcome)}
|
value={!loading && formatIncome(analytics?.currentTotals?.outcome)}
|
||||||
percentage={roundToInteger(analytics?.growth?.outcomeGrowth)}
|
percentage={roundToInteger(analytics?.growth?.outcomeGrowth)}
|
||||||
invert={true}
|
invert={true}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
@@ -614,8 +597,8 @@ const App = ({ forCafe = true, cafeId = -1,
|
|||||||
7 hari terakhir dengan 7 hari sebelumnya, dengan penghitungan dimulai dari data kemarin.`
|
7 hari terakhir dengan 7 hari sebelumnya, dengan penghitungan dimulai dari data kemarin.`
|
||||||
:
|
:
|
||||||
(filter == 'yesterday') ? `Data dihitung dengan membandingkan antara hari ini dan kemarin.`
|
(filter == 'yesterday') ? `Data dihitung dengan membandingkan antara hari ini dan kemarin.`
|
||||||
:
|
:
|
||||||
(filter == 'monthly') ? `Data dihitung dengan membandingkan antara awal hingga akhir bulan ini dan bulan lalu, dengan penghitungan berakhir pada data kemarin.` : `Data dihitung dengan membandingkan antara awal hingga akhir tahun ini dan tahun lalu, dengan penghitungan berakhir pada data kemarin.`}
|
(filter == 'monthly') ? `Data dihitung dengan membandingkan antara awal hingga akhir bulan ini dan bulan lalu, dengan penghitungan berakhir pada data kemarin.` : `Data dihitung dengan membandingkan antara awal hingga akhir tahun ini dan tahun lalu, dengan penghitungan berakhir pada data kemarin.`}
|
||||||
</h6>
|
</h6>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -653,29 +636,6 @@ const App = ({ forCafe = true, cafeId = -1,
|
|||||||
<div style={{ flex: 1 }}>
|
<div style={{ flex: 1 }}>
|
||||||
<CircularDiagram segments={circularFilter == 'item' ? segments : materialSegments} />
|
<CircularDiagram segments={circularFilter == 'item' ? segments : materialSegments} />
|
||||||
</div>
|
</div>
|
||||||
<div style={{ flex: 1, marginLeft: "20px" }}>
|
|
||||||
{(circularFilter === 'item' ? segments : materialSegments).map((item, index) => (
|
|
||||||
<div
|
|
||||||
key={index}
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
margin: "10px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
marginRight: "5px",
|
|
||||||
fontSize: "1.2em",
|
|
||||||
color: colors[index],
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
★
|
|
||||||
</div>
|
|
||||||
<h5 style={{ margin: 0, textAlign: "left" }}>{item.itemName}</h5>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.filterSelectorWrapper}>
|
<div className={styles.filterSelectorWrapper}>
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import React, { useRef, useEffect, useState } from "react";
|
import React, { useRef, useEffect, useState } from "react";
|
||||||
|
import qs from 'qs';
|
||||||
|
|
||||||
import styles from "./Transactions.module.css";
|
import styles from "./Transactions.module.css";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams, useNavigate } from "react-router-dom";
|
||||||
import { ColorRing } from "react-loader-spinner";
|
import { ColorRing } from "react-loader-spinner";
|
||||||
import {
|
import {
|
||||||
getTransaction,
|
getTransaction,
|
||||||
@@ -11,7 +13,7 @@ import { getTables } from "../helpers/tableHelper";
|
|||||||
import TableCanvas from "../components/TableCanvas";
|
import TableCanvas from "../components/TableCanvas";
|
||||||
import { useSearchParams } from "react-router-dom";
|
import { useSearchParams } from "react-router-dom";
|
||||||
|
|
||||||
export default function Transactions({ propsShopId, sendParam, deviceType, handleMoveToTransaction, depth, shopImg, setModal }) {
|
export default function Transactions({ propsShopId,setIsModalOpen,cafeIdentityName, sendParam, deviceType, handleMoveToTransaction, depth, shopImg, setModal }) {
|
||||||
const { shopId, tableId } = useParams();
|
const { shopId, tableId } = useParams();
|
||||||
if (sendParam) sendParam({ shopId, tableId });
|
if (sendParam) sendParam({ shopId, tableId });
|
||||||
|
|
||||||
@@ -25,6 +27,9 @@ export default function Transactions({ propsShopId, sendParam, deviceType, handl
|
|||||||
|
|
||||||
const [transactionRefreshKey, setTransactionRefreshKey] = useState(0);
|
const [transactionRefreshKey, setTransactionRefreshKey] = useState(0);
|
||||||
|
|
||||||
|
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const transactionId = searchParams.get("transactionId") || "";
|
const transactionId = searchParams.get("transactionId") || "";
|
||||||
|
|
||||||
@@ -115,10 +120,30 @@ export default function Transactions({ propsShopId, sendParam, deviceType, handl
|
|||||||
autoResizeTextArea(noteRef.current);
|
autoResizeTextArea(noteRef.current);
|
||||||
}
|
}
|
||||||
}, [transaction?.notes]);
|
}, [transaction?.notes]);
|
||||||
const handlePrint = () => {
|
|
||||||
window.print();
|
const handlePrint = (transaction) => {
|
||||||
|
// Pilih data yang ingin dikirim
|
||||||
|
const printableData = {
|
||||||
|
transactionId: transaction.transactionId,
|
||||||
|
items: transaction.DetailedTransactions.map(dt => ({
|
||||||
|
name: dt.Item.name,
|
||||||
|
qty: dt.qty,
|
||||||
|
price: dt.promoPrice || dt.price,
|
||||||
|
})),
|
||||||
|
total: calculateTotalPrice(transaction.DetailedTransactions),
|
||||||
|
date: transaction.createdAt,
|
||||||
|
payment_type: transaction.payment_type,
|
||||||
|
table: transaction.Table?.tableNo || "N/A",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Serialize to query string
|
||||||
|
const queryString = qs.stringify({ data: JSON.stringify(printableData) });
|
||||||
|
|
||||||
|
// Navigate to /print with query string
|
||||||
|
setIsModalOpen(false);
|
||||||
|
navigate(`/${cafeIdentityName}/print?${queryString}`);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={transactionRefreshKey} className={styles.Transaction}>
|
<div key={transactionRefreshKey} className={styles.Transaction}>
|
||||||
|
|
||||||
@@ -316,7 +341,7 @@ export default function Transactions({ propsShopId, sendParam, deviceType, handl
|
|||||||
{transaction.confirmed > 1 && (
|
{transaction.confirmed > 1 && (
|
||||||
<h5
|
<h5
|
||||||
className={styles.DeclineButton}
|
className={styles.DeclineButton}
|
||||||
onClick={() => handlePrint()}
|
onClick={() => handlePrint(transaction)}
|
||||||
>
|
>
|
||||||
Cetak struk
|
Cetak struk
|
||||||
</h5>
|
</h5>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react"; import qs from 'qs';
|
||||||
|
|
||||||
import styles from "./Transactions.module.css";
|
import styles from "./Transactions.module.css";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams, useNavigate } from "react-router-dom";
|
||||||
import { ColorRing } from "react-loader-spinner";
|
import { ColorRing } from "react-loader-spinner";
|
||||||
import {
|
import {
|
||||||
getMyTransactions,
|
getMyTransactions,
|
||||||
@@ -30,6 +31,9 @@ export default function Transactions({ shop, shopId, propsShopId, sendParam, dev
|
|||||||
|
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setMatchedItems(searchAndAggregateItems(transactions, searchTerm));
|
setMatchedItems(searchAndAggregateItems(transactions, searchTerm));
|
||||||
}, [searchTerm, transactions]);
|
}, [searchTerm, transactions]);
|
||||||
@@ -58,51 +62,57 @@ export default function Transactions({ shop, shopId, propsShopId, sendParam, dev
|
|||||||
return total + dt.qty * (dt.promoPrice ? dt.promoPrice : dt.price);
|
return total + dt.qty * (dt.promoPrice ? dt.promoPrice : dt.price);
|
||||||
}, 0);
|
}, 0);
|
||||||
};
|
};
|
||||||
const calculateAllTransactionsTotal = (transactions) => {
|
|
||||||
return transactions
|
const formatRupiah = (number) => {
|
||||||
.filter(transaction => transaction.confirmed > 1) // Filter transactions where confirmed > 1
|
return 'Rp' + number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, '.');
|
||||||
.reduce((grandTotal, transaction) => {
|
|
||||||
return grandTotal + calculateTotalPrice(transaction.DetailedTransactions);
|
|
||||||
}, 0);
|
|
||||||
};
|
};
|
||||||
const searchAndAggregateItems = (transactions, searchTerm) => {
|
|
||||||
if (!searchTerm.trim()) return [];
|
|
||||||
|
|
||||||
const normalizedTerm = searchTerm.trim().toLowerCase();
|
|
||||||
// Map with key = `${itemId}-${confirmedGroup}` to keep confirmed groups separate
|
|
||||||
const aggregatedItems = new Map();
|
|
||||||
|
|
||||||
transactions.forEach(transaction => {
|
const calculateAllTransactionsTotal = (transactions) => {
|
||||||
// Determine confirmed group as a string key
|
return transactions
|
||||||
const confirmedGroup = transaction.confirmed >= 0 && transaction.confirmed > 1 ? 'confirmed_gt_1' : 'confirmed_le_1';
|
.filter(transaction => transaction.confirmed > 1) // Filter transactions where confirmed > 1
|
||||||
|
.reduce((grandTotal, transaction) => {
|
||||||
|
return grandTotal + calculateTotalPrice(transaction.DetailedTransactions);
|
||||||
|
}, 0);
|
||||||
|
};
|
||||||
|
const searchAndAggregateItems = (transactions, searchTerm) => {
|
||||||
|
if (!searchTerm.trim()) return [];
|
||||||
|
|
||||||
transaction.DetailedTransactions.forEach(detail => {
|
const normalizedTerm = searchTerm.trim().toLowerCase();
|
||||||
const itemName = detail.Item.name;
|
// Map with key = `${itemId}-${confirmedGroup}` to keep confirmed groups separate
|
||||||
const itemNameLower = itemName.toLowerCase();
|
const aggregatedItems = new Map();
|
||||||
|
|
||||||
if (itemNameLower.includes(normalizedTerm)) {
|
transactions.forEach(transaction => {
|
||||||
// Combine itemId and confirmedGroup to keep them separated
|
// Determine confirmed group as a string key
|
||||||
const key = `${detail.itemId}-${confirmedGroup}`;
|
const confirmedGroup = transaction.confirmed >= 0 && transaction.confirmed > 1 ? 'confirmed_gt_1' : 'confirmed_le_1';
|
||||||
|
|
||||||
if (!aggregatedItems.has(key)) {
|
transaction.DetailedTransactions.forEach(detail => {
|
||||||
aggregatedItems.set(key, {
|
const itemName = detail.Item.name;
|
||||||
itemId: detail.itemId,
|
const itemNameLower = itemName.toLowerCase();
|
||||||
name: itemName,
|
|
||||||
totalQty: 0,
|
if (itemNameLower.includes(normalizedTerm)) {
|
||||||
totalPrice: 0,
|
// Combine itemId and confirmedGroup to keep them separated
|
||||||
confirmedGroup, // Keep track of which group this belongs to
|
const key = `${detail.itemId}-${confirmedGroup}`;
|
||||||
});
|
|
||||||
|
if (!aggregatedItems.has(key)) {
|
||||||
|
aggregatedItems.set(key, {
|
||||||
|
itemId: detail.itemId,
|
||||||
|
name: itemName,
|
||||||
|
totalQty: 0,
|
||||||
|
totalPrice: 0,
|
||||||
|
confirmedGroup, // Keep track of which group this belongs to
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const current = aggregatedItems.get(key);
|
||||||
|
current.totalQty += detail.qty;
|
||||||
|
current.totalPrice += detail.qty * (detail.promoPrice || detail.price);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
const current = aggregatedItems.get(key);
|
|
||||||
current.totalQty += detail.qty;
|
|
||||||
current.totalPrice += detail.qty * (detail.promoPrice || detail.price);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
console.log(aggregatedItems.values())
|
||||||
console.log(aggregatedItems.values())
|
return Array.from(aggregatedItems.values());
|
||||||
return Array.from(aggregatedItems.values());
|
};
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -142,6 +152,64 @@ console.log(aggregatedItems.values())
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handlePrint = (transaction) => {
|
||||||
|
const formatWaktu = (() => {
|
||||||
|
const date = new Date(transaction.createdAt);
|
||||||
|
const tanggal = date.toLocaleDateString('id-ID');
|
||||||
|
const jam = date.toLocaleTimeString('id-ID', {
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
hour12: false,
|
||||||
|
});
|
||||||
|
return `${tanggal} ${jam}`;
|
||||||
|
})();
|
||||||
|
|
||||||
|
const itemsStr = transaction.DetailedTransactions.map((dt) => {
|
||||||
|
const name = dt.Item.name.length > 11
|
||||||
|
? dt.Item.name.slice(0, 11)
|
||||||
|
: dt.Item.name.padEnd(11);
|
||||||
|
const qty = dt.qty.toString().padStart(3);
|
||||||
|
const total = formatRupiah(dt.qty * (dt.promoPrice || dt.price)).padStart(15);
|
||||||
|
return `${name} ${qty} ${total}`;
|
||||||
|
}).join('\n');
|
||||||
|
|
||||||
|
const totalHarga = calculateTotalPrice(transaction.DetailedTransactions);
|
||||||
|
|
||||||
|
const totalStr = `Total: ${formatRupiah(totalHarga)}`;
|
||||||
|
|
||||||
|
const receiptText = (
|
||||||
|
` CAFE HOREE
|
||||||
|
Jl. Ahmad Yani No. 12, Kediri
|
||||||
|
Telp: 0812-1617-6963
|
||||||
|
|
||||||
|
==============================
|
||||||
|
Tanggal : ${formatWaktu}
|
||||||
|
Bayar : ${transaction.payment_type}
|
||||||
|
------------------------------
|
||||||
|
Item Qty Total
|
||||||
|
------------------------------
|
||||||
|
${itemsStr}
|
||||||
|
${totalStr}
|
||||||
|
==============================
|
||||||
|
Terima kasih atas kunjungannya!
|
||||||
|
~~
|
||||||
|
supported by kedaimaster.com
|
||||||
|
|
||||||
|
|
||||||
|
\n\n\n\n\n`
|
||||||
|
);
|
||||||
|
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("content", receiptText);
|
||||||
|
params.append("encode_format", "UTF-8");
|
||||||
|
|
||||||
|
const printUrl = `btprinter://print?${params.toString()}`;
|
||||||
|
|
||||||
|
// Trigger aplikasi printer via URL scheme
|
||||||
|
window.location.href = printUrl;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
if (loading)
|
if (loading)
|
||||||
return (
|
return (
|
||||||
<div className="Loader">
|
<div className="Loader">
|
||||||
@@ -154,7 +222,7 @@ console.log(aggregatedItems.values())
|
|||||||
return (
|
return (
|
||||||
<div className={styles.Transactions}>
|
<div className={styles.Transactions}>
|
||||||
<h2 className={styles["Transactions-title"]}>
|
<h2 className={styles["Transactions-title"]}>
|
||||||
Transaksi selesai Rp {calculateAllTransactionsTotal(transactions)}
|
Transaksi selesai {formatRupiah(calculateAllTransactionsTotal(transactions))}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
@@ -172,8 +240,8 @@ console.log(aggregatedItems.values())
|
|||||||
|
|
||||||
{matchedItems.length > 0 && matchedItems.map(item => (
|
{matchedItems.length > 0 && matchedItems.map(item => (
|
||||||
<div
|
<div
|
||||||
key={`${item.itemId}-${item.confirmedGroup}`}
|
key={`${item.itemId}-${item.confirmedGroup}`}
|
||||||
className={styles.RoundedRectangle}
|
className={styles.RoundedRectangle}
|
||||||
style={{ overflow: "hidden" }}
|
style={{ overflow: "hidden" }}
|
||||||
>
|
>
|
||||||
<ul>
|
<ul>
|
||||||
@@ -183,7 +251,7 @@ console.log(aggregatedItems.values())
|
|||||||
</ul>
|
</ul>
|
||||||
<div className={styles.TotalContainer}>
|
<div className={styles.TotalContainer}>
|
||||||
<span>Total:</span>
|
<span>Total:</span>
|
||||||
<span>Rp {item.totalPrice}</span>
|
<span>{formatRupiah(item.totalPrice)}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@@ -304,8 +372,11 @@ console.log(aggregatedItems.values())
|
|||||||
<ul>
|
<ul>
|
||||||
{transaction.DetailedTransactions.map((detail) => (
|
{transaction.DetailedTransactions.map((detail) => (
|
||||||
<li key={detail.detailedTransactionId}>
|
<li key={detail.detailedTransactionId}>
|
||||||
<span>{detail.Item.name}</span> - {detail.qty < 1 ? 'tidak tersedia' : `${detail.qty} x Rp
|
<span>{detail.Item.name}</span> - {detail.qty < 1
|
||||||
${detail.promoPrice ? detail.promoPrice : detail.price}`}
|
? 'tidak tersedia'
|
||||||
|
: `${detail.qty} x ${formatRupiah(detail.promoPrice || detail.price)}`
|
||||||
|
}
|
||||||
|
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
@@ -350,38 +421,47 @@ console.log(aggregatedItems.values())
|
|||||||
<div className={styles.TotalContainer}>
|
<div className={styles.TotalContainer}>
|
||||||
<span>Total:</span>
|
<span>Total:</span>
|
||||||
<span>
|
<span>
|
||||||
Rp {calculateTotalPrice(transaction.DetailedTransactions)}
|
{formatRupiah(calculateTotalPrice(transaction.DetailedTransactions))}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.TotalContainer}>
|
<div className={styles.TotalContainer}>
|
||||||
{(deviceType == 'clerk' && !transaction.is_paid && (transaction.confirmed == 0 || transaction.confirmed == 1 || transaction.confirmed == 2)) &&
|
{(deviceType == 'clerk' && !transaction.is_paid && (transaction.confirmed == 0 || transaction.confirmed == 1 || transaction.confirmed == 2)) &&
|
||||||
<button
|
<>
|
||||||
className={styles.PayButton}
|
<button
|
||||||
onClick={() => handleConfirm(transaction.transactionId)}
|
className={styles.PayButton}
|
||||||
disabled={isPaymentLoading}
|
onClick={() => handleConfirm(transaction.transactionId)}
|
||||||
>
|
disabled={isPaymentLoading}
|
||||||
{
|
>
|
||||||
isPaymentLoading ? (
|
{
|
||||||
<ColorRing height="50" width="50" color="white" />
|
isPaymentLoading ? (
|
||||||
) : transaction.confirmed === 1 ? (
|
<ColorRing height="50" width="50" color="white" />
|
||||||
"Konfirmasi Telah Bayar"
|
) : transaction.confirmed === 1 ? (
|
||||||
) : transaction.confirmed === 2 ? (
|
"Konfirmasi Telah Bayar"
|
||||||
"Confirm item is ready"
|
) : transaction.confirmed === 2 ? (
|
||||||
) : (
|
"Confirm item is ready"
|
||||||
"Confirm availability"
|
) : (
|
||||||
)
|
"Confirm availability"
|
||||||
}
|
)
|
||||||
|
}
|
||||||
|
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
</>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{deviceType == 'clerk' && transaction.confirmed > 1 && (
|
||||||
|
<h5
|
||||||
|
className={styles.DeclineButton}
|
||||||
|
onClick={() => handlePrint(transaction)}
|
||||||
|
>
|
||||||
|
Cetak struk
|
||||||
|
</h5>
|
||||||
|
)}
|
||||||
{deviceType == 'guestDevice' && transaction.confirmed < 2 && transaction.payment_type != 'cash' && transaction.payment_type != 'paylater/cash' &&
|
{deviceType == 'guestDevice' && transaction.confirmed < 2 && transaction.payment_type != 'cash' && transaction.payment_type != 'paylater/cash' &&
|
||||||
<ButtonWithReplica
|
<ButtonWithReplica
|
||||||
paymentUrl={paymentUrl}
|
paymentUrl={paymentUrl}
|
||||||
price={
|
price={formatRupiah(calculateTotalPrice(transaction.DetailedTransactions))}
|
||||||
"Rp" + calculateTotalPrice(transaction.DetailedTransactions)
|
|
||||||
}
|
|
||||||
disabled={isPaymentLoading}
|
disabled={isPaymentLoading}
|
||||||
isPaymentLoading={isPaymentLoading}
|
isPaymentLoading={isPaymentLoading}
|
||||||
handleClick={() => handleConfirm(transaction.transactionId)}
|
handleClick={() => handleConfirm(transaction.transactionId)}
|
||||||
|
|||||||
@@ -403,48 +403,58 @@
|
|||||||
display: none; /* Hidden in normal view */
|
display: none; /* Hidden in normal view */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Print-specific styles */
|
|
||||||
@media print {
|
@media print {
|
||||||
.Transaction {
|
@page {
|
||||||
display: none; /* Hide everything else when printing */
|
size: portrait;
|
||||||
|
margin: 15mm;
|
||||||
}
|
}
|
||||||
|
|
||||||
.printContainer {
|
body * {
|
||||||
display: block !important;
|
visibility: hidden;
|
||||||
position: static;
|
|
||||||
background: white;
|
|
||||||
color: black;
|
|
||||||
font-family: 'Courier New', Courier, monospace;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.receipt {
|
#print-section,
|
||||||
|
#print-section * {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
#print-section {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 400px;
|
padding: 15mm;
|
||||||
margin: 0 auto;
|
background-color: white;
|
||||||
}
|
|
||||||
|
|
||||||
.center-text {
|
|
||||||
text-align: center;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-line {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.total {
|
|
||||||
text-align: right;
|
|
||||||
font-size: 1.2em;
|
|
||||||
font-weight: bold;
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
button, .DeclineButton, .addNewItem {
|
|
||||||
display: none !important;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.receipt {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
max-width: 400px;
|
||||||
|
margin: auto;
|
||||||
|
color: #000;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.receipt hr {
|
||||||
|
border: none;
|
||||||
|
border-top: 1px dashed #aaa;
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.receipt .center-text {
|
||||||
|
text-align: center;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.receipt .item-line {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.receipt .total {
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: right;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|||||||
84
src/print.css
Normal file
84
src/print.css
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
.print-test {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding: 20px;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls {
|
||||||
|
background-color: #333;
|
||||||
|
color: white;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 10px;
|
||||||
|
width: 90%;
|
||||||
|
max-width: 800px;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.orientation-selector button,
|
||||||
|
.print-button {
|
||||||
|
margin: 10px;
|
||||||
|
padding: 10px 20px;
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
border: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.orientation-selector button.active {
|
||||||
|
background-color: #007bff;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.print-area {
|
||||||
|
background: white;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.4;
|
||||||
|
padding: 10px;
|
||||||
|
width: 48mm;
|
||||||
|
max-width: 48mm;
|
||||||
|
color: black;
|
||||||
|
box-shadow: none;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Print media query */
|
||||||
|
@media print {
|
||||||
|
body {
|
||||||
|
background-color: white;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.print-area {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
border: none;
|
||||||
|
box-shadow: none;
|
||||||
|
padding: 0;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body * {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.print-area, .print-area * {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.print-area {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user