Compare commits
16 Commits
b726ae6919
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5f75b8658a | ||
|
|
222169be74 | ||
|
|
026813d1e0 | ||
|
|
dcf0455772 | ||
|
|
4fa272875f | ||
|
|
40830ee48c | ||
|
|
ba896106d4 | ||
|
|
e039fc8acc | ||
|
|
dd0227ab80 | ||
|
|
67cf759b31 | ||
|
|
53e091d3a4 | ||
|
|
3a431b1b14 | ||
|
|
69a07be3cd | ||
|
|
b012517568 | ||
|
|
3e35468f2c | ||
|
|
df7c4f737c |
11063
package-lock.json
generated
11063
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -18,6 +18,7 @@
|
||||
"jsqr": "^1.4.0",
|
||||
"qr-scanner": "^1.4.2",
|
||||
"qrcode.react": "^3.1.0",
|
||||
"qs": "^6.14.0",
|
||||
"react": "^18.3.1",
|
||||
"react-apexcharts": "^1.7.0",
|
||||
"react-bootstrap": "^2.10.4",
|
||||
@@ -28,6 +29,7 @@
|
||||
"react-router-dom": "^6.24.0",
|
||||
"react-scripts": "5.0.1",
|
||||
"react-switch": "^7.0.0",
|
||||
"react-to-print": "^3.1.1",
|
||||
"react-youtube": "^10.1.0",
|
||||
"smooth-scroll-into-view-if-needed": "^2.0.2",
|
||||
"socket.io-client": "^4.7.5",
|
||||
|
||||
@@ -28,6 +28,15 @@
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>KedaiMaster</title>
|
||||
|
||||
<!-- Google tag (gtag.js) -->
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-2SKSCVFB2N"></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){ dataLayer.push(arguments); }
|
||||
gtag('js', new Date());
|
||||
gtag('config', 'G-2SKSCVFB2N');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
|
||||
@@ -3,17 +3,17 @@
|
||||
"name": "jangan pernah ragukan pelanggan",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"src": "kedai.png",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "logo192.png",
|
||||
"src": "kedai.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "logo512.png",
|
||||
"src": "kedai.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
|
||||
@@ -3,12 +3,10 @@
|
||||
@import url("https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@200;300;400;500;600;700;800&ital,wght@0,200..800;1,200..800&display=swap");
|
||||
html,
|
||||
body {
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
}
|
||||
|
||||
.App {
|
||||
/* overflow-x: hidden; */
|
||||
}
|
||||
|
||||
.Cafe {
|
||||
|
||||
330
src/App.js
330
src/App.js
@@ -10,7 +10,7 @@ import {
|
||||
} from "react-router-dom";
|
||||
import socket from "./services/socketService";
|
||||
|
||||
|
||||
import Print from "./pages/PrintPage.js";
|
||||
import Dashboard from "./pages/Dashboard";
|
||||
import ScanMeja from "./pages/ScanMeja";
|
||||
import CafePage from "./pages/CafePage";
|
||||
@@ -27,7 +27,7 @@ import { getTableByCode } from "./helpers/tableHelper.js";
|
||||
|
||||
import {
|
||||
getTransactionsFromCafe,
|
||||
checkIsMyTransaction
|
||||
checkIsMyTransaction,
|
||||
} from "./helpers/transactionHelpers";
|
||||
import {
|
||||
getConnectedGuestSides,
|
||||
@@ -45,7 +45,8 @@ import {
|
||||
} from "./helpers/subscribeHelpers.js";
|
||||
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() {
|
||||
const location = useLocation();
|
||||
@@ -71,31 +72,34 @@ function App() {
|
||||
const [depth, setDepth] = useState(-1);
|
||||
const [queue, setQueue] = useState([]);
|
||||
|
||||
const [newTransaction, setNewTransaction] = useState({});
|
||||
|
||||
const queryParams = new URLSearchParams(location.search);
|
||||
const tokenParams = queryParams.get("token");
|
||||
if (tokenParams) localStorage.setItem("auth", tokenParams);
|
||||
|
||||
const validTransactionStates = [
|
||||
'new_transaction',
|
||||
'transaction_canceled',
|
||||
'transaction_pending',
|
||||
'transaction_confirmed',
|
||||
'payment_claimed',
|
||||
'transaction_success',
|
||||
'transaction_end',
|
||||
'transaction_failed',
|
||||
"new_transaction",
|
||||
"transaction_canceled",
|
||||
"transaction_pending",
|
||||
"transaction_confirmed",
|
||||
"payment_claimed",
|
||||
"transaction_success",
|
||||
"transaction_end",
|
||||
"transaction_failed",
|
||||
];
|
||||
|
||||
|
||||
const calculateTotalsFromLocalStorage = () => {
|
||||
const { totalCount, totalPrice } = calculateTotals(shopId);
|
||||
setTotalItemsCount(totalCount);
|
||||
setTotalPrice(totalPrice);
|
||||
|
||||
|
||||
// If 'lastTransaction' exists, proceed
|
||||
const lastTransaction = JSON.parse(localStorage.getItem("lastTransaction"));
|
||||
console.log(lastTransaction);
|
||||
|
||||
if (lastTransaction != null) {
|
||||
console.log(lastTransaction)
|
||||
console.log(lastTransaction);
|
||||
setLastTransaction(lastTransaction);
|
||||
}
|
||||
};
|
||||
@@ -104,46 +108,54 @@ function App() {
|
||||
const init = async () => {
|
||||
await checkLastTransaction();
|
||||
};
|
||||
|
||||
|
||||
init(); // call the async function
|
||||
|
||||
|
||||
const handleStorageChange = () => {
|
||||
calculateTotalsFromLocalStorage();
|
||||
|
||||
if (!localStorage.getItem("lastTransaction")) {
|
||||
setLastTransaction(null);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
window.addEventListener("localStorageUpdated", handleStorageChange);
|
||||
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("localStorageUpdated", handleStorageChange);
|
||||
};
|
||||
}, [shopId]);
|
||||
|
||||
|
||||
const checkLastTransaction = async () => {
|
||||
const { totalCount, totalPrice } = calculateTotals(shopId);
|
||||
setTotalItemsCount(totalCount);
|
||||
setTotalPrice(totalPrice);
|
||||
|
||||
|
||||
const lastTransactionStr = localStorage.getItem("lastTransaction");
|
||||
|
||||
|
||||
if (!lastTransactionStr) return;
|
||||
|
||||
|
||||
const lastTransaction = JSON.parse(lastTransactionStr);
|
||||
console.log(lastTransaction);
|
||||
|
||||
|
||||
if (!lastTransaction || !lastTransaction.transactionId) {
|
||||
localStorage.removeItem("lastTransaction");
|
||||
return;
|
||||
}
|
||||
|
||||
const myLastTransaction = await checkIsMyTransaction(lastTransaction.transactionId);
|
||||
console.log(myLastTransaction)
|
||||
|
||||
setModal("transaction_confirmed", {
|
||||
transactionId: lastTransaction.transactionId,
|
||||
});
|
||||
const myLastTransaction = await checkIsMyTransaction(
|
||||
lastTransaction.transactionId
|
||||
);
|
||||
console.log(myLastTransaction);
|
||||
if (myLastTransaction.isMyTransaction) {
|
||||
setLastTransaction(lastTransaction);
|
||||
} else {
|
||||
localStorage.removeItem("lastTransaction");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const handleSetParam = async ({ shopIdentifier, tableCode }) => {
|
||||
setShopIdentifier(shopIdentifier);
|
||||
@@ -159,12 +171,14 @@ function App() {
|
||||
const fetchData = async () => {
|
||||
console.log("gettingItems");
|
||||
try {
|
||||
const { response, cafe, data } = await getItemTypesWithItems(shopIdentifier);
|
||||
const { response, cafe, data } = await getItemTypesWithItems(
|
||||
shopIdentifier
|
||||
);
|
||||
if (response.status === 200) {
|
||||
setShopId(cafe.cafeId)
|
||||
setShopId(cafe.cafeId);
|
||||
setShop(cafe);
|
||||
setShopItems(data);
|
||||
console.log(data)
|
||||
console.log(data);
|
||||
// Filter out unavailable items
|
||||
const filteredData = data
|
||||
.map((itemType) => ({
|
||||
@@ -219,7 +233,7 @@ function App() {
|
||||
});
|
||||
} else {
|
||||
socket.emit("checkUserToken", {
|
||||
token: getLocalStorage("auth"),
|
||||
token: getLocalStorage("auth") || tokenParams,
|
||||
shopId,
|
||||
});
|
||||
}
|
||||
@@ -230,33 +244,34 @@ function App() {
|
||||
// Call `setModal` with content and parameters
|
||||
setModal("transaction_pending", data);
|
||||
|
||||
localStorage.setItem('cart', []);
|
||||
localStorage.setItem("cart", []);
|
||||
|
||||
calculateTotalsFromLocalStorage();
|
||||
});
|
||||
|
||||
socket.on("transaction_confirmed", async (data) => {
|
||||
console.log("transaction notification: " + data);
|
||||
console.log(JSON.stringify(data));
|
||||
setModal("transaction_confirmed", data);
|
||||
|
||||
localStorage.setItem('cart', []);
|
||||
localStorage.setItem("cart", []);
|
||||
|
||||
const startTime = Date.now(); // Capture the start time
|
||||
const timeout = 10000; // 10 seconds timeout in milliseconds
|
||||
// const startTime = Date.now(); // Capture the start time
|
||||
// const timeout = 10000; // 10 seconds timeout in milliseconds
|
||||
|
||||
calculateTotalsFromLocalStorage();
|
||||
|
||||
while (localStorage.getItem("lastTransaction") === null) {
|
||||
if (Date.now() - startTime > timeout) {
|
||||
return; // Exit the function and don't proceed further
|
||||
}
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second
|
||||
}
|
||||
// while (localStorage.getItem("lastTransaction") === null) {
|
||||
// if (Date.now() - startTime > timeout) {
|
||||
// return; // Exit the function and don't proceed further
|
||||
// }
|
||||
|
||||
// await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second
|
||||
// }
|
||||
|
||||
// If 'lastTransaction' exists, proceed
|
||||
const lastTransaction = JSON.parse(localStorage.getItem("lastTransaction"));
|
||||
const lastTransaction = JSON.parse(
|
||||
localStorage.getItem("lastTransaction")
|
||||
);
|
||||
console.log(lastTransaction);
|
||||
|
||||
if (lastTransaction != null) {
|
||||
@@ -264,23 +279,28 @@ function App() {
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
socket.on("transaction_success", async (data) => {
|
||||
console.log("transaction notification");
|
||||
setModal("transaction_success", data);
|
||||
|
||||
// If 'lastTransaction' exists, proceed
|
||||
localStorage.removeItem("lastTransaction");
|
||||
|
||||
if (lastTransaction != null) {
|
||||
if (localStorage.getItem("lastTransaction")) {
|
||||
setLastTransaction(null);
|
||||
console.log('remove last transaction')
|
||||
localStorage.removeItem("lastTransaction");
|
||||
window.dispatchEvent(new Event("localStorageUpdated"));
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("transaction_end", async (data) => {
|
||||
console.log("transaction notification");
|
||||
setModal("transaction_end", data);
|
||||
|
||||
// If 'lastTransaction' exists, proceed
|
||||
if (localStorage.getItem("lastTransaction")) {
|
||||
setLastTransaction(null);
|
||||
localStorage.removeItem("lastTransaction");
|
||||
window.dispatchEvent(new Event("localStorageUpdated"));
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("payment_claimed", async (data) => {
|
||||
@@ -289,10 +309,16 @@ function App() {
|
||||
});
|
||||
|
||||
socket.on("transaction_failed", async (data) => {
|
||||
console.log("transaction notification");
|
||||
console.log(JSON.stringify(data));
|
||||
setModal("transaction_failed", data);
|
||||
});
|
||||
|
||||
// If 'lastTransaction' exists, proceed
|
||||
if (localStorage.getItem("lastTransaction")) {
|
||||
setLastTransaction(null);
|
||||
localStorage.removeItem("lastTransaction");
|
||||
window.dispatchEvent(new Event("localStorageUpdated"));
|
||||
}
|
||||
});
|
||||
|
||||
const handleNotificationClick = async () => {
|
||||
const permission = await requestNotificationPermission();
|
||||
@@ -302,7 +328,7 @@ function App() {
|
||||
// Set up notifications or show a success modal
|
||||
} else {
|
||||
console.error("Notification permission denied.");
|
||||
setModal('blocked_notification'); // Show modal for blocked notifications
|
||||
setModal("blocked_notification"); // Show modal for blocked notifications
|
||||
}
|
||||
};
|
||||
|
||||
@@ -311,10 +337,21 @@ function App() {
|
||||
|
||||
// Check current permission
|
||||
const searchParams = new URLSearchParams(location.search);
|
||||
let searchModal = searchParams.get("modal") || ''; // Get transactionId or set it to empty string
|
||||
let searchModal = searchParams.get("modal") || ""; // Get transactionId or set it to empty string
|
||||
|
||||
if (permission !== "granted" && searchModal == '') {
|
||||
setModal("message", { captMessage: 'Notifikasi tidak aktif', descMessage: 'Aktifkan notifikasi supaya kamu tetap dapat info pesanan, meski sedang buka aplikasi lain.', yesText: 'Aktifkan', noText: 'Tutup' }, null, handleNotificationClick);
|
||||
if (permission !== "granted" && searchModal == "") {
|
||||
setModal(
|
||||
"message",
|
||||
{
|
||||
captMessage: "Notifikasi tidak aktif",
|
||||
descMessage:
|
||||
"Aktifkan notifikasi supaya kamu tetap dapat info pesanan, meski sedang buka aplikasi lain.",
|
||||
yesText: "Aktifkan",
|
||||
noText: "Tutup",
|
||||
},
|
||||
null,
|
||||
handleNotificationClick
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -323,10 +360,15 @@ function App() {
|
||||
removeLocalStorage("auth");
|
||||
setDeviceType("guestDevice");
|
||||
} else {
|
||||
console.log(data)
|
||||
if(data.data.user.cafeId != null) navigate(`/${data.data.user.cafeIdentityName}`, { replace: true });
|
||||
console.log(data);
|
||||
if (data.data.user.cafeId != null)
|
||||
navigate(`/${data.data.user.cafeIdentityName}`, { replace: true });
|
||||
setUser(data.data.user);
|
||||
if (data.data.latestOpenBillTransaction != null) localStorage.setItem('lastTransaction', JSON.stringify(data.data.latestOpenBillTransaction))
|
||||
if (data.data.latestOpenBillTransaction != null)
|
||||
localStorage.setItem(
|
||||
"lastTransaction",
|
||||
JSON.stringify(data.data.latestOpenBillTransaction)
|
||||
);
|
||||
if (
|
||||
data.data.user.password == "unsetunsetunset" &&
|
||||
localStorage.getItem("settings")
|
||||
@@ -337,7 +379,7 @@ function App() {
|
||||
setGuestSides(connectedGuestSides.sessionDatas);
|
||||
console.log("getting guest side");
|
||||
setDeviceType("clerk");
|
||||
checkNotifications();
|
||||
// checkNotifications();
|
||||
} else {
|
||||
setDeviceType("guestDevice");
|
||||
}
|
||||
@@ -370,7 +412,6 @@ function App() {
|
||||
socket.on("updateQueue", ({ queue }) => {
|
||||
setQueue(queue); // Only set the queue if it's a valid non-empty array
|
||||
console.log("Updated Queue:", queue); // Log the valid queue
|
||||
|
||||
});
|
||||
|
||||
return () => {
|
||||
@@ -378,30 +419,31 @@ function App() {
|
||||
};
|
||||
}, [socket, shopId]);
|
||||
|
||||
async function checkIfStillViewingOtherTransaction() {
|
||||
|
||||
async function checkIfStillViewingOtherTransaction(data) {
|
||||
console.log("transaction notification");
|
||||
console.log(modalContent);
|
||||
|
||||
let response;
|
||||
response = await getTransactionsFromCafe(shopId, 0, true);
|
||||
transactionList.current = response;
|
||||
console.log(response);
|
||||
|
||||
// Get current URL's search parameters inside the socket event handler
|
||||
const searchParams = new URLSearchParams(location.search);
|
||||
let transaction_info = searchParams.get("transactionId") || ''; // Get transactionId or set it to empty string
|
||||
console.log(transaction_info); // Log the updated transaction_info
|
||||
let transaction_info = searchParams.get("transactionId") || ""; // Get transactionId or set it to empty string
|
||||
|
||||
if (response[0].transactionId != transaction_info)
|
||||
transactionList.current = response;
|
||||
|
||||
let depthh = transactionList.current.findIndex(
|
||||
item => item.transactionId.toString() === transaction_info.toString()
|
||||
(item) => item.transactionId.toString() === transaction_info.toString()
|
||||
);
|
||||
if(depthh == 0 && transaction_info.toString() != '') depthh = 1;
|
||||
setDepth(depthh);
|
||||
console.log(transaction_info == response[0].transactionId)
|
||||
|
||||
if (transaction_info != response[0].transactionId) setDepth(depthh);
|
||||
else setModal("new_transaction", data);
|
||||
|
||||
console.log(transaction_info == response[0].transactionId);
|
||||
// If transaction_info is an empty string, set the modal
|
||||
if (transaction_info.toString() == '' || transaction_info.toString() == response[0].transactionId) return false;
|
||||
if (transaction_info.toString() == "") return false;
|
||||
else return true;
|
||||
}
|
||||
|
||||
@@ -409,12 +451,16 @@ function App() {
|
||||
// This will ensure that searchParams and transaction_info get updated on each render
|
||||
socket.on("transaction_created", async (data) => {
|
||||
console.log("transaction notification");
|
||||
const isViewingOtherTransaction = await checkIfStillViewingOtherTransaction();
|
||||
// If transaction_info is an empty string, set the modal
|
||||
if (!isViewingOtherTransaction) {
|
||||
setModal("new_transaction", data);
|
||||
}
|
||||
setNewTransaction(data);
|
||||
|
||||
if (!location.pathname.endsWith("/transactions")) {
|
||||
const isViewingOtherTransaction =
|
||||
await checkIfStillViewingOtherTransaction(data);
|
||||
// If transaction_info is an empty string, set the modal
|
||||
if (!isViewingOtherTransaction) {
|
||||
setModal("new_transaction", data);
|
||||
}
|
||||
}
|
||||
// Show browser notification
|
||||
let permission = Notification.permission;
|
||||
if (permission !== "granted") return;
|
||||
@@ -429,11 +475,15 @@ function App() {
|
||||
socket.on("transaction_canceled", async (data) => {
|
||||
console.log("transaction notification");
|
||||
|
||||
const isViewingOtherTransaction = await checkIfStillViewingOtherTransaction();
|
||||
// If transaction_info is an empty string, set the modal
|
||||
if (!isViewingOtherTransaction) {
|
||||
setModal("new_transaction", data);
|
||||
navigate(`?transactionId=${data.transactionId}`, { replace: true });
|
||||
setNewTransaction(data);
|
||||
if (location.pathname != "/transactions") {
|
||||
const isViewingOtherTransaction =
|
||||
await checkIfStillViewingOtherTransaction(data);
|
||||
// If transaction_info is an empty string, set the modal
|
||||
if (!isViewingOtherTransaction) {
|
||||
setModal("new_transaction", data);
|
||||
navigate(`?transactionId=${data.transactionId}`, { replace: true });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -442,45 +492,36 @@ function App() {
|
||||
socket.off("transaction_created");
|
||||
socket.off("transaction_canceled");
|
||||
};
|
||||
}, [socket, shopId, location]); // Ensure location is in the dependencies to respond to changes in the URL
|
||||
|
||||
// useEffect(() => {
|
||||
// if (user.cafeId != null && user.cafeId !== shopId) {
|
||||
// // Preserve existing query parameters
|
||||
// const currentParams = new URLSearchParams(location.search).toString();
|
||||
|
||||
// // Navigate to the new cafeId while keeping existing params
|
||||
// navigate(`/${user.cafeId}?${currentParams}`, { replace: true });
|
||||
// }
|
||||
|
||||
// }, [user, shopId]);
|
||||
}, [socket, shopId, location]); // Ensure location is in the dependencies to respond to changes in the URL
|
||||
|
||||
function handleMoveToTransaction(direction, from) {
|
||||
console.log(direction);
|
||||
console.log(from);
|
||||
|
||||
// Find the current index based on the 'from' transactionId
|
||||
const currentIndex = transactionList.current.findIndex(item => item.transactionId == from);
|
||||
|
||||
const currentIndex = transactionList.current.findIndex(
|
||||
(item) => item.transactionId == from
|
||||
);
|
||||
|
||||
// Determine the new transactionId based on the direction
|
||||
let newTransactionId;
|
||||
if (direction === 'next') {
|
||||
|
||||
if (direction === "next") {
|
||||
// If we're not at the last transaction, get the next transactionId
|
||||
newTransactionId = currentIndex < transactionList.current.length - 1
|
||||
? transactionList.current[currentIndex + 1].transactionId
|
||||
: from; // If already at the end, stay on the current transactionId
|
||||
} else if (direction === 'previous') {
|
||||
|
||||
setDepth(currentIndex -1);
|
||||
newTransactionId =
|
||||
currentIndex < transactionList.current.length - 1
|
||||
? transactionList.current[currentIndex + 1].transactionId
|
||||
: from; // If already at the end, stay on the current transactionId
|
||||
} else if (direction === "previous") {
|
||||
setDepth(currentIndex - 1);
|
||||
// If we're not at the first transaction, get the previous transactionId
|
||||
newTransactionId = currentIndex > 0
|
||||
? transactionList.current[currentIndex - 1].transactionId
|
||||
: from; // If already at the beginning, stay on the current transactionId
|
||||
newTransactionId =
|
||||
currentIndex > 0
|
||||
? transactionList.current[currentIndex - 1].transactionId
|
||||
: from; // If already at the beginning, stay on the current transactionId
|
||||
}
|
||||
|
||||
// Log the new transactionId
|
||||
console.log('New Transaction ID:', newTransactionId);
|
||||
console.log("New Transaction ID:", newTransactionId);
|
||||
|
||||
// Update the URL with the new transactionId using navigate
|
||||
navigate(`?transactionId=${newTransactionId}`, { replace: true });
|
||||
@@ -490,7 +531,6 @@ function App() {
|
||||
// setModalContent({ cafeId: shopId, transactionId: newTransactionId });
|
||||
}
|
||||
|
||||
|
||||
const handleModalFromURL = () => {
|
||||
const queryParams = new URLSearchParams(location.search);
|
||||
const modal = queryParams.get("modal");
|
||||
@@ -522,8 +562,8 @@ function App() {
|
||||
document.body.style.overflow = "hidden";
|
||||
|
||||
setIsModalOpen(true);
|
||||
setModalContent(content)
|
||||
console.log(onCloseFunction)
|
||||
setModalContent(content);
|
||||
console.log(onCloseFunction);
|
||||
|
||||
if (onCloseFunction) {
|
||||
setOnModalCloseFunction(() => onCloseFunction); // Store the close function
|
||||
@@ -540,7 +580,8 @@ function App() {
|
||||
const closeModal = (closeTheseContent = []) => {
|
||||
if (
|
||||
Array.isArray(closeTheseContent) &&
|
||||
(closeTheseContent.length === 0 || closeTheseContent.includes(modalContent))
|
||||
(closeTheseContent.length === 0 ||
|
||||
closeTheseContent.includes(modalContent))
|
||||
) {
|
||||
setIsModalOpen(false);
|
||||
setModalContent(null);
|
||||
@@ -549,16 +590,16 @@ function App() {
|
||||
const queryParams = new URLSearchParams(location.search);
|
||||
|
||||
// Clear all query parameters
|
||||
queryParams.keys() && [...queryParams.keys()].forEach(key => {
|
||||
queryParams.delete(key);
|
||||
});
|
||||
queryParams.keys() &&
|
||||
[...queryParams.keys()].forEach((key) => {
|
||||
queryParams.delete(key);
|
||||
});
|
||||
|
||||
// Update the URL without any query parameters
|
||||
navigate({ search: queryParams.toString() }, { replace: true });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// useEffect(() => {
|
||||
// const askNotificationPermission = async () => {
|
||||
// let permission = Notification.permission;
|
||||
@@ -617,11 +658,15 @@ function App() {
|
||||
|
||||
useEffect(() => {
|
||||
if ("serviceWorker" in navigator) {
|
||||
navigator.serviceWorker.register("/service-worker.js")
|
||||
.then(registration => {
|
||||
console.log("Service Worker registered with scope:", registration.scope);
|
||||
navigator.serviceWorker
|
||||
.register("/service-worker.js")
|
||||
.then((registration) => {
|
||||
console.log(
|
||||
"Service Worker registered with scope:",
|
||||
registration.scope
|
||||
);
|
||||
})
|
||||
.catch(error => {
|
||||
.catch((error) => {
|
||||
console.error("Service Worker registration failed:", error);
|
||||
});
|
||||
}
|
||||
@@ -631,6 +676,12 @@ function App() {
|
||||
<div className="App">
|
||||
<header className="App-header" id="header">
|
||||
<Routes>
|
||||
<Route
|
||||
path="/:shopIdentifier/print"
|
||||
element={
|
||||
<PrintPage />
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/"
|
||||
element={
|
||||
@@ -682,37 +733,10 @@ function App() {
|
||||
cartItemsLength={totalItemsCount}
|
||||
totalPrice={totalPrice}
|
||||
lastTransaction={lastTransaction}
|
||||
shop={shop}
|
||||
totalItemsCount={totalItemsCount}
|
||||
deviceType={deviceType}
|
||||
/>
|
||||
{/* <Footer
|
||||
showTable={true}
|
||||
shopId={shopIdentifier}
|
||||
table={table}
|
||||
cartItemsLength={totalItemsCount}
|
||||
selectedPage={0}
|
||||
/> */}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/:shopIdentifier/:tableCode?/search"
|
||||
element={
|
||||
<>
|
||||
<SearchResult
|
||||
cafeId={shopId}
|
||||
sendParam={handleSetParam}
|
||||
user={user}
|
||||
shopItems={shopItems}
|
||||
guestSides={guestSides}
|
||||
guestSideOfClerk={guestSideOfClerk}
|
||||
removeConnectedGuestSides={rmConnectedGuestSides}
|
||||
setModal={setModal} // Pass the function to open modal
|
||||
/>
|
||||
{/* <Footer
|
||||
shopId={shopIdentifier}
|
||||
table={table}
|
||||
cartItemsLength={totalItemsCount}
|
||||
selectedPage={1}
|
||||
/> */}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
@@ -732,12 +756,6 @@ function App() {
|
||||
shopItems={shopItems}
|
||||
setShopItems={setShopItems}
|
||||
/>
|
||||
{/* <Footer
|
||||
shopId={shopIdentifier}
|
||||
table={table}
|
||||
cartItemsLength={totalItemsCount}
|
||||
selectedPage={2}
|
||||
/> */}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
@@ -771,6 +789,8 @@ function App() {
|
||||
sendParam={handleSetParam}
|
||||
deviceType={deviceType}
|
||||
paymentUrl={shop.qrPayment}
|
||||
setModal={setModal}
|
||||
newTransaction={newTransaction}
|
||||
/>
|
||||
{/* <Footer
|
||||
shopId={shopIdentifier}
|
||||
@@ -799,8 +819,10 @@ function App() {
|
||||
welcomePageConfig={shop.welcomePageConfig}
|
||||
onClose={closeModal}
|
||||
setModal={setModal}
|
||||
setIsModalOpen={setIsModalOpen}
|
||||
onModalCloseFunction={onModalCloseFunction}
|
||||
onModalYesFunction={onModalYesFunction}
|
||||
shopIdentifier={shopIdentifier}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -44,10 +44,10 @@ const App = () => {
|
||||
|
||||
useEffect(() => {
|
||||
const shopId = window.location.pathname.split('/')[1]; // Get shopId from the URL
|
||||
const userId = localStorage.getItem('userId');
|
||||
const user_id = localStorage.getItem('user_id');
|
||||
|
||||
// Connect to Socket.IO if userId is present
|
||||
// if (userId) {
|
||||
// Connect to Socket.IO if user_id is present
|
||||
// if (user_id) {
|
||||
// connectSocket(shopId, 1);
|
||||
// }
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import dayjs from "dayjs";
|
||||
import Chart from "react-apexcharts";
|
||||
import styles from "./BarChart.module.css"; // Import the CSS module
|
||||
|
||||
@@ -7,7 +8,21 @@ const DailyCharts = ({ incomeGraph, transactionGraph, materialGraph, colors, typ
|
||||
|
||||
useEffect(() => {
|
||||
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) => {
|
||||
if (!graphData) return null;
|
||||
@@ -22,61 +37,73 @@ const DailyCharts = ({ incomeGraph, transactionGraph, materialGraph, colors, typ
|
||||
"18-21",
|
||||
"21-24",
|
||||
];
|
||||
console.log(dayData)
|
||||
const sumSold = (transactions) =>
|
||||
Array.isArray(transactions) ? transactions.reduce((acc, t) => acc + t.sold, 0) : transactions?.transaction || 0;
|
||||
|
||||
const sumTotal = (transactions) =>
|
||||
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;
|
||||
|
||||
|
||||
let seriesData = []
|
||||
if(graphFilter == 'transactions'){
|
||||
seriesData = [
|
||||
dayData.hour0To3Transactions.reduce((acc, t) => acc + t.sold, 0),
|
||||
dayData.hour3To6Transactions.reduce((acc, t) => acc + t.sold, 0),
|
||||
dayData.hour6To9Transactions.reduce((acc, t) => acc + t.sold, 0),
|
||||
dayData.hour9To12Transactions.reduce((acc, t) => acc + t.sold, 0),
|
||||
dayData.hour12To15Transactions.reduce((acc, t) => acc + t.sold, 0),
|
||||
dayData.hour15To18Transactions.reduce((acc, t) => acc + t.sold, 0),
|
||||
dayData.hour18To21Transactions.reduce((acc, t) => acc + t.sold, 0),
|
||||
dayData.hour21To24Transactions.reduce((acc, t) => acc + t.sold, 0),
|
||||
];
|
||||
}
|
||||
else if(graphFilter == 'income'){
|
||||
if (graphFilter === 'transactions') {
|
||||
seriesData = [
|
||||
dayData.hour0To3Transactions.reduce((acc, t) => acc + t.totalPrice, 0),
|
||||
dayData.hour3To6Transactions.reduce((acc, t) => acc + t.totalPrice, 0),
|
||||
dayData.hour6To9Transactions.reduce((acc, t) => acc + t.totalPrice, 0),
|
||||
dayData.hour9To12Transactions.reduce((acc, t) => acc + t.totalPrice, 0),
|
||||
dayData.hour12To15Transactions.reduce((acc, t) => acc + t.totalPrice, 0),
|
||||
dayData.hour15To18Transactions.reduce((acc, t) => acc + t.totalPrice, 0),
|
||||
dayData.hour18To21Transactions.reduce((acc, t) => acc + t.totalPrice, 0),
|
||||
dayData.hour21To24Transactions.reduce((acc, t) => acc + t.totalPrice, 0),
|
||||
sumSold(dayData?.hour0To3Transactions),
|
||||
sumSold(dayData?.hour3To6Transactions),
|
||||
sumSold(dayData?.hour6To9Transactions),
|
||||
sumSold(dayData?.hour9To12Transactions),
|
||||
sumSold(dayData?.hour12To15Transactions),
|
||||
sumSold(dayData?.hour15To18Transactions),
|
||||
sumSold(dayData?.hour18To21Transactions),
|
||||
sumSold(dayData?.hour21To24Transactions),
|
||||
];
|
||||
}
|
||||
else if(graphFilter == 'outcome'){
|
||||
else if (graphFilter === 'income') {
|
||||
seriesData = [
|
||||
dayData.hour3To6MaterialIds.reduce((acc, t) => acc + t.materialOutcome, 0),
|
||||
dayData.hour6To9MaterialIds.reduce((acc, t) => acc + t.materialOutcome, 0),
|
||||
dayData.hour0To3MaterialIds.reduce((acc, t) => acc + t.materialOutcome, 0),
|
||||
dayData.hour9To12MaterialIds.reduce((acc, t) => acc + t.materialOutcome, 0),
|
||||
dayData.hour12To15MaterialIds.reduce((acc, t) => acc + t.materialOutcome, 0),
|
||||
dayData.hour15To18MaterialIds.reduce((acc, t) => acc + t.materialOutcome, 0),
|
||||
dayData.hour18To21MaterialIds.reduce((acc, t) => acc + t.materialOutcome, 0),
|
||||
dayData.hour21To24MaterialIds.reduce((acc, t) => acc + t.materialOutcome, 0),
|
||||
sumTotal(dayData?.hour0To3Transactions),
|
||||
sumTotal(dayData?.hour3To6Transactions),
|
||||
sumTotal(dayData?.hour6To9Transactions),
|
||||
sumTotal(dayData?.hour9To12Transactions),
|
||||
sumTotal(dayData?.hour12To15Transactions),
|
||||
sumTotal(dayData?.hour15To18Transactions),
|
||||
sumTotal(dayData?.hour18To21Transactions),
|
||||
sumTotal(dayData?.hour21To24Transactions),
|
||||
];
|
||||
}
|
||||
else if (graphFilter === 'outcome') {
|
||||
seriesData = [
|
||||
sumOutcome(dayData?.hour0To3MaterialIds),
|
||||
sumOutcome(dayData?.hour3To6MaterialIds),
|
||||
sumOutcome(dayData?.hour6To9MaterialIds),
|
||||
sumOutcome(dayData?.hour9To12MaterialIds),
|
||||
sumOutcome(dayData?.hour12To15MaterialIds),
|
||||
sumOutcome(dayData?.hour15To18MaterialIds),
|
||||
sumOutcome(dayData?.hour18To21MaterialIds),
|
||||
sumOutcome(dayData?.hour21To24MaterialIds),
|
||||
];
|
||||
}
|
||||
let totalValue = seriesData.reduce((acc, val) => acc + val, 0);
|
||||
return {
|
||||
date: new Date(dayData.date).toLocaleDateString(),
|
||||
date: dayData.date,
|
||||
categories,
|
||||
series: [
|
||||
{
|
||||
name: `Transactions on ${new Date(dayData.date).toLocaleDateString()}`,
|
||||
name: graphFilter === 'transactions' ? 'Transaksi' : (graphFilter === 'income' ? 'Pemasukan' : 'Pengeluaran'),
|
||||
data: seriesData,
|
||||
},
|
||||
],
|
||||
totalValue, // ⬅️ Tambahkan ini
|
||||
};
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
const chartData = processData(graphFilter != 'outcome' ? transactionGraph : materialGraph);
|
||||
const chartData = processData(graphFilter !== 'outcome' ? transactionGraph : materialGraph);
|
||||
|
||||
let globalMax = null;
|
||||
if (chartData)
|
||||
if (chartData) {
|
||||
globalMax = chartData.reduce(
|
||||
(max, data) => {
|
||||
const localMax = Math.max(...data.series[0].data);
|
||||
@@ -84,24 +111,17 @@ const DailyCharts = ({ incomeGraph, transactionGraph, materialGraph, colors, typ
|
||||
},
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
const formatDate = (dateString) => {
|
||||
const d = dayjs(dateString, ["YYYY-MM-DD", "YYYY-MM-DDTHH:mm:ssZ"]);
|
||||
return { month: d.format("MMM"), day: d.format("D") };
|
||||
};
|
||||
|
||||
|
||||
const formatDate = (dateString) => {
|
||||
const date = new Date(dateString); // Parse the date string
|
||||
|
||||
// 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 (
|
||||
<div className={`${styles.chartItemContainer} ${selectedIndex !== -1 ? styles.expanded : ''}`}>
|
||||
|
||||
{chartData &&
|
||||
chartData.map((data, index) => (
|
||||
<div
|
||||
@@ -119,33 +139,78 @@ const DailyCharts = ({ incomeGraph, transactionGraph, materialGraph, colors, typ
|
||||
key={indexx}
|
||||
className={`${styles.dateSelector} ${index === indexx ? styles.dateSelectorActive : styles.dateSelectorInactive
|
||||
}`}
|
||||
style={{position: 'relative' }}
|
||||
style={{ position: 'relative', width: 'calc(100% / 7)' }}
|
||||
onClick={() =>
|
||||
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={{ color: index === indexx ? 'black' : 'transparent' }}>
|
||||
{indexx !== chartData.length - 1 ? (
|
||||
<>
|
||||
{day}{" "}
|
||||
{(indexx === 0 || (formatDate(chartData[indexx - 1].date).month !== month && type != 'weekly')) && month}
|
||||
</>
|
||||
) : (
|
||||
'Kemarin'
|
||||
)}
|
||||
|
||||
<div style={{ position: 'absolute', bottom: '21px', left: '10%', right: '10%', borderBottom: index == indexx ? `2px solid ${colors[index % colors.length]}` : 'none' }}></div>
|
||||
<div
|
||||
style={{ color: index === indexx ? 'black' : 'transparent' }}>
|
||||
{indexx !== chartData.length - 1 ? (
|
||||
<p style={{ fontSize: '13px' }}>{day}{" "}
|
||||
{(
|
||||
indexx === 0 ||
|
||||
(indexx > 0 &&
|
||||
dayjs(chartData[indexx - 1].date).month() !== dayjs(item.date).month() &&
|
||||
type !== "weekly")
|
||||
) && month}
|
||||
</p>
|
||||
) : (
|
||||
<p style={{ fontSize: '13px' }}>
|
||||
{type != 'weekly' ? 'Hari ini' : day + ' ' + month}
|
||||
</p>
|
||||
)}
|
||||
</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 className={styles.chartWrapper}>
|
||||
<Chart
|
||||
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: {
|
||||
id: `chart-${index}`,
|
||||
type: "area",
|
||||
@@ -160,7 +225,7 @@ const DailyCharts = ({ incomeGraph, transactionGraph, materialGraph, colors, typ
|
||||
categories: data.categories,
|
||||
labels: {
|
||||
style: {
|
||||
colors: index === 0 || index == selectedIndex || selectedIndex == 0 && index == 1 ? "#000" : "transparent",
|
||||
colors: index === 0 || index === selectedIndex || (selectedIndex === 0 && index === 1) ? "#000" : "transparent",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -172,6 +237,9 @@ const DailyCharts = ({ incomeGraph, transactionGraph, materialGraph, colors, typ
|
||||
style: {
|
||||
colors: "transparent",
|
||||
},
|
||||
formatter: function (val) {
|
||||
return formatRupiah(val);
|
||||
},
|
||||
},
|
||||
},
|
||||
grid: {
|
||||
@@ -189,10 +257,9 @@ const DailyCharts = ({ incomeGraph, transactionGraph, materialGraph, colors, typ
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DailyCharts;
|
||||
export default DailyCharts;
|
||||
@@ -35,11 +35,16 @@ const Title = styled.h2`
|
||||
font-family: "Plus Jakarta Sans", sans-serif;
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
font-size:${(props) => (props.HeaderSize)};
|
||||
font-size: ${(props) => props.HeaderSize};
|
||||
color: rgba(88, 55, 50, 1);
|
||||
text-transform: uppercase;
|
||||
|
||||
@media (min-width: 768px) {
|
||||
font-size: 2vw;
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
const ProfileName = styled.h2`
|
||||
position: absolute;
|
||||
font-family: "Plus Jakarta Sans", sans-serif;
|
||||
@@ -81,8 +86,8 @@ const gg = keyframes`
|
||||
height: 60px;
|
||||
}
|
||||
100% {
|
||||
top: 45px;
|
||||
right: 51px;
|
||||
top: 5px;
|
||||
right: 20px;
|
||||
width: 200px;
|
||||
height: 40px;
|
||||
}
|
||||
@@ -90,8 +95,8 @@ const gg = keyframes`
|
||||
|
||||
const ss = keyframes`
|
||||
0% {
|
||||
top: 45px;
|
||||
right: 51px;
|
||||
top: 5px;
|
||||
right: 20px;
|
||||
width: 200px;
|
||||
height: 40px;
|
||||
}
|
||||
@@ -108,7 +113,7 @@ const ProfileImage = styled.img`
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 50%;
|
||||
object-fit: contain;
|
||||
object-fit: cover;
|
||||
cursor: pointer;
|
||||
z-index: 199;
|
||||
animation: ${(props) => {
|
||||
@@ -127,8 +132,8 @@ const g = keyframes`
|
||||
height: 60px;
|
||||
}
|
||||
100% {
|
||||
top: 28px;
|
||||
right: 242px;
|
||||
top: 34px;
|
||||
right: 229px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
@@ -137,7 +142,7 @@ const g = keyframes`
|
||||
const s = keyframes`
|
||||
0% {
|
||||
top: 28px;
|
||||
right: 242px;
|
||||
right: 229px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
@@ -151,14 +156,15 @@ const s = keyframes`
|
||||
|
||||
const grow = keyframes`
|
||||
0% {
|
||||
right: 12px;
|
||||
right: 0px;
|
||||
top: 0px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-top-left-radius: 50%;
|
||||
border-bottom-left-radius: 50%;
|
||||
}
|
||||
100% {
|
||||
right: 28px;
|
||||
right: -17px;
|
||||
width: 300px;
|
||||
border-top-left-radius: 15px;
|
||||
border-bottom-left-radius: 15px;
|
||||
@@ -167,13 +173,14 @@ const grow = keyframes`
|
||||
|
||||
const shrink = keyframes`
|
||||
0% {
|
||||
right: 12px;
|
||||
right: -17px;
|
||||
width: 300px;
|
||||
height: auto;
|
||||
border-radius: 20px;
|
||||
}
|
||||
100% {
|
||||
right: 28px;
|
||||
right: 0px;
|
||||
top: 0px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 50%;
|
||||
@@ -182,7 +189,7 @@ const shrink = keyframes`
|
||||
const Rectangle = styled.div`
|
||||
overflow-y: hidden;
|
||||
position: absolute;
|
||||
top: 39px;
|
||||
top: 9px;
|
||||
right: 12px;
|
||||
width: 200px;
|
||||
max-height: 87vh; /* or another appropriate value */
|
||||
@@ -321,143 +328,78 @@ const Header = ({
|
||||
// Otherwise, use the possessive function
|
||||
return `${cafeName}'s menu`;
|
||||
};
|
||||
return (
|
||||
return (
|
||||
<HeaderBarbackground shopName={shopName}>
|
||||
<HeaderBar HeaderMargin={HeaderMargin} shopName={shopName}>
|
||||
<Title HeaderSize={HeaderSize}>
|
||||
{shopName == null
|
||||
? HeaderText == null
|
||||
? "kedaimaster"
|
||||
: HeaderText
|
||||
: shopName}
|
||||
</Title>
|
||||
<div style={{ visibility: showProfile ? "visible" : "hidden" }}>
|
||||
<ProfileImage
|
||||
src={shopImage && !shopImage.includes('undefined') ? shopImage || 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcS-DjX_bGBax4NL14ULvkAdU4FP3FKoWXWu5w&s' : "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcS-DjX_bGBax4NL14ULvkAdU4FP3FKoWXWu5w&s"}
|
||||
alt="Profile"
|
||||
onClick={user.username !== undefined ? handleImageClick : null}
|
||||
animate={showRectangle && animate}
|
||||
/>
|
||||
<ProfileName animate={showRectangle && animate}>
|
||||
{showProfile && user.username !== undefined ? user.username : "guest"}
|
||||
</ProfileName>
|
||||
{showRectangle && (
|
||||
<Rectangle ref={rectangleRef} animate={animate}>
|
||||
<ChildContainer>
|
||||
{guestSideOfClerk && guestSideOfClerk.clerkUsername && (
|
||||
<Child hasChildren>
|
||||
this is the guest side of {guestSideOfClerk.clerkUsername}
|
||||
</Child>
|
||||
)}
|
||||
{user.username !== undefined && (
|
||||
<Child onClick={() => setModal("edit_account")}>
|
||||
Kelola akun
|
||||
</Child>
|
||||
)}
|
||||
{user.roleId == 0 && (
|
||||
<Child onClick={()=>setModal('create_coupon', {})}>Buat Voucher</Child>)}
|
||||
{shopId && user.roleId == 1 && (
|
||||
<Child onClick={goToAdminCafes}>Dashboard</Child>)}
|
||||
{shopId &&
|
||||
user.userId == shopOwnerId &&
|
||||
user.username !== undefined &&
|
||||
user.roleId === 1 && (
|
||||
<>
|
||||
<Child hasChildren>
|
||||
<Child>
|
||||
{shopName}
|
||||
</Child>
|
||||
<Child>
|
||||
Mode pengembangan
|
||||
<Switch
|
||||
borderRadius={0}
|
||||
checked={isEditMode}
|
||||
onChange={() => setIsEditMode(!isEditMode)}
|
||||
/>
|
||||
</Child>
|
||||
<Child onClick={() => setModal("reports")}>Lihat laporan</Child>
|
||||
<Child onClick={() => setModal("add_material")}>
|
||||
Kelola bahan baku
|
||||
</Child>
|
||||
|
||||
<Child hasChildren>
|
||||
<Child>Konfigurasi</Child>
|
||||
<Child onClick={() => setModal("welcome_config")}>
|
||||
Desain kafe
|
||||
</Child>
|
||||
<Child onClick={() => setModal("edit_tables")}>
|
||||
Identifikasi kedai
|
||||
</Child>
|
||||
<Child onClick={() => setModal("payment_option")}>
|
||||
Metode pembayaran
|
||||
</Child>
|
||||
</Child>
|
||||
<Child hasChildren>
|
||||
<Child>Kasir</Child>
|
||||
<Child onClick={() => setModal("create_clerk")}>
|
||||
+ Tambah
|
||||
</Child>
|
||||
{shopClerks &&
|
||||
shopClerks.map((key, index) => (
|
||||
<Child key={index}>
|
||||
{shopClerks[index].username}
|
||||
<button
|
||||
onClick={() =>
|
||||
removeConnectedGuestSides(
|
||||
guestSides[index][3]
|
||||
)
|
||||
}
|
||||
>
|
||||
remove
|
||||
</button>
|
||||
</Child>
|
||||
))}
|
||||
</Child>
|
||||
</Child>
|
||||
</>
|
||||
)}
|
||||
{user.username !== undefined &&
|
||||
user.cafeId == shopId &&
|
||||
user.roleId === 2 && (
|
||||
<HeaderBar HeaderMargin={HeaderMargin} shopName={shopName}>
|
||||
<Title HeaderSize={HeaderSize}>
|
||||
{shopName == null
|
||||
? HeaderText == null
|
||||
? "kedaimaster"
|
||||
: HeaderText
|
||||
: shopName}
|
||||
</Title>
|
||||
<div style={{ visibility: showProfile ? "visible" : "hidden", position: 'relative' }}>
|
||||
<ProfileImage
|
||||
src={shopImage && !shopImage.includes('undefined') ? shopImage || 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcS-DjX_bGBax4NL14ULvkAdU4FP3FKoWXWu5w&s' : "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcS-DjX_bGBax4NL14ULvkAdU4FP3FKoWXWu5w&s"}
|
||||
alt="Profile"
|
||||
onClick={user.username !== undefined ? handleImageClick : null}
|
||||
animate={showRectangle && animate}
|
||||
/>
|
||||
<ProfileName animate={showRectangle && animate}>
|
||||
{showProfile && user.username !== undefined ? user.username : "guest"}
|
||||
</ProfileName>
|
||||
{showRectangle && (
|
||||
<Rectangle ref={rectangleRef} animate={animate}>
|
||||
<ChildContainer>
|
||||
{guestSideOfClerk && guestSideOfClerk.clerkUsername && (
|
||||
<Child hasChildren>
|
||||
<Child>{shopName}</Child>
|
||||
this is the guest side of {guestSideOfClerk.clerkUsername}
|
||||
</Child>
|
||||
)}
|
||||
{user.username !== undefined && (
|
||||
<Child onClick={() => setModal("edit_account")}>
|
||||
Kelola akun
|
||||
</Child>
|
||||
)}
|
||||
{user.roleId == 0 && (
|
||||
<Child onClick={() => setModal('create_coupon', {})}>Buat Voucher</Child>)}
|
||||
{shopId && user.roleId == 1 && (
|
||||
<Child onClick={goToAdminCafes}>Dashboard</Child>)}
|
||||
{shopId &&
|
||||
user.user_id == shopOwnerId &&
|
||||
user.username !== undefined &&
|
||||
user.roleId === 1 && (
|
||||
<>
|
||||
<Child hasChildren>
|
||||
<Child>
|
||||
{shopName}
|
||||
</Child>
|
||||
<Child onClick={() => setModal("reports")}>Lihat laporan</Child>
|
||||
<Child onClick={() => setModal("add_material")}>
|
||||
Kelola stok
|
||||
</Child>
|
||||
|
||||
<Child>
|
||||
Mode pengembangan
|
||||
<Switch
|
||||
borderRadius={0}
|
||||
checked={isEditMode}
|
||||
onChange={() => setIsEditMode(!isEditMode)}
|
||||
/>
|
||||
</Child>
|
||||
<Child onClick={() => setModal("add_material")}>
|
||||
Kelola bahan baku
|
||||
</Child>
|
||||
|
||||
<Child hasChildren>
|
||||
<Child>Konfigurasi</Child>
|
||||
<Child onClick={() => setModal("welcome_config")}>
|
||||
Desain kafe
|
||||
</Child>
|
||||
<Child onClick={() => setModal("edit_tables")}>
|
||||
Identifikasi kedai
|
||||
</Child>
|
||||
<Child onClick={() => setModal("payment_option")}>
|
||||
Metode pembayaran
|
||||
</Child>
|
||||
</Child>
|
||||
{user.username !== undefined &&
|
||||
user.roleId == 2 &&
|
||||
user.cafeId == shopId && (
|
||||
<Child hasChildren>
|
||||
Tablet tamu
|
||||
<Child onClick={goToGuestSideLogin}>
|
||||
<Child>Konfigurasi</Child>
|
||||
<Child onClick={() => setModal("welcome_config")}>
|
||||
Desain kafe
|
||||
</Child>
|
||||
<Child onClick={() => setModal("edit_tables")}>
|
||||
Identifikasi kedai
|
||||
</Child>
|
||||
<Child onClick={() => setModal("payment_option")}>
|
||||
Metode pembayaran
|
||||
</Child>
|
||||
</Child>
|
||||
<Child hasChildren>
|
||||
<Child>Kasir</Child>
|
||||
<Child onClick={() => setModal("create_clerk")}>
|
||||
+ Tambah
|
||||
</Child>
|
||||
{guestSides &&
|
||||
guestSides.map((key, index) => (
|
||||
{shopClerks &&
|
||||
shopClerks.map((key, index) => (
|
||||
<Child key={index}>
|
||||
guest side {index + 1}
|
||||
{shopClerks[index].username}
|
||||
<button
|
||||
onClick={() =>
|
||||
removeConnectedGuestSides(
|
||||
@@ -470,19 +412,76 @@ const Header = ({
|
||||
</Child>
|
||||
))}
|
||||
</Child>
|
||||
)}
|
||||
</Child>
|
||||
</>
|
||||
)}
|
||||
{user.username !== undefined &&
|
||||
user.cafeId == shopId &&
|
||||
user.roleId === 2 && (
|
||||
<Child hasChildren>
|
||||
<Child>{shopName}</Child>
|
||||
|
||||
<Child onClick={() => setModal("reports")}>Laporan</Child>
|
||||
</Child>
|
||||
<Child>
|
||||
Mode Edit
|
||||
<Switch
|
||||
borderRadius={0}
|
||||
checked={isEditMode}
|
||||
onChange={() => setIsEditMode(!isEditMode)}
|
||||
/>
|
||||
</Child>
|
||||
<Child onClick={() => setModal("add_material")}>
|
||||
Kelola stok
|
||||
</Child>
|
||||
|
||||
<Child hasChildren>
|
||||
<Child>Konfigurasi</Child>
|
||||
<Child onClick={() => setModal("welcome_config")}>
|
||||
Desain kafe
|
||||
</Child>
|
||||
<Child onClick={() => setModal("edit_tables")}>
|
||||
Identifikasi kedai
|
||||
</Child>
|
||||
<Child onClick={() => setModal("payment_option")}>
|
||||
Metode pembayaran
|
||||
</Child>
|
||||
</Child>
|
||||
{user.username !== undefined &&
|
||||
user.roleId == 2 &&
|
||||
user.cafeId == shopId && (
|
||||
<Child hasChildren>
|
||||
Tablet tamu
|
||||
<Child onClick={goToGuestSideLogin}>
|
||||
+ Tambah
|
||||
</Child>
|
||||
{guestSides &&
|
||||
guestSides.map((key, index) => (
|
||||
<Child key={index}>
|
||||
guest side {index + 1}
|
||||
<button
|
||||
onClick={() =>
|
||||
removeConnectedGuestSides(
|
||||
guestSides[index][3]
|
||||
)
|
||||
}
|
||||
>
|
||||
remove
|
||||
</button>
|
||||
</Child>
|
||||
))}
|
||||
</Child>
|
||||
)}
|
||||
|
||||
<Child onClick={() => setModal("reports")}>Laporan</Child>
|
||||
</Child>
|
||||
)}
|
||||
{user.username !== undefined && (
|
||||
<Child hasChildren ><Child onClick={isLogout}>Logout</Child></Child>
|
||||
)}
|
||||
{user.username !== undefined && (
|
||||
<Child hasChildren ><Child onClick={isLogout}>Logout</Child></Child>
|
||||
)}
|
||||
</ChildContainer>
|
||||
</Rectangle>
|
||||
)}
|
||||
</div>
|
||||
</HeaderBar></HeaderBarbackground>
|
||||
</ChildContainer>
|
||||
</Rectangle>
|
||||
)}
|
||||
</div>
|
||||
</HeaderBar></HeaderBarbackground>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -29,6 +29,51 @@ const Item = ({
|
||||
const [itemDescription, setItemDescription] = useState(initialDescription);
|
||||
const fileInputRef = useRef(null);
|
||||
|
||||
|
||||
const formatToRupiah = (value) => {
|
||||
if (typeof value !== "number") return value;
|
||||
return value.toLocaleString("id-ID");
|
||||
|
||||
// const stringValue = (value || '').toString();
|
||||
|
||||
// // Ambil hanya angka
|
||||
// const cleaned = stringValue.replace(/[^0-9]/g, '');
|
||||
|
||||
// // Pastikan cleaned bukan kosong
|
||||
// if (!cleaned) return 'Rp0';
|
||||
|
||||
// // Ubah ke number dan kalikan 1000
|
||||
// const number = Number(cleaned) * 1000;
|
||||
|
||||
// // Kalau gagal parsing, fallback
|
||||
// if (isNaN(number)) return 'Rp0';
|
||||
|
||||
// // Format rupiah tanpa desimal
|
||||
// return number.toLocaleString('id-ID', {
|
||||
// style: 'currency',
|
||||
// currency: 'IDR',
|
||||
// minimumFractionDigits: 0,
|
||||
// maximumFractionDigits: 0
|
||||
// });
|
||||
};
|
||||
|
||||
const rupiahFormat = (angka, prefix = "Rp. ") => {
|
||||
let number_string = angka.toString().replace(/[^,\d]/g, '');
|
||||
let split = number_string.split(',');
|
||||
let sisa = split[0].length % 3;
|
||||
let rupiah = split[0].substr(0, sisa);
|
||||
let ribuan = split[0].substr(sisa).match(/\d{3}/gi);
|
||||
|
||||
// tambahkan titik jika yang di input sudah menjadi angka ribuan
|
||||
if(ribuan){
|
||||
let separator = sisa ? '.' : '';
|
||||
rupiah += separator + ribuan.join('.');
|
||||
}
|
||||
|
||||
rupiah = split[1] != undefined ? rupiah + ',' + split[1] : rupiah;
|
||||
return rupiah ? prefix + rupiah : "";
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
console.log(imageUrl);
|
||||
console.log(selectedImage);
|
||||
@@ -156,7 +201,6 @@ const Item = ({
|
||||
</h3>
|
||||
{forInvoice && (
|
||||
<>
|
||||
<p className={styles.multiplySymbol}>x</p>
|
||||
<p className={styles.qtyInvoice}>{itemQty}</p>
|
||||
</>
|
||||
)}
|
||||
@@ -200,7 +244,7 @@ const Item = ({
|
||||
lineHeight: '1rem',
|
||||
justifyContent: 'center'
|
||||
}}>
|
||||
Promo {(((initialPrice - promoPrice) / initialPrice) * 100).toFixed(0)}%
|
||||
Promo
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex' }}>
|
||||
@@ -208,12 +252,12 @@ const Item = ({
|
||||
marginLeft: '1rem',
|
||||
marginRight: '0.5rem',
|
||||
marginTop: '0.125rem'
|
||||
}}>{promoPrice}</span>
|
||||
}}>{formatToRupiah(promoPrice)}</span>
|
||||
<span style={{
|
||||
marginTop: '0.125rem',
|
||||
color: 'rgb(114, 114, 114)',
|
||||
textDecoration: 'line-through'
|
||||
}}>{initialPrice}</span>
|
||||
}}>{formatToRupiah(initialPrice)}</span>
|
||||
</div>
|
||||
</>
|
||||
:
|
||||
@@ -223,7 +267,7 @@ const Item = ({
|
||||
<span style={{
|
||||
marginRight: '0.5rem',
|
||||
marginTop: '0.125rem'
|
||||
}}>{initialPrice}</span>
|
||||
}}>{formatToRupiah(initialPrice)}</span>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
@@ -302,7 +346,9 @@ const Item = ({
|
||||
))}
|
||||
|
||||
{forInvoice && (
|
||||
<p className={styles.itemPriceInvoice}>Rp {itemQty * (promoPrice > 0? promoPrice : itemPrice)}</p>
|
||||
<p className={styles.itemPriceInvoice}>Rp {formatToRupiah(itemQty * (promoPrice > 0? promoPrice : itemPrice))}</p>
|
||||
|
||||
// <p className={styles.itemPriceInvoice}>{itemQty * (promoPrice > 0? rupiahFormat(promoPrice) : rupiahFormat(itemPrice))}</p>
|
||||
)}
|
||||
</div>
|
||||
{forCart && (
|
||||
|
||||
@@ -13,9 +13,12 @@
|
||||
margin-bottom: 5px;
|
||||
color: rgba(88, 55, 50, 1);
|
||||
font-size: 32px;
|
||||
box-sizing: border-box; /* Include padding and border in the element's total width */
|
||||
width: 100%; /* Ensure the item does not exceed the parent's width */
|
||||
overflow: hidden; /* Prevent internal overflow */
|
||||
box-sizing: border-box;
|
||||
/* Include padding and border in the element's total width */
|
||||
width: 100%;
|
||||
/* Ensure the item does not exceed the parent's width */
|
||||
overflow: hidden;
|
||||
/* Prevent internal overflow */
|
||||
padding-top: 10px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
@@ -24,7 +27,7 @@
|
||||
/* border-top: 2px solid #00000017; */
|
||||
}
|
||||
|
||||
.notLast{
|
||||
.notLast {
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 2px solid #00000017;
|
||||
}
|
||||
@@ -40,7 +43,8 @@
|
||||
}
|
||||
|
||||
.itemInvoice:last-child {
|
||||
margin-bottom: 0; /* Remove margin-bottom for the last child */
|
||||
margin-bottom: 0;
|
||||
/* Remove margin-bottom for the last child */
|
||||
}
|
||||
|
||||
.itemImage {
|
||||
@@ -102,7 +106,8 @@
|
||||
.itemName {
|
||||
font-family: "Plus Jakarta Sans", sans-serif;
|
||||
font-style: normal;
|
||||
width: calc(100% - 15px); /* Adjust the width to prevent overflow */
|
||||
width: calc(100% - 15px);
|
||||
/* Adjust the width to prevent overflow */
|
||||
font-size: 5vw;
|
||||
font-weight: 500;
|
||||
margin-top: 0;
|
||||
@@ -131,7 +136,8 @@
|
||||
font-family: "Plus Jakarta Sans", sans-serif;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
width: calc(100% - 15px); /* Adjust the width to prevent overflow */
|
||||
width: calc(100% - 15px);
|
||||
/* Adjust the width to prevent overflow */
|
||||
font-size: 3.3vw;
|
||||
/* margin-bottom: 35px; */
|
||||
margin-left: 5px;
|
||||
@@ -175,7 +181,8 @@
|
||||
font-family: "Plus Jakarta Sans", sans-serif;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
width: 30px; /* Adjust the width to prevent overflow */
|
||||
width: 30px;
|
||||
/* Adjust the width to prevent overflow */
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: 10px;
|
||||
text-align: center;
|
||||
@@ -184,18 +191,19 @@
|
||||
|
||||
.addButton {
|
||||
background-color: #ffffff;
|
||||
border: 2px solid #a8c7a9;
|
||||
/* border: none; */
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
width: 87px;
|
||||
height: 32px;
|
||||
margin-left: 5px;
|
||||
margin-top: 5px;
|
||||
border-radius: 20px;
|
||||
border: 2px solid #a8c7a9;
|
||||
/* border: none; */
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
width: 87px;
|
||||
height: 32px;
|
||||
margin-left: 5px;
|
||||
margin-top: 5px;
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
.grayscale {
|
||||
filter: grayscale(100%);
|
||||
}
|
||||
@@ -203,6 +211,7 @@
|
||||
.disabled {
|
||||
color: gray;
|
||||
}
|
||||
|
||||
.plusNegative {
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
@@ -217,6 +226,7 @@
|
||||
left: -33px;
|
||||
top: 21px;
|
||||
}
|
||||
|
||||
.remove {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
@@ -249,3 +259,267 @@
|
||||
left: 15px;
|
||||
right: 15px;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.itemContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
/* gap: 10px; */
|
||||
}
|
||||
|
||||
.item {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
justify-content: space-between;
|
||||
padding-left: 5px;
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
color: rgba(88, 55, 50, 1);
|
||||
font-size: 32px;
|
||||
box-sizing: border-box;
|
||||
/* Include padding and border in the element's total width */
|
||||
width: 100%;
|
||||
/* Ensure the item does not exceed the parent's width */
|
||||
overflow: hidden;
|
||||
/* Prevent internal overflow */
|
||||
padding-top: 10px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.item:not(.itemInvoice) {
|
||||
/* border-top: 2px solid #00000017; */
|
||||
}
|
||||
|
||||
.notLast {
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 2px solid #00000017;
|
||||
}
|
||||
|
||||
.itemInvoice {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
font-size: 18px;
|
||||
margin-top: 0px;
|
||||
margin-bottom: 0px;
|
||||
padding-top: 0px;
|
||||
}
|
||||
|
||||
.itemInvoice:last-child {
|
||||
margin-bottom: 0;
|
||||
/* Remove margin-bottom for the last child */
|
||||
}
|
||||
|
||||
.itemImage {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.imageContainer {
|
||||
position: relative;
|
||||
width: 20%;
|
||||
height: 20%;
|
||||
border-radius: 12px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.overlay {
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
left: 8px;
|
||||
right: 8px;
|
||||
bottom: 15px;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
color: white;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 20px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease;
|
||||
font-size: 3.3vw;
|
||||
}
|
||||
|
||||
.overlay:hover {
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
|
||||
.fileInput {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.itemDetails {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.itemInvoiceDetails {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
margin-left: 10px;
|
||||
margin-top: -15px;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.itemName {
|
||||
font-family: "Plus Jakarta Sans", sans-serif;
|
||||
font-style: normal;
|
||||
width: calc(100% - 15px);
|
||||
/* Adjust the width to prevent overflow */
|
||||
font-size: 5vw;
|
||||
font-weight: 500;
|
||||
margin-top: 0;
|
||||
margin: 0 5px;
|
||||
color: rgba(88, 55, 50, 1);
|
||||
background-color: transparent;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.itemInvoiceName {
|
||||
width: calc(260% - 15px);
|
||||
background-color: transparent;
|
||||
font-size: 1.3rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.multiplySymbol {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.qtyInvoice {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.itemPrice {
|
||||
font-family: "Plus Jakarta Sans", sans-serif;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
width: calc(100% - 15px);
|
||||
/* Adjust the width to prevent overflow */
|
||||
font-size: 3.3vw;
|
||||
/* margin-bottom: 35px; */
|
||||
margin-left: 5px;
|
||||
color: #3a3a3a;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.itemPriceInvoice {
|
||||
font-family: "Plus Jakarta Sans", sans-serif;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-size: 0.9rem;
|
||||
margin-left: 5px;
|
||||
color: #d9c61c;
|
||||
text-align: right;
|
||||
margin-top: 22px;
|
||||
}
|
||||
|
||||
.itemQty {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 0.9rem;
|
||||
margin-left: 5px;
|
||||
color: #a8c7a9;
|
||||
fill: #a8c7a9;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.itemQtyValue {
|
||||
font-family: "Plus Jakarta Sans", sans-serif;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
margin-top: 19px;
|
||||
margin-left: 1px;
|
||||
margin-right: 1px;
|
||||
width: 25px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.itemQtyInput {
|
||||
font-family: "Plus Jakarta Sans", sans-serif;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
width: 30px;
|
||||
/* Adjust the width to prevent overflow */
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: 10px;
|
||||
text-align: center;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.addButton {
|
||||
background-color: #ffffff;
|
||||
border: 2px solid #a8c7a9;
|
||||
/* border: none; */
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
width: 87px;
|
||||
height: 32px;
|
||||
margin-left: 5px;
|
||||
margin-top: 5px;
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
.grayscale {
|
||||
filter: grayscale(100%);
|
||||
}
|
||||
|
||||
.disabled {
|
||||
color: gray;
|
||||
}
|
||||
|
||||
.plusNegative {
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
margin: 2.5px 0 -0.5px 0px;
|
||||
}
|
||||
|
||||
.plusNegative2 {
|
||||
width: 84px;
|
||||
height: 21px;
|
||||
position: absolute;
|
||||
transform: rotate(45deg);
|
||||
left: -33px;
|
||||
top: 21px;
|
||||
}
|
||||
|
||||
.remove {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
margin-top: -10px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.itemInvoice .itemDetails {
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.itemInvoice .itemName,
|
||||
.itemInvoice .itemPrice,
|
||||
.itemInvoice .itemQty .qtyInvoice .multiplySymbol {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.blank {
|
||||
border: 1px solid #000000;
|
||||
}
|
||||
|
||||
.notblank {
|
||||
border: 1px solid #ffffff00;
|
||||
}
|
||||
|
||||
.createItem {
|
||||
position: absolute;
|
||||
left: 15px;
|
||||
right: 15px;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import styles from "./Modal.module.css";
|
||||
import { getImageUrl } from "../helpers/itemHelper.js";
|
||||
import { ThreeDots } from "react-loader-spinner";
|
||||
|
||||
|
||||
const ItemConfig = ({
|
||||
name: initialName,
|
||||
@@ -24,11 +25,14 @@ const ItemConfig = ({
|
||||
const fileInputRef = useRef(null);
|
||||
const textareaRef = useRef(null);
|
||||
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
// Prevent scrolling when modal is open
|
||||
document.body.style.overflow = "hidden";
|
||||
if(selectedImage){
|
||||
if (selectedImage) {
|
||||
const reader = new FileReader();
|
||||
reader.onloadend = () => {
|
||||
setPreviewUrl(reader.result);
|
||||
@@ -79,18 +83,27 @@ const ItemConfig = ({
|
||||
return () => textarea.removeEventListener("input", handleResize);
|
||||
}
|
||||
}, [textareaRef.current]);
|
||||
const handleCreate = async () => {
|
||||
setIsSaving(true);
|
||||
try {
|
||||
await handleCreateItem(itemName, itemPrice, selectedImage, previewUrl, itemDescription, itemPromoPrice);
|
||||
document.body.style.overflow = "auto";
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCreate = () => {
|
||||
console.log(itemPromoPrice)
|
||||
handleCreateItem(itemName, itemPrice, selectedImage, previewUrl, itemDescription, itemPromoPrice);
|
||||
document.body.style.overflow = "auto";
|
||||
};
|
||||
const handleUpdate = () => {
|
||||
console.log(itemName, itemPrice, selectedImage, itemDescription, itemPromoPrice)
|
||||
handleUpdateItem(itemName, itemPrice, selectedImage, itemDescription, itemPromoPrice);
|
||||
document.body.style.overflow = "auto";
|
||||
const handleUpdate = async () => {
|
||||
setIsSaving(true);
|
||||
try {
|
||||
await handleUpdateItem(itemName, itemPrice, selectedImage, itemDescription, itemPromoPrice);
|
||||
document.body.style.overflow = "auto";
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<div onClick={handleOverlayClick} style={{ position: 'fixed', width: '100vw', height: '100vh', left: 0, bottom: 0, display: 'flex', flexDirection: 'column-reverse', zIndex: 301, backgroundColor: '#00000061' }}>
|
||||
<div onClick={handleContentClick} style={{ display: 'flex', flexDirection: 'column', padding: '15px', backgroundColor: 'white', borderRadius: '20px 20px 0 0', overflowY: 'auto' }}>
|
||||
@@ -131,7 +144,7 @@ const ItemConfig = ({
|
||||
transition: 'all 0.3s ease',
|
||||
boxSizing: 'border-box', // Make sure the padding doesn't cause overflow
|
||||
}}
|
||||
onChange={(e)=>setItemName(e.target.value)}
|
||||
onChange={(e) => setItemName(e.target.value)}
|
||||
onFocus={(e) => e.target.style.borderColor = '#60d37e'}
|
||||
onBlur={(e) => e.target.style.borderColor = '#ccc'}
|
||||
/>
|
||||
@@ -153,7 +166,7 @@ const ItemConfig = ({
|
||||
transition: 'all 0.3s ease',
|
||||
boxSizing: 'border-box',
|
||||
}}
|
||||
onChange={(e)=>setItemPrice(e.target.value)}
|
||||
onChange={(e) => setItemPrice(e.target.value)}
|
||||
onFocus={(e) => e.target.style.borderColor = '#60d37e'}
|
||||
onBlur={(e) => e.target.style.borderColor = '#ccc'}
|
||||
/>
|
||||
@@ -174,7 +187,7 @@ const ItemConfig = ({
|
||||
transition: 'all 0.3s ease',
|
||||
boxSizing: 'border-box',
|
||||
}}
|
||||
onChange={(e)=>setItemPromoPrice(e.target.value)}
|
||||
onChange={(e) => setItemPromoPrice(e.target.value)}
|
||||
onFocus={(e) => e.target.style.borderColor = '#60d37e'}
|
||||
onBlur={(e) => e.target.style.borderColor = '#ccc'}
|
||||
/>
|
||||
@@ -200,17 +213,40 @@ const ItemConfig = ({
|
||||
}}
|
||||
placeholder="Tambah deskripsi..."
|
||||
value={itemDescription}
|
||||
onChange={(e)=>setItemDescription(e.target.value)}
|
||||
onChange={(e) => setItemDescription(e.target.value)}
|
||||
onFocus={(e) => e.target.style.borderColor = '#60d37e'}
|
||||
onBlur={(e) => e.target.style.borderColor = '#ccc'}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
onClick={() => {
|
||||
if (!isSaving) {
|
||||
isBeingEdit ? handleUpdate() : handleCreate();
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '40px',
|
||||
alignContent: 'center',
|
||||
textAlign: 'center',
|
||||
borderRadius: '10px',
|
||||
border: '1px solid #60d37e',
|
||||
color: isSaving ? '#aaa' : '#60d37e',
|
||||
backgroundColor: 'white',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
cursor: isSaving ? 'not-allowed' : 'pointer'
|
||||
}}
|
||||
>
|
||||
{isSaving ? (
|
||||
|
||||
<div style={{ width: '100%', height: '35px', display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
|
||||
<div onClick={() => {isBeingEdit ? handleUpdate() : handleCreate()} } style={{ width: '100%', height: '40px', alignContent: 'center', textAlign: 'center', borderRadius: '10px', border: '1px solid #60d37e', color: '#60d37e', backgroundColor: 'white', display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
|
||||
{isBeingEdit? 'Simpan' : 'Buat Item'}
|
||||
</div>
|
||||
<ThreeDots height={20} width={20} />
|
||||
) : (
|
||||
isBeingEdit ? 'Simpan' : 'Simpan'
|
||||
)}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -598,7 +598,7 @@ const ItemLister = ({
|
||||
return (
|
||||
<>
|
||||
{(items.length > 0 ||
|
||||
(user && (user.cafeId == shopId || user.userId == shopOwnerId))) && (
|
||||
(user && (user.cafeId == shopId || user.user_id == shopOwnerId))) && (
|
||||
<div
|
||||
key={itemTypeId}
|
||||
className={`${styles["item-lister"]} ${isEdit ? styles["fullscreen"] : ""
|
||||
@@ -713,15 +713,6 @@ const ItemLister = ({
|
||||
}
|
||||
imageUrl={getImageUrl("uploads/assets/addnew.png")}
|
||||
/>
|
||||
{/* {typeImage != null && !previewUrl.includes(typeImage) && (
|
||||
<ItemType
|
||||
rectangular={true}
|
||||
onClick={(previewUrl, selectedImage) =>
|
||||
handleImageChange(previewUrl, selectedImage)
|
||||
}
|
||||
imageUrl={getImageUrl(typeImage)}
|
||||
/>
|
||||
)} */}
|
||||
|
||||
<ItemType
|
||||
rectangular={true}
|
||||
@@ -857,7 +848,6 @@ const ItemLister = ({
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<button onClick={() => setIsFirstStep(false)} style={{ width: '100%', height: '40px', borderRadius: '20px' }}>selanjutnya</button>
|
||||
</>
|
||||
)}
|
||||
{(isEdit && !isFirstStep || !isEdit) &&
|
||||
@@ -866,7 +856,7 @@ const ItemLister = ({
|
||||
<h2 className={styles["item-list-title"]}>{items && items.length < 1 ? 'Buat item' : 'Daftar item'}</h2></div>}
|
||||
<div className={styles["item-list"]}>
|
||||
{user && (
|
||||
user.userId == shopOwnerId || user.cafeId == shopId) &&
|
||||
user.user_id == shopOwnerId || user.cafeId == shopId) &&
|
||||
isEditMode && (
|
||||
<>
|
||||
{!isAddingNewItem && (
|
||||
@@ -1113,7 +1103,7 @@ const ItemLister = ({
|
||||
|
||||
{user &&
|
||||
user.roleId == 1 &&
|
||||
user.userId == shopOwnerId &&
|
||||
user.user_id == shopOwnerId &&
|
||||
isEdit && (
|
||||
<>
|
||||
{/* <button
|
||||
|
||||
@@ -24,60 +24,8 @@
|
||||
/* padding: 10px; */
|
||||
/* max-height: calc(3 * (25vw - 20px) + 20px); */
|
||||
overflow-y: auto;
|
||||
height: calc(49vw - 20px);
|
||||
}
|
||||
|
||||
@media (min-height: 0px) {
|
||||
.grid-container {
|
||||
height: 27vh;
|
||||
}
|
||||
}
|
||||
@media (min-height: 630px) {
|
||||
.grid-container {
|
||||
height: 27vh;
|
||||
}
|
||||
}
|
||||
@media (min-height: 636px) {
|
||||
.grid-container {
|
||||
height: 29vh;
|
||||
}
|
||||
}
|
||||
@media (min-height: 650px) {
|
||||
.grid-container {
|
||||
height: 34vh;
|
||||
}
|
||||
}
|
||||
@media (min-height: 705px) {
|
||||
.grid-container {
|
||||
height: 37vh;
|
||||
}
|
||||
}
|
||||
@media (min-height: 735px) {
|
||||
.grid-container {
|
||||
height: 38vh;
|
||||
}
|
||||
}
|
||||
@media (min-height: 759px) {
|
||||
.grid-container {
|
||||
height: 40vh;
|
||||
}
|
||||
}
|
||||
@media (min-height: 819px) {
|
||||
.grid-container {
|
||||
height: 44vh;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-height: 830px) {
|
||||
.grid-container {
|
||||
height: 47vh;
|
||||
}
|
||||
}
|
||||
@media (min-height: 892px) {
|
||||
.grid-container {
|
||||
height: 49vh;
|
||||
}
|
||||
}
|
||||
|
||||
.title-container {
|
||||
display: flex;
|
||||
@@ -171,7 +119,7 @@
|
||||
.TotalContainer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 80vw;
|
||||
/* width: 80vw; */
|
||||
margin: 0 auto;
|
||||
font-family: "Plus Jakarta Sans", sans-serif;
|
||||
font-weight: 600;
|
||||
|
||||
@@ -9,6 +9,7 @@ export default function ItemType({
|
||||
imageUrl,
|
||||
selected,
|
||||
rectangular,
|
||||
typeLength
|
||||
}) {
|
||||
const inputRef = useRef(null);
|
||||
const [namee, setName] = useState(name);
|
||||
@@ -68,7 +69,7 @@ export default function ItemType({
|
||||
: "item-type-nomargin"
|
||||
]
|
||||
}
|
||||
style={{ zIndex: blank ? 301 : "inherit" }}
|
||||
style={{ zIndex: blank ? 301 : "inherit", width: `calc(${100 / (Math.min(typeLength, 3) + 1)}% - 10px)` }}
|
||||
>
|
||||
<div
|
||||
onClick={
|
||||
|
||||
@@ -94,3 +94,102 @@
|
||||
.noborder {
|
||||
border: 1px solid #ffffff00;
|
||||
}
|
||||
|
||||
@media (min-width: 768px){
|
||||
.item-type {
|
||||
width: calc(25% - 20px);
|
||||
height: calc(30% - 20px);
|
||||
margin: 1px 10px 0px;
|
||||
overflow: visible;
|
||||
text-align: center;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
}
|
||||
.item-type-rectangular {
|
||||
width: calc(100% - 20px);
|
||||
height: calc(100% - 20px);
|
||||
overflow: visible;
|
||||
text-align: center;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: start;
|
||||
position: relative; /* Ensure absolute positioning inside works */
|
||||
}
|
||||
.item-type-nomargin {
|
||||
width: calc(25% - 20px);
|
||||
height: calc(39% - 20px);
|
||||
overflow: visible;
|
||||
text-align: center;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
position: relative; /* Ensure absolute positioning inside works */
|
||||
}
|
||||
.item-type-rect {
|
||||
position: relative;
|
||||
height: 30%;
|
||||
width: 30%;
|
||||
object-fit: cover;
|
||||
border-radius: 15px;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.item-type-name {
|
||||
font-family: "Plus Jakarta Sans", sans-serif;
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
width: calc(25vw - 30px);
|
||||
text-align: center;
|
||||
background-color: transparent;
|
||||
position: relative; /* Needed for positioning the button */
|
||||
}
|
||||
|
||||
.item-type-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.item-type-image-container {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.item-type-image-input {
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.item-type-create {
|
||||
position: absolute;
|
||||
top: 76%; /* Position below the input */
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
margin-top: 10px; /* Space between input and button */
|
||||
width: 20vw;
|
||||
text-align: center; /* Center button text */
|
||||
}
|
||||
|
||||
.border {
|
||||
border: 1px solid #000000;
|
||||
}
|
||||
|
||||
.noborder {
|
||||
border: 1px solid #ffffff00;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
.item-type-lister {
|
||||
width: 100vw;
|
||||
overflow-x: auto;
|
||||
white-space: nowrap;
|
||||
padding: 3px 0px;
|
||||
@@ -11,6 +10,7 @@
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
overflow-y: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.item-type {
|
||||
|
||||
@@ -103,7 +103,7 @@ const ItemTypeLister = ({
|
||||
{isEditMode &&
|
||||
!isAddingNewItem &&
|
||||
user && (
|
||||
user.userId == shopOwnerId || user.cafeId == shopId) && (
|
||||
user.user_id == shopOwnerId || user.cafeId == shopId) && (
|
||||
<ItemType
|
||||
onClick={toggleAddNewItem}
|
||||
name={"buat baru"}
|
||||
@@ -111,7 +111,7 @@ const ItemTypeLister = ({
|
||||
/>
|
||||
)}
|
||||
{user &&(
|
||||
user.userId == shopOwnerId || user.cafeId == shopId) &&
|
||||
user.user_id == shopOwnerId || user.cafeId == shopId) &&
|
||||
isAddingNewItem && (
|
||||
<>
|
||||
<ItemLister
|
||||
@@ -141,13 +141,14 @@ const ItemTypeLister = ({
|
||||
itemTypes.map(
|
||||
(itemType) =>
|
||||
(
|
||||
itemType.itemList.length > 0 || (user && (user.userId == shopOwnerId || user.cafeId == shopId))) && (
|
||||
|
||||
<ItemType
|
||||
key={itemType.itemTypeId}
|
||||
name={itemType.name}
|
||||
imageUrl={getImageUrl(itemType.image)}
|
||||
onClick={() => onFilterChange(itemType.itemTypeId)}
|
||||
selected={filterId === itemType.itemTypeId}
|
||||
typeLength={itemTypes.length}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
|
||||
@@ -35,7 +35,7 @@ import CreateCoupon from "../pages/CreateCoupon";
|
||||
import CheckCoupon from "../pages/CheckCoupon";
|
||||
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 [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 === "edit_tables" && <TablesPage shop={shop} />}
|
||||
{modalContent === "new_transaction" && (
|
||||
<Transaction propsShopId={shop.cafeId} handleMoveToTransaction={handleMoveToTransaction} depth={depth} shopImg={shopImg} />
|
||||
<Transaction propsShopId={shop.cafeId} setIsModalOpen={setIsModalOpen} cafeIdentityName={shopIdentifier} handleMoveToTransaction={handleMoveToTransaction} depth={depth} shopImg={shopImg} setModal={setModal}/>
|
||||
)}
|
||||
{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_item" && <Transaction_item />}
|
||||
|
||||
@@ -36,3 +36,4 @@
|
||||
.closeButton:hover {
|
||||
color: #f44336; /* Change color on hover for better UX */
|
||||
}
|
||||
|
||||
|
||||
@@ -367,7 +367,7 @@ export function MusicPlayer({ socket, shopId, user, shopOwnerId, isSpotifyNeedLo
|
||||
|
||||
const [text, setText] = useState("Menunggu musik favoritmu");
|
||||
const textIndex = useRef(0);
|
||||
const [messages, setMessages] = useState(["Menunggu musik favoritmu", "Klik untuk putar musik favoritmu"]);
|
||||
const [messages, setMessages] = useState(["Menunggu musik favoritmu", "Upgrade to use"]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
@@ -376,7 +376,7 @@ export function MusicPlayer({ socket, shopId, user, shopOwnerId, isSpotifyNeedLo
|
||||
currentSong != null && currentSong[0]?.trackId != 'kCGs5_oCtBE' && currentSong[0]?.trackId != 'O8eYd7oAZtA' && currentSong[0]?.name != undefined
|
||||
? `${currentSong[0]?.name} - ${currentSong[0]?.artist}`
|
||||
: "Menunggu musik favoritmu",
|
||||
"Klik untuk putar musik favoritmu"
|
||||
"Upgrade to use"
|
||||
];
|
||||
|
||||
setMessages(newMessages);
|
||||
@@ -421,7 +421,7 @@ export function MusicPlayer({ socket, shopId, user, shopOwnerId, isSpotifyNeedLo
|
||||
return (
|
||||
<div className={`music-player`} style={{ marginBottom: `${viewing ? '-10px' : ''}` }}>
|
||||
<div
|
||||
onClick={toggleView}
|
||||
// onClick={toggleView}
|
||||
className="current-bgr"
|
||||
style={{ backgroundImage: `url(${backgroundImage})` }}
|
||||
// style={{ backgroundImage: `url(${videoSrc != "" ? '' : backgroundImage})` }}
|
||||
@@ -479,7 +479,7 @@ export function MusicPlayer({ socket, shopId, user, shopOwnerId, isSpotifyNeedLo
|
||||
className={`expandable-container ${expanded ? "expanded" : ""}`}
|
||||
ref={expandableContainerRef}
|
||||
>
|
||||
{user.cafeId == shopId || user.userId == shopOwnerId && (
|
||||
{user.cafeId == shopId || user.user_id == shopOwnerId && (
|
||||
<>
|
||||
<div className="auth-box">
|
||||
<div
|
||||
|
||||
@@ -7,182 +7,195 @@ const PeriodCharts = ({ type, graphFilter, aggregatedCurrentReports, aggregatedP
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedIndex(-1);
|
||||
}, [aggregatedCurrentReports, aggregatedPreviousReports]);
|
||||
}, [aggregatedCurrentReports, aggregatedPreviousReports, graphFilter]);
|
||||
|
||||
const monthly = ["1 - 7", "8 - 14", "15 - 21", "22 - 28", "29 - 31"];
|
||||
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) {
|
||||
currentIncomeData = aggregatedCurrentReports.map((report) => report.income);
|
||||
currentOutcomeData = aggregatedCurrentReports.map((report) => report.outcome);
|
||||
currentTransactionData = aggregatedCurrentReports.map((report) => report.transactions);
|
||||
currentIncomeData = aggregatedCurrentReports.map((r) => r.income);
|
||||
currentOutcomeData = aggregatedCurrentReports.map((r) => r.outcome);
|
||||
currentTransactionData = aggregatedCurrentReports.map((r) => r.transactions);
|
||||
|
||||
if (type == 'monthly' && currentIncomeData.length === 4) {
|
||||
if (type === "monthly" && currentIncomeData.length === 4) {
|
||||
currentIncomeData.push(null);
|
||||
}
|
||||
if (type == 'monthly' && currentOutcomeData.length === 4) {
|
||||
currentOutcomeData.push(null);
|
||||
}
|
||||
if (type == 'monthly' && currentTransactionData.length === 4) {
|
||||
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);
|
||||
}
|
||||
if (type == 'monthly' && previousOutcomeData.length === 4) {
|
||||
previousOutcomeData.push(null);
|
||||
}
|
||||
if (type == 'monthly' && previousTransactionData.length === 4) {
|
||||
previousTransactionData.push(null);
|
||||
}
|
||||
}
|
||||
let globalMax = null;
|
||||
if (aggregatedCurrentReports || aggregatedPreviousReports) {
|
||||
// Find the global maximum for the y-axis
|
||||
globalMax = Math.max(
|
||||
...(graphFilter === 'income'
|
||||
? [...currentIncomeData, ...previousIncomeData]
|
||||
: graphFilter === 'outcome'
|
||||
? [...currentOutcomeData, ...previousOutcomeData]
|
||||
: [...currentTransactionData, ...previousTransactionData])
|
||||
);
|
||||
}
|
||||
|
||||
// cari global max untuk y-axis
|
||||
let globalMax = 0;
|
||||
if (aggregatedCurrentReports || aggregatedPreviousReports) {
|
||||
const dataset =
|
||||
graphFilter === "income"
|
||||
? [...(currentIncomeData || []), ...(previousIncomeData || [])]
|
||||
: graphFilter === "outcome"
|
||||
? [...(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 (
|
||||
<div className={`${styles.chartItemContainer} ${selectedIndex !== -1 ? styles.expanded : ''}`}>
|
||||
{aggregatedPreviousReports && (
|
||||
<div className={`${styles.chartItemWrapper} ${selectedIndex !== -1 && selectedIndex !== 0
|
||||
? styles.chartItemWrapperActive
|
||||
: styles.chartItemWrapperInactive
|
||||
}`}>
|
||||
|
||||
<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
|
||||
className={`${styles.chartItemContainer} ${selectedIndex !== -1 ? styles.expanded : ""
|
||||
}`}
|
||||
>
|
||||
{[aggregatedPreviousReports, aggregatedCurrentReports].map(
|
||||
(dataset, i) =>
|
||||
dataset && (
|
||||
<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>{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', aggregatedCurrentReports?.length == 4 ? 'transparent' : 'black'],
|
||||
<div className={styles.dateSelectorWrapper}>
|
||||
{[0, 1].map((idx) => (
|
||||
<div
|
||||
key={idx}
|
||||
className={`${styles.dateSelector} ${idx === i ? styles.dateSelectorActive : styles.dateSelectorInactive
|
||||
}`}
|
||||
style={{ position: "relative" }}
|
||||
onClick={() =>
|
||||
selectedIndex == idx ? setSelectedIndex(-1) : setSelectedIndex(idx)
|
||||
}
|
||||
}
|
||||
},
|
||||
yaxis: { max: globalMax, min: 0, labels: { maxWidth: 20, style: { colors: "transparent" } } },
|
||||
grid: { show: false },
|
||||
fill: { opacity: 0.5 },
|
||||
colors: [colors[1]],
|
||||
}}
|
||||
series={[
|
||||
// { name: "Pemasukan", data: currentIncomeData },
|
||||
// { name: "Pengeluaran", data: currentOutcomeData },
|
||||
{ name: "Total transaksi", data: graphFilter == 'income' ? currentIncomeData : graphFilter == 'outcome' ? currentOutcomeData : currentTransactionData },
|
||||
]}
|
||||
type="area"
|
||||
height={200}
|
||||
width="100%"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
>
|
||||
{idx === i && (
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
bottom: 0,
|
||||
left: "10%",
|
||||
right: "10%",
|
||||
borderBottom: `2px solid ${colors[i]}`,
|
||||
}}
|
||||
></div>
|
||||
)}
|
||||
<div style={{ color: idx === i ? "black" : "transparent" }}>
|
||||
{type === "monthly"
|
||||
? idx === 0
|
||||
? "bulan lalu"
|
||||
: "bulan ini"
|
||||
: 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>
|
||||
);
|
||||
|
||||
@@ -106,10 +106,10 @@ export async function getCafeByIdentifier(cafeIdentifyName) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
export async function getOwnedCafes(userId) {
|
||||
export async function getOwnedCafes(user_id) {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`${API_BASE_URL}/cafe/get-cafe-by-ownerId/` + userId,
|
||||
`${API_BASE_URL}/cafe/get-cafe-by-ownerId/` + user_id,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
|
||||
@@ -217,7 +217,8 @@ export const handlePaymentFromClerk = async (
|
||||
user_email,
|
||||
payment_type,
|
||||
serving_type,
|
||||
tableNo
|
||||
tableNo,
|
||||
notes
|
||||
) => {
|
||||
try {
|
||||
const token = getLocalStorage("auth");
|
||||
@@ -245,15 +246,15 @@ export const handlePaymentFromClerk = async (
|
||||
serving_type,
|
||||
tableNo,
|
||||
transactions: structuredItems,
|
||||
notes
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
if (response.ok) {
|
||||
// Handle success response
|
||||
console.log("Transaction successful!");
|
||||
// Optionally return response data or handle further actions upon success
|
||||
return true;
|
||||
const data = await response.json();
|
||||
console.log("Transaction successful!", data);
|
||||
return data;
|
||||
} else {
|
||||
// Handle error response
|
||||
console.error("Transaction failed:", response.statusText);
|
||||
|
||||
@@ -10,22 +10,35 @@ import {
|
||||
|
||||
import "../App.css";
|
||||
|
||||
import API_BASE_URL from '../config';
|
||||
import API_BASE_URL from "../config";
|
||||
import Watermark from "../components/Watermark";
|
||||
import { getImageUrl, createItem, updateItem, moveItemType } from "../helpers/itemHelper.js";
|
||||
import {
|
||||
getImageUrl,
|
||||
createItem,
|
||||
updateItem,
|
||||
moveItemType,
|
||||
} from "../helpers/itemHelper.js";
|
||||
import SearchInput from "../components/SearchInput";
|
||||
import ItemTypeLister from "../components/ItemTypeLister";
|
||||
import { MusicPlayer } from "../components/MusicPlayer";
|
||||
import ItemLister from "../components/ItemLister";
|
||||
import Header from "../components/Header";
|
||||
|
||||
import Switch from "react-switch";
|
||||
|
||||
import { ThreeDots } from "react-loader-spinner";
|
||||
|
||||
import { getLocalStorage, updateLocalStorage, removeLocalStorage } from "../helpers/localStorageHelpers";
|
||||
import {
|
||||
getLocalStorage,
|
||||
updateLocalStorage,
|
||||
removeLocalStorage,
|
||||
} from "../helpers/localStorageHelpers";
|
||||
import { unsubscribeUser } from "../helpers/subscribeHelpers.js";
|
||||
import WelcomePage from "./WelcomePage.js";
|
||||
import { useNavigationHelpers } from "../helpers/navigationHelpers";
|
||||
|
||||
import Cart from "./Cart";
|
||||
|
||||
function CafePage({
|
||||
shopId,
|
||||
table,
|
||||
@@ -46,21 +59,21 @@ function CafePage({
|
||||
queue,
|
||||
cartItemsLength,
|
||||
totalPrice,
|
||||
lastTransaction
|
||||
lastTransaction,
|
||||
shop,
|
||||
totalItemsCount,
|
||||
deviceType,
|
||||
}) {
|
||||
|
||||
|
||||
|
||||
const location = useLocation();
|
||||
const [searchParams] = useSearchParams();
|
||||
const token = searchParams.get("token");
|
||||
const { shopIdentifier, tableCode } = useParams();
|
||||
sendParam({ shopIdentifier, tableCode });
|
||||
|
||||
const {
|
||||
goToCart,
|
||||
goToTransactions
|
||||
} = useNavigationHelpers(shopIdentifier, table.tableCode);
|
||||
const { goToCart, goToTransactions } = useNavigationHelpers(
|
||||
shopIdentifier,
|
||||
table.tableCode
|
||||
);
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
@@ -77,17 +90,49 @@ function CafePage({
|
||||
|
||||
const [beingEditedType, setBeingEditedType] = useState(0);
|
||||
|
||||
const checkWelcomePageConfig = () => {
|
||||
const parsedConfig = JSON.parse(welcomePageConfig);
|
||||
if (parsedConfig.isWelcomePageActive == "true") {
|
||||
const clicked = sessionStorage.getItem("getStartedClicked");
|
||||
if (!clicked) {
|
||||
sessionStorage.setItem("getStartedClicked", true);
|
||||
document.body.style.overflow = "hidden";
|
||||
setIsStarted(true);
|
||||
}
|
||||
// const checkWelcomePageConfig = () => {
|
||||
// const parsedConfig = JSON.parse(welcomePageConfig);
|
||||
// if (parsedConfig.isWelcomePageActive == "true") {
|
||||
// const clicked = sessionStorage.getItem("getStartedClicked");
|
||||
// if (!clicked) {
|
||||
// sessionStorage.setItem("getStartedClicked", true);
|
||||
// document.body.style.overflow = "hidden";
|
||||
// setIsStarted(true);
|
||||
// }
|
||||
// }
|
||||
// };
|
||||
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(() => {
|
||||
const handleResize = () => {
|
||||
setIsTablet(window.innerWidth >= 768);
|
||||
};
|
||||
|
||||
window.addEventListener("resize", handleResize);
|
||||
return () => window.removeEventListener("resize", handleResize);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (window.gtag && shopIdentifier) {
|
||||
window.gtag("event", "page_view", {
|
||||
page_title: `Cafe - ${shopIdentifier}`,
|
||||
page_location: window.location.href,
|
||||
page_path: `/` + shopIdentifier,
|
||||
shop_id: shopId || null, // opsional jika kamu mau track ID juga
|
||||
});
|
||||
}
|
||||
}, [shopIdentifier]);
|
||||
|
||||
useEffect(() => {
|
||||
if (welcomePageConfig) {
|
||||
@@ -100,25 +145,29 @@ function CafePage({
|
||||
isActive: parsedConfig.isWelcomePageActive === "true",
|
||||
});
|
||||
}
|
||||
checkWelcomePageConfig();
|
||||
// checkWelcomePageConfig();
|
||||
}, [welcomePageConfig]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
function fetchData() {
|
||||
console.log(user.userId == shopOwnerId)
|
||||
console.log(user.user_id == shopOwnerId);
|
||||
setModal("create_item");
|
||||
|
||||
}
|
||||
console.log(getLocalStorage('auth'))
|
||||
|
||||
console.log(getLocalStorage("auth"));
|
||||
if (getLocalStorage("auth") != null) {
|
||||
const executeFetch = async () => {
|
||||
while (user.length == 0) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 100)); // Wait until the user is not null
|
||||
}
|
||||
console.log(user)
|
||||
console.log('open')
|
||||
if (user.length != 0 && user.userId == shopOwnerId && shopItems.length == 0) fetchData();
|
||||
console.log(user);
|
||||
console.log("open");
|
||||
if (
|
||||
user.length != 0 &&
|
||||
user.user_id == shopOwnerId &&
|
||||
shopItems.length == 0
|
||||
)
|
||||
fetchData();
|
||||
};
|
||||
executeFetch();
|
||||
}
|
||||
@@ -141,13 +190,39 @@ function CafePage({
|
||||
socket.on("joined-room", (response) => {
|
||||
const { isSpotifyNeedLogin, isExceededDeadline } = response;
|
||||
setNeedSpotifyLogin(isSpotifyNeedLogin);
|
||||
if (isExceededDeadline) setModal("message",{captMessage:'Kafe sedang tidak tersedia'});
|
||||
if (isExceededDeadline)
|
||||
setModal("message", { captMessage: "Kafe sedang tidak tersedia" });
|
||||
setIsExceededDeadline(isExceededDeadline);
|
||||
});
|
||||
}
|
||||
|
||||
if (socket) fetchData();
|
||||
}, [socket]);
|
||||
useEffect(() => {
|
||||
const isTablet = window.innerWidth >= 768;
|
||||
|
||||
const handleFirstClick = async () => {
|
||||
if (
|
||||
isTablet &&
|
||||
document.fullscreenEnabled &&
|
||||
!document.fullscreenElement
|
||||
) {
|
||||
try {
|
||||
await document.documentElement.requestFullscreen();
|
||||
document.removeEventListener("click", handleFirstClick);
|
||||
} catch (err) {
|
||||
console.warn("Gagal masuk fullscreen:", err);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Tambahkan listener satu kali
|
||||
document.addEventListener("click", handleFirstClick);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener("click", handleFirstClick);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleGetStarted = () => {
|
||||
setIsStarted(false);
|
||||
@@ -162,16 +237,19 @@ function CafePage({
|
||||
const newItems = [...shopItems];
|
||||
|
||||
let targetIndex;
|
||||
if (direction === 'up' && index > 0) {
|
||||
if (direction === "up" && index > 0) {
|
||||
targetIndex = index - 1;
|
||||
} else if (direction === 'down' && index < newItems.length - 1) {
|
||||
} else if (direction === "down" && index < newItems.length - 1) {
|
||||
targetIndex = index + 1;
|
||||
}
|
||||
console.log(index);
|
||||
console.log(targetIndex);
|
||||
if (targetIndex !== undefined) {
|
||||
// Swap items
|
||||
[newItems[index], newItems[targetIndex]] = [newItems[targetIndex], newItems[index]];
|
||||
[newItems[index], newItems[targetIndex]] = [
|
||||
newItems[targetIndex],
|
||||
newItems[index],
|
||||
];
|
||||
newItems[index].order = targetIndex;
|
||||
newItems[targetIndex].order = index;
|
||||
|
||||
@@ -179,14 +257,83 @@ function CafePage({
|
||||
|
||||
// Call the API to move the item type
|
||||
try {
|
||||
await moveItemType(itemTypeId, previousItems[targetIndex].itemTypeId, index, targetIndex);
|
||||
await moveItemType(
|
||||
itemTypeId,
|
||||
previousItems[targetIndex].itemTypeId,
|
||||
index,
|
||||
targetIndex
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Error moving item type:', error);
|
||||
console.error("Error moving item type:", error);
|
||||
// Revert the changes if the backend fails
|
||||
setShopItems(previousItems);
|
||||
}
|
||||
}
|
||||
};
|
||||
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)
|
||||
return (
|
||||
@@ -211,133 +358,266 @@ function CafePage({
|
||||
isFullscreen={true}
|
||||
/>
|
||||
) : ( */}
|
||||
<div className={`Cafe ${isExceededDeadline ? 'grayscale' : ''}`}>
|
||||
|
||||
{API_BASE_URL != 'https://dev.api.kedaimaster.com' && API_BASE_URL != 'https://api.kedaimaster.com' &&
|
||||
<div
|
||||
className={`Cafe ${isExceededDeadline ? "grayscale" : ""}`}
|
||||
style={{
|
||||
display: isTablet ? "flex" : "block",
|
||||
flexDirection: "row",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
{API_BASE_URL != "https://dev.api.kedaimaster.com" &&
|
||||
API_BASE_URL != "https://api.kedaimaster.com" && (
|
||||
<div className="Watermark"></div>
|
||||
}
|
||||
<div className="App-header">
|
||||
<Header
|
||||
HeaderText={"Menu"}
|
||||
showProfile={true}
|
||||
setModal={setModal}
|
||||
isLogout={handleLogout}
|
||||
shopId={shopId}
|
||||
shopName={shopName}
|
||||
shopImage={config.image}
|
||||
shopOwnerId={shopOwnerId}
|
||||
shopClerks={shopClerks}
|
||||
tableCode={table.tableCode}
|
||||
user={user}
|
||||
guestSides={guestSides}
|
||||
guestSideOfClerk={guestSideOfClerk}
|
||||
removeConnectedGuestSides={removeConnectedGuestSides}
|
||||
setIsEditMode={(e) => setIsEditMode(e)}
|
||||
isEditMode={isEditMode}
|
||||
/>
|
||||
<MusicPlayer
|
||||
socket={socket}
|
||||
shopId={shopId}
|
||||
user={user}
|
||||
shopOwnerId={shopOwnerId}
|
||||
isSpotifyNeedLogin={isSpotifyNeedLogin}
|
||||
queue={queue}
|
||||
setModal={setModal}
|
||||
/>
|
||||
<div></div>
|
||||
<ItemTypeLister
|
||||
user={user}
|
||||
shopOwnerId={shopOwnerId}
|
||||
shopId={shopId}
|
||||
itemTypes={shopItems}
|
||||
setShopItems={setShopItems}
|
||||
isEditMode={isEditMode}
|
||||
onFilterChange={(e) => setFilterId(e)}
|
||||
filterId={filterId}
|
||||
beingEditedType={beingEditedType}
|
||||
setBeingEditedType={setBeingEditedType}
|
||||
/>
|
||||
{/* <div style={{ marginTop: "15px" }}></div> */}
|
||||
<div>
|
||||
{shopItems
|
||||
.filter(
|
||||
(itemType) =>
|
||||
filterId == 0 || itemType.itemTypeId === filterId
|
||||
)
|
||||
.map((itemType, index) => (
|
||||
<ItemLister
|
||||
index={index}
|
||||
indexTotal={shopItems.length}
|
||||
shopId={shopId}
|
||||
shopOwnerId={shopOwnerId}
|
||||
user={user}
|
||||
key={itemType.itemTypeId}
|
||||
itemTypeId={itemType.itemTypeId}
|
||||
typeName={itemType.name}
|
||||
typeImage={itemType.image}
|
||||
setShopItems={setShopItems}
|
||||
itemList={itemType.itemList}
|
||||
typeVisibility={itemType.visibility}
|
||||
moveItemTypeUp={(e) => moveItemTypeHandler(e, 'up', index)}
|
||||
moveItemTypeDown={(e) => moveItemTypeHandler(e, 'down', index)}
|
||||
isEditMode={isEditMode}
|
||||
beingEditedType={beingEditedType}
|
||||
setBeingEditedType={setBeingEditedType}
|
||||
raw={isEditMode || filterId == 0 ? false : true}
|
||||
handleCreateItem={(
|
||||
)}
|
||||
<div style={{ width: isTablet ? "60%" : "100%" }}>
|
||||
<div className="App-header">
|
||||
{isTablet && !isFullscreen && <FullscreenButton onClick={handleFullscreen} />}
|
||||
<Header
|
||||
HeaderText={"Menu"}
|
||||
showProfile={true}
|
||||
setModal={setModal}
|
||||
isLogout={handleLogout}
|
||||
shopId={shopId}
|
||||
shopName={shopName}
|
||||
shopImage={config.image}
|
||||
shopOwnerId={shopOwnerId}
|
||||
shopClerks={shopClerks}
|
||||
tableCode={table.tableCode}
|
||||
user={user}
|
||||
guestSides={guestSides}
|
||||
guestSideOfClerk={guestSideOfClerk}
|
||||
removeConnectedGuestSides={removeConnectedGuestSides}
|
||||
setIsEditMode={(e) => setIsEditMode(e)}
|
||||
isEditMode={isEditMode}
|
||||
/>
|
||||
<MusicPlayer
|
||||
socket={socket}
|
||||
shopId={shopId}
|
||||
user={user}
|
||||
shopOwnerId={shopOwnerId}
|
||||
isSpotifyNeedLogin={isSpotifyNeedLogin}
|
||||
queue={queue}
|
||||
setModal={setModal}
|
||||
/>
|
||||
{user.username !== undefined &&
|
||||
(user.cafeId === shopId || user.user_id === shopOwnerId) &&
|
||||
(user.roleId === 1 || user.roleId === 2) && (
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: "#5c7c5c",
|
||||
padding: "7px 28px",
|
||||
margin: "0 10px",
|
||||
borderRadius: "15px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
textShadow: "2px 2px 4px rgba(0, 0, 0, 0.7)",
|
||||
fontSize: "16px",
|
||||
}}
|
||||
>
|
||||
Mode Edit
|
||||
<Switch
|
||||
borderRadius={0}
|
||||
checked={isEditMode}
|
||||
onChange={() => setIsEditMode(!isEditMode)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<ItemTypeLister
|
||||
user={user}
|
||||
shopOwnerId={shopOwnerId}
|
||||
shopId={shopId}
|
||||
itemTypes={shopItems}
|
||||
setShopItems={setShopItems}
|
||||
isEditMode={isEditMode}
|
||||
onFilterChange={(e) => setFilterId(e)}
|
||||
filterId={filterId}
|
||||
beingEditedType={beingEditedType}
|
||||
setBeingEditedType={setBeingEditedType}
|
||||
/>
|
||||
{/* <div style={{ marginTop: "15px" }}></div> */}
|
||||
<div>
|
||||
{shopItems
|
||||
.filter(
|
||||
(itemType) =>
|
||||
filterId == 0 || itemType.itemTypeId === filterId
|
||||
)
|
||||
.map((itemType, index) => (
|
||||
<ItemLister
|
||||
index={index}
|
||||
indexTotal={shopItems.length}
|
||||
shopId={shopId}
|
||||
shopOwnerId={shopOwnerId}
|
||||
user={user}
|
||||
key={itemType.itemTypeId}
|
||||
itemTypeId={itemType.itemTypeId}
|
||||
typeName={itemType.name}
|
||||
typeImage={itemType.image}
|
||||
setShopItems={setShopItems}
|
||||
itemList={itemType.itemList}
|
||||
typeVisibility={itemType.visibility}
|
||||
moveItemTypeUp={(e) =>
|
||||
moveItemTypeHandler(e, "up", index)
|
||||
}
|
||||
moveItemTypeDown={(e) =>
|
||||
moveItemTypeHandler(e, "down", index)
|
||||
}
|
||||
isEditMode={isEditMode}
|
||||
beingEditedType={beingEditedType}
|
||||
setBeingEditedType={setBeingEditedType}
|
||||
raw={isEditMode || filterId == 0 ? false : true}
|
||||
handleCreateItem={(
|
||||
itemTypeID,
|
||||
name,
|
||||
price,
|
||||
selectedImage,
|
||||
description,
|
||||
promoPrice
|
||||
) =>
|
||||
createItem(
|
||||
shopId,
|
||||
name,
|
||||
price,
|
||||
selectedImage,
|
||||
itemTypeID,
|
||||
description,
|
||||
promoPrice
|
||||
)
|
||||
}
|
||||
handleUpdateItem={(
|
||||
itemId,
|
||||
name,
|
||||
price,
|
||||
selectedImage,
|
||||
description,
|
||||
promoPrice
|
||||
) =>
|
||||
updateItem(
|
||||
itemId,
|
||||
name,
|
||||
price,
|
||||
selectedImage,
|
||||
description,
|
||||
promoPrice,
|
||||
) =>
|
||||
createItem(
|
||||
shopId,
|
||||
name,
|
||||
price,
|
||||
selectedImage,
|
||||
itemTypeID,
|
||||
description,
|
||||
promoPrice,
|
||||
)
|
||||
}
|
||||
handleUpdateItem={(itemId, name, price, selectedImage, description, promoPrice) =>
|
||||
updateItem(itemId, name, price, selectedImage, description, promoPrice)
|
||||
}
|
||||
/>
|
||||
))}
|
||||
{!isEditMode && (user.username || cartItemsLength > 0) &&
|
||||
<div style={{ marginTop: '10px', height: '40px', position: 'sticky', bottom: '40px', display: 'flex', justifyContent: 'center', alignItems: 'center', textAlign: 'center' }}>
|
||||
{(lastTransaction != null || cartItemsLength > 0) &&
|
||||
<div onClick={goToCart} style={{ backgroundColor: '#73a585', width: user.username ? '55vw' : '70vw', height: '40px', borderRadius: '30px', display: 'flex', justifyContent: 'space-between', padding: '0 20px' }}>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', alignContent: 'center' }}>{lastTransaction != null && '+'}{cartItemsLength} item</div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'flex-end', width: '130px' }}>
|
||||
{((lastTransaction == null || lastTransaction?.payment_type != 'paylater')) ?
|
||||
<span style={{ whiteSpace: 'nowrap' }}>Rp{totalPrice}</span>
|
||||
:
|
||||
<span style={{ whiteSpace: 'nowrap' }}>Open bill</span>
|
||||
}
|
||||
<div style={{ display: 'flex', alignItems: 'center', marginLeft: '5px', width: '20px' }}>
|
||||
<svg viewBox="0 0 34 34" style={{ fill: 'white', marginTop: '4px' }}>
|
||||
<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>
|
||||
</svg>
|
||||
</div>
|
||||
promoPrice
|
||||
)
|
||||
}
|
||||
/>
|
||||
))}
|
||||
{!isEditMode && (user.username || cartItemsLength > 0) && (
|
||||
<div
|
||||
style={{
|
||||
marginTop: "10px",
|
||||
height: "40px",
|
||||
position: "sticky",
|
||||
bottom: "40px",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
textAlign: "center",
|
||||
}}
|
||||
>
|
||||
{(!isTablet &&(lastTransaction != null || cartItemsLength > 0)) && (
|
||||
<div
|
||||
onClick={goToCart}
|
||||
style={{
|
||||
backgroundColor: "#73a585",
|
||||
width: user.username ? "55vw" : "70vw",
|
||||
height: "40px",
|
||||
borderRadius: "30px",
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
padding: "0 20px",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexWrap: "wrap",
|
||||
alignContent: "center",
|
||||
}}
|
||||
>
|
||||
{lastTransaction != null && "+"}
|
||||
{cartItemsLength} item
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-end",
|
||||
width: "130px",
|
||||
}}
|
||||
>
|
||||
{lastTransaction == null ||
|
||||
lastTransaction?.payment_type != "paylater" ? (
|
||||
<span style={{ whiteSpace: "nowrap" }}>
|
||||
Rp{totalPrice}
|
||||
</span>
|
||||
) : (
|
||||
<span style={{ whiteSpace: "nowrap" }}>
|
||||
Open bill
|
||||
</span>
|
||||
)}
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
marginLeft: "5px",
|
||||
width: "20px",
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
viewBox="0 0 34 34"
|
||||
style={{ fill: "white", marginTop: "4px" }}
|
||||
>
|
||||
<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>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
{user.username &&
|
||||
<div onClick={goToTransactions} style={{ backgroundColor: '#73a585', width: '15vw', height: '40px', borderRadius: '30px', display: 'flex', justifyContent: 'center', marginLeft: lastTransaction != null || cartItemsLength > 0 ? '6px' : '0px' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', width: '38px', marginRight: '5px' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', marginLeft: '5px', width: '20px' }}>
|
||||
<svg viewBox="0 0 512 512">
|
||||
<g
|
||||
transform="translate(0 460) scale(0.09 -0.09)"
|
||||
style={{ fill: 'white', marginTop: '4px' }}
|
||||
stroke="none"
|
||||
>
|
||||
<path
|
||||
d="M1639 5107 c-47 -13 -70 -28 -177 -109 -119 -90 -246 -102 -381 -34
|
||||
</div>
|
||||
)}
|
||||
{user.username && (
|
||||
<div
|
||||
onClick={goToTransactions}
|
||||
style={{
|
||||
backgroundColor: "#73a585",
|
||||
width: "15vw",
|
||||
height: "40px",
|
||||
borderRadius: "30px",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
marginLeft:
|
||||
lastTransaction != null || cartItemsLength > 0
|
||||
? "6px"
|
||||
: "0px",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
width: "38px",
|
||||
marginRight: "5px",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
marginLeft: "5px",
|
||||
width: "20px",
|
||||
}}
|
||||
>
|
||||
<svg viewBox="0 0 512 512">
|
||||
<g
|
||||
transform="translate(0 460) scale(0.09 -0.09)"
|
||||
style={{ fill: "white", marginTop: "4px" }}
|
||||
stroke="none"
|
||||
>
|
||||
<path
|
||||
d="M1639 5107 c-47 -13 -70 -28 -177 -109 -119 -90 -246 -102 -381 -34
|
||||
-53 27 -83 36 -121 36 -88 0 -167 -57 -190 -138 -8 -26 -10 -620 -8 -1982 l3
|
||||
-1945 24 -38 c13 -21 42 -50 64 -65 l41 -27 1535 -5 1536 -5 58 -22 c158 -60
|
||||
291 -205 322 -352 10 -45 74 -108 119 -117 78 -14 154 25 182 93 12 27 14 398
|
||||
@@ -353,24 +633,50 @@ function CafePage({
|
||||
20 6 475 9 1183 8 l1150 -2 38 -24z m4 -903 c62 -41 88 -90 88 -168 0 -78 -26
|
||||
-127 -88 -168 l-41 -27 -665 0 -666 0 -38 24 c-76 47 -111 140 -88 229 14 51
|
||||
76 117 123 131 20 6 291 9 684 8 l650 -2 41 -27z"
|
||||
/>
|
||||
<path
|
||||
d="M592 489 c-47 -14 -109 -79 -123 -131 -33 -122 37 -265 159 -325 l57
|
||||
/>
|
||||
<path
|
||||
d="M592 489 c-47 -14 -109 -79 -123 -131 -33 -122 37 -265 159 -325 l57
|
||||
-28 1815 -2 c1736 -2 1813 -2 1765 15 -125 43 -186 126 -205 279 -12 89
|
||||
-39 138 -97 174 l-38 24 -1650 2 c-1023 1 -1662 -2 -1683 -8z"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<Watermark/>
|
||||
</div>
|
||||
{/* <Watermark /> */}
|
||||
</div>
|
||||
{isTablet &&
|
||||
<div
|
||||
style={{
|
||||
width: "40%",
|
||||
position: "fixed",
|
||||
right: 0,
|
||||
top: 0,
|
||||
zIndex: 199
|
||||
}}
|
||||
>
|
||||
<Cart
|
||||
shopId={shopId}
|
||||
shop={shop}
|
||||
table={table}
|
||||
setModal={setModal}
|
||||
sendParam={sendParam}
|
||||
socket={socket}
|
||||
totalItemsCount={totalItemsCount}
|
||||
deviceType={deviceType}
|
||||
shopItems={shopItems}
|
||||
setShopItems={setShopItems}
|
||||
isTablet={true}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
{/* )} */}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
handlePaymentFromGuestSide,
|
||||
handlePaymentFromGuestDevice,
|
||||
handleExtendFromGuestDevice,
|
||||
handleCloseBillFromGuestDevice
|
||||
handleCloseBillFromGuestDevice,
|
||||
} from "../helpers/transactionHelpers";
|
||||
|
||||
import { getItemsByCafeId } from "../helpers/cartHelpers.js";
|
||||
@@ -22,15 +22,27 @@ import { getItemsByCafeId } from "../helpers/cartHelpers.js";
|
||||
import Dropdown from "./Dropdown.js";
|
||||
import { useNavigationHelpers } from "../helpers/navigationHelpers";
|
||||
|
||||
const formatToRupiah = (value) => {
|
||||
if (typeof value !== "number") return value;
|
||||
return value.toLocaleString("id-ID");
|
||||
};
|
||||
|
||||
export default function Invoice({ shopId, setModal, table, sendParam, deviceType, socket, shopItems, setShopItems }) {
|
||||
export default function Invoice({
|
||||
shopId,
|
||||
setModal,
|
||||
table,
|
||||
sendParam,
|
||||
deviceType,
|
||||
socket,
|
||||
shopItems,
|
||||
setShopItems,
|
||||
isTablet
|
||||
}) {
|
||||
const { shopIdentifier, tableCode } = useParams();
|
||||
|
||||
sendParam({ shopIdentifier, tableCode });
|
||||
|
||||
const {
|
||||
goToShop
|
||||
} = useNavigationHelpers(shopIdentifier, table.tableCode);
|
||||
const { goToShop } = useNavigationHelpers(shopIdentifier, table.tableCode);
|
||||
|
||||
const [cartItems, setCartItems] = useState([]);
|
||||
const [totalPrice, setTotalPrice] = useState(0);
|
||||
@@ -51,14 +63,14 @@ export default function Invoice({ shopId, setModal, table, sendParam, deviceType
|
||||
const fetchPaymentMethods = async () => {
|
||||
try {
|
||||
const methods = await getPaymentMethods(shopId);
|
||||
console.log(methods)
|
||||
const lastTransaction = JSON.parse(localStorage.getItem('lastTransaction'));
|
||||
if (lastTransaction?.payment_type == 'paylater') methods.isOpenBillAvailable = false;
|
||||
setPaymentMethods(methods)
|
||||
|
||||
} catch (err) {
|
||||
|
||||
}
|
||||
console.log(methods);
|
||||
const lastTransaction = JSON.parse(
|
||||
localStorage.getItem("lastTransaction")
|
||||
);
|
||||
if (lastTransaction?.payment_type == "paylater")
|
||||
methods.isOpenBillAvailable = false;
|
||||
setPaymentMethods(methods);
|
||||
} catch (err) { }
|
||||
};
|
||||
|
||||
if (shopId) {
|
||||
@@ -70,29 +82,27 @@ export default function Invoice({ shopId, setModal, table, sendParam, deviceType
|
||||
const fetchCartItems = async () => {
|
||||
try {
|
||||
const cart = getItemsByCafeId(shopId);
|
||||
const itemMap = new Map(cart.map(item => [item.itemId, item.qty]));
|
||||
const itemMap = new Map(cart.map((item) => [item.itemId, item.qty]));
|
||||
|
||||
// Step 2: Filter and transform shopItems
|
||||
const filteredItems = shopItems
|
||||
.map(itemType => ({
|
||||
.map((itemType) => ({
|
||||
itemTypeId: itemType.itemTypeId,
|
||||
cafeId: itemType.cafeId,
|
||||
typeName: itemType.name,
|
||||
itemList: itemType.itemList
|
||||
.filter(item => itemMap.has(item.itemId)) // Keep only items in getItemsByCafeId
|
||||
.map(item => ({
|
||||
.filter((item) => itemMap.has(item.itemId)) // Keep only items in getItemsByCafeId
|
||||
.map((item) => ({
|
||||
itemId: item.itemId,
|
||||
price: (item.promoPrice ? item.promoPrice : item.price),
|
||||
price: item.promoPrice ? item.promoPrice : item.price,
|
||||
name: item.name,
|
||||
image: item.image,
|
||||
qty: itemMap.get(item.itemId), // Add qty from getItemsByCafeId
|
||||
availability: item.availability
|
||||
}))
|
||||
availability: item.availability,
|
||||
})),
|
||||
}))
|
||||
.filter(itemType => itemType.itemList.length > 0); // Remove empty itemTypes
|
||||
console.log(filteredItems)
|
||||
|
||||
|
||||
.filter((itemType) => itemType.itemList.length > 0); // Remove empty itemTypes
|
||||
console.log(filteredItems);
|
||||
|
||||
// Update local storage by removing unavailable items
|
||||
const updatedLocalStorage =
|
||||
@@ -120,7 +130,10 @@ export default function Invoice({ shopId, setModal, table, sendParam, deviceType
|
||||
return (
|
||||
total +
|
||||
itemType.itemList.reduce((subtotal, item) => {
|
||||
return subtotal + item.qty * (item.promoPrice ? item.promoPrice : item.price);
|
||||
return (
|
||||
subtotal +
|
||||
item.qty * (item.promoPrice ? item.promoPrice : item.price)
|
||||
);
|
||||
}, 0)
|
||||
);
|
||||
}, 0);
|
||||
@@ -128,8 +141,8 @@ export default function Invoice({ shopId, setModal, table, sendParam, deviceType
|
||||
setTimeout(function () {
|
||||
setCartItems(filteredItems);
|
||||
setTotalPrice(totalPrice);
|
||||
setIsLoading(false)
|
||||
}, 100); //delay is in milliseconds
|
||||
setIsLoading(false);
|
||||
}, 100); //delay is in milliseconds
|
||||
} catch (error) {
|
||||
console.error("Error fetching cart items:", error);
|
||||
// Handle error if needed
|
||||
@@ -138,18 +151,17 @@ export default function Invoice({ shopId, setModal, table, sendParam, deviceType
|
||||
|
||||
fetchCartItems();
|
||||
|
||||
|
||||
const getNewestCartItems = async () => {
|
||||
try {
|
||||
// Fetch items from the cart details (latest state)
|
||||
const items = await getCartDetails(shopId);
|
||||
|
||||
// Loop through each item type in the items from the cart details
|
||||
items.forEach(itemType => {
|
||||
itemType.itemList.forEach(item => {
|
||||
items.forEach((itemType) => {
|
||||
itemType.itemList.forEach((item) => {
|
||||
// Loop through the shopItems and find the corresponding itemId
|
||||
shopItems.forEach(shopItemType => {
|
||||
shopItemType.itemList.forEach(shopItem => {
|
||||
shopItems.forEach((shopItemType) => {
|
||||
shopItemType.itemList.forEach((shopItem) => {
|
||||
if (shopItem.itemId === item.itemId) {
|
||||
// Update shopItems with the new data from items (e.g., availability, price)
|
||||
shopItem.availability = item.availability;
|
||||
@@ -179,7 +191,8 @@ export default function Invoice({ shopId, setModal, table, sendParam, deviceType
|
||||
}, 100); // delay is in milliseconds
|
||||
|
||||
// Update local storage by removing unavailable items and updating prices
|
||||
const updatedLocalStorage = JSON.parse(localStorage.getItem("cart")) || [];
|
||||
const updatedLocalStorage =
|
||||
JSON.parse(localStorage.getItem("cart")) || [];
|
||||
const newLocalStorage = updatedLocalStorage.map((cafe) => {
|
||||
if (cafe.cafeId === shopId) {
|
||||
return {
|
||||
@@ -194,8 +207,10 @@ export default function Invoice({ shopId, setModal, table, sendParam, deviceType
|
||||
// Update the price in the local storage item
|
||||
return {
|
||||
...item,
|
||||
price: updatedItem.promoPrice ? updatedItem.promoPrice : updatedItem.price,
|
||||
availability: updatedItem.availability
|
||||
price: updatedItem.promoPrice
|
||||
? updatedItem.promoPrice
|
||||
: updatedItem.price,
|
||||
availability: updatedItem.availability,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -231,7 +246,10 @@ export default function Invoice({ shopId, setModal, table, sendParam, deviceType
|
||||
return (
|
||||
total +
|
||||
itemType.itemList.reduce((subtotal, item) => {
|
||||
return subtotal + item.qty * (item.promoPrice ? item.promoPrice : item.price);
|
||||
return (
|
||||
subtotal +
|
||||
item.qty * (item.promoPrice ? item.promoPrice : item.price)
|
||||
);
|
||||
}, 0)
|
||||
);
|
||||
}, 0);
|
||||
@@ -240,13 +258,12 @@ export default function Invoice({ shopId, setModal, table, sendParam, deviceType
|
||||
console.error("Error fetching cart items:", error);
|
||||
// Handle error if needed
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
getNewestCartItems();
|
||||
}, [shopId]);
|
||||
}, [shopId, localStorage.getItem('cart')]);
|
||||
|
||||
const handlePayCloseBill = async (orderMethod) =>{
|
||||
const handlePayCloseBill = async (orderMethod) => {
|
||||
setIsPaymentLoading(true);
|
||||
console.log("tipe" + deviceType);
|
||||
if (transactionData) {
|
||||
@@ -257,7 +274,76 @@ export default function Invoice({ shopId, setModal, table, sendParam, deviceType
|
||||
socketId
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
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) => {
|
||||
setIsPaymentLoading(true);
|
||||
@@ -271,39 +357,49 @@ export default function Invoice({ shopId, setModal, table, sendParam, deviceType
|
||||
orderMethod,
|
||||
socketId
|
||||
);
|
||||
localStorage.removeItem('lastTransaction')
|
||||
localStorage.removeItem("lastTransaction");
|
||||
// Dispatch the custom event
|
||||
window.dispatchEvent(new Event("localStorageUpdated"));
|
||||
}
|
||||
else
|
||||
|
||||
if (deviceType == "clerk") {
|
||||
const pay = await handlePaymentFromClerk(
|
||||
shopId,
|
||||
email,
|
||||
orderMethod,
|
||||
orderType,
|
||||
tableNumber
|
||||
);
|
||||
} else if (deviceType == "guestSide") {
|
||||
const pay = await handlePaymentFromGuestSide(
|
||||
shopId,
|
||||
email,
|
||||
orderMethod,
|
||||
orderType,
|
||||
tableNumber
|
||||
);
|
||||
} else if (deviceType == "guestDevice") {
|
||||
const socketId = socket.id;
|
||||
const pay = await handlePaymentFromGuestDevice(
|
||||
shopId,
|
||||
orderMethod,
|
||||
orderType,
|
||||
table.tableNo || tableNumber,
|
||||
textareaRef.current.value,
|
||||
socketId
|
||||
);
|
||||
} else if (deviceType == "clerk") {
|
||||
const pay = await handlePaymentFromClerk(
|
||||
shopId,
|
||||
email,
|
||||
orderMethod,
|
||||
orderType,
|
||||
tableNumber,
|
||||
textareaRef.current.value
|
||||
);
|
||||
if (pay) {
|
||||
handlePrint(pay.transaction);
|
||||
localStorage.removeItem("cart");
|
||||
localStorage.removeItem("lastTransaction");
|
||||
setCartItems([]);
|
||||
setTotalPrice(0);
|
||||
window.dispatchEvent(new Event("localStorageUpdated"));
|
||||
}
|
||||
}
|
||||
else if (deviceType == "guestSide") {
|
||||
const pay = await handlePaymentFromGuestSide(
|
||||
shopId,
|
||||
email,
|
||||
orderMethod,
|
||||
orderType,
|
||||
tableNumber
|
||||
);
|
||||
if (pay) window.location.reload();
|
||||
|
||||
} else if (deviceType == "guestDevice") {
|
||||
const socketId = socket.id;
|
||||
const pay = await handlePaymentFromGuestDevice(
|
||||
shopId,
|
||||
orderMethod,
|
||||
orderType,
|
||||
table.tableNo || tableNumber,
|
||||
textareaRef.current.value,
|
||||
socketId
|
||||
);
|
||||
if (pay) window.location.reload();
|
||||
}
|
||||
|
||||
console.log("transaction from " + deviceType + "success");
|
||||
setIsPaymentLoading(false);
|
||||
@@ -331,26 +427,34 @@ export default function Invoice({ shopId, setModal, table, sendParam, deviceType
|
||||
}, [table]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log(localStorage.getItem('cart'))
|
||||
console.log(cartItems)
|
||||
console.log(localStorage.getItem("cart"));
|
||||
console.log(cartItems);
|
||||
|
||||
if (localStorage.getItem('cart') == null || localStorage.getItem('cart') == '' || localStorage.getItem('cart') == '[]') return;
|
||||
if (
|
||||
localStorage.getItem("cart") == null ||
|
||||
localStorage.getItem("cart") == "" ||
|
||||
localStorage.getItem("cart") == "[]"
|
||||
)
|
||||
return;
|
||||
|
||||
// Parse the local storage cart
|
||||
const localStorageCart = JSON.parse(localStorage.getItem('cart'));
|
||||
console.log(localStorageCart)
|
||||
const localStorageCart = JSON.parse(localStorage.getItem("cart"));
|
||||
console.log(localStorageCart);
|
||||
// Create a set of itemIds from the local storage cart for quick lookup
|
||||
const localStorageItemIds = new Set(localStorageCart[0].items.map(item => item.itemId));
|
||||
const localStorageItemIds = new Set(
|
||||
localStorageCart[0].items.map((item) => item.itemId)
|
||||
);
|
||||
|
||||
// Filter out items from cartItems that do not exist in the local storage cart
|
||||
const updatedCartItems = cartItems.map(itemType => ({
|
||||
const updatedCartItems = cartItems.map((itemType) => ({
|
||||
...itemType,
|
||||
itemList: itemType.itemList.filter(item => localStorageItemIds.has(item.itemId))
|
||||
itemList: itemType.itemList.filter((item) =>
|
||||
localStorageItemIds.has(item.itemId)
|
||||
),
|
||||
}));
|
||||
|
||||
setCartItems(updatedCartItems);
|
||||
|
||||
|
||||
const totalPrice = updatedCartItems.reduce((total, itemType) => {
|
||||
return (
|
||||
total +
|
||||
@@ -360,8 +464,7 @@ export default function Invoice({ shopId, setModal, table, sendParam, deviceType
|
||||
);
|
||||
}, 0);
|
||||
setTotalPrice(totalPrice);
|
||||
}, [localStorage.getItem('cart')]);
|
||||
|
||||
}, [localStorage.getItem("cart")]);
|
||||
|
||||
const handleOrderTypeChange = (event) => {
|
||||
setOrderType(event.target.value);
|
||||
@@ -379,44 +482,81 @@ export default function Invoice({ shopId, setModal, table, sendParam, deviceType
|
||||
setEmail(event.target.value);
|
||||
};
|
||||
|
||||
const transactionData = JSON.parse(localStorage.getItem('lastTransaction'));
|
||||
const transactionData = JSON.parse(localStorage.getItem("lastTransaction"));
|
||||
|
||||
return (
|
||||
<div className={styles.Invoice} style={{ height: (getItemsByCafeId(shopId).length > 0 ? '' : '100vh'), minHeight: (getItemsByCafeId(shopId).length > 0 ? '100vh' : '') }}>
|
||||
<div
|
||||
className={styles.Invoice}
|
||||
style={{
|
||||
height: getItemsByCafeId(shopId).length > 0 ? "" : "100vh",
|
||||
minHeight: getItemsByCafeId(shopId).length > 0 ? "100vh" : "",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
onClick={goToShop}
|
||||
style={{
|
||||
marginLeft: "22px",
|
||||
marginTop: "49px",
|
||||
marginRight: "10px",
|
||||
display: "flex",
|
||||
flexWrap: "nowrap",
|
||||
alignItems: "center",
|
||||
fontSize: "25px",
|
||||
}}
|
||||
>
|
||||
{!isTablet &&
|
||||
<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>
|
||||
}
|
||||
Keranjang
|
||||
</div>
|
||||
|
||||
<div onClick={goToShop} style={{ marginLeft: '22px', marginTop: '49px', marginRight: '10px', display: 'flex', flexWrap: 'nowrap', alignItems: 'center', fontSize: '25px' }} ><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>Keranjang</div>
|
||||
|
||||
{(transactionData == null && getItemsByCafeId(shopId).length < 1) ?
|
||||
<div style={{ height: '75vh', display: 'flex', justifyContent: 'center', flexDirection: 'column', alignContent: 'center', alignItems: 'center' }}>
|
||||
<div style={{ width: '50%' }}>
|
||||
<svg
|
||||
viewBox="0 0 32 32"
|
||||
style={{ fill: "#8F8787" }}
|
||||
>
|
||||
{transactionData == null && getItemsByCafeId(shopId).length < 1 ? (
|
||||
<div
|
||||
style={{
|
||||
height: "75vh",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
flexDirection: "column",
|
||||
alignContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<div style={{ width: isTablet ? "30%" : "50%" }}>
|
||||
<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" />
|
||||
</svg>
|
||||
</div>
|
||||
<h1 style={{ fontSize: '120%', color: '#8F8787' }}>Tidak ada item di keranjang</h1>
|
||||
<h1 style={{ fontSize: "120%", color: "#8F8787" }}>
|
||||
Tidak ada item di keranjang
|
||||
</h1>
|
||||
</div>
|
||||
:
|
||||
(isLoading ? <></> :
|
||||
<>
|
||||
{getItemsByCafeId(shopId).length > 0 &&
|
||||
<div className={styles.RoundedRectangle}>
|
||||
{cartItems.map((itemType) => (
|
||||
<ItemLister
|
||||
shopId={shopId}
|
||||
forInvoice={true}
|
||||
key={itemType.id}
|
||||
typeName={itemType.typeName}
|
||||
itemList={itemType.itemList}
|
||||
/>
|
||||
))}
|
||||
) : isLoading ? (
|
||||
<></>
|
||||
) : (
|
||||
<>
|
||||
{getItemsByCafeId(shopId).length > 0 && (
|
||||
<div className={styles.RoundedRectangle}>
|
||||
{cartItems.map((itemType) => (
|
||||
<ItemLister
|
||||
shopId={shopId}
|
||||
forInvoice={true}
|
||||
key={itemType.id}
|
||||
typeName={itemType.typeName}
|
||||
itemList={itemType.itemList}
|
||||
/>
|
||||
))}
|
||||
|
||||
{table.tableNo != null && (
|
||||
<div className={styles.OrderTypeContainer}>
|
||||
<span htmlFor="orderType">Diantar ke {table.tableNo}</span>
|
||||
{/* <select
|
||||
{table.tableNo != null && (
|
||||
<div className={styles.OrderTypeContainer}>
|
||||
<span htmlFor="orderType">Diantar ke {table.tableNo}</span>
|
||||
{/* <select
|
||||
id="orderType"
|
||||
value={orderType}
|
||||
onChange={handleOrderTypeChange}
|
||||
@@ -427,154 +567,232 @@ export default function Invoice({ shopId, setModal, table, sendParam, deviceType
|
||||
<option value="pickup">Pickup</option>
|
||||
{table == null && <option value="serve">Serve</option>}
|
||||
</select> */}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{orderType === "serve" && table.length < 1 && (
|
||||
<div className={styles.OrderTypeContainer}>
|
||||
<span htmlFor="orderType">Serve to:</span>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Table Number"
|
||||
value={tableNumber}
|
||||
onChange={handleTableNumberChange}
|
||||
className={styles.TableNumberInput}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className={styles.NoteContainer}>
|
||||
<span>Catatan :</span>
|
||||
<span></span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className={styles.NoteContainer}>
|
||||
<textarea
|
||||
ref={textareaRef}
|
||||
className={styles.NoteInput}
|
||||
placeholder="Tambahkan catatan..."
|
||||
{orderType === "serve" && table.length < 1 && (
|
||||
<div className={styles.OrderTypeContainer}>
|
||||
<span htmlFor="orderType">Serve to:</span>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Table Number"
|
||||
value={tableNumber}
|
||||
onChange={handleTableNumberChange}
|
||||
className={styles.TableNumberInput}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className={styles.NoteContainer}>
|
||||
<span>Atas Nama :</span>
|
||||
<span></span>
|
||||
</div>
|
||||
}
|
||||
|
||||
{transactionData &&
|
||||
<div className={styles.RoundedRectangle} style={{ backgroundColor: '#c3c3c3', fontSize: '15px', display: 'flex', justifyContent: 'space-between' }}>
|
||||
{transactionData.payment_type != 'paylater' ?
|
||||
<>
|
||||
<div onClick={() => setModal('transaction_item', { transactionId: transactionData.transactionId })} className={styles.AddedLastTransaction}>
|
||||
Pembayaran akan ditambahkan ke transaksi sebelumnya
|
||||
</div>
|
||||
<div className={styles.CancelAddedLastTransaction} onClick={() => { window.location.reload(); localStorage.removeItem('lastTransaction') }}>
|
||||
<svg
|
||||
style={{ width: '40px', height: '40px' }}
|
||||
className={styles['plusNegative2']}
|
||||
clipRule="evenodd"
|
||||
fillRule="evenodd"
|
||||
strokeLinejoin="round"
|
||||
strokeMiterlimit="2"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="m12.002 2c5.518 0 9.998 4.48 9.998 9.998 0 5.517-4.48 9.997-9.998 9.997-5.517 0-9.997-4.48-9.997-9.997 0-5.518 4.48-9.998 9.997-9.998zm0 1.5c-4.69 0-8.497 3.808-8.497 8.498s3.807 8.497 8.497 8.497 8.498-3.807 8.498-8.497-3.808-8.498-8.498-8.498zm-.747 7.75h-3.5c-.414 0-.75.336-.75.75s.336.75.75.75h3.5v3.5c0 .414.336.75.75.75s.75-.336.75-.75v-3.5h3.5c.414 0 .75-.336.75-.75s-.336-.75-.75-.75h-3.5v-3.5c0-.414-.336-.75-.75-.75s-.75.336-.75.75z"
|
||||
fillRule="nonzero"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</>
|
||||
:
|
||||
|
||||
<div className={styles.AddedLastTransaction}>
|
||||
<div>
|
||||
Open bill
|
||||
<div onClick={() => setModal('transaction_item', { transactionId: transactionData.transactionId })}>
|
||||
Lihat tagihan
|
||||
</div>
|
||||
</div>
|
||||
{getItemsByCafeId(shopId).length > 0 ?
|
||||
|
||||
<button className={styles.PayButton3} onClick={() => handlePay(orderMethod)}>
|
||||
{isPaymentLoading ? (
|
||||
<ColorRing height="50" width="50" color="white" />
|
||||
) : (
|
||||
<div>
|
||||
{transactionData ?
|
||||
|
||||
<span>Tambahkan</span>
|
||||
:
|
||||
<span>Pesan</span>
|
||||
}
|
||||
|
||||
<span>Rp{totalPrice}</span>
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
:
|
||||
|
||||
<button className={styles.PayButton3} style={{ backgroundColor: 'rgb(42 145 24)', letterSpacing: '1px' }} onClick={goToShop}>
|
||||
<div>
|
||||
<span>Tambahkan item lain</span>
|
||||
</div>
|
||||
</button>}
|
||||
</div>
|
||||
}
|
||||
<div className={styles.NoteContainer}>
|
||||
<input
|
||||
className={styles.NoteInput}
|
||||
placeholder="Tambahkan catatan..."
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
<div className={styles.PaymentOption}>
|
||||
<div className={styles.TotalContainer}>
|
||||
<span>Pembayaran</span>
|
||||
<span>
|
||||
{paymentMethods != null && <Dropdown setDropdownKey={() => setDropdownKey(dropdownKey + 1)} paymentMethods={paymentMethods} onChange={handleOrderMethodChange} />}
|
||||
</span>
|
||||
|
||||
<div className={styles.NoteContainer} style={{ height: "18px" }}>
|
||||
<span>Catatan :</span>
|
||||
<span></span>
|
||||
</div>
|
||||
{transactionData && transactionData.payment_type === 'paylater' ?
|
||||
<div style={{ display: 'flex', paddingLeft: '25px', paddingRight: '25px', marginTop: '17px' }}>
|
||||
<button className={styles.PayButton} onClick={() => handlePayCloseBill(orderMethod)}>
|
||||
{isPaymentLoading ? (
|
||||
<ColorRing height="50" width="50" color="white" />
|
||||
) : (
|
||||
<div>
|
||||
<span>Tutup bill</span>
|
||||
|
||||
<span>Rp{
|
||||
transactionData.DetailedTransactions.reduce((total, transaction) => {
|
||||
return total + (transaction.promoPrice == 0 || transaction.promoPrice == null
|
||||
? transaction.price * transaction.qty
|
||||
: transaction.promoPrice * transaction.qty);
|
||||
}, 0)
|
||||
}</span>
|
||||
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
:
|
||||
|
||||
<div style={{ display: 'flex', paddingLeft: '25px', paddingRight: '25px', marginTop: '17px' }}>
|
||||
<button className={styles.PayButton} onClick={() => handlePay(orderMethod)}>
|
||||
{isPaymentLoading ? (
|
||||
<ColorRing height="50" width="50" color="white" />
|
||||
) : (
|
||||
<div>
|
||||
{transactionData ?
|
||||
|
||||
<span>Tambahkan</span>
|
||||
:
|
||||
<span>Pesan</span>
|
||||
}
|
||||
|
||||
<span>Rp{totalPrice}</span>
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
<div className={styles.NoteContainer}>
|
||||
<textarea
|
||||
ref={textareaRef}
|
||||
className={styles.NoteInput}
|
||||
placeholder="Tambahkan catatan..."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.PaymentOptionMargin}></div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
)}
|
||||
|
||||
{transactionData && (
|
||||
<div
|
||||
className={styles.RoundedRectangle}
|
||||
style={{
|
||||
backgroundColor: "#c3c3c3",
|
||||
fontSize: "15px",
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
{transactionData.payment_type != "paylater" ? (
|
||||
<>
|
||||
<div
|
||||
onClick={() =>
|
||||
setModal("transaction_item", {
|
||||
transactionId: transactionData.transactionId,
|
||||
})
|
||||
}
|
||||
className={styles.AddedLastTransaction}
|
||||
>
|
||||
Pembayaran akan ditambahkan ke transaksi sebelumnya
|
||||
</div>
|
||||
<div
|
||||
className={styles.CancelAddedLastTransaction}
|
||||
onClick={() => {
|
||||
window.location.reload();
|
||||
localStorage.removeItem("lastTransaction");
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
style={{ width: "40px", height: "40px" }}
|
||||
className={styles["plusNegative2"]}
|
||||
clipRule="evenodd"
|
||||
fillRule="evenodd"
|
||||
strokeLinejoin="round"
|
||||
strokeMiterlimit="2"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="m12.002 2c5.518 0 9.998 4.48 9.998 9.998 0 5.517-4.48 9.997-9.998 9.997-5.517 0-9.997-4.48-9.997-9.997 0-5.518 4.48-9.998 9.997-9.998zm0 1.5c-4.69 0-8.497 3.808-8.497 8.498s3.807 8.497 8.497 8.497 8.498-3.807 8.498-8.497-3.808-8.498-8.498-8.498zm-.747 7.75h-3.5c-.414 0-.75.336-.75.75s.336.75.75.75h3.5v3.5c0 .414.336.75.75.75s.75-.336.75-.75v-3.5h3.5c.414 0 .75-.336.75-.75s-.336-.75-.75-.75h-3.5v-3.5c0-.414-.336-.75-.75-.75s-.75.336-.75.75z"
|
||||
fillRule="nonzero"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className={styles.AddedLastTransaction}>
|
||||
<div>
|
||||
Open bill
|
||||
<div
|
||||
onClick={() =>
|
||||
setModal("transaction_item", {
|
||||
transactionId: transactionData.transactionId,
|
||||
})
|
||||
}
|
||||
>
|
||||
Lihat tagihan
|
||||
</div>
|
||||
</div>
|
||||
{getItemsByCafeId(shopId).length > 0 ? (
|
||||
<button
|
||||
className={styles.PayButton3}
|
||||
onClick={() => handlePay(orderMethod)}
|
||||
>
|
||||
{isPaymentLoading ? (
|
||||
<ColorRing height="40" width="40" color="white" />
|
||||
) : (
|
||||
<div>
|
||||
{transactionData ? (
|
||||
<span>Tambahkan</span>
|
||||
) : (
|
||||
<span>Pesan</span>
|
||||
)}
|
||||
|
||||
<span>Rp{formatToRupiah(totalPrice)}</span>
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
className={styles.PayButton3}
|
||||
style={{
|
||||
backgroundColor: "rgb(42 145 24)",
|
||||
letterSpacing: "1px",
|
||||
}}
|
||||
onClick={goToShop}
|
||||
>
|
||||
<div>
|
||||
<span>Tambahkan item lain</span>
|
||||
</div>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div className={styles.PaymentOption}>
|
||||
<div className={styles.TotalContainer}>
|
||||
<span>Pembayaran</span>
|
||||
<span>
|
||||
{paymentMethods != null && (
|
||||
<Dropdown
|
||||
setDropdownKey={() => setDropdownKey(dropdownKey + 1)}
|
||||
paymentMethods={paymentMethods}
|
||||
onChange={handleOrderMethodChange}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
{transactionData && transactionData.payment_type === "paylater" ? (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
paddingLeft: "25px",
|
||||
paddingRight: "25px",
|
||||
marginTop: "17px",
|
||||
}}
|
||||
>
|
||||
<button
|
||||
className={styles.PayButton}
|
||||
onClick={() => handlePayCloseBill(orderMethod)}
|
||||
>
|
||||
{isPaymentLoading ? (
|
||||
<ColorRing height="50" width="50" color="white" />
|
||||
) : (
|
||||
<div>
|
||||
<span>Tutup bill</span>
|
||||
|
||||
<span>
|
||||
Rp
|
||||
{formatToRupiah(
|
||||
transactionData.DetailedTransactions.reduce(
|
||||
(total, transaction) => {
|
||||
return (
|
||||
total +
|
||||
(transaction.promoPrice == 0 ||
|
||||
transaction.promoPrice == null
|
||||
? transaction.price * transaction.qty
|
||||
: transaction.promoPrice * transaction.qty)
|
||||
);
|
||||
},
|
||||
0
|
||||
)
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
paddingLeft: "25px",
|
||||
paddingRight: "25px",
|
||||
marginTop: "17px",
|
||||
}}
|
||||
>
|
||||
<button
|
||||
className={styles.PayButton}
|
||||
onClick={() => handlePay(orderMethod)}
|
||||
>
|
||||
{isPaymentLoading ? (
|
||||
<ColorRing height="50" width="50" color="white" />
|
||||
) : (
|
||||
<div>
|
||||
{transactionData ? (
|
||||
<span>Tambahkan</span>
|
||||
) : (
|
||||
<span>Pesan</span>
|
||||
)}
|
||||
|
||||
<span>Rp{formatToRupiah(totalPrice)}</span>
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.PaymentOptionMargin}></div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,62 +1,92 @@
|
||||
import React from "react";
|
||||
import React, { useState } from "react";
|
||||
|
||||
const CircularDiagram = ({ segments }) => {
|
||||
const radius = 70; // Radius of the circle
|
||||
const strokeWidth = 20; // Width of each portion
|
||||
const circumference = 2 * Math.PI * (radius - strokeWidth / 2);
|
||||
const HorizontalBarDiagram = ({ segments, width = 300 }) => {
|
||||
const [showAll, setShowAll] = useState(false);
|
||||
|
||||
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 (
|
||||
<svg
|
||||
width={radius * 2}
|
||||
height={radius * 2}
|
||||
viewBox={`0 0 ${radius * 2} ${radius * 2}`}
|
||||
style={svgStyles}
|
||||
>
|
||||
<circle
|
||||
cx={radius}
|
||||
cy={radius}
|
||||
r={radius - strokeWidth / 2}
|
||||
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;
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<svg
|
||||
width={width}
|
||||
height={height}
|
||||
style={{ display: "block", margin: "0 auto" }}
|
||||
>
|
||||
{visibleSegments.map((segment, index) => {
|
||||
const { name, value, color, unit} = segment;
|
||||
const percentage = total > 0 ? ((value / total) * 100).toFixed(1) : 0;
|
||||
const barWidth = (width * percentage) / 100;
|
||||
|
||||
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 (
|
||||
<circle
|
||||
key={index}
|
||||
cx={radius}
|
||||
cy={radius}
|
||||
r={radius - strokeWidth / 2}
|
||||
stroke={color}
|
||||
strokeWidth={strokeWidth}
|
||||
fill="none"
|
||||
strokeDasharray={`${segmentLength} ${
|
||||
circumference - segmentLength
|
||||
}`}
|
||||
strokeDashoffset={strokeDashoffset}
|
||||
strokeLinecap="round" // Rounds the edges of each segment
|
||||
transform={`rotate(-90 ${radius} ${radius})`}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</svg>
|
||||
{/* Filled bar */}
|
||||
<rect
|
||||
x={0}
|
||||
y={0}
|
||||
width={barWidth}
|
||||
height={barHeight}
|
||||
fill={color}
|
||||
rx={8}
|
||||
ry={8}
|
||||
/>
|
||||
|
||||
{/* Name + Value + Percentage di dalam bar */}
|
||||
<text
|
||||
x={width - 8} // dekat ujung kanan
|
||||
y={barHeight / 2}
|
||||
dy=".35em"
|
||||
textAnchor="end"
|
||||
fill="black"
|
||||
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;
|
||||
|
||||
@@ -28,7 +28,7 @@ const CreateClerk = ({ shopId }) => {
|
||||
try {
|
||||
const create = await createClerks(shopId || cafeIdParam, username, password);
|
||||
|
||||
if (create) setMessage('Clerk created successfully');
|
||||
if (create) {setMessage('Clerk created successfully');}
|
||||
else setMessage('Failed to create clerk');
|
||||
} catch (error) {
|
||||
setMessage('Error creating clerk');
|
||||
|
||||
@@ -49,7 +49,7 @@ const Dashboard = ({ user, setModal }) => {
|
||||
// Create admin functionality
|
||||
createCafeOwner(newItem.email, newItem.username, newItem.password)
|
||||
.then((newitem) => {
|
||||
setItems([...items, { userId: newitem.userId, name: newitem.username }]);
|
||||
setItems([...items, { user_id: newitem.user_id, name: newitem.username }]);
|
||||
setIsCreating(false);
|
||||
setNewItem({ name: "", type: "" });
|
||||
})
|
||||
|
||||
@@ -71,9 +71,9 @@ const LinktreePage = ({ user, setModal }) => {
|
||||
|
||||
// Handle manual coupon code check
|
||||
const handleGetkCoupons = async () => {
|
||||
const result = await getUserCoupons();
|
||||
setCoupons(result.coupons);
|
||||
console.log(result)
|
||||
// const result = await getUserCoupons();
|
||||
// setCoupons(result.coupons);
|
||||
// console.log(result)
|
||||
};
|
||||
|
||||
// Handle user transactions
|
||||
@@ -95,24 +95,20 @@ const LinktreePage = ({ user, setModal }) => {
|
||||
}
|
||||
};
|
||||
|
||||
// Handle login
|
||||
const handleLogin = async () => {
|
||||
try {
|
||||
setError(false);
|
||||
setLoading(true);
|
||||
const response = await loginUser(username, password);
|
||||
if (response.success) {
|
||||
localStorage.setItem('auth', response.token);
|
||||
console.log(response)
|
||||
window.location.href = response.cafeIdentifyName ? `/${response.cafeIdentifyName}` : '/';
|
||||
} else {
|
||||
setError(true);
|
||||
}
|
||||
} catch (error) {
|
||||
setError(true);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
const handleLogin = () => {
|
||||
const baseUrl = "https://kediritechnopark.com/";
|
||||
const modal = "product";
|
||||
const productId = 1;
|
||||
|
||||
const authorizedUri = "https://kedaimaster.com?token=";
|
||||
const unauthorizedUri = `${baseUrl}?modal=${modal}&product_id=${productId}`;
|
||||
|
||||
const url =
|
||||
`${baseUrl}?modal=${modal}&product_id=${productId}` +
|
||||
`&authorized_uri=${encodeURIComponent(authorizedUri)}` +
|
||||
`&unauthorized_uri=${encodeURIComponent(unauthorizedUri)}`;
|
||||
|
||||
window.location.href = url;
|
||||
};
|
||||
|
||||
// Handle logout
|
||||
@@ -152,7 +148,7 @@ const LinktreePage = ({ user, setModal }) => {
|
||||
try {
|
||||
if (user.roleId < 1) {
|
||||
const newOwner = await createCafeOwner(newItem.email, newItem.username, newItem.password);
|
||||
setItems([...items, { userId: newOwner.userId, name: newOwner.username }]);
|
||||
setItems([...items, { user_id: newOwner.user_id, name: newOwner.username }]);
|
||||
} else {
|
||||
const newCafe = await createCafe(newItem.name);
|
||||
setItems([...items, { cafeId: newCafe.cafeId, name: newCafe.name }]);
|
||||
@@ -202,7 +198,7 @@ const LinktreePage = ({ user, setModal }) => {
|
||||
];
|
||||
console.log(items)
|
||||
|
||||
const selectedItems = items?.items?.find(item => (item.userId || item.cafeId) === selectedItemId);
|
||||
const selectedItems = items?.items?.find(item => (item.user_id || item.cafeId) === selectedItemId);
|
||||
|
||||
// If the selected tenant is found, extract the cafes
|
||||
const selectedSubItems = selectedItems?.subItems || [];
|
||||
@@ -238,7 +234,7 @@ const LinktreePage = ({ user, setModal }) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
{user && user.roleId < 2 ? (
|
||||
{getLocalStorage("auth") ? (
|
||||
<div>
|
||||
<div className={styles.header}>
|
||||
|
||||
@@ -278,7 +274,7 @@ const LinktreePage = ({ user, setModal }) => {
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
gratis 3 bulan pertama
|
||||
gratis 1 bulan pertama
|
||||
</div>
|
||||
:
|
||||
<div className={styles.mainHeading}>
|
||||
@@ -290,57 +286,20 @@ const LinktreePage = ({ user, setModal }) => {
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
Gratis 3 bulan pertama
|
||||
Gratis 1 bulan pertama
|
||||
</div>
|
||||
}
|
||||
<div className={styles.subHeading}>
|
||||
Solusi berbasis web untuk memudahkan pengelolaan kedai, dengan fitur yang mempermudah pemilik, kasir, dan tamu berinteraksi.
|
||||
</div>
|
||||
|
||||
{getLocalStorage('auth') == null && (
|
||||
<div className={styles.LoginForm}>
|
||||
<div className={`${styles.FormUsername} ${inputtingPassword ? styles.animateForm : wasInputtingPassword ? styles.reverseForm : ''}`}>
|
||||
<label htmlFor="username" className={styles.usernameLabel}>---- Masuk -----------------------------</label>
|
||||
<input
|
||||
id="username"
|
||||
placeholder="username"
|
||||
maxLength="30"
|
||||
className={!error ? styles.usernameInput : styles.usernameInputError}
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
/>
|
||||
<button onClick={() => { setInputtingPassword(true); setWasInputtingPassword(true) }} className={styles.claimButton}>
|
||||
<span>➜</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className={`${styles.FormPassword} ${inputtingPassword ? styles.animateForm : wasInputtingPassword ? styles.reverseForm : styles.idleForm}`}>
|
||||
<span>
|
||||
<label onClick={() => setInputtingPassword(false)} htmlFor="password" className={styles.usernameLabel}> <--- <-- Kembali </label>
|
||||
<label htmlFor="password" className={styles.usernameLabel}> ----- </label>
|
||||
<label onClick={() => setModal('reset-password', { username: username })} className={styles.usernameLabel}>
|
||||
lupa password?
|
||||
</label>
|
||||
</span>
|
||||
<input
|
||||
id="password"
|
||||
placeholder="password"
|
||||
type="password"
|
||||
maxLength="30"
|
||||
className={!error ? styles.usernameInput : styles.usernameInputError}
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
/>
|
||||
<button
|
||||
onClick={handleLogin}
|
||||
className={`${styles.claimButton} ${loading ? styles.loading : ''}`}
|
||||
disabled={loading}
|
||||
>
|
||||
<span>{loading ? 'Loading...' : 'Masuk'}</span>
|
||||
<div className={`${styles.FormUsername} ${inputtingPassword ? styles.animateForm : wasInputtingPassword ? styles.reverseForm : ''}`}>
|
||||
<button onClick={() => handleLogin()} className={styles.claimButton}>
|
||||
<span>Masuk</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className={styles.footer}>
|
||||
<div className={styles.footerLinks}>
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.Invoice {
|
||||
overflow-x: hidden;
|
||||
background-color: white;
|
||||
@@ -40,11 +42,10 @@
|
||||
font-size: calc(10px + 2vmin);
|
||||
color: rgba(88, 55, 50, 1);
|
||||
border-radius: 15px 15px 0 0;
|
||||
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.PaymentOptionMargin {
|
||||
@@ -59,12 +60,16 @@
|
||||
|
||||
position: relative;
|
||||
height: 220px;
|
||||
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
width: 40%;
|
||||
}
|
||||
|
||||
.TotalContainer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 80vw;
|
||||
/* width: 80vw; */
|
||||
margin: 0 auto;
|
||||
font-family: "Plus Jakarta Sans", sans-serif;
|
||||
font-weight: 600;
|
||||
@@ -92,13 +97,13 @@
|
||||
margin-bottom: 23px;
|
||||
}
|
||||
|
||||
.PayButton div{
|
||||
.PayButton div {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
.PayButton3{
|
||||
.PayButton3 {
|
||||
font-family: "Plus Jakarta Sans", sans-serif;
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
@@ -115,8 +120,7 @@
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
|
||||
.PayButton3 div{
|
||||
.PayButton3 div {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding-left: 20px;
|
||||
@@ -191,7 +195,7 @@
|
||||
|
||||
.NoteInput {
|
||||
width: 78vw;
|
||||
height: 12vw;
|
||||
height: 18px;
|
||||
border-radius: 20px;
|
||||
margin: 0 auto;
|
||||
padding: 10px;
|
||||
@@ -201,22 +205,275 @@
|
||||
overflow-wrap: break-word; /* Ensure text wraps */
|
||||
}
|
||||
|
||||
|
||||
.AddedLastTransaction{
|
||||
.AddedLastTransaction {
|
||||
width: 100%;
|
||||
font-size: 1em;
|
||||
padding: 10px 20px;
|
||||
margin-bottom: 7px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.AddedLastTransaction div{
|
||||
.AddedLastTransaction div {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.CancelAddedLastTransaction{
|
||||
.CancelAddedLastTransaction {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin-right: 30px;
|
||||
margin-top: 10px;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.Invoice {
|
||||
overflow-x: hidden;
|
||||
background-color: white;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
font-size: calc(10px + 2vmin);
|
||||
color: rgba(88, 55, 50, 1);
|
||||
background-color: #e9e9e9;
|
||||
}
|
||||
|
||||
.TotalContainer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-family: "Plus Jakarta Sans", sans-serif;
|
||||
font-weight: 600;
|
||||
font-style: normal;
|
||||
font-size: 1em;
|
||||
padding: 10px 30px;
|
||||
padding-top: 20px;
|
||||
/* margin-bottom: 17px; */
|
||||
}
|
||||
|
||||
.Invoice-title {
|
||||
font-family: "Plus Jakarta Sans", sans-serif;
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
font-size: 6vw;
|
||||
color: rgba(88, 55, 50, 1);
|
||||
text-align: left;
|
||||
margin-left: 20px;
|
||||
margin-top: 7px;
|
||||
}
|
||||
|
||||
.Invoice-detail {
|
||||
font-family: "Plus Jakarta Sans", sans-serif;
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
font-size: 20px;
|
||||
color: rgba(88, 55, 50, 1);
|
||||
text-align: left;
|
||||
margin-left: 20px;
|
||||
margin-top: 17px;
|
||||
}
|
||||
|
||||
.PaymentOption {
|
||||
overflow: visible;
|
||||
background-color: white;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
font-size: calc(10px + 2vmin);
|
||||
color: rgba(88, 55, 50, 1);
|
||||
border-radius: 15px 15px 0 0;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
width: 40%;
|
||||
left: 60%;
|
||||
}
|
||||
|
||||
.PaymentOptionMargin {
|
||||
z-index: -1;
|
||||
overflow-x: hidden;
|
||||
background-color: white;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
font-size: calc(10px + 2vmin);
|
||||
color: rgba(88, 55, 50, 1);
|
||||
|
||||
position: relative;
|
||||
height: 220px;
|
||||
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
width: 40%;
|
||||
}
|
||||
|
||||
.PayButton {
|
||||
font-family: "Plus Jakarta Sans", sans-serif;
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
font-size: 20px;
|
||||
|
||||
width: 80vw;
|
||||
height: 40px;
|
||||
border-radius: 50px;
|
||||
background-color: rgba(88, 55, 50, 1);
|
||||
color: white;
|
||||
border: none;
|
||||
margin: 0px auto;
|
||||
cursor: pointer;
|
||||
margin-bottom: 23px;
|
||||
}
|
||||
|
||||
.PayButton div {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
.PayButton3 {
|
||||
font-family: "Plus Jakarta Sans", sans-serif;
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
font-size: 20px;
|
||||
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
border-radius: 50px;
|
||||
background-color: rgba(88, 55, 50, 1);
|
||||
color: white;
|
||||
border: none;
|
||||
margin: 0px auto;
|
||||
cursor: pointer;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.PayButton3 div {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
.Pay2Button {
|
||||
text-align: center;
|
||||
color: rgba(88, 55, 50, 1);
|
||||
font-size: 1em;
|
||||
margin-bottom: 25px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.Confirm {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 80vw;
|
||||
margin: 0 auto;
|
||||
font-family: "Plus Jakarta Sans", sans-serif;
|
||||
font-weight: 600;
|
||||
font-style: normal;
|
||||
font-size: 1.5em;
|
||||
padding: 10px 0;
|
||||
margin-bottom: 17px;
|
||||
}
|
||||
|
||||
.RoundedRectangle {
|
||||
border-radius: 20px;
|
||||
padding-top: 5px;
|
||||
margin: 12px;
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
.EmailContainer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 80vw;
|
||||
margin: 0 auto;
|
||||
font-size: 1em;
|
||||
padding: 10px 0;
|
||||
margin-bottom: 7px;
|
||||
}
|
||||
.OrderTypeContainer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 80vw;
|
||||
margin: 0 auto;
|
||||
font-size: 1em;
|
||||
padding: 10px 0;
|
||||
margin-bottom: 7px;
|
||||
}
|
||||
|
||||
.Note {
|
||||
text-align: left;
|
||||
color: rgba(88, 55, 50, 1);
|
||||
font-size: 1em;
|
||||
margin-bottom: 13px;
|
||||
margin-left: 50px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.NoteContainer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 80vw;
|
||||
margin: 0 auto;
|
||||
font-size: 1em;
|
||||
padding: 10px 0;
|
||||
margin-bottom: 7px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.NoteInput {
|
||||
width: 78vw;
|
||||
height: 18px;
|
||||
border-radius: 20px;
|
||||
margin: 0 auto;
|
||||
padding: 10px;
|
||||
font-size: 15px;
|
||||
border: 1px solid rgba(88, 55, 50, 0.5);
|
||||
resize: none; /* Prevent resizing */
|
||||
overflow-wrap: break-word; /* Ensure text wraps */
|
||||
}
|
||||
|
||||
.AddedLastTransaction {
|
||||
width: 100%;
|
||||
font-size: 1em;
|
||||
padding: 10px 20px;
|
||||
margin-bottom: 7px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.AddedLastTransaction div {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.CancelAddedLastTransaction {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin-right: 30px;
|
||||
margin-top: 10px;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.PaymentOptionMargin {
|
||||
z-index: -1;
|
||||
overflow-x: hidden;
|
||||
background-color: white;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
font-size: calc(10px + 2vmin);
|
||||
color: rgba(88, 55, 50, 1);
|
||||
|
||||
position: relative;
|
||||
height: 220px;
|
||||
}
|
||||
|
||||
.TotalContainer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
/* width: 80vw; */
|
||||
margin: 0 auto;
|
||||
font-family: "Plus Jakarta Sans", sans-serif;
|
||||
font-weight: 600;
|
||||
font-style: normal;
|
||||
font-size: 1.3em;
|
||||
padding: 10px 0;
|
||||
padding-top: 20px;
|
||||
/* margin-bottom: 17px; */
|
||||
}
|
||||
}
|
||||
@@ -110,7 +110,7 @@ const LinktreePage = ({ data, setModal }) => {
|
||||
</div>
|
||||
<div className={styles.linktreeForm}>
|
||||
<button onClick={()=>window.open("https://api.whatsapp.com/send?phone=6281318894994&text=Saya%20ingin%20coba%20gratis%203%20bulan")} className={styles.claimButton}>
|
||||
<span>Dapatkan voucher gratis 3 bulan</span>
|
||||
<span>Dapatkan voucher gratis 1 bulan</span>
|
||||
</button>
|
||||
</div>
|
||||
<div className={styles.footer}>
|
||||
|
||||
@@ -42,8 +42,12 @@
|
||||
line-height: 2.25rem;
|
||||
letter-spacing: -1px;
|
||||
margin-bottom: 6px;
|
||||
margin: 10px
|
||||
}
|
||||
|
||||
.buttonWrapper {
|
||||
display: flex;
|
||||
}
|
||||
.descHeading {
|
||||
width: 99%;
|
||||
font-weight: 700;
|
||||
|
||||
@@ -338,7 +338,6 @@
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.footerLinks {
|
||||
|
||||
@@ -180,7 +180,7 @@ const SetPaymentQr = ({ cafeId }) => {
|
||||
setLatestMutation(latestMutation);
|
||||
setCurrentQuantity(latestMutation.newStock);
|
||||
setCurrentPrice(formatCurrency(latestMutation.priceAtp));
|
||||
setNewPrice(formatCurrency(latestMutation.priceAtp));
|
||||
setNewPrice(formatCurrency(latestMutation.priceAtp) || 0);
|
||||
} else {
|
||||
setCurrentQuantity(0); // Default value if no mutations exist
|
||||
setLatestMutation({ newStock: 0 });
|
||||
@@ -195,12 +195,12 @@ const SetPaymentQr = ({ cafeId }) => {
|
||||
|
||||
const handleUpdateStock = async () => {
|
||||
setLoading(true);
|
||||
console.log('aaa')
|
||||
try {
|
||||
const newprice = convertToInteger(newPrice)
|
||||
const newStock = currentQuantity + quantityChange;
|
||||
const formData = new FormData();
|
||||
formData.append("newStock", newStock);
|
||||
formData.append("priceAtp", newprice);
|
||||
formData.append("priceAtp", newPrice);
|
||||
formData.append("reason", "Stock update");
|
||||
|
||||
await createMaterialMutation(materials[selectedMaterialIndex].materialId, formData);
|
||||
@@ -231,7 +231,7 @@ const SetPaymentQr = ({ cafeId }) => {
|
||||
<></>
|
||||
) : (
|
||||
<>
|
||||
<h3 className={styles.title}>Bahan baku</h3>
|
||||
<h3 className={styles.title}>Stok</h3>
|
||||
<Carousel items={materials} onSelect={(e) => setSelectedMaterialIndex(e)} selectedIndex={selectedMaterialIndex} />
|
||||
{selectedMaterialIndex !== -1 ? (
|
||||
<>
|
||||
|
||||
@@ -27,8 +27,10 @@ const LinktreePage = ({ handleYes, handleNo }) => {
|
||||
<div className={styles.dashboardContainer} >
|
||||
<div className={styles.mainHeading}>{captMessage}</div>
|
||||
{descMessage && <div className={styles.descHeading}>{descMessage}</div>}
|
||||
{handleYes && <div onClick={handleYes} className={styles.button}>{yesText}</div>}
|
||||
<div className={styles.buttonWrapper}>
|
||||
<div onClick={handleYes} className={styles.button}>{yesText}</div>
|
||||
{noText && <div onClick={handleNo} className={styles.button}>{noText}</div>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -164,15 +164,15 @@ export default function Transactions({ propsShopId, sendParam, deviceType }) {
|
||||
{isPaymentLoading ? (
|
||||
<ColorRing height="50" width="50" color="white" />
|
||||
) : transaction.confirmed === 1 ? (
|
||||
"Confirm has paid" // Display "Confirm has paid" if the transaction is confirmed (1)
|
||||
"Konfirmasi telah bayar" // Display "Confirm has paid" if the transaction is confirmed (1)
|
||||
) : transaction.confirmed === -1 ? (
|
||||
"Declined" // Display "Declined" if the transaction is declined (-1)
|
||||
"Ditolah" // Display "Declined" if the transaction is declined (-1)
|
||||
) : transaction.confirmed === 2 ? (
|
||||
"Confirm item has ready" // Display "Item ready" if the transaction is ready (2)
|
||||
"Konfirmasi item telah siap" // Display "Item ready" if the transaction is ready (2)
|
||||
) : transaction.confirmed === 3 ? (
|
||||
"Transaction success" // Display "Item ready" if the transaction is ready (2)
|
||||
"Transaksi selesai" // Display "Item ready" if the transaction is ready (2)
|
||||
) : (
|
||||
"Confirm availability" // Display "Confirm availability" if the transaction is not confirmed (0)
|
||||
"Konfirmasi ketersediaan" // Display "Confirm availability" if the transaction is not confirmed (0)
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
@@ -181,7 +181,7 @@ export default function Transactions({ propsShopId, sendParam, deviceType }) {
|
||||
className={styles.DeclineButton}
|
||||
onClick={() => handleDecline(transaction.transactionId)}
|
||||
>
|
||||
decline
|
||||
Tolak
|
||||
</h5>
|
||||
)}
|
||||
</div>
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
@@ -13,10 +13,6 @@ import MultiSwitch from "react-multi-switch-toggle";
|
||||
import DailyCharts from '../components/DailyCharts.js';
|
||||
import PeriodCharts from '../components/PeriodCharts.js';
|
||||
|
||||
import Coupon from "../components/Coupon.js";
|
||||
|
||||
import CreateCouponPage from "./CreateCoupon.js";
|
||||
|
||||
const RoundedRectangle = ({
|
||||
onClick,
|
||||
title,
|
||||
@@ -59,6 +55,9 @@ const RoundedRectangle = ({
|
||||
? "rgb(85 85 85)"
|
||||
: !isChildren && !children && backgroundColor,
|
||||
color: loading ? "transparent" : color,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between'
|
||||
};
|
||||
|
||||
const valueAndPercentageContainerStyle = {
|
||||
@@ -82,7 +81,7 @@ const RoundedRectangle = ({
|
||||
};
|
||||
|
||||
const percentageStyle = {
|
||||
fontSize: "16px",
|
||||
fontSize: "14px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
textAlign: "right",
|
||||
@@ -95,19 +94,17 @@ const RoundedRectangle = ({
|
||||
|
||||
return (
|
||||
<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 && (
|
||||
<div style={valueAndPercentageContainerStyle}>
|
||||
<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>
|
||||
)}
|
||||
{children && <div>{children}</div>} {/* Properly render children */}
|
||||
@@ -122,7 +119,7 @@ const App = ({ forCafe = true, cafeId = -1,
|
||||
const [selectedCafeId, setSelectedCafeId] = useState(cafeId);
|
||||
const [analytics, setAnalytics] = useState({});
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [filter, setFilter] = useState("monthly");
|
||||
const [filter, setFilter] = useState("yesterday");
|
||||
const [circularFilter, setCircularFilter] = useState("item");
|
||||
const [graphFilter, setGraphFilter] = useState("income");
|
||||
|
||||
@@ -199,102 +196,84 @@ const App = ({ forCafe = true, cafeId = -1,
|
||||
// Define a color palette or generate colors dynamically
|
||||
const colorPalette = colors;
|
||||
|
||||
// Ensure that each segment gets a unique color
|
||||
let colorIndex = 0;
|
||||
|
||||
console.log(filteredItems)
|
||||
let segments = (selectedCafeId == 0 || selectedCafeId == -1) ? filteredItems.flatMap((cafe) => {
|
||||
const cafeItems = cafe.report?.itemSales || [];
|
||||
console.log(cafeItems); // Log all items for the cafe
|
||||
// Segment penjualan item
|
||||
let segments =
|
||||
selectedCafeId == 0 || selectedCafeId == -1
|
||||
? 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) => {
|
||||
const percentage = totalSoldAcrossAllCafes > 0
|
||||
? ((item.sold / totalSoldAcrossAllCafes) * 100).toFixed(2)
|
||||
: 0;
|
||||
// Urutkan descending berdasarkan value
|
||||
segments.sort((a, b) => b.value - a.value);
|
||||
|
||||
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
|
||||
const color = colorPalette[colorIndex % colorPalette.length]; // Use modulo to cycle through colors
|
||||
// Segment pengeluaran material
|
||||
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 {
|
||||
itemName: item.itemName,
|
||||
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,
|
||||
};
|
||||
});
|
||||
// Urutkan descending berdasarkan value
|
||||
materialSegments.sort((a, b) => b.value - a.value);
|
||||
|
||||
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(segments)
|
||||
|
||||
const formatIncome = (amount) => {
|
||||
if (amount >= 1_000_000_000) {
|
||||
// Format for billions
|
||||
const billions = amount / 1_000_000_000;
|
||||
return billions.toFixed(0) + "b"; // No decimal places for billions
|
||||
} else if (amount >= 1_000_000) {
|
||||
// Format for millions
|
||||
const millions = amount / 1_000_000;
|
||||
return millions.toFixed(2).replace(/\.00$/, "") + "m"; // Two decimal places, remove trailing '.00'
|
||||
} else if (amount >= 1_000) {
|
||||
// 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();
|
||||
}
|
||||
if (amount == null) return "0";
|
||||
|
||||
const formatter = new Intl.NumberFormat("id-ID", {
|
||||
style: "currency",
|
||||
currency: "IDR",
|
||||
minimumFractionDigits: 0,
|
||||
});
|
||||
|
||||
return formatter.format(amount);
|
||||
};
|
||||
|
||||
function roundToInteger(num) {
|
||||
@@ -312,7 +291,7 @@ const App = ({ forCafe = true, cafeId = -1,
|
||||
filterTexts[["yesterday", "weekly", "monthly", "yearly"].indexOf(filter)];
|
||||
|
||||
const [resetKey, setResetKey] = useState(0); // A key to force re-render
|
||||
const [texts, setTexts] = useState(['Buat bisnis']); // initially show only first 3 texts
|
||||
const [texts, setTexts] = useState([]); // initially show only first 3 texts
|
||||
const [fullTexts, setFullTexts] = useState(null); // initially show only first 3 texts
|
||||
const [fullTextsVisible, setFullTextsVisible] = useState(null); // initially show only first 3 texts
|
||||
|
||||
@@ -322,27 +301,25 @@ const App = ({ forCafe = true, cafeId = -1,
|
||||
let updatedFullTexts;
|
||||
if (otherCafes.length === 0) {
|
||||
// Only include the role-specific option if user.roleId is 1
|
||||
updatedFullTexts = user.roleId == 1 ? [["Buat Bisnis", 0]] : [];
|
||||
updatedFullTexts = user.roleId == 1 ? [[0]] : [];
|
||||
setSelectedCafeId(-1);
|
||||
} else if (otherCafes.length === 1) {
|
||||
updatedFullTexts = [
|
||||
[otherCafes[0].cafeIdentifyName || otherCafes[0].username, otherCafes[0].cafeId || otherCafes[0].userId],
|
||||
// Only add the "Buat Bisnis" option for user.roleId == 1
|
||||
...(user.roleId == 1 ? [["Buat Bisnis", -1]] : [])
|
||||
[otherCafes[0].cafeIdentifyName || otherCafes[0].username, otherCafes[0].cafeId || otherCafes[0].user_id],
|
||||
];
|
||||
|
||||
|
||||
setSelectedCafeId(otherCafes[0].cafeId); // Get the cafeId (second part of the pair)
|
||||
} else {
|
||||
updatedFullTexts = [
|
||||
["semua", 0], // First entry is "semua"
|
||||
...otherCafes.map(item => [item.cafeIdentifyName || item.username, item.cafeId || item.userId]), // Map over cafes to get name and cafeId pairs
|
||||
// Only add "Buat Bisnis +" option for user.roleId == 1
|
||||
...(user.roleId == 1 ? [["Buat Bisnis +", -1]] : [])
|
||||
...otherCafes.map(item => [item.cafeIdentifyName || item.username, item.cafeId || item.user_id]), // Map over cafes to get name and cafeId pairs
|
||||
|
||||
...(user.roleId == 1 ? [] : [])
|
||||
];
|
||||
|
||||
|
||||
setSelectedCafeId(0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
setFullTexts(updatedFullTexts); // Set fullTexts with the original structure
|
||||
// Set fullTextsVisible to an array of names only
|
||||
@@ -408,24 +385,24 @@ const App = ({ forCafe = true, cafeId = -1,
|
||||
setSelectedCafeId(selectedItem[1]); // Get the cafeId (second part of the pair)
|
||||
}
|
||||
let nextSelectedId = selectedItem[1]
|
||||
|
||||
|
||||
console.log(analytics)
|
||||
if (user && user.roleId === 0 && analytics) {
|
||||
// Filter the analytics items based on userId
|
||||
if(selectedItem[1] != 0 && selectedItem[1] != -1){
|
||||
const filteredData = analytics.items.filter(
|
||||
(data) => data.userId === nextSelectedId
|
||||
);
|
||||
|
||||
// Extract coupons from the filtered data
|
||||
const couponsToAdd = filteredData.flatMap((data) => data.coupons);
|
||||
|
||||
// Log the coupons to be added
|
||||
console.log(couponsToAdd);
|
||||
// Assuming setCouponList is a function that updates the coupon list
|
||||
setCouponList(couponsToAdd || []);
|
||||
}
|
||||
else setCouponList([])
|
||||
// Filter the analytics items based on user_id
|
||||
if (selectedItem[1] != 0 && selectedItem[1] != -1) {
|
||||
const filteredData = analytics.items.filter(
|
||||
(data) => data.user_id === nextSelectedId
|
||||
);
|
||||
|
||||
// Extract coupons from the filtered data
|
||||
const couponsToAdd = filteredData.flatMap((data) => data.coupons);
|
||||
|
||||
// Log the coupons to be added
|
||||
console.log(couponsToAdd);
|
||||
// Assuming setCouponList is a function that updates the coupon list
|
||||
setCouponList(couponsToAdd || []);
|
||||
}
|
||||
else setCouponList([])
|
||||
}
|
||||
|
||||
setResetKey((prevKey) => prevKey + 1); // Increase the key to force re-render
|
||||
@@ -455,8 +432,8 @@ const App = ({ forCafe = true, cafeId = -1,
|
||||
} else {
|
||||
setModal('loading');
|
||||
const create = await createCafe(itemName);
|
||||
setModal("message", {
|
||||
captMessage: create ? 'Berhasil membuat cafe' : 'Gagal membuat cafe'
|
||||
setModal("message", {
|
||||
captMessage: create ? 'Berhasil membuat cafe' : 'Gagal membuat cafe'
|
||||
});
|
||||
|
||||
// Add a 2-second delay before proceeding
|
||||
@@ -484,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>}
|
||||
<div style={{ marginTop: '10px' }}>
|
||||
{!forCafe &&
|
||||
<div className={styles.dateSelectorWrapper} style={{ fontSize: '12px' }}>
|
||||
<div className={styles.dateSelectorWrapper} style={{ fontSize: '16px', textTransform: 'uppercase' }}>
|
||||
{texts.map((item, indexx) => {
|
||||
return (
|
||||
<div
|
||||
@@ -511,7 +488,7 @@ const App = ({ forCafe = true, cafeId = -1,
|
||||
marginTop: '30px'
|
||||
}}>
|
||||
<MultiSwitch
|
||||
texts={["Kemarin", "Minggu ini", "Bulan ini", "Tahun ini"]}
|
||||
texts={["Hari ini", "Minggu ini", "Bulan ini", "Tahun ini"]}
|
||||
selectedSwitch={["yesterday", "weekly", "monthly", "yearly"].indexOf(
|
||||
filter
|
||||
)}
|
||||
@@ -537,7 +514,7 @@ const App = ({ forCafe = true, cafeId = -1,
|
||||
<RoundedRectangle
|
||||
title="Pendapatan"
|
||||
fontSize="12px"
|
||||
value={!loading && "Rp" + formatIncome(analytics?.currentTotals?.income)}
|
||||
value={!loading && formatIncome(analytics?.currentTotals?.income)}
|
||||
percentage={roundToInteger(analytics?.growth?.incomeGrowth)}
|
||||
invert={false}
|
||||
loading={loading}
|
||||
@@ -549,7 +526,7 @@ const App = ({ forCafe = true, cafeId = -1,
|
||||
<RoundedRectangle
|
||||
title="Pengeluaran"
|
||||
fontSize="12px"
|
||||
value={!loading && "Rp" + formatIncome(analytics?.currentTotals?.outcome)}
|
||||
value={!loading && formatIncome(analytics?.currentTotals?.outcome)}
|
||||
percentage={roundToInteger(analytics?.growth?.outcomeGrowth)}
|
||||
invert={true}
|
||||
loading={loading}
|
||||
@@ -602,7 +579,7 @@ const App = ({ forCafe = true, cafeId = -1,
|
||||
{!forCafe && selectedCafeId != -1 && selectedCafeId != 0 && (
|
||||
<RoundedRectangle
|
||||
title={"Kunjungi bisnis"}
|
||||
width= {`calc(${'100%'} - 10px)`}
|
||||
width={`calc(${'100%'} - 10px)`}
|
||||
height='10px'
|
||||
onClick={() => window.location.href = window.location.origin + '/' + otherCafes.find(item => item.cafeId === selectedCafeId).cafeIdentifyName}
|
||||
marginBottom={'0px'}
|
||||
@@ -615,11 +592,13 @@ const App = ({ forCafe = true, cafeId = -1,
|
||||
>
|
||||
<div style={{ marginRight: "5px", fontSize: "1.2em" }}>ⓘ</div>
|
||||
<h6 style={{ margin: 0, textAlign: "left", fontSize: '10px', fontWeight: 500 }}>
|
||||
{(filter == 'yesterday' || filter == 'weekly') ?
|
||||
{(filter == 'weekly') ?
|
||||
`Data dihitung dengan membandingkan
|
||||
${comparisonText} hari terakhir dengan ${comparisonText} hari sebelumnya, dengan penghitungan dimulai dari data kemarin.`
|
||||
7 hari terakhir dengan 7 hari sebelumnya, dengan penghitungan dimulai dari 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.`}
|
||||
(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.`}
|
||||
</h6>
|
||||
</div>
|
||||
</div>
|
||||
@@ -632,7 +611,7 @@ const App = ({ forCafe = true, cafeId = -1,
|
||||
}
|
||||
style={{ color: 'black', position: 'relative' }}
|
||||
>
|
||||
<div>Item laku</div>
|
||||
<div>Penjualan</div>
|
||||
</div>
|
||||
<div
|
||||
className={`${styles.filterSelector} ${circularFilter == 'material' ? '' : styles.filterSelectorInactive}
|
||||
@@ -657,29 +636,6 @@ const App = ({ forCafe = true, cafeId = -1,
|
||||
<div style={{ flex: 1 }}>
|
||||
<CircularDiagram segments={circularFilter == 'item' ? segments : materialSegments} />
|
||||
</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 className={styles.filterSelectorWrapper}>
|
||||
@@ -720,77 +676,6 @@ const App = ({ forCafe = true, cafeId = -1,
|
||||
}
|
||||
</div>
|
||||
}
|
||||
{!forCafe && selectedCafeId == -1 && user.roleId == 1 &&
|
||||
<div style={{
|
||||
textAlign: "center",
|
||||
}}>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexWrap: "wrap",
|
||||
justifyContent: "center",
|
||||
padding: "20px",
|
||||
}}
|
||||
>
|
||||
|
||||
<RoundedRectangle
|
||||
title={"Masukkan nama bisnis"}
|
||||
width="calc(100% - 10px)"
|
||||
>
|
||||
<input
|
||||
value={itemName}
|
||||
onChange={(e) => setItemName(e.target.value)}
|
||||
style={{
|
||||
width: '70%',
|
||||
fontSize: '25px',
|
||||
borderRadius: '7px',
|
||||
border: '1px solid black'
|
||||
}}
|
||||
/>
|
||||
</RoundedRectangle>
|
||||
|
||||
|
||||
<RoundedRectangle
|
||||
title={"Buat Bisnis"}
|
||||
width="calc(100% - 10px)"
|
||||
onClick={handleClick}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
{!forCafe &&
|
||||
<>
|
||||
<div className={`${styles.couponContainer}`}>
|
||||
<div>
|
||||
{!forCafe &&
|
||||
<div className={styles.dateSelectorWrapper} style={{ fontSize: '13px' }}>
|
||||
<div
|
||||
className={`${styles.dateSelector} ${styles.dateSelectorActive}`} style={{ position: 'relative', width: 'calc(32vw - 30px)' }}
|
||||
>
|
||||
<div style={{ position: 'absolute', bottom: 0, left: '10%', right: '10%', borderBottom: `1px solid green` }}></div>
|
||||
<div
|
||||
style={{ color: 'black' }}>
|
||||
Voucher
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ padding: '25px', paddingTop: '0', paddingBottom: '0' }}>
|
||||
{/* <h1>{couponList.length}</h1> */}
|
||||
{couponList && couponList.map((coupon) => {
|
||||
return <Coupon
|
||||
code={coupon?.code || null}
|
||||
value={coupon?.discountValue}
|
||||
period={coupon?.discountPeriods}
|
||||
expiration={coupon?.discountEndDate}
|
||||
/>
|
||||
})}
|
||||
<button className={`${styles.addCoupon}`} onClick={() => setModal('claim-coupon')}>Tambahkan Voucher</button>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import React, { useRef, useEffect, useState } from "react";
|
||||
import qs from 'qs';
|
||||
|
||||
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 {
|
||||
getTransaction,
|
||||
@@ -11,7 +13,7 @@ import { getTables } from "../helpers/tableHelper";
|
||||
import TableCanvas from "../components/TableCanvas";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
|
||||
export default function Transactions({ propsShopId, sendParam, deviceType, handleMoveToTransaction, depth, shopImg }) {
|
||||
export default function Transactions({ propsShopId,setIsModalOpen,cafeIdentityName, sendParam, deviceType, handleMoveToTransaction, depth, shopImg, setModal }) {
|
||||
const { shopId, tableId } = useParams();
|
||||
if (sendParam) sendParam({ shopId, tableId });
|
||||
|
||||
@@ -25,6 +27,9 @@ export default function Transactions({ propsShopId, sendParam, deviceType, handl
|
||||
|
||||
const [transactionRefreshKey, setTransactionRefreshKey] = useState(0);
|
||||
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
const transactionId = searchParams.get("transactionId") || "";
|
||||
|
||||
@@ -67,7 +72,7 @@ export default function Transactions({ propsShopId, sendParam, deviceType, handl
|
||||
if (c) {
|
||||
console.log(c)
|
||||
setTransaction(c);
|
||||
setTransactionRefreshKey(transactionRefreshKey+1);
|
||||
setTransactionRefreshKey(transactionRefreshKey + 1);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error processing payment:", error);
|
||||
@@ -84,7 +89,7 @@ export default function Transactions({ propsShopId, sendParam, deviceType, handl
|
||||
if (c) {
|
||||
console.log(c)
|
||||
setTransaction({ ...transaction, confirmed: c.confirmed });
|
||||
setTransactionRefreshKey(transactionRefreshKey+1);
|
||||
setTransactionRefreshKey(transactionRefreshKey + 1);
|
||||
}
|
||||
// if (c) {
|
||||
// // Update the confirmed status locally
|
||||
@@ -116,6 +121,29 @@ export default function Transactions({ propsShopId, sendParam, deviceType, handl
|
||||
}
|
||||
}, [transaction?.notes]);
|
||||
|
||||
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 (
|
||||
<div key={transactionRefreshKey} className={styles.Transaction}>
|
||||
|
||||
@@ -128,18 +156,17 @@ export default function Transactions({ propsShopId, sendParam, deviceType, handl
|
||||
setSelectedTable(transaction.Table || { tableId: 0 })
|
||||
}
|
||||
>
|
||||
|
||||
<div className={styles['receipt-header']}>
|
||||
<div className={styles['receipt-info']}>
|
||||
<h3>{transaction.payment_type == 'cash' ? 'Tunai' : transaction.payment_type == 'cashless' ? 'Non tunai' : transaction.payment_type == 'paylater' ? 'Open bill' :'Close bill' }</h3>
|
||||
<h3>{transaction.payment_type == 'cash' ? 'Tunai' : transaction.payment_type == 'cashless' ? 'Non tunai' : transaction.payment_type == 'paylater' ? 'Open bill' : 'Close bill'}</h3>
|
||||
<p>Transaction ID: {transaction.transactionId}</p>
|
||||
{
|
||||
transaction.payment_type == 'paylater/cash' ?
|
||||
<p>Cash</p>
|
||||
:
|
||||
(transaction.payment_type == 'paylater/cashless' &&
|
||||
<p>Non tunai</p>
|
||||
)
|
||||
<p>Cash</p>
|
||||
:
|
||||
(transaction.payment_type == 'paylater/cashless' &&
|
||||
<p>Non tunai</p>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@@ -161,8 +188,8 @@ export default function Transactions({ propsShopId, sendParam, deviceType, handl
|
||||
</svg>
|
||||
</div>
|
||||
{depth > 0 &&
|
||||
<div className={styles.circle}>{depth}</div>
|
||||
}
|
||||
<div className={styles.circle}>{depth}</div>
|
||||
}
|
||||
</div>
|
||||
<div className={styles['line']} ></div>
|
||||
<div className={styles['circle-right']} onClick={() => handleMoveToTransaction('next', transaction.transactionId)}>
|
||||
@@ -188,7 +215,7 @@ export default function Transactions({ propsShopId, sendParam, deviceType, handl
|
||||
<div>
|
||||
{transaction.DetailedTransactions.map((detail) => (
|
||||
<div className={styles['itemContainer']} key={detail.detailedTransactionId}>
|
||||
<div className={styles['plusNegative']} style={{ visibility: transaction.confirmed >= 0 && detail.acceptedStatus == 0 ? 'visible' : 'hidden', margin: transaction.confirmed >= 0 && detail.acceptedStatus == 0 ? '4px 10px 0px 10px' : '4px 0px 0px 0px' }}
|
||||
<div className={styles['plusNegative']} style={{ visibility: transaction.confirmed >= 0 && detail.acceptedStatus == 0 ? 'visible' : 'hidden', margin: transaction.confirmed >= 0 && detail.acceptedStatus == 0 ? '4px 10px 0px 10px' : '4px 0px 0px 0px' }}
|
||||
onClick={() =>
|
||||
setNotAcceptedItems(prevState =>
|
||||
prevState.includes(detail.detailedTransactionId)
|
||||
@@ -225,19 +252,35 @@ export default function Transactions({ propsShopId, sendParam, deviceType, handl
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis'
|
||||
}}>
|
||||
{detail.Item.name}</span> - {notAcceptedItems.includes(detail.detailedTransactionId) || detail.qty < 1 ? 'tidak tersedia' : `${detail.qty} x Rp
|
||||
{detail.Item.name}</span> - {notAcceptedItems.includes(detail.detailedTransactionId) || detail.qty < 1 ? 'tidak tersedia' : `${detail.qty} x Rp
|
||||
${detail.promoPrice ? detail.promoPrice : detail.price}`}
|
||||
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{!transaction.is_paid && transaction.confirmed > -1 &&
|
||||
<div
|
||||
onClick={() => {
|
||||
localStorage.setItem('lastTransaction', JSON.stringify(transaction));
|
||||
setModal("message", { captMessage: 'Silahkan tambahkan pesanan', descMessage: 'Pembayaran akan ditambahkan ke transaksi sebelumnya.' }, null, null);
|
||||
|
||||
// Dispatch the custom event
|
||||
window.dispatchEvent(new Event("localStorageUpdated"));
|
||||
}}
|
||||
className={styles["addNewItem"]}
|
||||
>
|
||||
Tambah pesanan
|
||||
</div>
|
||||
}
|
||||
|
||||
<h2 className={styles["Transactions-detail"]}>
|
||||
{transaction.serving_type === "pickup"
|
||||
? "Ambil sendiri"
|
||||
: `Diantar ke ${transaction.Table ? transaction.Table.tableNo : "N/A"
|
||||
}`}
|
||||
</h2>
|
||||
{transaction.notes != null && (
|
||||
{transaction.notes != '' && (
|
||||
<>
|
||||
<div className={styles.NoteContainer}>
|
||||
<span>Note :</span>
|
||||
@@ -295,9 +338,55 @@ export default function Transactions({ propsShopId, sendParam, deviceType, handl
|
||||
batalkan
|
||||
</h5>
|
||||
)}
|
||||
{transaction.confirmed > 1 && (
|
||||
<h5
|
||||
className={styles.DeclineButton}
|
||||
onClick={() => handlePrint(transaction)}
|
||||
>
|
||||
Cetak struk
|
||||
</h5>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{transaction &&
|
||||
<div id="print-section" className={styles.printContainer}>
|
||||
<div className="receipt">
|
||||
<h2 className="center-text">Struk Pembayaran</h2>
|
||||
<hr />
|
||||
|
||||
<p>ID Transaksi: {transaction.transactionId}</p>
|
||||
<p>Metode: {transaction.payment_type === 'cash' ? 'Tunai' :
|
||||
transaction.payment_type === 'cashless' ? 'Non Tunai' :
|
||||
'Open Bill'}</p>
|
||||
<p>Meja: {transaction.Table?.tableNo || '-'}</p>
|
||||
<hr />
|
||||
|
||||
{transaction.DetailedTransactions.map((detail) => (
|
||||
<div key={detail.detailedTransactionId} className="item-line">
|
||||
<p>{detail.Item.name}</p>
|
||||
<p>{detail.qty} x Rp{detail.promoPrice || detail.price}</p>
|
||||
</div>
|
||||
))}
|
||||
|
||||
<hr />
|
||||
<p className="total">Total: Rp {calculateTotalPrice(transaction.DetailedTransactions)}</p>
|
||||
|
||||
{transaction.notes && (
|
||||
<>
|
||||
<hr />
|
||||
<p>Catatan:</p>
|
||||
<p>{transaction.notes}</p>
|
||||
</>
|
||||
)}
|
||||
|
||||
<hr />
|
||||
<p className="center-text">Terima Kasih!</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
}
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -192,7 +192,7 @@ export default function Transactions({
|
||||
))}
|
||||
</ul>
|
||||
|
||||
{transaction.payment_type != 'paylater/cash' && transaction.payment_type != 'paylater/cashless' &&
|
||||
{(transaction.payment_type != 'paylater/cash' && transaction.payment_type != 'paylater/cashless') &&
|
||||
<div
|
||||
onClick={() => {
|
||||
localStorage.setItem('lastTransaction', JSON.stringify(transaction));
|
||||
|
||||
@@ -16,12 +16,8 @@ export default function Transaction_pending() {
|
||||
<div className={styles.Transaction}>
|
||||
<div className={containerStyle}>
|
||||
<div style={{ marginTop: "30px", textAlign: "center" }}>
|
||||
<h2>transaction failed</h2>
|
||||
<img
|
||||
className={styles.expression}
|
||||
src="https://i.imgur.com/B6k9exa.png"
|
||||
alt="Failed"
|
||||
/>
|
||||
<h2>Transaksi dibatalkan</h2>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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 { useParams } from "react-router-dom";
|
||||
import { useParams, useNavigate } from "react-router-dom";
|
||||
import { ColorRing } from "react-loader-spinner";
|
||||
import {
|
||||
getMyTransactions,
|
||||
@@ -18,7 +19,7 @@ import ButtonWithReplica from "../components/ButtonWithReplica";
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
|
||||
export default function Transactions({ shop, shopId, propsShopId, sendParam, deviceType, paymentUrl }) {
|
||||
export default function Transactions({ shop, shopId, propsShopId, sendParam, deviceType, paymentUrl, setModal, newTransaction }) {
|
||||
const { shopIdentifier, tableId } = useParams();
|
||||
if (sendParam) sendParam({ shopIdentifier, tableId });
|
||||
|
||||
@@ -26,30 +27,35 @@ export default function Transactions({ shop, shopId, propsShopId, sendParam, dev
|
||||
const [isPaymentLoading, setIsPaymentLoading] = useState(false);
|
||||
const [isPaymentOpen, setIsPaymentOpen] = useState(false);
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [matchedItems, setMatchedItems] = useState([]);
|
||||
const [matchedItems, setMatchedItems] = useState([]);
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setMatchedItems(searchAndAggregateItems(transactions, searchTerm));
|
||||
}, [searchTerm, transactions]);
|
||||
const navigate = useNavigate();
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
setMatchedItems(searchAndAggregateItems(transactions, searchTerm));
|
||||
}, [searchTerm, transactions]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchTransactions = async () => {
|
||||
try {
|
||||
|
||||
// response = await getMyTransactions(shopId || propsShopId, 5);
|
||||
// setMyTransactions(response);
|
||||
setLoading(true);
|
||||
let response = await getTransactionsFromCafe(shopId || propsShopId, 5, false);
|
||||
|
||||
let response = await getTransactionsFromCafe(shopId || propsShopId, -1, false);
|
||||
|
||||
setLoading(false);
|
||||
if (response) setTransactions(response);
|
||||
} catch (error) {
|
||||
console.error("Error fetching transactions:", error);
|
||||
}
|
||||
};
|
||||
|
||||
console.log(deviceType)
|
||||
fetchTransactions();
|
||||
}, [deviceType]);
|
||||
}, [deviceType, newTransaction]);
|
||||
|
||||
const calculateTotalPrice = (detailedTransactions) => {
|
||||
return detailedTransactions.reduce((total, dt) => {
|
||||
@@ -57,45 +63,59 @@ useEffect(() => {
|
||||
}, 0);
|
||||
};
|
||||
|
||||
const calculateAllTransactionsTotal = (transactions) => {
|
||||
return transactions.reduce((grandTotal, transaction) => {
|
||||
return grandTotal + calculateTotalPrice(transaction.DetailedTransactions);
|
||||
}, 0);
|
||||
};
|
||||
const searchAndAggregateItems = (transactions, searchTerm) => {
|
||||
if (!searchTerm.trim()) return [];
|
||||
|
||||
const normalizedTerm = searchTerm.trim().toLowerCase();
|
||||
const aggregatedItems = new Map();
|
||||
|
||||
transactions.forEach(transaction => {
|
||||
transaction.DetailedTransactions.forEach(detail => {
|
||||
const itemName = detail.Item.name;
|
||||
const itemNameLower = itemName.toLowerCase();
|
||||
|
||||
if (itemNameLower.includes(normalizedTerm)) {
|
||||
const key = detail.itemId;
|
||||
|
||||
if (!aggregatedItems.has(key)) {
|
||||
aggregatedItems.set(key, {
|
||||
itemId: detail.itemId,
|
||||
name: itemName,
|
||||
totalQty: 0,
|
||||
totalPrice: 0,
|
||||
});
|
||||
}
|
||||
|
||||
const current = aggregatedItems.get(key);
|
||||
current.totalQty += detail.qty;
|
||||
current.totalPrice += detail.qty * (detail.promoPrice || detail.price);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return Array.from(aggregatedItems.values());
|
||||
const formatRupiah = (number) => {
|
||||
return 'Rp' + number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, '.');
|
||||
};
|
||||
|
||||
|
||||
const calculateAllTransactionsTotal = (transactions) => {
|
||||
return transactions
|
||||
.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 [];
|
||||
|
||||
const normalizedTerm = searchTerm.trim().toLowerCase();
|
||||
// Map with key = `${itemId}-${confirmedGroup}` to keep confirmed groups separate
|
||||
const aggregatedItems = new Map();
|
||||
|
||||
transactions.forEach(transaction => {
|
||||
// Determine confirmed group as a string key
|
||||
const confirmedGroup = transaction.confirmed >= 0 && transaction.confirmed > 1 ? 'confirmed_gt_1' : 'confirmed_le_1';
|
||||
|
||||
transaction.DetailedTransactions.forEach(detail => {
|
||||
const itemName = detail.Item.name;
|
||||
const itemNameLower = itemName.toLowerCase();
|
||||
|
||||
if (itemNameLower.includes(normalizedTerm)) {
|
||||
// Combine itemId and confirmedGroup to keep them separated
|
||||
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);
|
||||
}
|
||||
});
|
||||
});
|
||||
console.log(aggregatedItems.values())
|
||||
return Array.from(aggregatedItems.values());
|
||||
};
|
||||
|
||||
|
||||
|
||||
const handleConfirm = async (transactionId) => {
|
||||
setIsPaymentLoading(true);
|
||||
try {
|
||||
@@ -103,7 +123,7 @@ const searchAndAggregateItems = (transactions, searchTerm) => {
|
||||
if (result) {
|
||||
setTransactions(prev =>
|
||||
prev.map(t =>
|
||||
t.transactionId === transactionId ? { ...t, confirmed: 1 } : t
|
||||
t.transactionId === transactionId ? result : t
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -132,6 +152,64 @@ const searchAndAggregateItems = (transactions, searchTerm) => {
|
||||
}
|
||||
};
|
||||
|
||||
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)
|
||||
return (
|
||||
<div className="Loader">
|
||||
@@ -144,39 +222,39 @@ const searchAndAggregateItems = (transactions, searchTerm) => {
|
||||
return (
|
||||
<div className={styles.Transactions}>
|
||||
<h2 className={styles["Transactions-title"]}>
|
||||
Daftar transaksi Rp {calculateAllTransactionsTotal(transactions)}
|
||||
Transaksi selesai {formatRupiah(calculateAllTransactionsTotal(transactions))}
|
||||
</h2>
|
||||
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Cari nama item..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
style={{ border:'0px',height: '42px',borderRadius: '15px', margin: '7px auto 10px', width: '88%', paddingLeft: '8px' }}
|
||||
/>
|
||||
type="text"
|
||||
placeholder="Cari nama item..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
style={{ border: '0px', height: '42px', borderRadius: '15px', margin: '7px auto 10px', width: '88%', paddingLeft: '8px' }}
|
||||
/>
|
||||
|
||||
|
||||
|
||||
{/* Existing Transactions List (keep all your JSX below unchanged) */}
|
||||
<div className={styles.TransactionListContainer} style={{ padding: '0 8px' }}>
|
||||
|
||||
{matchedItems.length > 0 && matchedItems.map(item => (
|
||||
<div
|
||||
key={item.itemId}
|
||||
className={styles.RoundedRectangle}
|
||||
style={{ overflow: "hidden" }}
|
||||
>
|
||||
<ul>
|
||||
<li>
|
||||
<strong>{item.name}</strong> x {item.totalQty}
|
||||
</li>
|
||||
</ul>
|
||||
<div className={styles.TotalContainer}>
|
||||
<span>Total:</span>
|
||||
<span>Rp {item.totalPrice}</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{matchedItems.length > 0 && matchedItems.map(item => (
|
||||
<div
|
||||
key={`${item.itemId}-${item.confirmedGroup}`}
|
||||
className={styles.RoundedRectangle}
|
||||
style={{ overflow: "hidden" }}
|
||||
>
|
||||
<ul>
|
||||
<li>
|
||||
<strong>{item.name}</strong> x {item.totalQty}
|
||||
</li>
|
||||
</ul>
|
||||
<div className={styles.TotalContainer}>
|
||||
<span>Total:</span>
|
||||
<span>{formatRupiah(item.totalPrice)}</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{transactions &&
|
||||
transactions.map((transaction) => (
|
||||
<div
|
||||
@@ -188,7 +266,7 @@ const searchAndAggregateItems = (transactions, searchTerm) => {
|
||||
<div className={styles['receipt-header']}>
|
||||
{transaction.confirmed === 1 ? (
|
||||
<ColorRing className={styles['receipt-logo']} />
|
||||
) : transaction.confirmed === -1 || transaction.confirmed === -2 ? (
|
||||
) : (transaction.confirmed == -1 && !transaction.is_paid) || (transaction.confirmed === -2 && !transaction.is_paid) ? (
|
||||
<div style={{ display: 'flex', justifyContent: 'center', margin: '16px 0px' }}>
|
||||
<svg
|
||||
style={{ width: '60px', transform: 'Rotate(45deg)' }}
|
||||
@@ -205,9 +283,9 @@ const searchAndAggregateItems = (transactions, searchTerm) => {
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
) : transaction.confirmed === 2 ? (
|
||||
) : transaction.confirmed === 2 && !transaction.is_paid ? (
|
||||
<ColorRing className={styles['receipt-logo']} />
|
||||
) : transaction.confirmed === 3 ? (
|
||||
) : transaction.confirmed === 3 || transaction.is_paid ? (
|
||||
<div>
|
||||
<svg
|
||||
height="60px"
|
||||
@@ -242,15 +320,15 @@ const searchAndAggregateItems = (transactions, searchTerm) => {
|
||||
|
||||
<div className={styles['receipt-info']}>
|
||||
{deviceType == 'clerk' ?
|
||||
<h3>{transaction.confirmed === 1 ? (
|
||||
<h3>{transaction.confirmed === 1 && !transaction.is_paid ? (
|
||||
"Silahkan Cek Pembayaran"
|
||||
) : transaction.confirmed === -1 ? (
|
||||
) : transaction.confirmed === -1 && !transaction.is_paid ? (
|
||||
"Dibatalkan Oleh Kasir"
|
||||
) : transaction.confirmed === -2 ? (
|
||||
) : transaction.confirmed === -2 && !transaction.is_paid ? (
|
||||
"Dibatalkan Oleh Pelanggan"
|
||||
) : transaction.confirmed === 2 ? (
|
||||
) : transaction.confirmed === 2 && !transaction.is_paid ? (
|
||||
"Sedang Diproses"
|
||||
) : transaction.confirmed === 3 ? (
|
||||
) : transaction.confirmed === 3 || transaction.is_paid ? (
|
||||
"Transaksi Sukses"
|
||||
) : (
|
||||
"Silahkan Cek Ketersediaan"
|
||||
@@ -294,11 +372,28 @@ const searchAndAggregateItems = (transactions, searchTerm) => {
|
||||
<ul>
|
||||
{transaction.DetailedTransactions.map((detail) => (
|
||||
<li key={detail.detailedTransactionId}>
|
||||
<span>{detail.Item.name}</span> - {detail.qty < 1 ? 'tidak tersedia' : `${detail.qty} x Rp
|
||||
${detail.promoPrice ? detail.promoPrice : detail.price}`}
|
||||
<span>{detail.Item.name}</span> - {detail.qty < 1
|
||||
? 'tidak tersedia'
|
||||
: `${detail.qty} x ${formatRupiah(detail.promoPrice || detail.price)}`
|
||||
}
|
||||
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
{!transaction.is_paid && transaction.confirmed > -1 &&
|
||||
<div
|
||||
onClick={() => {
|
||||
localStorage.setItem('lastTransaction', JSON.stringify(transaction));
|
||||
setModal("message", { captMessage: 'Silahkan tambahkan pesanan', descMessage: 'Pembayaran akan ditambahkan ke transaksi sebelumnya.' }, null, null);
|
||||
|
||||
// Dispatch the custom event
|
||||
window.dispatchEvent(new Event("localStorageUpdated"));
|
||||
}}
|
||||
className={styles["addNewItem"]}
|
||||
>
|
||||
Tambah pesanan
|
||||
</div>
|
||||
}
|
||||
<h2 className={styles["Transactions-detail"]}>
|
||||
{transaction.serving_type === "pickup"
|
||||
? "Self pickup"
|
||||
@@ -326,38 +421,47 @@ const searchAndAggregateItems = (transactions, searchTerm) => {
|
||||
<div className={styles.TotalContainer}>
|
||||
<span>Total:</span>
|
||||
<span>
|
||||
Rp {calculateTotalPrice(transaction.DetailedTransactions)}
|
||||
{formatRupiah(calculateTotalPrice(transaction.DetailedTransactions))}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className={styles.TotalContainer}>
|
||||
{(deviceType == 'clerk' && (transaction.confirmed == 0 || transaction.confirmed == 1 || transaction.confirmed == 2)) &&
|
||||
<button
|
||||
className={styles.PayButton}
|
||||
onClick={() => handleConfirm(transaction.transactionId)}
|
||||
disabled={isPaymentLoading}
|
||||
>
|
||||
{
|
||||
isPaymentLoading ? (
|
||||
<ColorRing height="50" width="50" color="white" />
|
||||
) : transaction.confirmed === 1 ? (
|
||||
"Konfirmasi Telah Bayar"
|
||||
) : transaction.confirmed === 2 ? (
|
||||
"Confirm item is ready"
|
||||
) : (
|
||||
"Confirm availability"
|
||||
)
|
||||
}
|
||||
{(deviceType == 'clerk' && !transaction.is_paid && (transaction.confirmed == 0 || transaction.confirmed == 1 || transaction.confirmed == 2)) &&
|
||||
<>
|
||||
<button
|
||||
className={styles.PayButton}
|
||||
onClick={() => handleConfirm(transaction.transactionId)}
|
||||
disabled={isPaymentLoading}
|
||||
>
|
||||
{
|
||||
isPaymentLoading ? (
|
||||
<ColorRing height="50" width="50" color="white" />
|
||||
) : transaction.confirmed === 1 ? (
|
||||
"Konfirmasi Telah Bayar"
|
||||
) : transaction.confirmed === 2 ? (
|
||||
"Confirm item is ready"
|
||||
) : (
|
||||
"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' &&
|
||||
<ButtonWithReplica
|
||||
paymentUrl={paymentUrl}
|
||||
price={
|
||||
"Rp" + calculateTotalPrice(transaction.DetailedTransactions)
|
||||
}
|
||||
price={formatRupiah(calculateTotalPrice(transaction.DetailedTransactions))}
|
||||
disabled={isPaymentLoading}
|
||||
isPaymentLoading={isPaymentLoading}
|
||||
handleClick={() => handleConfirm(transaction.transactionId)}
|
||||
|
||||
@@ -395,4 +395,66 @@
|
||||
text-align: center;
|
||||
color: white;
|
||||
line-height: 27px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Transactions.module.css */
|
||||
|
||||
.printContainer {
|
||||
display: none; /* Hidden in normal view */
|
||||
}
|
||||
|
||||
|
||||
@media print {
|
||||
@page {
|
||||
size: portrait;
|
||||
margin: 15mm;
|
||||
}
|
||||
|
||||
body * {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
#print-section,
|
||||
#print-section * {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
#print-section {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
padding: 15mm;
|
||||
background-color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.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