From 9c3a14366cdbb292ef76f716828aa7b8aa47e203 Mon Sep 17 00:00:00 2001 From: client perkafean Date: Mon, 12 Aug 2024 09:28:22 +0000 Subject: [PATCH] ok --- package-lock.json | 6 + package.json | 1 + src/App.js | 21 +++- src/components/Header.js | 118 ++++++++++-------- src/components/ItemType.js | 22 +++- src/components/ItemType.module.css | 12 +- src/components/ItemTypeLister.js | 7 +- src/components/Modal.js | 20 ++- src/components/PaymentOptions.js | 165 +++++++++++++++++++++++++ src/components/QR.js | 86 +++++++------ src/components/TableList.js | 88 ++++++------- src/components/TablesPage.js | 13 +- src/config.js | 2 +- src/helpers/cafeHelpers.js | 93 +++++++++----- src/helpers/transactionHelpers.js | 49 ++++++++ src/pages/Transaction_confirmed.js | 190 +++++++++++++++++++++++++++++ src/pages/Transaction_failed.js | 2 +- src/pages/Transaction_pending.js | 15 ++- src/pages/Transactions.js | 10 +- src/pages/Transactions.module.css | 1 - 20 files changed, 725 insertions(+), 196 deletions(-) create mode 100644 src/components/PaymentOptions.js create mode 100644 src/pages/Transaction_confirmed.js diff --git a/package-lock.json b/package-lock.json index 598b0cd..4f4532e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", "html2canvas": "^1.4.1", + "jsqr": "^1.4.0", "qrcode.react": "^3.1.0", "react": "^18.3.1", "react-bootstrap": "^2.10.4", @@ -12528,6 +12529,11 @@ "node": ">=0.10.0" } }, + "node_modules/jsqr": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jsqr/-/jsqr-1.4.0.tgz", + "integrity": "sha512-dxLob7q65Xg2DvstYkRpkYtmKm2sPJ9oFhrhmudT1dZvNFFTlroai3AWSpLey/w5vMcLBXRgOJsbXpdN9HzU/A==" + }, "node_modules/jsx-ast-utils": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", diff --git a/package.json b/package.json index 62b3a14..1e91f63 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", "html2canvas": "^1.4.1", + "jsqr": "^1.4.0", "qrcode.react": "^3.1.0", "react": "^18.3.1", "react-bootstrap": "^2.10.4", diff --git a/src/App.js b/src/App.js index 6125ddd..b1ac7ec 100644 --- a/src/App.js +++ b/src/App.js @@ -123,9 +123,15 @@ function App() { //for guest socket.on("transaction_pending", async (data) => { console.log("transaction notification"); + // Call `setModal` with content and parameters setModal("transaction_pending"); }); + socket.on("transaction_confirmed", async (data) => { + console.log("transaction notification"); + setModal("transaction_confirmed", data); + }); + socket.on("transaction_success", async (data) => { console.log("transaction notification"); setModal("transaction_success"); @@ -207,11 +213,20 @@ function App() { }, [navigate]); // Function to open the modal - const setModal = (content) => { + const setModal = (content, params = {}) => { setIsModalOpen(true); setModalContent(content); + + // Prepare query parameters + const queryParams = new URLSearchParams({ + modal: content, + ...params, // Spread additional parameters + }).toString(); + // Update URL with new parameters + navigate(`?${queryParams}`, { replace: true }); + + // Prevent scrolling when modal is open document.body.style.overflow = "hidden"; - navigate(`?modal=` + content, { replace: true }); }; // Function to close the modal @@ -373,7 +388,7 @@ function App() { (props.animate === "grow" ? grow : shrink)} 0.5s forwards; - overflow: hidden; + overflow-y: auto; /* Enable vertical scrolling */ padding: 10px; box-sizing: border-box; `; @@ -246,6 +245,7 @@ const Header = ({ if (rectangleRef.current && !rectangleRef.current.contains(event.target)) { setAnimate("shrink"); setTimeout(() => setShowRectangle(false), 500); + rectangleRef.current.style.overflow = "hidden"; } }; @@ -304,7 +304,9 @@ const Header = ({ Click to login )} {user.username !== undefined && ( - setModal("edit_account")}>Edit + setModal("edit_account")}> + Edit profile + )} {shopId && user.userId == shopOwnerId && @@ -312,59 +314,77 @@ const Header = ({ user.roleId === 1 && ( <> see your other cafes - setModal("edit_tables")}> - {shopName} table maps - - - {shopName} clerks - setModal("craete_account_clerk")}> - + Add clerk - - {shopClerks && - shopClerks.map((key, index) => ( - - {shopClerks[index].username} - - - ))} - setModal("add_material")}> add material + setModal("update_stock")}> + update stock + + + {shopName} + setModal("edit_tables")}> + table maps + + + clerks + setModal("craete_account_clerk")}> + + Add clerk + + {shopClerks && + shopClerks.map((key, index) => ( + + {shopClerks[index].username} + + + ))} + + setModal("payment_option")}> + payment options + + )} {user.username !== undefined && - ((user.userId == shopOwnerId && user.roleId === 1) || - (user.cafeId == shopId && user.roleId === 2)) && ( - setModal("update_stock")}> - update stock - - )} - {user.username !== undefined && - user.roleId == 2 && - user.cafeId == shopId && ( + user.cafeId == shopId && + user.roleId === 2 && ( - connected guest sides - + Add guest side - {guestSides && - guestSides.map((key, index) => ( - - guest side {index + 1} - + {shopName} + setModal("update_stock")}> + update stock + + {user.username !== undefined && + user.roleId == 2 && + user.cafeId == shopId && ( + + connected guest sides + + + Add guest side + + {guestSides && + guestSides.map((key, index) => ( + + guest side {index + 1} + + + ))} - ))} + )} )} {user.username !== undefined && ( diff --git a/src/components/ItemType.js b/src/components/ItemType.js index 4d051d4..50067fa 100644 --- a/src/components/ItemType.js +++ b/src/components/ItemType.js @@ -58,12 +58,22 @@ export default function ItemType({ className={styles["item-type-image"]} /> {blank && ( - +
+ + Click to add image + +
)} {
{itemTypes && itemTypes.length > 1 && ( - )} {itemTypes && itemTypes.map( diff --git a/src/components/Modal.js b/src/components/Modal.js index 61e896a..223553a 100644 --- a/src/components/Modal.js +++ b/src/components/Modal.js @@ -1,15 +1,17 @@ import React from "react"; import styles from "./Modal.module.css"; import TablesPage from "./TablesPage.js"; +import PaymentOptions from "./PaymentOptions.js"; import TableMaps from "../components/TableMaps"; import Transactions from "../pages/Transactions"; import Transaction_pending from "../pages/Transaction_pending"; +import Transaction_confirmed from "../pages/Transaction_confirmed"; import Transaction_success from "../pages/Transaction_success"; import Transaction_failed from "../pages/Transaction_failed"; import MaterialList from "../pages/MaterialList.js"; import MaterialMutationsPage from "../pages/MaterialMutationsPage.js"; -const Modal = ({ shopId, isOpen, onClose, modalContent }) => { +const Modal = ({ shop, isOpen, onClose, modalContent }) => { if (!isOpen) return null; // Function to handle clicks on the overlay @@ -30,16 +32,24 @@ const Modal = ({ shopId, isOpen, onClose, modalContent }) => { - {modalContent === "edit_tables" && } + {modalContent === "edit_tables" && } {modalContent === "new_transaction" && ( - + )}{" "} {modalContent === "transaction_pending" && } + {modalContent === "transaction_confirmed" && ( + + )} {modalContent === "transaction_success" && } {modalContent === "transaction_failed" && } - {modalContent === "add_material" && } + {modalContent === "payment_option" && ( + + )} + {modalContent === "add_material" && ( + + )} {modalContent === "update_stock" && ( - + )}
diff --git a/src/components/PaymentOptions.js b/src/components/PaymentOptions.js new file mode 100644 index 0000000..64c6cd5 --- /dev/null +++ b/src/components/PaymentOptions.js @@ -0,0 +1,165 @@ +import React, { useState, useRef, useEffect } from "react"; +import jsQR from "jsqr"; // Import jsQR library +import { getImageUrl } from "../helpers/itemHelper"; +import { saveCafeDetails } from "../helpers/cafeHelpers"; // Import the helper function + +const SetPaymentQr = ({ + isConfigure, + tableNo, + qrCodeUrl, + paymentUrl, + initialQrPosition, + initialQrSize, + handleQrSave, + shopId, // Pass cafeId as a prop to identify which cafe to update +}) => { + const [qrPosition, setQrPosition] = useState(initialQrPosition); + const [qrSize, setQrSize] = useState(initialQrSize); + const [qrPayment, setQrPayment] = useState(getImageUrl(paymentUrl)); + const [qrCodeDetected, setQrCodeDetected] = useState(false); + const qrPaymentInputRef = useRef(null); + const overlayTextRef = useRef(null); + const qrCodeContainerRef = useRef(null); + + // Use useEffect to detect QR code after qrPayment updates + useEffect(() => { + if (qrPayment) { + detectQRCodeFromContainer(); + } + }, [qrPayment]); + + const handleFileChange = (e) => { + const file = e.target.files[0]; + if (file) { + const newqrPayment = URL.createObjectURL(file); // Create a temporary URL for display + setQrPayment(newqrPayment); + } + }; + + const detectQRCodeFromContainer = () => { + const container = qrCodeContainerRef.current; + const canvas = document.createElement("canvas"); + const context = canvas.getContext("2d"); + + // Create an image element to load the background image + const img = new Image(); + img.crossOrigin = "Anonymous"; // Handle CORS if needed + img.onload = () => { + // Set canvas size + canvas.width = container.offsetWidth; + canvas.height = container.offsetHeight; + + // Draw image on canvas + context.drawImage(img, 0, 0, canvas.width, canvas.height); + + const imageData = context.getImageData(0, 0, canvas.width, canvas.height); + const qrCode = jsQR(imageData.data, canvas.width, canvas.height); + + if (qrCode) { + setQrCodeDetected(true); + console.log("QR Code detected:", qrCode.data); + } else { + setQrCodeDetected(false); + console.log("No QR Code detected"); + } + }; + img.src = qrPayment; // Load the image URL + }; + + const handleSave = () => { + const qrPaymentFile = qrPaymentInputRef.current.files[0]; // Get the selected file for qrPayment + + // Prepare the details object + const details = { + qrPosition, + qrSize, + qrPaymentFile, // Include qrPayment file + }; + + // Call saveCafeDetails function with the updated details object + saveCafeDetails(shopId, details) + .then((response) => { + console.log("Cafe details saved:", response); + // handleQrSave(qrPosition, qrSize, qrPayment); + }) + .catch((error) => { + console.error("Error saving cafe details:", error); + }); + }; + + return ( +
+
+
qrPaymentInputRef.current.click()} + > + Click To Change Image +
+ +
+
+ {qrCodeDetected ?

QR Code Detected

:

No QR Code Detected

} +
+ +
+
+
+ ); +}; + +const styles = { + overlayText: { + position: "absolute", + top: "50%", + left: "50%", + transform: "translate(-50%, -50%)", + backgroundColor: "rgba(0, 0, 0, 0.5)", + color: "#fff", + padding: "10px", + borderRadius: "5px", + cursor: "pointer", + }, + // Other styles omitted for brevity +}; + +export default SetPaymentQr; diff --git a/src/components/QR.js b/src/components/QR.js index a79bf62..faa75f4 100644 --- a/src/components/QR.js +++ b/src/components/QR.js @@ -1,5 +1,7 @@ import React, { useState, useRef } from "react"; import html2canvas from "html2canvas"; +import { getImageUrl } from "../helpers/itemHelper"; +import { saveCafeDetails } from "../helpers/cafeHelpers"; // Import the helper function const QRCodeWithBackground = ({ isConfigure, @@ -9,11 +11,12 @@ const QRCodeWithBackground = ({ initialQrPosition, initialQrSize, handleQrSave, + shopId, // Pass cafeId as a prop to identify which cafe to update }) => { const [qrPosition, setQrPosition] = useState(initialQrPosition); const [qrSize, setQrSize] = useState(initialQrSize); - const [bgImage, setBgImage] = useState(backgroundUrl); - const fileInputRef = useRef(null); + const [bgImage, setBgImage] = useState(getImageUrl(backgroundUrl)); // URL or File + const qrBackgroundInputRef = useRef(null); const overlayTextRef = useRef(null); const handlePositionChange = (e) => { @@ -31,13 +34,30 @@ const QRCodeWithBackground = ({ const handleFileChange = (e) => { const file = e.target.files[0]; if (file) { - const newBgImage = URL.createObjectURL(file); + const newBgImage = URL.createObjectURL(file); // Create a temporary URL for display setBgImage(newBgImage); } }; const handleSave = () => { - handleQrSave(qrPosition, qrSize, bgImage); + const qrBackgroundFile = qrBackgroundInputRef.current.files[0]; // Get the selected file for qrBackground + + // Prepare the details object + const details = { + qrPosition, + qrSize, + qrBackgroundFile, // Include qrBackground file + }; + + // Call saveCafeDetails function with the updated details object + saveCafeDetails(shopId, details) + .then((response) => { + console.log("Cafe details saved:", response); + handleQrSave(qrPosition, qrSize, bgImage); + }) + .catch((error) => { + console.error("Error saving cafe details:", error); + }); }; const printQRCode = () => { @@ -118,7 +138,7 @@ const QRCodeWithBackground = ({ position: "relative", width: "300px", height: "300px", - background: `url(${bgImage}) no-repeat center center`, + background: `center center / contain no-repeat url(${bgImage})`, backgroundSize: "contain", overflow: "hidden", border: "1px solid #ddd", @@ -141,7 +161,7 @@ const QRCodeWithBackground = ({
fileInputRef.current.click()} + onClick={() => qrBackgroundInputRef.current.click()} > Click To Change Image
@@ -150,7 +170,7 @@ const QRCodeWithBackground = ({ @@ -276,16 +296,15 @@ const QRCodeWithBackground = ({ borderRadius: "4px", cursor: "pointer", transition: "background-color 0.3s", - marginLeft: "10px", }} onMouseOver={(e) => - (e.currentTarget.style.backgroundColor = "#138496") + (e.currentTarget.style.backgroundColor = "#117a8b") } onMouseOut={(e) => (e.currentTarget.style.backgroundColor = "#17a2b8") } > - Save Image + Save QR Code )} @@ -293,54 +312,41 @@ const QRCodeWithBackground = ({ ); }; -// Styles for the configuration labels and inputs const styles = { - label: { - display: "block", - fontSize: "16px", - fontWeight: "bold", - marginBottom: "5px", + overlayText: { + position: "absolute", + top: "50%", + left: "50%", + transform: "translate(-50%, -50%)", + backgroundColor: "rgba(0, 0, 0, 0.5)", + color: "#fff", + padding: "10px", + borderRadius: "5px", + cursor: "pointer", }, sliderContainer: { marginBottom: "20px", }, + label: { + display: "block", + marginBottom: "10px", + }, sliderWrapper: { display: "flex", alignItems: "center", - gap: "10px", - marginTop: "5px", }, input: { flex: "1", margin: "0 10px", }, - value: { - fontSize: "16px", - minWidth: "50px", - textAlign: "right", - }, labelStart: { - fontSize: "14px", + marginRight: "10px", }, labelEnd: { - fontSize: "14px", - }, - fileInput: { marginLeft: "10px", }, - overlayText: { - position: "absolute", - fontSize: "70px", - backgroundColor: "rgba(0, 0, 0, 0.7)", - top: "0", - bottom: "0", - color: "white", - width: "100%", - display: "flex", - alignItems: "center", - justifyContent: "center", - cursor: "pointer", // Indicates it's clickable - zIndex: 10, // Ensure it appears above other elements + value: { + marginLeft: "10px", }, }; diff --git a/src/components/TableList.js b/src/components/TableList.js index 4efc83c..523355d 100644 --- a/src/components/TableList.js +++ b/src/components/TableList.js @@ -1,12 +1,13 @@ import React, { useState, useEffect } from "react"; import QRCodeWithBackground from "./QR"; // Adjust path as needed - -const TableList = ({ shopUrl, tables, onSelectTable, selectedTable }) => { - const [initialPos, setInitialPos] = useState({ left: 50, top: 50 }); - const [initialSize, setInitialSize] = useState(20); - const [bgImageUrl, setBgImageUrl] = useState( - "https://example.com/your-background-image.png" - ); +const TableList = ({ shop, tables, onSelectTable, selectedTable }) => { + const [initialPos, setInitialPos] = useState({ + left: shop.xposition, + top: shop.yposition, + }); + const [initialSize, setInitialSize] = useState(shop.scale); + 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( @@ -51,6 +52,7 @@ const TableList = ({ shopUrl, tables, onSelectTable, selectedTable }) => {
configure
{-1 == selectedTable?.tableId && ( { /> )} - {tables - .filter((table) => table.tableNo !== 0) - .map((table) => ( -
  • onSelectTable(table)} - > -
    - Table {table.tableNo} -
    - {table.tableId === selectedTable?.tableId && ( - <> - -
    {shopUrl + "/" + table.tableCode}
    - - )} -
  • - ))} + {tables && + tables + .filter((table) => table.tableNo !== 0) + .map((table) => ( +
  • onSelectTable(table)} + > +
    + Table {table.tableNo} +
    + {table.tableId === selectedTable?.tableId && ( + <> + +
    {shopUrl + "/" + table.tableCode}
    + + )} +
  • + ))} ); diff --git a/src/components/TablesPage.js b/src/components/TablesPage.js index 603a404..995bdcf 100644 --- a/src/components/TablesPage.js +++ b/src/components/TablesPage.js @@ -3,7 +3,7 @@ import { getTables, updateTable, createTable } from "../helpers/tableHelper"; import TableCanvas from "./TableCanvas"; import TableList from "./TableList"; -const TablesPage = ({ shopId }) => { +const TablesPage = ({ shop }) => { const [tables, setTables] = useState([]); const [selectedTable, setSelectedTable] = useState(null); const [newTable, setNewTable] = useState(null); @@ -13,7 +13,8 @@ const TablesPage = ({ shopId }) => { useEffect(() => { const fetchData = async () => { try { - const fetchedTables = await getTables(shopId); + console.log(shop); + const fetchedTables = await getTables(shop.cafeId); setTables(fetchedTables); setOriginalTables(fetchedTables); } catch (error) { @@ -22,7 +23,7 @@ const TablesPage = ({ shopId }) => { }; fetchData(); - }, [shopId]); + }, [shop.cafeId]); const handleAddTable = () => { const nextId = Math.random().toString(36).substr(2, 11); @@ -131,7 +132,7 @@ const TablesPage = ({ shopId }) => { const handleSave = async () => { if (newTable) { try { - const createdTable = await createTable(shopId, { + const createdTable = await createTable(shop.cafeId, { ...newTable, tableNo, }); @@ -144,7 +145,7 @@ const TablesPage = ({ shopId }) => { } } else if (selectedTable) { try { - const updatedTable = await updateTable(shopId, { + const updatedTable = await updateTable(shop.cafeId, { ...selectedTable, tableNo, }); @@ -202,7 +203,7 @@ const TablesPage = ({ shopId }) => { tableNo={tableNo} /> */} { + const transactionId = searchParams.get("transactionId") || ""; + + const fetchData = async () => { + try { + const fetchedTransaction = await getTransaction(transactionId); + setTransaction(fetchedTransaction); + console.log(fetchedTransaction); + } catch (error) { + console.error("Error fetching transaction:", error); + } + }; + fetchData(); + }, [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"); + } + }; + } + }; + + detectQRCode(); + }, [paymentUrl]); + + 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; + }, 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); + } + } else { + console.log("QR Code element not found."); + } + }; + + return ( +
    +
    +

    Transaction Confirmed

    +
    +
    +
    + {qrData ? ( +
    +
    + {/* Generate QR code */} +
    +

    Generated QR Code from Data

    + +
    + ) : ( +
    + +

    Loading QR Code Data...

    +
    + )} + + {transaction && transaction.DetailedTransactions ? ( +
    + Total: + + Rp{" "} + {calculateTotalPrice( + transaction.DetailedTransactions + ).toLocaleString()} + +
    + ) : ( +
    + +

    Loading Transaction Data...

    +
    + )} + + +
    +
    +
    + ); +} diff --git a/src/pages/Transaction_failed.js b/src/pages/Transaction_failed.js index 0b40c8f..10a0c42 100644 --- a/src/pages/Transaction_failed.js +++ b/src/pages/Transaction_failed.js @@ -19,7 +19,7 @@ export default function Transaction_pending() {

    transaction failed

    Failed diff --git a/src/pages/Transaction_pending.js b/src/pages/Transaction_pending.js index 8104882..954acbc 100644 --- a/src/pages/Transaction_pending.js +++ b/src/pages/Transaction_pending.js @@ -16,8 +16,19 @@ export default function Transaction_pending() {
    -

    waiting for confirmation

    - +

    Transation Pending

    +
    + +

    Waiting for clerk confirmation...

    +
    diff --git a/src/pages/Transactions.js b/src/pages/Transactions.js index 9cb73f7..a8142a2 100644 --- a/src/pages/Transactions.js +++ b/src/pages/Transactions.js @@ -98,7 +98,7 @@ export default function Transactions({ propsShopId, sendParam, deviceType }) {

    Transactions

    - + {/* */}
    {transactions && transactions.map((transaction) => ( @@ -153,9 +153,11 @@ export default function Transactions({ propsShopId, sendParam, deviceType }) { )}
    -
    handleDecline(transaction.transactionId)}> - decline -
    + {transaction.confirmed == 0 && ( +
    handleDecline(transaction.transactionId)}> + decline +
    + )} ))} diff --git a/src/pages/Transactions.module.css b/src/pages/Transactions.module.css index c0df030..9887054 100644 --- a/src/pages/Transactions.module.css +++ b/src/pages/Transactions.module.css @@ -33,7 +33,6 @@ .TransactionListContainer { overflow-y: auto; /* Enables vertical scrolling */ - max-height: 600px; /* Adjust based on your design */ } .TotalContainer {