diff --git a/src/App.js b/src/App.js
index 92e49ae..ceb1dc5 100644
--- a/src/App.js
+++ b/src/App.js
@@ -343,6 +343,7 @@ function App() {
diff --git a/src/components/TableList.js b/src/components/TableList.js
index 523355d..ffabe4d 100644
--- a/src/components/TableList.js
+++ b/src/components/TableList.js
@@ -9,10 +9,17 @@ const TableList = ({ shop, tables, onSelectTable, selectedTable }) => {
const [bgImageUrl, setBgImageUrl] = useState(shop.qrBackground);
const shopUrl = window.location.hostname + "/" + shop.cafeId;
- const generateQRCodeUrl = (tableCode) =>
- `https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${encodeURIComponent(
- shopUrl + "/" + tableCode
- )}`;
+ const generateQRCodeUrl = (tableCode) => {
+ if (tableCode != null) {
+ return `https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${encodeURIComponent(
+ shopUrl + "/" + tableCode
+ )}`;
+ } else {
+ return `https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${encodeURIComponent(
+ shopUrl
+ )}`;
+ }
+ };
const handleBackgroundUrlChange = (newUrl) => {
setBgImageUrl(newUrl);
@@ -57,7 +64,7 @@ const TableList = ({ shop, tables, onSelectTable, selectedTable }) => {
handleQrSave={handleQrSave}
setInitialPos={setInitialPos}
setInitialSize={setInitialSize}
- qrCodeUrl={generateQRCodeUrl("sample")}
+ qrCodeUrl={generateQRCodeUrl("")}
backgroundUrl={bgImageUrl}
initialQrPosition={initialPos}
initialQrSize={initialSize}
diff --git a/src/helpers/transactionHelpers.js b/src/helpers/transactionHelpers.js
index 703be3f..1fa44a8 100644
--- a/src/helpers/transactionHelpers.js
+++ b/src/helpers/transactionHelpers.js
@@ -50,6 +50,30 @@ export async function declineTransaction(transactionId) {
}
}
+export async function cancelTransaction(transactionId) {
+ try {
+ console.log(transactionId);
+ const token = getLocalStorage("auth");
+ const response = await fetch(
+ `${API_BASE_URL}/transaction/claim-transaction/${transactionId}`,
+ {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${token}`,
+ },
+ }
+ );
+
+ if (!response.ok) {
+ return false;
+ }
+
+ return true;
+ } catch (error) {
+ console.error("Error:", error);
+ }
+}
export async function handleClaimHasPaid(transactionId) {
try {
console.log(transactionId);
@@ -264,6 +288,7 @@ export const handlePaymentFromGuestDevice = async (
payment_type,
serving_type,
tableNo,
+ notes,
socketId
) => {
try {
@@ -291,6 +316,7 @@ export const handlePaymentFromGuestDevice = async (
serving_type,
tableNo,
transactions: structuredItems,
+ notes: notes,
socketId,
}),
}
diff --git a/src/pages/Cart copy.js b/src/pages/Cart copy.js
new file mode 100644
index 0000000..5818453
--- /dev/null
+++ b/src/pages/Cart copy.js
@@ -0,0 +1,266 @@
+import React, { useRef, useEffect, useState } from "react";
+import styles from "./Cart.module.css";
+import ItemLister from "../components/ItemLister";
+import { ThreeDots, ColorRing } from "react-loader-spinner";
+import { useParams } from "react-router-dom";
+import { useNavigationHelpers } from "../helpers/navigationHelpers";
+import { getTable } from "../helpers/tableHelper.js";
+import { getCartDetails } from "../helpers/itemHelper.js";
+import { getItemsByCafeId } from "../helpers/cartHelpers"; // Import getItemsByCafeId
+import Modal from "../components/Modal"; // Import the reusable Modal component
+
+export default function Cart({
+ table,
+ sendParam,
+ totalItemsCount,
+ deviceType,
+}) {
+ const { shopId, tableCode } = useParams();
+ sendParam({ shopId, tableCode });
+
+ const { goToShop, goToInvoice } = useNavigationHelpers(shopId, tableCode);
+ const [cartItems, setCartItems] = useState([]);
+ const [totalPrice, setTotalPrice] = useState(0);
+ const [orderType, setOrderType] = useState("serve");
+ const [tableNumber, setTableNumber] = useState("");
+ const [loading, setLoading] = useState(true);
+ const [isModalOpen, setIsModalOpen] = useState(false);
+ const [modalContent, setModalContent] = useState(null);
+ const [isCheckoutLoading, setIsCheckoutLoading] = useState(false); // State for checkout button loading animation
+ const [email, setEmail] = useState("");
+
+ const textareaRef = useRef(null);
+
+ useEffect(() => {
+ const fetchCartItems = async () => {
+ try {
+ setLoading(true);
+ const items = await getCartDetails(shopId);
+ setLoading(false);
+
+ if (items) setCartItems(items);
+
+ const initialTotalPrice = items.reduce((total, itemType) => {
+ return (
+ total +
+ itemType.itemList.reduce((subtotal, item) => {
+ return subtotal + item.qty * item.price;
+ }, 0)
+ );
+ }, 0);
+ setTotalPrice(initialTotalPrice);
+ } catch (error) {
+ console.error("Error fetching cart items:", error);
+ }
+ };
+
+ fetchCartItems();
+
+ const textarea = textareaRef.current;
+ if (textarea) {
+ const handleResize = () => {
+ textarea.style.height = "auto";
+ textarea.style.height = `${textarea.scrollHeight}px`;
+ };
+ textarea.addEventListener("input", handleResize);
+ handleResize();
+ return () => textarea.removeEventListener("input", handleResize);
+ }
+ }, [shopId]);
+
+ useEffect(() => {
+ const textarea = textareaRef.current;
+
+ if (textarea) {
+ const handleResize = () => {
+ textarea.style.height = "auto";
+ textarea.style.height = `${textarea.scrollHeight}px`;
+ };
+
+ handleResize(); // Initial resize
+
+ textarea.addEventListener("input", handleResize);
+ return () => textarea.removeEventListener("input", handleResize);
+ }
+ }, [textareaRef.current]);
+
+ const refreshTotal = async () => {
+ try {
+ const items = await getItemsByCafeId(shopId);
+ const updatedTotalPrice = items.reduce((total, localItem) => {
+ const cartItem = cartItems.find((itemType) =>
+ itemType.itemList.some((item) => item.itemId === localItem.itemId)
+ );
+
+ if (cartItem) {
+ const itemDetails = cartItem.itemList.find(
+ (item) => item.itemId === localItem.itemId
+ );
+ return total + localItem.qty * itemDetails.price;
+ }
+ return total;
+ }, 0);
+
+ setTotalPrice(updatedTotalPrice);
+ } catch (error) {
+ console.error("Error refreshing total price:", error);
+ }
+ };
+
+ const handleOrderTypeChange = (event) => {
+ setOrderType(event.target.value);
+ };
+
+ const handleTableNumberChange = (event) => {
+ setTableNumber(event.target.value);
+ };
+
+ const handleEmailChange = (event) => {
+ setEmail(event.target.value);
+ };
+
+ const handlCloseModal = () => {
+ setIsModalOpen(false);
+ setIsCheckoutLoading(false);
+ };
+
+ const isValidEmail = (email) => {
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
+ return emailRegex.test(email);
+ };
+
+ const handleCheckout = async () => {
+ setIsCheckoutLoading(true); // Start loading animation
+
+ if (email != "" && !isValidEmail(email)) {
+ setModalContent(
Please enter a valid email address.
);
+ setIsModalOpen(true);
+ setIsCheckoutLoading(false); // Stop loading animation
+ return;
+ }
+
+ if (orderType === "serve") {
+ console.log("serve");
+ if (tableNumber !== "" && table.tableNo == undefined) {
+ console.log("getting with tableNumber");
+ const table = await getTable(shopId, tableNumber);
+ if (!table) {
+ setModalContent(
+ Table not found. Please enter a valid table number.
+ );
+ setIsModalOpen(true);
+ } else {
+ goToInvoice(orderType, table.tableNo, email);
+ }
+ } else if (table.tableNo != undefined) {
+ console.log("getting with table code" + table.tableNo);
+ goToInvoice(orderType, null, email);
+ } else {
+ setModalContent(Please enter a table number.
);
+ setIsModalOpen(true);
+ }
+ } else {
+ console.log("getting with pickup");
+ goToInvoice(orderType, tableNumber, email);
+ }
+
+ setIsCheckoutLoading(false); // Stop loading animation
+ };
+
+ if (loading)
+ return (
+
+ );
+ else
+ return (
+
+
+
+ {totalItemsCount} {totalItemsCount !== 1 ? "items" : "item"} in Cart
+
+
+ {cartItems.map((itemType) => (
+
+ ))}
+ {deviceType != "guestDevice" && (
+
+
+
+
+ )}
+
+ Order Type:
+
+ {orderType === "serve" && table.length < 1 && (
+
+ )}
+
+
+
+ Note
+
+
+
+
+
+
+ Total:
+ Rp {totalPrice}
+
+
+
+ Back to menu
+
+
+
handlCloseModal()}>
+ {modalContent}
+
+
+ );
+}
diff --git a/src/pages/Cart.js b/src/pages/Cart.js
index 5818453..4a0a250 100644
--- a/src/pages/Cart.js
+++ b/src/pages/Cart.js
@@ -1,46 +1,44 @@
import React, { useRef, useEffect, useState } from "react";
-import styles from "./Cart.module.css";
-import ItemLister from "../components/ItemLister";
+import styles from "./Invoice.module.css";
+import { useParams, useLocation } from "react-router-dom"; // Changed from useSearchParams to useLocation
import { ThreeDots, ColorRing } from "react-loader-spinner";
-import { useParams } from "react-router-dom";
-import { useNavigationHelpers } from "../helpers/navigationHelpers";
-import { getTable } from "../helpers/tableHelper.js";
-import { getCartDetails } from "../helpers/itemHelper.js";
-import { getItemsByCafeId } from "../helpers/cartHelpers"; // Import getItemsByCafeId
-import Modal from "../components/Modal"; // Import the reusable Modal component
-export default function Cart({
- table,
- sendParam,
- totalItemsCount,
- deviceType,
-}) {
+import ItemLister from "../components/ItemLister";
+import { getCartDetails } from "../helpers/itemHelper";
+import {
+ handlePaymentFromClerk,
+ handlePaymentFromGuestSide,
+ handlePaymentFromGuestDevice,
+} from "../helpers/transactionHelpers";
+
+export default function Invoice({ table, sendParam, deviceType, socket }) {
const { shopId, tableCode } = useParams();
sendParam({ shopId, tableCode });
- const { goToShop, goToInvoice } = useNavigationHelpers(shopId, tableCode);
+ const location = useLocation(); // Use useLocation hook instead of useSearchParams
+ const searchParams = new URLSearchParams(location.search); // Pass location.search directly
+
+ // const email = searchParams.get("email");
+ // const orderType = searchParams.get("orderType");
+ // const tableNumber = searchParams.get("tableNumber");
+
const [cartItems, setCartItems] = useState([]);
const [totalPrice, setTotalPrice] = useState(0);
- const [orderType, setOrderType] = useState("serve");
- const [tableNumber, setTableNumber] = useState("");
- const [loading, setLoading] = useState(true);
- const [isModalOpen, setIsModalOpen] = useState(false);
- const [modalContent, setModalContent] = useState(null);
- const [isCheckoutLoading, setIsCheckoutLoading] = useState(false); // State for checkout button loading animation
- const [email, setEmail] = useState("");
+ const [isPaymentLoading, setIsPaymentLoading] = useState(false); // State for payment button loading animation
const textareaRef = useRef(null);
+ const [orderType, setOrderType] = useState("serve");
+ const [tableNumber, setTableNumber] = useState("");
+ const [email, setEmail] = useState("");
useEffect(() => {
const fetchCartItems = async () => {
try {
- setLoading(true);
const items = await getCartDetails(shopId);
- setLoading(false);
+ setCartItems(items);
- if (items) setCartItems(items);
-
- const initialTotalPrice = items.reduce((total, itemType) => {
+ // Calculate total price based on fetched cart items
+ const totalPrice = items.reduce((total, itemType) => {
return (
total +
itemType.itemList.reduce((subtotal, item) => {
@@ -48,26 +46,51 @@ export default function Cart({
}, 0)
);
}, 0);
- setTotalPrice(initialTotalPrice);
+ setTotalPrice(totalPrice);
} catch (error) {
console.error("Error fetching cart items:", error);
+ // Handle error if needed
}
};
fetchCartItems();
-
- const textarea = textareaRef.current;
- if (textarea) {
- const handleResize = () => {
- textarea.style.height = "auto";
- textarea.style.height = `${textarea.scrollHeight}px`;
- };
- textarea.addEventListener("input", handleResize);
- handleResize();
- return () => textarea.removeEventListener("input", handleResize);
- }
}, [shopId]);
+ const handlePay = async (isCash) => {
+ setIsPaymentLoading(true);
+ console.log("tipe" + deviceType);
+ if (deviceType == "clerk") {
+ const pay = await handlePaymentFromClerk(
+ shopId,
+ email,
+ isCash ? "cash" : "cashless",
+ orderType,
+ tableNumber
+ );
+ } else if (deviceType == "guestSide") {
+ const pay = await handlePaymentFromGuestSide(
+ shopId,
+ email,
+ isCash ? "cash" : "cashless",
+ orderType,
+ tableNumber
+ );
+ } else if (deviceType == "guestDevice") {
+ const socketId = socket.id;
+ const pay = await handlePaymentFromGuestDevice(
+ shopId,
+ isCash ? "cash" : "cashless",
+ orderType,
+ table.tableNo || tableNumber,
+ textareaRef.current.value,
+ socketId
+ );
+ }
+
+ console.log("transaction from " + deviceType + "success");
+ setIsPaymentLoading(false);
+ };
+
useEffect(() => {
const textarea = textareaRef.current;
@@ -84,29 +107,6 @@ export default function Cart({
}
}, [textareaRef.current]);
- const refreshTotal = async () => {
- try {
- const items = await getItemsByCafeId(shopId);
- const updatedTotalPrice = items.reduce((total, localItem) => {
- const cartItem = cartItems.find((itemType) =>
- itemType.itemList.some((item) => item.itemId === localItem.itemId)
- );
-
- if (cartItem) {
- const itemDetails = cartItem.itemList.find(
- (item) => item.itemId === localItem.itemId
- );
- return total + localItem.qty * itemDetails.price;
- }
- return total;
- }, 0);
-
- setTotalPrice(updatedTotalPrice);
- } catch (error) {
- console.error("Error refreshing total price:", error);
- }
- };
-
const handleOrderTypeChange = (event) => {
setOrderType(event.target.value);
};
@@ -118,94 +118,22 @@ export default function Cart({
const handleEmailChange = (event) => {
setEmail(event.target.value);
};
-
- const handlCloseModal = () => {
- setIsModalOpen(false);
- setIsCheckoutLoading(false);
- };
-
- const isValidEmail = (email) => {
- const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
- return emailRegex.test(email);
- };
-
- const handleCheckout = async () => {
- setIsCheckoutLoading(true); // Start loading animation
-
- if (email != "" && !isValidEmail(email)) {
- setModalContent(Please enter a valid email address.
);
- setIsModalOpen(true);
- setIsCheckoutLoading(false); // Stop loading animation
- return;
- }
-
- if (orderType === "serve") {
- console.log("serve");
- if (tableNumber !== "" && table.tableNo == undefined) {
- console.log("getting with tableNumber");
- const table = await getTable(shopId, tableNumber);
- if (!table) {
- setModalContent(
- Table not found. Please enter a valid table number.
- );
- setIsModalOpen(true);
- } else {
- goToInvoice(orderType, table.tableNo, email);
- }
- } else if (table.tableNo != undefined) {
- console.log("getting with table code" + table.tableNo);
- goToInvoice(orderType, null, email);
- } else {
- setModalContent(Please enter a table number.
);
- setIsModalOpen(true);
- }
- } else {
- console.log("getting with pickup");
- goToInvoice(orderType, tableNumber, email);
- }
-
- setIsCheckoutLoading(false); // Stop loading animation
- };
-
- if (loading)
- return (
-
- );
- else
- return (
-
-
-
- {totalItemsCount} {totalItemsCount !== 1 ? "items" : "item"} in Cart
-
-
+ return (
+
+
+
Cart
+
+
{cartItems.map((itemType) => (
))}
- {deviceType != "guestDevice" && (
-
-
-
-
- )}
+
Order Type:
+ {orderType === "serve" && table.length < 1 && (
+
+ Serve to:
- )}
-
+
+ )}
- Note
+ Note :
-
-
+
+
+
Total:
Rp {totalPrice}
-
+
+
+ Payment Option
+
+
+
-
- Back to menu
+
handlePay(true)}>
+ {isPaymentLoading ? (
+
+ ) : (
+ "Cash"
+ )}
-
-
handlCloseModal()}>
- {modalContent}
-
- );
+
+
+ );
}
diff --git a/src/pages/Invoice.module.css b/src/pages/Invoice.module.css
index 59a069f..894ea1d 100644
--- a/src/pages/Invoice.module.css
+++ b/src/pages/Invoice.module.css
@@ -118,3 +118,54 @@
margin: 26px;
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;
+}
+
+.NoteInput {
+ width: 78vw;
+ height: 12vw;
+ border-radius: 20px;
+ margin: 0 auto;
+ padding: 10px;
+ font-size: 1.2em;
+ border: 1px solid rgba(88, 55, 50, 0.5);
+ margin-bottom: 27px;
+ resize: none; /* Prevent resizing */
+ overflow-wrap: break-word; /* Ensure text wraps */
+}
diff --git a/src/pages/Payment_claimed.js b/src/pages/Payment_claimed.js
index 0523085..31cb0a3 100644
--- a/src/pages/Payment_claimed.js
+++ b/src/pages/Payment_claimed.js
@@ -1,16 +1,23 @@
-import React, { useState, useEffect, useRef } from "react";
-import { ColorRing } from "react-loader-spinner";
-import jsQR from "jsqr";
-import QRCode from "qrcode.react";
+import React, { useEffect, useState } from "react";
import styles from "./Transactions.module.css";
-import { getImageUrl } from "../helpers/itemHelper";
-import { useSearchParams } from "react-router-dom";
+import { useParams } from "react-router-dom";
+import { ColorRing } from "react-loader-spinner";
import {
getTransaction,
- handleConfirmHasPaid,
+ confirmTransaction,
+ declineTransaction,
} from "../helpers/transactionHelpers";
+import { getTables } from "../helpers/tableHelper";
+import TableCanvas from "../components/TableCanvas";
+import { useSearchParams } from "react-router-dom";
-export default function Transaction_pending() {
+export default function Transactions({ propsShopId, sendParam, deviceType }) {
+ const { shopId, tableId } = useParams();
+ if (sendParam) sendParam({ shopId, tableId });
+
+ const [tables, setTables] = useState([]);
+ const [selectedTable, setSelectedTable] = useState(null);
+ const [isPaymentLoading, setIsPaymentLoading] = useState(false);
const [searchParams] = useSearchParams();
const [transaction, setTransaction] = useState(null);
@@ -29,31 +36,75 @@ export default function Transaction_pending() {
fetchData();
}, [searchParams]);
- const calculateTotalPrice = (detailedTransactions) => {
- if (!Array.isArray(detailedTransactions)) return 0;
-
- return detailedTransactions.reduce((total, dt) => {
- if (
- dt.Item &&
- typeof dt.Item.price === "number" &&
- typeof dt.qty === "number"
- ) {
- return total + dt.Item.price * dt.qty;
+ useEffect(() => {
+ const fetchData = async () => {
+ try {
+ const fetchedTables = await getTables(shopId || propsShopId);
+ setTables(fetchedTables);
+ } catch (error) {
+ console.error("Error fetching tables:", error);
}
- return total;
+ };
+
+ fetchData();
+ }, [shopId || propsShopId]);
+
+ const calculateTotalPrice = (detailedTransactions) => {
+ return detailedTransactions.reduce((total, dt) => {
+ return total + dt.qty * dt.Item.price;
}, 0);
};
+ const handleConfirm = async (transactionId) => {
+ if (isPaymentLoading) return;
+ setIsPaymentLoading(true);
+ try {
+ const c = await confirmTransaction(transactionId);
+ if (c) {
+ setTransaction({ ...transaction, confirmed: c.confirmed });
+ }
+ } catch (error) {
+ console.error("Error processing payment:", error);
+ } finally {
+ setIsPaymentLoading(false);
+ }
+ };
+
+ const handleDecline = async (transactionId) => {
+ if (isPaymentLoading) return;
+ setIsPaymentLoading(true);
+ try {
+ const c = await declineTransaction(transactionId);
+ // if (c) {
+ // // Update the confirmed status locally
+ // setTransactions((prevTransactions) =>
+ // prevTransactions.map((transaction) =>
+ // transaction.transactionId === transactionId
+ // ? { ...transaction, confirmed: -1 } // Set to confirmed
+ // : transaction
+ // )
+ // );
+ // }
+ } catch (error) {
+ console.error("Error processing payment:", error);
+ } finally {
+ setIsPaymentLoading(false);
+ }
+ };
+
return (
-
Payment Claimed
-
+
Transactions
+ {/*
*/}
{transaction && (
+ setSelectedTable(transaction.Table || { tableId: 0 })
+ }
>
Transaction ID: {transaction.transactionId}
@@ -76,6 +127,22 @@ export default function Transaction_pending() {
transaction.Table ? transaction.Table.tableNo : "N/A"
}`}
+ {transaction.notes != null && (
+ <>
+
+ Note :
+
+
+
+
+
+
+ >
+ )}
Total:
@@ -85,11 +152,31 @@ export default function Transaction_pending() {
+ onClick={() => handleConfirm(transaction.transactionId)}
+ disabled={isPaymentLoading} // Disable button if confirmed (1) or declined (-1) or loading
+ >
+ {isPaymentLoading ? (
+
+ ) : transaction.confirmed === 1 ? (
+ "Confirm has paid" // Display "Confirm has paid" if the transaction is confirmed (1)
+ ) : transaction.confirmed === -1 ? (
+ "Declined" // Display "Declined" if the transaction is declined (-1)
+ ) : transaction.confirmed === 2 ? (
+ "Confirm item has ready" // Display "Item ready" if the transaction is ready (2)
+ ) : transaction.confirmed === 3 ? (
+ "Transaction success" // Display "Item ready" if the transaction is ready (2)
+ ) : (
+ "Confirm availability" // Display "Confirm availability" if the transaction is not confirmed (0)
+ )}
+
{transaction.confirmed == 0 && (
- decline
+ handleDecline(transaction.transactionId)}
+ >
+ decline
+
)}
)}
diff --git a/src/pages/Transaction.js b/src/pages/Transaction.js
index 2bdd9a2..cc1f7e7 100644
--- a/src/pages/Transaction.js
+++ b/src/pages/Transaction.js
@@ -1,4 +1,4 @@
-import React, { useEffect, useState } from "react";
+import React, { useRef, useEffect, useState } from "react";
import styles from "./Transactions.module.css";
import { useParams } from "react-router-dom";
import { ColorRing } from "react-loader-spinner";
@@ -20,6 +20,7 @@ export default function Transactions({ propsShopId, sendParam, deviceType }) {
const [isPaymentLoading, setIsPaymentLoading] = useState(false);
const [searchParams] = useSearchParams();
const [transaction, setTransaction] = useState(null);
+ const noteRef = useRef(null);
useEffect(() => {
const transactionId = searchParams.get("transactionId") || "";
@@ -92,11 +93,23 @@ export default function Transactions({ propsShopId, sendParam, deviceType }) {
}
};
+ const autoResizeTextArea = (textarea) => {
+ if (textarea) {
+ textarea.style.height = "auto"; // Reset height
+ textarea.style.height = `${textarea.scrollHeight}px`; // Set new height
+ }
+ };
+
+ useEffect(() => {
+ if (noteRef.current) {
+ autoResizeTextArea(noteRef.current);
+ }
+ }, [transaction?.notes]);
+
return (
Transactions
-
{/*
*/}
{transaction && (
@@ -128,6 +141,23 @@ export default function Transactions({ propsShopId, sendParam, deviceType }) {
transaction.Table ? transaction.Table.tableNo : "N/A"
}`}
+ {transaction.notes != "" && (
+ <>
+
+ Note :
+
+
+
+
+
+
+ >
+ )}
Total:
diff --git a/src/pages/Transaction_confirmed.js b/src/pages/Transaction_confirmed.js
index f3d6ede..f1a0c81 100644
--- a/src/pages/Transaction_confirmed.js
+++ b/src/pages/Transaction_confirmed.js
@@ -1,21 +1,27 @@
-import React, { useState, useEffect, useRef } from "react";
-import { ColorRing } from "react-loader-spinner";
-import jsQR from "jsqr";
-import QRCode from "qrcode.react";
+import React, { useRef, useEffect, useState } from "react";
import styles from "./Transactions.module.css";
-import { getImageUrl } from "../helpers/itemHelper";
-import { useSearchParams } from "react-router-dom";
+import { useParams } from "react-router-dom";
+import { ColorRing } from "react-loader-spinner";
import {
- handleClaimHasPaid,
getTransaction,
+ confirmTransaction,
+ declineTransaction,
+ cancelTransaction,
} from "../helpers/transactionHelpers";
-import html2canvas from "html2canvas";
+import { getTables } from "../helpers/tableHelper";
+import TableCanvas from "../components/TableCanvas";
+import { useSearchParams } from "react-router-dom";
-export default function Transaction_pending({ paymentUrl }) {
+export default function Transactions({ propsShopId, sendParam, deviceType }) {
+ const { shopId, tableId } = useParams();
+ if (sendParam) sendParam({ shopId, tableId });
+
+ const [tables, setTables] = useState([]);
+ const [selectedTable, setSelectedTable] = useState(null);
+ const [isPaymentLoading, setIsPaymentLoading] = useState(false);
const [searchParams] = useSearchParams();
- const [qrData, setQrData] = useState(null);
const [transaction, setTransaction] = useState(null);
- const qrCodeRef = useRef(null);
+ const noteRef = useRef(null);
useEffect(() => {
const transactionId = searchParams.get("transactionId") || "";
@@ -24,7 +30,7 @@ export default function Transaction_pending({ paymentUrl }) {
try {
const fetchedTransaction = await getTransaction(transactionId);
setTransaction(fetchedTransaction);
- console.log(fetchedTransaction);
+ console.log(transaction);
} catch (error) {
console.error("Error fetching transaction:", error);
}
@@ -33,160 +39,151 @@ export default function Transaction_pending({ paymentUrl }) {
}, [searchParams]);
useEffect(() => {
- const detectQRCode = async () => {
- if (paymentUrl) {
- const img = new Image();
- img.crossOrigin = "Anonymous"; // Handle CORS if needed
- img.src = getImageUrl(paymentUrl);
-
- img.onload = () => {
- const canvas = document.createElement("canvas");
- const context = canvas.getContext("2d");
- canvas.width = img.width;
- canvas.height = img.height;
-
- // Draw image on canvas
- context.drawImage(img, 0, 0, img.width, img.height);
-
- // Get image data
- const imageData = context.getImageData(
- 0,
- 0,
- canvas.width,
- canvas.height
- );
- const qrCode = jsQR(imageData.data, canvas.width, canvas.height);
-
- if (qrCode) {
- setQrData(qrCode.data); // Set the QR data
- console.log(qrCode.data);
- } else {
- console.log("No QR Code detected");
- }
- };
+ const fetchData = async () => {
+ try {
+ const fetchedTables = await getTables(shopId || propsShopId);
+ setTables(fetchedTables);
+ } catch (error) {
+ console.error("Error fetching tables:", error);
}
};
- detectQRCode();
- }, [paymentUrl]);
+ fetchData();
+ }, [shopId || propsShopId]);
const calculateTotalPrice = (detailedTransactions) => {
- if (!Array.isArray(detailedTransactions)) return 0;
-
return detailedTransactions.reduce((total, dt) => {
- if (
- dt.Item &&
- typeof dt.Item.price === "number" &&
- typeof dt.qty === "number"
- ) {
- return total + dt.Item.price * dt.qty;
- }
- return total;
+ return total + dt.qty * dt.Item.price;
}, 0);
};
- const downloadQRCode = async () => {
- if (qrCodeRef.current) {
- try {
- const canvas = await html2canvas(qrCodeRef.current);
- const link = document.createElement("a");
- link.href = canvas.toDataURL("image/png");
- link.download = "qr-code.png";
- link.click();
- } catch (error) {
- console.error("Error downloading QR Code:", error);
+ const handleConfirm = async (transactionId) => {
+ if (isPaymentLoading) return;
+ setIsPaymentLoading(true);
+ try {
+ const c = await confirmTransaction(transactionId);
+ if (c) {
+ setTransaction({ ...transaction, confirmed: c.confirmed });
}
- } else {
- console.log("QR Code element not found.");
+ } catch (error) {
+ console.error("Error processing payment:", error);
+ } finally {
+ setIsPaymentLoading(false);
}
};
+ const handleDecline = async (transactionId) => {
+ if (isPaymentLoading) return;
+ setIsPaymentLoading(true);
+ try {
+ const c = await cancelTransaction(transactionId);
+ } catch (error) {
+ console.error("Error processing payment:", error);
+ } finally {
+ setIsPaymentLoading(false);
+ }
+ };
+
+ const autoResizeTextArea = (textarea) => {
+ if (textarea) {
+ textarea.style.height = "auto"; // Reset height
+ textarea.style.height = `${textarea.scrollHeight}px`; // Set new height
+ }
+ };
+
+ useEffect(() => {
+ if (noteRef.current) {
+ autoResizeTextArea(noteRef.current);
+ }
+ }, [transaction?.notes]);
+
return (
-
Transaction Confirmed
-
+
Transactions
+ {/*
*/}
-
- {qrData ? (
-
-
- {/* Generate QR code */}
-
-
-
- ) : (
-
-
-
Loading QR Code Data...
-
- )}
+ {transaction && (
+
+ setSelectedTable(transaction.Table || { tableId: 0 })
+ }
+ >
+
+ Transaction ID: {transaction.transactionId}
+
+
+ Payment Type: {transaction.payment_type}
+
+
+ {transaction.DetailedTransactions.map((detail) => (
+ -
+ {detail.Item.name} - {detail.qty} x Rp{" "}
+ {detail.Item.price}
+
+ ))}
+
+
+ {transaction.serving_type === "pickup"
+ ? "Self pickup"
+ : `Serve to ${
+ transaction.Table ? transaction.Table.tableNo : "N/A"
+ }`}
+
+ {transaction.notes != "" && (
+ <>
+
+ Note :
+
+
- {transaction && transaction.DetailedTransactions ? (
-
+
+
+
+ >
+ )}
+
Total:
- Rp{" "}
- {calculateTotalPrice(
- transaction.DetailedTransactions
- ).toLocaleString()}
+ Rp {calculateTotalPrice(transaction.DetailedTransactions)}
- ) : (
-
-
-
Loading Transaction Data...
+
+
- )}
-
-
-
-
+
handleDecline(transaction.transactionId)}
+ >
+ cancel
+
+
+ )}
);
diff --git a/src/pages/Transactions.module.css b/src/pages/Transactions.module.css
index 9dcecd6..31118f6 100644
--- a/src/pages/Transactions.module.css
+++ b/src/pages/Transactions.module.css
@@ -90,3 +90,29 @@
.expression {
width: 100%;
}
+
+.Note {
+ text-align: left;
+ color: rgba(88, 55, 50, 1);
+ font-size: 1em;
+ cursor: pointer;
+}
+
+.NoteContainer {
+ display: flex;
+ justify-content: space-between;
+ margin-left: 20px;
+ font-size: 1em;
+ margin-bottom: 15px;
+}
+
+.NoteInput {
+ height: 12vw;
+ border-radius: 20px;
+ margin: 0 auto;
+ padding: 10px;
+ font-size: 1.2em;
+ border: 1px solid rgba(88, 55, 50, 0.5);
+ resize: none; /* Prevent resizing */
+ overflow-wrap: break-word; /* Ensure text wraps */
+}