This commit is contained in:
client perkafean
2024-08-12 09:28:22 +00:00
parent 6102db3f56
commit 9c3a14366c
20 changed files with 725 additions and 196 deletions

6
package-lock.json generated
View File

@@ -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",

View File

@@ -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",

View File

@@ -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() {
</Routes>
</header>
<Modal
shopId={shopId}
shop={shop}
isOpen={isModalOpen}
modalContent={modalContent}
onClose={closeModal}

View File

@@ -156,19 +156,18 @@ const shrink = keyframes`
border-radius: 50%;
}
`;
const Rectangle = styled.div`
position: absolute;
top: 45px;
right: 15px;
width: 200px;
height: auto;
max-height: 87vh; /* or another appropriate value */
background-color: white;
z-index: 10;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
animation: ${(props) => (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 = ({
<Child onClick={goToLogin}>Click to login</Child>
)}
{user.username !== undefined && (
<Child onClick={() => setModal("edit_account")}>Edit</Child>
<Child onClick={() => setModal("edit_account")}>
Edit profile
</Child>
)}
{shopId &&
user.userId == shopOwnerId &&
@@ -312,11 +314,19 @@ const Header = ({
user.roleId === 1 && (
<>
<Child onClick={goToAdminCafes}>see your other cafes</Child>
<Child onClick={() => setModal("edit_tables")}>
{shopName} table maps
<Child onClick={() => setModal("add_material")}>
add material
</Child>
<Child onClick={() => setModal("update_stock")}>
update stock
</Child>
<Child hasChildren>
{shopName} clerks
{shopName}
<Child onClick={() => setModal("edit_tables")}>
table maps
</Child>
<Child hasChildren>
clerks
<Child onClick={() => setModal("craete_account_clerk")}>
+ Add clerk
</Child>
@@ -326,7 +336,9 @@ const Header = ({
{shopClerks[index].username}
<button
onClick={() =>
removeConnectedGuestSides(guestSides[index][3])
removeConnectedGuestSides(
guestSides[index][3]
)
}
>
remove
@@ -334,31 +346,37 @@ const Header = ({
</Child>
))}
</Child>
<Child onClick={() => setModal("add_material")}>
add material
<Child onClick={() => setModal("payment_option")}>
payment options
</Child>
</Child>
</>
)}
{user.username !== undefined &&
((user.userId == shopOwnerId && user.roleId === 1) ||
(user.cafeId == shopId && user.roleId === 2)) && (
user.cafeId == shopId &&
user.roleId === 2 && (
<Child hasChildren>
{shopName}
<Child onClick={() => setModal("update_stock")}>
update stock
</Child>
)}
{user.username !== undefined &&
user.roleId == 2 &&
user.cafeId == shopId && (
<Child hasChildren>
connected guest sides
<Child onClick={goToGuestSideLogin}>+ Add guest side</Child>
<Child onClick={goToGuestSideLogin}>
+ Add guest side
</Child>
{guestSides &&
guestSides.map((key, index) => (
<Child key={index}>
guest side {index + 1}
<button
onClick={() =>
removeConnectedGuestSides(guestSides[index][3])
removeConnectedGuestSides(
guestSides[index][3]
)
}
>
remove
@@ -367,6 +385,8 @@ const Header = ({
))}
</Child>
)}
</Child>
)}
{user.username !== undefined && (
<Child onClick={isLogout}>Logout</Child>
)}

View File

@@ -58,12 +58,22 @@ export default function ItemType({
className={styles["item-type-image"]}
/>
{blank && (
<div className={styles["item-type-image-container"]}>
<input
type="file"
accept="image/*"
className={styles["item-type-image-input"]}
onChange={handleImageChange}
id="image-input"
/>
Click to add image
<label
htmlFor="image-input"
className={styles["item-type-image-text"]}
>
Click to add image
</label>
</div>
)}
</div>
<input

View File

@@ -41,16 +41,24 @@
border-radius: 15px;
}
.item-type-image-input {
.item-type-image-container {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
.item-type-image-input {
opacity: 0;
position: absolute;
top: 0;
width: 100%;
height: 100%;
}
.item-type-create {
position: absolute;
top: 80%; /* Position below the input */
top: 76%; /* Position below the input */
left: 50%;
transform: translateX(-50%);
margin-top: 10px; /* Space between input and button */

View File

@@ -19,12 +19,7 @@ const ItemTypeLister = ({ shopId, shopOwnerId, user, itemTypes }) => {
<div className="item-type-lister">
<div className="item-type-list">
{itemTypes && itemTypes.length > 1 && (
<ItemType
name={"All"}
imageUrl={
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOEAAADhCAMAAAAJbSJIAAAAY1BMVEX///8BAQEEBAT09PT8/PzFxcWGhobu7u7R0dHLy8uoqKhTU1N7e3tXV1fIyMguLi7d3d08PDyenp41NTWBgYF1dXVHR0dMTEwTExPj4+O6urrp6emWlpasrKy2trZeXl4aGhrSJRZxAAADF0lEQVR4nO3Zi3aiMBCAYQIR71VbtXft+z/lJoFigAkNbYHdPf/X3VPqGeMkwdxIEgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAT0dGBVaSOe5vuV/4giqR1mXOPXL6M1X4r9Cn5l+kqCx3R2Nq7igjWPYoeWjbPkpg09LdiJ/dyPK3P22O+iIid5ZvtebvJZxGxi/y4Pa9Px5efJvh99i56fVM3q+4RJFv5seHeseVqP/btdYDsY5hE7nZVGqn5/7jvqOHiUASVDsE+NyXsH5UfvLub4qtoP/Hd5lEkYn+Zf+Fb9d7lXAa73/fB2KUryytZvQ9Qgy9pm3TqNbW7WAai72udojqruKwq5sdOMJ4ub51SXNnrp2cxdqH8YHcd7PHnp7J+foebphu9inMl+pAyyQ5i7KE13Nj3ftQ6sDIfvkoNef22U59fmoswKqzaCTurRqz942ILaZZs/j6OVrPS805o6dS8dG3Uz65fAhVUqrG6MROFvkpdaF7ZjT1nvAj5Fm2/b1VxpsSszc+s2d96r+rf2Fv02FP/SeoSl9ildZfmUtLulbxV7kW6+Z3TOBWrVBVqWbdiN3LKxqYVuxbj3CeNUS2PON45D80+zLbBGm6z5lDzEIg0Hzdm9ZIkPHioXTN0/hiMPbfmgF0wlj78be5TxVz+l+/hSZ7vVXMstXuh7rFU14IvwaYbeyztmg/rd6mdD8UZLm3Nhzo8H6rR50N5TWN+rllz2dZjTZPYNU0qr2nkNf2AOtalPleBjnVpy0Uaw8TFwdCkvUVq9xaC6L2F9SG3xuh7C11uVG91c20fuT9UX+wPG7vlNLy1HtDnHr/KojORnnv81LtVXbmT7PHdOU119FL8Crd0/3Mav+RJzmnks7bwAbx/1pb+C2dthZ+cl4aO9l0prfPSCQ/2izPvzQBn3pupz7wL3vOFqEczsc8tkuSveW5RinqcJF/Lwf7TxsmfPcWmoIWrUGjt+eF3Uvolfdq3Pg71ehsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACASfwBIesXINu4PFgAAAAASUVORK5CYII="
}
/>
<ItemType name={"All"} imageUrl={"uploads/1718732420960.png"} />
)}
{itemTypes &&
itemTypes.map(

View File

@@ -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 }) => {
<button onClick={onClose} className={styles.closeButton}>
&times;
</button>
{modalContent === "edit_tables" && <TablesPage shopId={shopId} />}
{modalContent === "edit_tables" && <TablesPage shop={shop} />}
{modalContent === "new_transaction" && (
<Transactions propsShopId={shopId} />
<Transactions propsShopId={shop.cafeId} />
)}{" "}
{modalContent === "transaction_pending" && <Transaction_pending />}
{modalContent === "transaction_confirmed" && (
<Transaction_confirmed paymentUrl={shop.qrPayment} />
)}
{modalContent === "transaction_success" && <Transaction_success />}
{modalContent === "transaction_failed" && <Transaction_failed />}
{modalContent === "add_material" && <MaterialList cafeId={shopId} />}
{modalContent === "payment_option" && (
<PaymentOptions paymentUrl={shop.qrPayment} shopId={shop.cafeId} />
)}
{modalContent === "add_material" && (
<MaterialList cafeId={shop.cafeId} />
)}
{modalContent === "update_stock" && (
<MaterialMutationsPage cafeId={shopId} />
<MaterialMutationsPage cafeId={shop.cafeId} />
)}
</div>
</div>

View File

@@ -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 (
<div>
<div
id="qr-code-container"
ref={qrCodeContainerRef}
style={{
position: "relative",
width: "300px",
height: "300px",
background: `center center / contain no-repeat url(${qrPayment})`,
backgroundSize: "contain",
overflow: "hidden",
border: "1px solid #ddd",
}}
>
<div
ref={overlayTextRef}
style={styles.overlayText}
onClick={() => qrPaymentInputRef.current.click()}
>
Click To Change Image
</div>
<input
type="file"
accept="image/*"
ref={qrPaymentInputRef}
style={{ display: "none" }}
onChange={handleFileChange}
/>
</div>
<div style={{ marginTop: "20px" }}>
{qrCodeDetected ? <p>QR Code Detected</p> : <p>No QR Code Detected</p>}
<div style={{ marginTop: "20px" }}>
<button
onClick={handleSave}
style={{
padding: "10px 20px",
fontSize: "16px",
backgroundColor: "#28a745",
color: "#fff",
border: "none",
borderRadius: "4px",
cursor: "pointer",
transition: "background-color 0.3s",
}}
onMouseOver={(e) =>
(e.currentTarget.style.backgroundColor = "#218838")
}
onMouseOut={(e) =>
(e.currentTarget.style.backgroundColor = "#28a745")
}
>
Save
</button>
</div>
</div>
</div>
);
};
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;

View File

@@ -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 = () => {
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 = ({
<div
ref={overlayTextRef}
style={styles.overlayText}
onClick={() => fileInputRef.current.click()}
onClick={() => qrBackgroundInputRef.current.click()}
>
Click To Change Image
</div>
@@ -150,7 +170,7 @@ const QRCodeWithBackground = ({
<input
type="file"
accept="image/*"
ref={fileInputRef}
ref={qrBackgroundInputRef}
style={{ display: "none" }}
onChange={handleFileChange}
/>
@@ -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
</button>
</div>
)}
@@ -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",
},
};

View File

@@ -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 }) => {
<div style={{ marginBottom: "10px" }}>configure</div>
{-1 == selectedTable?.tableId && (
<QRCodeWithBackground
shopId={shop.cafeId}
isConfigure={true}
handleQrSave={handleQrSave}
setInitialPos={setInitialPos}
@@ -62,7 +64,8 @@ const TableList = ({ shopUrl, tables, onSelectTable, selectedTable }) => {
/>
)}
</li>
{tables
{tables &&
tables
.filter((table) => table.tableNo !== 0)
.map((table) => (
<li
@@ -91,6 +94,7 @@ const TableList = ({ shopUrl, tables, onSelectTable, selectedTable }) => {
backgroundUrl={bgImageUrl}
initialQrPosition={initialPos}
initialQrSize={initialSize}
cafeId={shop.cafeId}
/>
<h5>{shopUrl + "/" + table.tableCode}</h5>
</>

View File

@@ -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}
/> */}
<TableList
shopUrl={window.location.hostname + "/" + shopId}
shop={shop}
tables={tables}
onSelectTable={handleSelect}
selectedTable={selectedTable}

View File

@@ -1,5 +1,5 @@
// src/config.js
const API_BASE_URL = "https://sswsts-5000.csb.app"; // Replace with your actual backend URL
const API_BASE_URL = "https://2jtmkn-5000.csb.app"; // Replace with your actual backend URL
export default API_BASE_URL;

View File

@@ -18,7 +18,7 @@ export async function getOwnedCafes(userId) {
);
if (!response.ok) {
throw new Error("Failed to fetch cart details");
throw new Error("Failed to fetch cafes");
}
const cafes = await response.json();
@@ -30,48 +30,85 @@ export async function getOwnedCafes(userId) {
export async function createCafe(userId) {
try {
const response = await fetch(
`${API_BASE_URL}/cafe/get-cafe-by-ownerId/` + userId,
{
const response = await fetch(`${API_BASE_URL}/cafe/create-cafe`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${getAuthToken()}`,
},
}
);
body: JSON.stringify({ ownerId: userId }),
});
if (!response.ok) {
throw new Error("Failed to fetch cart details");
throw new Error("Failed to create cafe");
}
const cafes = await response.json();
return cafes;
const cafe = await response.json();
return cafe;
} catch (error) {
console.error("Error:", error);
}
}
export async function updateCafe(userId) {
export async function updateCafe(cafeId, cafeDetails) {
try {
const response = await fetch(
`${API_BASE_URL}/cafe/get-cafe-by-ownerId/` + userId,
{
method: "POST",
const response = await fetch(`${API_BASE_URL}/cafe/update-cafe/${cafeId}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${getAuthToken()}`,
},
}
);
body: JSON.stringify(cafeDetails),
});
if (!response.ok) {
throw new Error("Failed to fetch cart details");
throw new Error("Failed to update cafe");
}
const cafes = await response.json();
return cafes;
const updatedCafe = await response.json();
return updatedCafe;
} catch (error) {
console.error("Error:", error);
}
}
// helpers/cafeHelpers.js
export async function saveCafeDetails(cafeId, details) {
try {
const formData = new FormData();
// Append qrBackground file if it exists
if (details.qrBackgroundFile) {
formData.append("qrBackground", details.qrBackgroundFile);
}
// Append qrPayment file if it exists
if (details.qrPaymentFile) {
formData.append("qrPayment", details.qrPaymentFile);
}
// Append other form fields
if (details.qrPosition) {
formData.append("xposition", details.qrPosition.left);
formData.append("yposition", details.qrPosition.top);
}
if (details.qrSize) formData.append("scale", details.qrSize);
const response = await fetch(`${API_BASE_URL}/cafe/set-cafe/${cafeId}`, {
method: "PUT",
headers: {
Authorization: `Bearer ${getAuthToken()}`,
},
body: formData,
});
if (!response.ok) {
throw new Error("Failed to save cafe details");
}
return await response.json();
} catch (error) {
console.error("Error saving cafe details:", error);
return null;
}
}

