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/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0", "@testing-library/user-event": "^13.5.0",
"html2canvas": "^1.4.1", "html2canvas": "^1.4.1",
"jsqr": "^1.4.0",
"qrcode.react": "^3.1.0", "qrcode.react": "^3.1.0",
"react": "^18.3.1", "react": "^18.3.1",
"react-bootstrap": "^2.10.4", "react-bootstrap": "^2.10.4",
@@ -12528,6 +12529,11 @@
"node": ">=0.10.0" "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": { "node_modules/jsx-ast-utils": {
"version": "3.3.5", "version": "3.3.5",
"resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", "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/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0", "@testing-library/user-event": "^13.5.0",
"html2canvas": "^1.4.1", "html2canvas": "^1.4.1",
"jsqr": "^1.4.0",
"qrcode.react": "^3.1.0", "qrcode.react": "^3.1.0",
"react": "^18.3.1", "react": "^18.3.1",
"react-bootstrap": "^2.10.4", "react-bootstrap": "^2.10.4",

View File

@@ -123,9 +123,15 @@ function App() {
//for guest //for guest
socket.on("transaction_pending", async (data) => { socket.on("transaction_pending", async (data) => {
console.log("transaction notification"); console.log("transaction notification");
// Call `setModal` with content and parameters
setModal("transaction_pending"); setModal("transaction_pending");
}); });
socket.on("transaction_confirmed", async (data) => {
console.log("transaction notification");
setModal("transaction_confirmed", data);
});
socket.on("transaction_success", async (data) => { socket.on("transaction_success", async (data) => {
console.log("transaction notification"); console.log("transaction notification");
setModal("transaction_success"); setModal("transaction_success");
@@ -207,11 +213,20 @@ function App() {
}, [navigate]); }, [navigate]);
// Function to open the modal // Function to open the modal
const setModal = (content) => { const setModal = (content, params = {}) => {
setIsModalOpen(true); setIsModalOpen(true);
setModalContent(content); 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"; document.body.style.overflow = "hidden";
navigate(`?modal=` + content, { replace: true });
}; };
// Function to close the modal // Function to close the modal
@@ -373,7 +388,7 @@ function App() {
</Routes> </Routes>
</header> </header>
<Modal <Modal
shopId={shopId} shop={shop}
isOpen={isModalOpen} isOpen={isModalOpen}
modalContent={modalContent} modalContent={modalContent}
onClose={closeModal} onClose={closeModal}

View File

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

View File

@@ -58,12 +58,22 @@ export default function ItemType({
className={styles["item-type-image"]} className={styles["item-type-image"]}
/> />
{blank && ( {blank && (
<div className={styles["item-type-image-container"]}>
<input <input
type="file" type="file"
accept="image/*" accept="image/*"
className={styles["item-type-image-input"]} className={styles["item-type-image-input"]}
onChange={handleImageChange} 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> </div>
<input <input

View File

@@ -41,16 +41,24 @@
border-radius: 15px; border-radius: 15px;
} }
.item-type-image-input { .item-type-image-container {
position: absolute; position: absolute;
left: 0; left: 0;
top: 0;
width: 100%;
height: 100%;
}
.item-type-image-input {
opacity: 0;
position: absolute;
top: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
.item-type-create { .item-type-create {
position: absolute; position: absolute;
top: 80%; /* Position below the input */ top: 76%; /* Position below the input */
left: 50%; left: 50%;
transform: translateX(-50%); transform: translateX(-50%);
margin-top: 10px; /* Space between input and button */ 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-lister">
<div className="item-type-list"> <div className="item-type-list">
{itemTypes && itemTypes.length > 1 && ( {itemTypes && itemTypes.length > 1 && (
<ItemType <ItemType name={"All"} imageUrl={"uploads/1718732420960.png"} />
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="
}
/>
)} )}
{itemTypes && {itemTypes &&
itemTypes.map( itemTypes.map(

View File

@@ -1,15 +1,17 @@
import React from "react"; import React from "react";
import styles from "./Modal.module.css"; import styles from "./Modal.module.css";
import TablesPage from "./TablesPage.js"; import TablesPage from "./TablesPage.js";
import PaymentOptions from "./PaymentOptions.js";
import TableMaps from "../components/TableMaps"; import TableMaps from "../components/TableMaps";
import Transactions from "../pages/Transactions"; import Transactions from "../pages/Transactions";
import Transaction_pending from "../pages/Transaction_pending"; import Transaction_pending from "../pages/Transaction_pending";
import Transaction_confirmed from "../pages/Transaction_confirmed";
import Transaction_success from "../pages/Transaction_success"; import Transaction_success from "../pages/Transaction_success";
import Transaction_failed from "../pages/Transaction_failed"; import Transaction_failed from "../pages/Transaction_failed";
import MaterialList from "../pages/MaterialList.js"; import MaterialList from "../pages/MaterialList.js";
import MaterialMutationsPage from "../pages/MaterialMutationsPage.js"; import MaterialMutationsPage from "../pages/MaterialMutationsPage.js";
const Modal = ({ shopId, isOpen, onClose, modalContent }) => { const Modal = ({ shop, isOpen, onClose, modalContent }) => {
if (!isOpen) return null; if (!isOpen) return null;
// Function to handle clicks on the overlay // Function to handle clicks on the overlay
@@ -30,16 +32,24 @@ const Modal = ({ shopId, isOpen, onClose, modalContent }) => {
<button onClick={onClose} className={styles.closeButton}> <button onClick={onClose} className={styles.closeButton}>
&times; &times;
</button> </button>
{modalContent === "edit_tables" && <TablesPage shopId={shopId} />} {modalContent === "edit_tables" && <TablesPage shop={shop} />}
{modalContent === "new_transaction" && ( {modalContent === "new_transaction" && (
<Transactions propsShopId={shopId} /> <Transactions propsShopId={shop.cafeId} />
)}{" "} )}{" "}
{modalContent === "transaction_pending" && <Transaction_pending />} {modalContent === "transaction_pending" && <Transaction_pending />}
{modalContent === "transaction_confirmed" && (
<Transaction_confirmed paymentUrl={shop.qrPayment} />
)}
{modalContent === "transaction_success" && <Transaction_success />} {modalContent === "transaction_success" && <Transaction_success />}
{modalContent === "transaction_failed" && <Transaction_failed />} {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" && ( {modalContent === "update_stock" && (
<MaterialMutationsPage cafeId={shopId} /> <MaterialMutationsPage cafeId={shop.cafeId} />
)} )}
</div> </div>
</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 React, { useState, useRef } from "react";
import html2canvas from "html2canvas"; import html2canvas from "html2canvas";
import { getImageUrl } from "../helpers/itemHelper";
import { saveCafeDetails } from "../helpers/cafeHelpers"; // Import the helper function
const QRCodeWithBackground = ({ const QRCodeWithBackground = ({
isConfigure, isConfigure,
@@ -9,11 +11,12 @@ const QRCodeWithBackground = ({
initialQrPosition, initialQrPosition,
initialQrSize, initialQrSize,
handleQrSave, handleQrSave,
shopId, // Pass cafeId as a prop to identify which cafe to update
}) => { }) => {
const [qrPosition, setQrPosition] = useState(initialQrPosition); const [qrPosition, setQrPosition] = useState(initialQrPosition);
const [qrSize, setQrSize] = useState(initialQrSize); const [qrSize, setQrSize] = useState(initialQrSize);
const [bgImage, setBgImage] = useState(backgroundUrl); const [bgImage, setBgImage] = useState(getImageUrl(backgroundUrl)); // URL or File
const fileInputRef = useRef(null); const qrBackgroundInputRef = useRef(null);
const overlayTextRef = useRef(null); const overlayTextRef = useRef(null);
const handlePositionChange = (e) => { const handlePositionChange = (e) => {
@@ -31,13 +34,30 @@ const QRCodeWithBackground = ({
const handleFileChange = (e) => { const handleFileChange = (e) => {
const file = e.target.files[0]; const file = e.target.files[0];
if (file) { if (file) {
const newBgImage = URL.createObjectURL(file); const newBgImage = URL.createObjectURL(file); // Create a temporary URL for display
setBgImage(newBgImage); setBgImage(newBgImage);
} }
}; };
const handleSave = () => { 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); handleQrSave(qrPosition, qrSize, bgImage);
})
.catch((error) => {
console.error("Error saving cafe details:", error);
});
}; };
const printQRCode = () => { const printQRCode = () => {
@@ -118,7 +138,7 @@ const QRCodeWithBackground = ({
position: "relative", position: "relative",
width: "300px", width: "300px",
height: "300px", height: "300px",
background: `url(${bgImage}) no-repeat center center`, background: `center center / contain no-repeat url(${bgImage})`,
backgroundSize: "contain", backgroundSize: "contain",
overflow: "hidden", overflow: "hidden",
border: "1px solid #ddd", border: "1px solid #ddd",
@@ -141,7 +161,7 @@ const QRCodeWithBackground = ({
<div <div
ref={overlayTextRef} ref={overlayTextRef}
style={styles.overlayText} style={styles.overlayText}
onClick={() => fileInputRef.current.click()} onClick={() => qrBackgroundInputRef.current.click()}
> >
Click To Change Image Click To Change Image
</div> </div>
@@ -150,7 +170,7 @@ const QRCodeWithBackground = ({
<input <input
type="file" type="file"
accept="image/*" accept="image/*"
ref={fileInputRef} ref={qrBackgroundInputRef}
style={{ display: "none" }} style={{ display: "none" }}
onChange={handleFileChange} onChange={handleFileChange}
/> />
@@ -276,16 +296,15 @@ const QRCodeWithBackground = ({
borderRadius: "4px", borderRadius: "4px",
cursor: "pointer", cursor: "pointer",
transition: "background-color 0.3s", transition: "background-color 0.3s",
marginLeft: "10px",
}} }}
onMouseOver={(e) => onMouseOver={(e) =>
(e.currentTarget.style.backgroundColor = "#138496") (e.currentTarget.style.backgroundColor = "#117a8b")
} }
onMouseOut={(e) => onMouseOut={(e) =>
(e.currentTarget.style.backgroundColor = "#17a2b8") (e.currentTarget.style.backgroundColor = "#17a2b8")
} }
> >
Save Image Save QR Code
</button> </button>
</div> </div>
)} )}
@@ -293,54 +312,41 @@ const QRCodeWithBackground = ({
); );
}; };
// Styles for the configuration labels and inputs
const styles = { const styles = {
label: { overlayText: {
display: "block", position: "absolute",
fontSize: "16px", top: "50%",
fontWeight: "bold", left: "50%",
marginBottom: "5px", transform: "translate(-50%, -50%)",
backgroundColor: "rgba(0, 0, 0, 0.5)",
color: "#fff",
padding: "10px",
borderRadius: "5px",
cursor: "pointer",
}, },
sliderContainer: { sliderContainer: {
marginBottom: "20px", marginBottom: "20px",
}, },
label: {
display: "block",
marginBottom: "10px",
},
sliderWrapper: { sliderWrapper: {
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
gap: "10px",
marginTop: "5px",
}, },
input: { input: {
flex: "1", flex: "1",
margin: "0 10px", margin: "0 10px",
}, },
value: {
fontSize: "16px",
minWidth: "50px",
textAlign: "right",
},
labelStart: { labelStart: {
fontSize: "14px", marginRight: "10px",
}, },
labelEnd: { labelEnd: {
fontSize: "14px",
},
fileInput: {
marginLeft: "10px", marginLeft: "10px",
}, },
overlayText: { value: {
position: "absolute", marginLeft: "10px",
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
}, },
}; };

View File

@@ -1,12 +1,13 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import QRCodeWithBackground from "./QR"; // Adjust path as needed import QRCodeWithBackground from "./QR"; // Adjust path as needed
const TableList = ({ shop, tables, onSelectTable, selectedTable }) => {
const TableList = ({ shopUrl, tables, onSelectTable, selectedTable }) => { const [initialPos, setInitialPos] = useState({
const [initialPos, setInitialPos] = useState({ left: 50, top: 50 }); left: shop.xposition,
const [initialSize, setInitialSize] = useState(20); top: shop.yposition,
const [bgImageUrl, setBgImageUrl] = useState( });
"https://example.com/your-background-image.png" const [initialSize, setInitialSize] = useState(shop.scale);
); const [bgImageUrl, setBgImageUrl] = useState(shop.qrBackground);
const shopUrl = window.location.hostname + "/" + shop.cafeId;
const generateQRCodeUrl = (tableCode) => const generateQRCodeUrl = (tableCode) =>
`https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${encodeURIComponent( `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> <div style={{ marginBottom: "10px" }}>configure</div>
{-1 == selectedTable?.tableId && ( {-1 == selectedTable?.tableId && (
<QRCodeWithBackground <QRCodeWithBackground
shopId={shop.cafeId}
isConfigure={true} isConfigure={true}
handleQrSave={handleQrSave} handleQrSave={handleQrSave}
setInitialPos={setInitialPos} setInitialPos={setInitialPos}
@@ -62,7 +64,8 @@ const TableList = ({ shopUrl, tables, onSelectTable, selectedTable }) => {
/> />
)} )}
</li> </li>
{tables {tables &&
tables
.filter((table) => table.tableNo !== 0) .filter((table) => table.tableNo !== 0)
.map((table) => ( .map((table) => (
<li <li
@@ -91,6 +94,7 @@ const TableList = ({ shopUrl, tables, onSelectTable, selectedTable }) => {
backgroundUrl={bgImageUrl} backgroundUrl={bgImageUrl}
initialQrPosition={initialPos} initialQrPosition={initialPos}
initialQrSize={initialSize} initialQrSize={initialSize}
cafeId={shop.cafeId}
/> />
<h5>{shopUrl + "/" + table.tableCode}</h5> <h5>{shopUrl + "/" + table.tableCode}</h5>
</> </>

View File

@@ -3,7 +3,7 @@ import { getTables, updateTable, createTable } from "../helpers/tableHelper";
import TableCanvas from "./TableCanvas"; import TableCanvas from "./TableCanvas";
import TableList from "./TableList"; import TableList from "./TableList";
const TablesPage = ({ shopId }) => { const TablesPage = ({ shop }) => {
const [tables, setTables] = useState([]); const [tables, setTables] = useState([]);
const [selectedTable, setSelectedTable] = useState(null); const [selectedTable, setSelectedTable] = useState(null);
const [newTable, setNewTable] = useState(null); const [newTable, setNewTable] = useState(null);
@@ -13,7 +13,8 @@ const TablesPage = ({ shopId }) => {
useEffect(() => { useEffect(() => {
const fetchData = async () => { const fetchData = async () => {
try { try {
const fetchedTables = await getTables(shopId); console.log(shop);
const fetchedTables = await getTables(shop.cafeId);
setTables(fetchedTables); setTables(fetchedTables);
setOriginalTables(fetchedTables); setOriginalTables(fetchedTables);
} catch (error) { } catch (error) {
@@ -22,7 +23,7 @@ const TablesPage = ({ shopId }) => {
}; };
fetchData(); fetchData();
}, [shopId]); }, [shop.cafeId]);
const handleAddTable = () => { const handleAddTable = () => {
const nextId = Math.random().toString(36).substr(2, 11); const nextId = Math.random().toString(36).substr(2, 11);
@@ -131,7 +132,7 @@ const TablesPage = ({ shopId }) => {
const handleSave = async () => { const handleSave = async () => {
if (newTable) { if (newTable) {
try { try {
const createdTable = await createTable(shopId, { const createdTable = await createTable(shop.cafeId, {
...newTable, ...newTable,
tableNo, tableNo,
}); });
@@ -144,7 +145,7 @@ const TablesPage = ({ shopId }) => {
} }
} else if (selectedTable) { } else if (selectedTable) {
try { try {
const updatedTable = await updateTable(shopId, { const updatedTable = await updateTable(shop.cafeId, {
...selectedTable, ...selectedTable,
tableNo, tableNo,
}); });
@@ -202,7 +203,7 @@ const TablesPage = ({ shopId }) => {
tableNo={tableNo} tableNo={tableNo}
/> */} /> */}
<TableList <TableList
shopUrl={window.location.hostname + "/" + shopId} shop={shop}
tables={tables} tables={tables}
onSelectTable={handleSelect} onSelectTable={handleSelect}
selectedTable={selectedTable} selectedTable={selectedTable}

View File

@@ -1,5 +1,5 @@
// src/config.js // 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; export default API_BASE_URL;

View File

@@ -18,7 +18,7 @@ export async function getOwnedCafes(userId) {
); );
if (!response.ok) { if (!response.ok) {
throw new Error("Failed to fetch cart details"); throw new Error("Failed to fetch cafes");
} }
const cafes = await response.json(); const cafes = await response.json();
@@ -30,48 +30,85 @@ export async function getOwnedCafes(userId) {
export async function createCafe(userId) { export async function createCafe(userId) {
try { try {
const response = await fetch( const response = await fetch(`${API_BASE_URL}/cafe/create-cafe`, {
`${API_BASE_URL}/cafe/get-cafe-by-ownerId/` + userId,
{
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
Authorization: `Bearer ${getAuthToken()}`, Authorization: `Bearer ${getAuthToken()}`,
}, },
} body: JSON.stringify({ ownerId: userId }),
); });
if (!response.ok) { if (!response.ok) {
throw new Error("Failed to fetch cart details"); throw new Error("Failed to create cafe");
} }
const cafes = await response.json(); const cafe = await response.json();
return cafes; return cafe;
} catch (error) { } catch (error) {
console.error("Error:", error); console.error("Error:", error);
} }
} }
export async function updateCafe(userId) { export async function updateCafe(cafeId, cafeDetails) {
try { try {
const response = await fetch( const response = await fetch(`${API_BASE_URL}/cafe/update-cafe/${cafeId}`, {
`${API_BASE_URL}/cafe/get-cafe-by-ownerId/` + userId, method: "PUT",
{
method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
Authorization: `Bearer ${getAuthToken()}`, Authorization: `Bearer ${getAuthToken()}`,
}, },
} body: JSON.stringify(cafeDetails),
); });
if (!response.ok) { if (!response.ok) {
throw new Error("Failed to fetch cart details"); throw new Error("Failed to update cafe");
} }
const cafes = await response.json(); const updatedCafe = await response.json();
return cafes; return updatedCafe;
} catch (error) { } catch (error) {
console.error("Error:", 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) { export async function getTransactions(shopId, demand) {
try { try {
const token = getLocalStorage("auth"); 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> <h2>transaction failed</h2>
<img <img
className={styles.expression} className={styles.expression}
src="https://i.imgur.com/5j3yIw6.png" src="https://i.imgur.com/B6k9exa.png"
alt="Failed" alt="Failed"
/> />
</div> </div>

View File

@@ -16,8 +16,19 @@ export default function Transaction_pending() {
<div className={styles.Transactions}> <div className={styles.Transactions}>
<div className={containerStyle}> <div className={containerStyle}>
<div style={{ marginTop: "30px", textAlign: "center" }}> <div style={{ marginTop: "30px", textAlign: "center" }}>
<h2>waiting for confirmation</h2> <h2>Transation Pending</h2>
<ColorRing height="50" width="50" color="white" /> <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> </div>
</div> </div>

View File

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

View File

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