View File

@@ -50,6 +50,55 @@ export async function declineTransaction(transactionId) {
}
}
export async function handleClaimHasPaid(transactionId) {
try {
const token = getLocalStorage("auth");
const response = await fetch(
`${API_BASE_URL}/transaction/payment-claimed/${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 getTransaction(transactionId) {
try {
const token = getLocalStorage("auth");
const response = await fetch(
`${API_BASE_URL}/transaction/get-transaction/${transactionId}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
}
);
if (!response.ok) {
return false;
}
const transaction = await response.json();
return transaction;
} catch (error) {
console.error("Error:", error);
}
}
export async function getTransactions(shopId, demand) {
try {
const token = getLocalStorage("auth");

View File

@@ -0,0 +1,190 @@
import React, { useState, useEffect, useRef } from "react";
import { ColorRing } from "react-loader-spinner";
import jsQR from "jsqr";
import QRCode from "qrcode.react";
import styles from "./Transactions.module.css";
import { getImageUrl } from "../helpers/itemHelper";
import { useSearchParams } from "react-router-dom";
import {
handleClaimHasPaid,
getTransaction,
} from "../helpers/transactionHelpers";
import html2canvas from "html2canvas";
export default function Transaction_pending({ paymentUrl }) {
const [searchParams] = useSearchParams();
const [qrData, setQrData] = useState(null);
const [transaction, setTransaction] = useState(null);
const qrCodeRef = useRef(null);
useEffect(() => {
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 (
<div className={styles.Transactions}>
<div style={{ marginTop: "30px" }}></div>
<h2 className={styles["Transactions-title"]}>Transaction Confirmed</h2>
<div style={{ marginTop: "30px" }}></div>
<div className={styles.TransactionListContainer}>
<div style={{ marginTop: "30px", textAlign: "center" }}>
{qrData ? (
<div style={{ marginTop: "20px" }}>
<div ref={qrCodeRef}>
<QRCode value={qrData} size={256} /> {/* Generate QR code */}
</div>
<p>Generated QR Code from Data</p>
<button
onClick={downloadQRCode}
style={{
marginTop: "20px",
padding: "10px 20px",
fontSize: "16px",
backgroundColor: "#007bff",
color: "#fff",
border: "none",
borderRadius: "4px",
cursor: "pointer",
transition: "background-color 0.3s",
}}
onMouseOver={(e) =>
(e.currentTarget.style.backgroundColor = "#0056b3")
}
onMouseOut={(e) =>
(e.currentTarget.style.backgroundColor = "#007bff")
}
>
Download QR Code
</button>
</div>
) : (
<div style={{ marginTop: "20px" }}>
<ColorRing
visible={true}
height="80"
width="80"
ariaLabel="blocks-loading"
wrapperStyle={{}}
wrapperClass="blocks-wrapper"
colors={["#4fa94d", "#f7c34c", "#ffa53c", "#e34f53", "#d23a8d"]}
/>
<p>Loading QR Code Data...</p>
</div>
)}
{transaction && transaction.DetailedTransactions ? (
<div
className={styles.TotalContainer}
style={{ marginBottom: "20px" }}
>
<span>Total:</span>
<span>
Rp{" "}
{calculateTotalPrice(
transaction.DetailedTransactions
).toLocaleString()}
</span>
</div>
) : (
<div style={{ marginTop: "20px" }}>
<ColorRing
visible={true}
height="80"
width="80"
ariaLabel="blocks-loading"
wrapperStyle={{}}
wrapperClass="blocks-wrapper"
colors={["#4fa94d", "#f7c34c", "#ffa53c", "#e34f53", "#d23a8d"]}
/>
<p>Loading Transaction Data...</p>
</div>
)}
<button onClick={handleClaimHasPaid} className={styles.PayButton}>
I've already paid
</button>
</div>
</div>
</div>
);
}

View File

@@ -19,7 +19,7 @@ export default function Transaction_pending() {
<h2>transaction failed</h2>
<img
className={styles.expression}
src="https://i.imgur.com/5j3yIw6.png"
src="https://i.imgur.com/B6k9exa.png"
alt="Failed"
/>
</div>

View File

@@ -16,8 +16,19 @@ export default function Transaction_pending() {
<div className={styles.Transactions}>
<div className={containerStyle}>
<div style={{ marginTop: "30px", textAlign: "center" }}>
<h2>waiting for confirmation</h2>
<ColorRing height="50" width="50" color="white" />
<h2>Transation Pending</h2>
<div style={{ marginTop: "20px" }}>
<ColorRing
visible={true}
height="80"
width="80"
ariaLabel="blocks-loading"
wrapperStyle={{}}
wrapperClass="blocks-wrapper"
colors={["#4fa94d", "#f7c34c", "#ffa53c", "#e34f53", "#d23a8d"]}
/>
<p>Waiting for clerk confirmation...</p>
</div>
</div>
</div>
</div>

View File

@@ -98,7 +98,7 @@ export default function Transactions({ propsShopId, sendParam, deviceType }) {
<div style={{ marginTop: "30px" }}></div>
<h2 className={styles["Transactions-title"]}>Transactions</h2>
<div style={{ marginTop: "30px" }}></div>
<TableCanvas tables={tables} selectedTable={selectedTable} />
{/* <TableCanvas tables={tables} selectedTable={selectedTable} /> */}
<div className={styles.TransactionListContainer}>
{transactions &&
transactions.map((transaction) => (
@@ -153,9 +153,11 @@ export default function Transactions({ propsShopId, sendParam, deviceType }) {
)}
</button>
</div>
{transaction.confirmed == 0 && (
<h5 onClick={() => handleDecline(transaction.transactionId)}>
decline
</h5>
)}
</div>
))}
</div>

View File

@@ -33,7 +33,6 @@
.TransactionListContainer {
overflow-y: auto; /* Enables vertical scrolling */
max-height: 600px; /* Adjust based on your design */
}
.TotalContainer {