This commit is contained in:
zadit
2024-12-04 21:40:36 +07:00
parent 529a7e505c
commit 198d0b3053
9 changed files with 517 additions and 109 deletions

View File

@@ -1,4 +1,4 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect, useRef } from "react";
import "./App.css"; import "./App.css";
import "./components/Loading.css"; import "./components/Loading.css";
import { import {
@@ -26,6 +26,9 @@ import GuestSide from "./pages/GuestSide";
import { getItemTypesWithItems } from "./helpers/itemHelper.js"; import { getItemTypesWithItems } from "./helpers/itemHelper.js";
import { getTableByCode } from "./helpers/tableHelper.js"; import { getTableByCode } from "./helpers/tableHelper.js";
import {
getTransactionsFromCafe,
} from "./helpers/transactionHelpers";
import { import {
getConnectedGuestSides, getConnectedGuestSides,
getClerks, getClerks,
@@ -57,8 +60,21 @@ function App() {
const [shopItems, setShopItems] = useState([]); const [shopItems, setShopItems] = useState([]);
const [isModalOpen, setIsModalOpen] = useState(false); const [isModalOpen, setIsModalOpen] = useState(false);
const [modalContent, setModalContent] = useState(null); const [modalContent, setModalContent] = useState(null);
const transactionList = useRef(null);
const [queue, setQueue] = useState([]); const [queue, setQueue] = useState([]);
const validTransactionStates = [
'new_transaction',
'transaction_canceled',
'transaction_pending',
'transaction_confirmed',
'payment_claimed',
'transaction_success',
'transaction_end',
'transaction_failed',
];
useEffect(() => { useEffect(() => {
const calculateTotalsFromLocalStorage = () => { const calculateTotalsFromLocalStorage = () => {
const { totalCount } = calculateTotals(shopId); const { totalCount } = calculateTotals(shopId);
@@ -193,30 +209,16 @@ function App() {
setModal("transaction_canceled", data); setModal("transaction_canceled", data);
}); });
//for clerk
socket.on("transaction_created", async (data) => {
console.log("transaction notification");
setModal("new_transaction", data);
let permission = Notification.permission;
if (permission != "granted") return;
navigator.serviceWorker.ready.then((registration) => {
registration.showNotification("New Transaction", {
body: `A new transaction was created: ${data.transactionDetails}`, // Customize as needed
icon: "icon.png", // Optional icon
});
});
});
const checkNotifications = () => { const checkNotifications = () => {
let permission = Notification.permission; let permission = Notification.permission;
// Check current permission // Check current permission
if (permission !== "granted") { if (permission !== "granted") {
setModal("req_notification"); setModal("req_notification");
} }
}; };
socket.on("checkUserTokenRes", async (data) => { socket.on("checkUserTokenRes", async (data) => {
if (data.status !== 200) { if (data.status !== 200) {
removeLocalStorage("auth"); removeLocalStorage("auth");
@@ -266,15 +268,85 @@ function App() {
}); });
socket.on("updateQueue", (response) => { socket.on("updateQueue", (response) => {
setQueue(response); // Only set the queue if it's a valid non-empty array setQueue(response); // Only set the queue if it's a valid non-empty array
console.log("Updated Queue:", response); // Log the valid queue console.log("Updated Queue:", response); // Log the valid queue
}); });
return () => { return () => {
socket.off("signout-guest-session"); socket.off("signout-guest-session");
}; };
}, [socket, shopId]); }, [socket, shopId]);
useEffect(() => {
// This will ensure that searchParams and transaction_info get updated on each render
socket.on("transaction_created", async (data) => {
console.log("transaction notification");
console.log(modalContent);
let response;
response = await getTransactionsFromCafe(shopId, 0, true);
console.log(data);
transactionList.current = response;
// Get current URL's search parameters inside the socket event handler
const searchParams = new URLSearchParams(location.search);
let transaction_info = searchParams.get("transactionId") || ''; // Get transactionId or set it to empty string
console.log(transaction_info); // Log the updated transaction_info
// If transaction_info is an empty string, set the modal
if (transaction_info === '') {
setModal("new_transaction", data);
}
// Show browser notification
let permission = Notification.permission;
if (permission !== "granted") return;
navigator.serviceWorker.ready.then((registration) => {
registration.showNotification("New Transaction", {
body: `A new transaction was created: ${data.transactionDetails}`,
icon: "icon.png", // Optional icon
});
});
});
// Clean up the socket event listener on unmount or when dependencies change
return () => {
socket.off("transaction_created");
};
}, [socket, shopId, location]); // Ensure location is in the dependencies to respond to changes in the URL
function handleMoveToTransaction(direction, from) {
console.log(direction);
console.log(from);
// Find the current index based on the 'from' transactionId
const currentIndex = transactionList.current.findIndex(item => item.transactionId == from);
// Determine the new transactionId based on the direction
let newTransactionId;
if (direction === 'next') {
// If we're not at the last transaction, get the next transactionId
newTransactionId = currentIndex < transactionList.current.length - 1
? transactionList.current[currentIndex + 1].transactionId
: from; // If already at the end, stay on the current transactionId
} else if (direction === 'previous') {
// If we're not at the first transaction, get the previous transactionId
newTransactionId = currentIndex > 0
? transactionList.current[currentIndex - 1].transactionId
: from; // If already at the beginning, stay on the current transactionId
}
// Log the new transactionId
console.log('New Transaction ID:', newTransactionId);
// Update the URL with the new transactionId using navigate
navigate(`?transactionId=${newTransactionId}`, { replace: true });
// Optionally, update state or perform further actions based on the new transactionId
// Example:
// setModalContent({ cafeId: shopId, transactionId: newTransactionId });
}
const handleModalFromURL = () => { const handleModalFromURL = () => {
const queryParams = new URLSearchParams(location.search); const queryParams = new URLSearchParams(location.search);
@@ -307,7 +379,7 @@ function App() {
document.body.style.overflow = "hidden"; document.body.style.overflow = "hidden";
setIsModalOpen(true); setIsModalOpen(true);
setModalContent(content); setModalContent(content)
}; };
// Function to close the modal // Function to close the modal
@@ -318,6 +390,7 @@ function App() {
closeTheseContent.includes(modalContent)) closeTheseContent.includes(modalContent))
) { ) {
setIsModalOpen(false); setIsModalOpen(false);
setModalContent(null);
document.body.style.overflow = "auto"; document.body.style.overflow = "auto";
const queryParams = new URLSearchParams(location.search); const queryParams = new URLSearchParams(location.search);
@@ -334,15 +407,15 @@ function App() {
// useEffect(() => { // useEffect(() => {
// const askNotificationPermission = async () => { // const askNotificationPermission = async () => {
// let permission = Notification.permission; // let permission = Notification.permission;
// // Check current permission // // Check current permission
// if (permission === "default") { // if (permission === "default") {
// setModal("req_notification"); // setModal("req_notification");
// // Request permission and wait for the result // // Request permission and wait for the result
// permission = await Notification.requestPermission(); // permission = await Notification.requestPermission();
// } // }
// // Handle permission results // // Handle permission results
// if (permission === "granted") { // if (permission === "granted") {
// await resetNotificationSubscription(); // await resetNotificationSubscription();
@@ -352,22 +425,22 @@ function App() {
// console.error("Notification permission denied."); // console.error("Notification permission denied.");
// } // }
// }; // };
// const handleTransactionConfirmed = async (data) => { // const handleTransactionConfirmed = async (data) => {
// console.log("transaction notification", data); // console.log("transaction notification", data);
// await askNotificationPermission(); // await askNotificationPermission();
// setModal("transaction_success", data); // setModal("transaction_success", data);
// }; // };
// // Add socket listener for transaction confirmations // // Add socket listener for transaction confirmations
// socket.on("transaction_success", handleTransactionConfirmed); // socket.on("transaction_success", handleTransactionConfirmed);
// // Cleanup the socket listener on component unmount // // Cleanup the socket listener on component unmount
// return () => { // return () => {
// socket.off("transaction_success", handleTransactionConfirmed); // socket.off("transaction_success", handleTransactionConfirmed);
// }; // };
// }, [user]); // }, [user]);
useEffect(() => { useEffect(() => {
const startPermissionPolling = async () => { const startPermissionPolling = async () => {
const checkInterval = 5000; // Check every 5 seconds const checkInterval = 5000; // Check every 5 seconds
@@ -386,7 +459,7 @@ function App() {
startPermissionPolling(); startPermissionPolling();
}, []); }, []);
useEffect(() => { useEffect(() => {
if ("serviceWorker" in navigator) { if ("serviceWorker" in navigator) {
navigator.serviceWorker.register("/service-worker.js") navigator.serviceWorker.register("/service-worker.js")
@@ -398,7 +471,7 @@ function App() {
}); });
} }
}, []); }, []);
return ( return (
<div className="App"> <div className="App">
<header className="App-header" id="header"> <header className="App-header" id="header">
@@ -448,7 +521,7 @@ function App() {
guestSideOfClerk={guestSideOfClerk} guestSideOfClerk={guestSideOfClerk}
removeConnectedGuestSides={rmConnectedGuestSides} removeConnectedGuestSides={rmConnectedGuestSides}
setModal={setModal} // Pass the function to open modal setModal={setModal} // Pass the function to open modal
loading={shop.name==null} loading={shop.name == null}
queue={queue} queue={queue}
/> />
<Footer <Footer
@@ -551,6 +624,8 @@ function App() {
shop={shop} shop={shop}
isOpen={isModalOpen} isOpen={isModalOpen}
modalContent={modalContent} modalContent={modalContent}
handleMoveToTransaction={handleMoveToTransaction}
welcomePageConfig={shop.welcomePageConfig}
onClose={closeModal} onClose={closeModal}
setModal={setModal} setModal={setModal}
/> />

View File

@@ -1,4 +1,4 @@
import React from "react"; import React, {useState, useEffect} from "react";
import styles from "./Modal.module.css"; import styles from "./Modal.module.css";
import CreateClerk from "../pages/CreateClerk" import CreateClerk from "../pages/CreateClerk"
import CreateCafe from "../pages/CreateCafe" import CreateCafe from "../pages/CreateCafe"
@@ -23,7 +23,19 @@ import GuidePage from "../pages/GuidePage";
import Join from "../pages/Join"; import Join from "../pages/Join";
import Login from "../pages/Login"; import Login from "../pages/Login";
import ResetPassword from "../pages/ResetPassword"; import ResetPassword from "../pages/ResetPassword";
const Modal = ({ shop, isOpen, onClose, modalContent, setModal }) => { import { getImageUrl } from "../helpers/itemHelper.js";
const Modal = ({ shop, isOpen, onClose, modalContent, setModal, handleMoveToTransaction,welcomePageConfig }) => {
const [shopImg, setShopImg] = useState('');
useEffect(() => {
if (welcomePageConfig) {
const parsedConfig = JSON.parse(welcomePageConfig);
setShopImg( getImageUrl(parsedConfig.image) || "")
}
}, [welcomePageConfig]);
if (!isOpen) return null; if (!isOpen) return null;
// Function to handle clicks on the overlay // Function to handle clicks on the overlay
@@ -49,7 +61,7 @@ const Modal = ({ shop, isOpen, onClose, modalContent, setModal }) => {
{modalContent === "create_tenant" && <CreateTenant shopId={shop.cafeId} />} {modalContent === "create_tenant" && <CreateTenant shopId={shop.cafeId} />}
{modalContent === "edit_tables" && <TablesPage shop={shop} />} {modalContent === "edit_tables" && <TablesPage shop={shop} />}
{modalContent === "new_transaction" && ( {modalContent === "new_transaction" && (
<Transaction propsShopId={shop.cafeId} /> <Transaction propsShopId={shop.cafeId} handleMoveToTransaction={handleMoveToTransaction} shopImg={shopImg} />
)} )}
{modalContent === "transaction_canceled" && ( {modalContent === "transaction_canceled" && (
<Transaction propsShopId={shop.cafeId} /> <Transaction propsShopId={shop.cafeId} />

View File

@@ -12,9 +12,7 @@
} }
.modalContent { .modalContent {
width: 90%; width: 80%;
/* height: 80%; */
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
position: relative; position: relative;
overflow: visible; /* Add this line to enable scrolling */ overflow: visible; /* Add this line to enable scrolling */
} }

View File

@@ -170,11 +170,11 @@ export async function getMyTransactions() {
console.error("Error:", error); console.error("Error:", error);
} }
} }
export async function getTransactionsFromCafe(shopId, demand) { export async function getTransactionsFromCafe(shopId, demand, idsOnly) {
try { try {
const token = getLocalStorage("auth"); const token = getLocalStorage("auth");
const response = await fetch( const response = await fetch(
`${API_BASE_URL}/transaction/get-transactions-from-cafe/${shopId}?demandLength=${demand}`, `${API_BASE_URL}/transaction/get-transactions/${shopId}?demandLength=${demand}&&idsOnly=${idsOnly}`,
{ {
method: "GET", method: "GET",
headers: { headers: {

View File

@@ -11,7 +11,7 @@ import { getTables } from "../helpers/tableHelper";
import TableCanvas from "../components/TableCanvas"; import TableCanvas from "../components/TableCanvas";
import { useSearchParams } from "react-router-dom"; import { useSearchParams } from "react-router-dom";
export default function Transactions({ propsShopId, sendParam, deviceType }) { export default function Transactions({ propsShopId, sendParam, deviceType, handleMoveToTransaction, shopImg }) {
const { shopId, tableId } = useParams(); const { shopId, tableId } = useParams();
if (sendParam) sendParam({ shopId, tableId }); if (sendParam) sendParam({ shopId, tableId });
@@ -108,9 +108,7 @@ export default function Transactions({ propsShopId, sendParam, deviceType }) {
return ( return (
<div className={styles.Transactions}> <div className={styles.Transactions}>
<div style={{ marginTop: "30px" }}></div>
<h2 className={styles["Transactions-title"]}>Transactions</h2>
{/* <TableCanvas tables={tables} selectedTable={selectedTable} /> */}
<div className={styles.TransactionListContainer}> <div className={styles.TransactionListContainer}>
{transaction && ( {transaction && (
<div <div
@@ -120,13 +118,59 @@ export default function Transactions({ propsShopId, sendParam, deviceType }) {
setSelectedTable(transaction.Table || { tableId: 0 }) setSelectedTable(transaction.Table || { tableId: 0 })
} }
> >
<div className={styles['receipt-header']}>
<img
src={shopImg} // Placeholder image URL
alt='logo'
className={styles['receipt-logo']}
/>
<div className={styles['receipt-info']}>
<h3>Receipt Information</h3>
<p>Transaction ID: {transaction.transactionId}</p>
<p>Payment Type: {transaction.payment_type}</p>
</div>
</div>
<div className={styles['dotted-line']}>
<div className={styles['circle-left']} onClick={() => handleMoveToTransaction('previous', transaction.transactionId)}>
<div className={styles["inner-circle"]}>
<svg
viewBox="0 0 16 16"
xmlns="http://www.w3.org/2000/svg"
fill="#000000"
style={{ transform: 'Rotate(-90deg)', width: '100%', height: '100%' }} // Ensure SVG fits the div
>
<g id="SVGRepo_bgCarrier" strokeWidth="0"></g>
<g id="SVGRepo_tracerCarrier" strokeLinecap="round" strokeLinejoin="round"></g>
<g id="SVGRepo_iconCarrier">
<path d="M 0.198 14.001 C -0.192 14.392 -0.088 11.275 0.1 11.088 L 7.293 4.293 C 7.684 3.902 8.316 3.902 8.707 4.293 L 15.833 11.258 C 16.021 11.445 16.203 14.644 15.812 14.253 L 8 6.414 L 0.198 14.001 Z" fill="#dbdbdb"></path>
</g>
</svg>
</div>
<div className={styles.circle}>1</div>
</div>
<div className={styles['line']} ></div>
<div className={styles['circle-right']} onClick={() => handleMoveToTransaction('next', transaction.transactionId)}>
<div className={styles["inner-circle"]}>
<span className="inner-text"><svg
viewBox="0 0 16 16"
xmlns="http://www.w3.org/2000/svg"
fill="#000000"
style={{ transform: 'Rotate(90deg)', width: '100%', height: '100%' }} // Ensure SVG fits the div
>
<g id="SVGRepo_bgCarrier" strokeWidth="0"></g>
<g id="SVGRepo_tracerCarrier" strokeLinecap="round" strokeLinejoin="round"></g>
<g id="SVGRepo_iconCarrier">
<path d="M 0.198 14.001 C -0.192 14.392 -0.088 11.275 0.1 11.088 L 7.293 4.293 C 7.684 3.902 8.316 3.902 8.707 4.293 L 15.833 11.258 C 16.021 11.445 16.203 14.644 15.812 14.253 L 8 6.414 L 0.198 14.001 Z" fill="#dbdbdb"></path>
</g>
</svg>
</span>
</div>
</div>
</div>
<div className={styles.RibbonBanner}></div> <div className={styles.RibbonBanner}></div>
<h2 className={styles["Transactions-detail"]}>
Transaction ID: {transaction.transactionId}
</h2>
<h2 className={styles["Transactions-detail"]}>
Payment Type: {transaction.payment_type}
</h2>
<ul> <ul>
{transaction.DetailedTransactions.map((detail) => ( {transaction.DetailedTransactions.map((detail) => (
<li key={detail.detailedTransactionId}> <li key={detail.detailedTransactionId}>
@@ -137,10 +181,9 @@ export default function Transactions({ propsShopId, sendParam, deviceType }) {
</ul> </ul>
<h2 className={styles["Transactions-detail"]}> <h2 className={styles["Transactions-detail"]}>
{transaction.serving_type === "pickup" {transaction.serving_type === "pickup"
? "Self pickup" ? "Ambil sendiri"
: `Serve to ${ : `Diantar ke meja ${transaction.Table ? transaction.Table.tableNo : "N/A"
transaction.Table ? transaction.Table.tableNo : "N/A" }`}
}`}
</h2> </h2>
{transaction.notes != "" && ( {transaction.notes != "" && (
<> <>
@@ -178,17 +221,17 @@ export default function Transactions({ propsShopId, sendParam, deviceType }) {
{isPaymentLoading ? ( {isPaymentLoading ? (
<ColorRing height="50" width="50" color="white" /> <ColorRing height="50" width="50" color="white" />
) : transaction.confirmed === 1 ? ( ) : transaction.confirmed === 1 ? (
"Confirm has paid" // Display "Confirm has paid" if the transaction is confirmed (1) "Konfirmasi telah bayar" // Display "Confirm has paid" if the transaction is confirmed (1)
) : transaction.confirmed === -1 ? ( ) : transaction.confirmed === -1 ? (
"Declined" // Display "Declined" if the transaction is declined (-1) "Ditolak" // Display "Declined" if the transaction is declined (-1)
) : transaction.confirmed === -2 ? ( ) : transaction.confirmed === -2 ? (
"Canceled" // Display "Declined" if the transaction is declined (-1) "Dibatalkan" // Display "Declined" if the transaction is declined (-1)
) : transaction.confirmed === 2 ? ( ) : transaction.confirmed === 2 ? (
"Confirm item has ready" // Display "Item ready" if the transaction is ready (2) "Konfirmasi pesanan siap" // Display "Item ready" if the transaction is ready (2)
) : transaction.confirmed === 3 ? ( ) : transaction.confirmed === 3 ? (
"Transaction success" // Display "Item ready" if the transaction is ready (2) "Transaction success" // Display "Item ready" if the transaction is ready (2)
) : ( ) : (
"Confirm availability" // Display "Confirm availability" if the transaction is not confirmed (0) "Konfirmasi ketersediaan" // Display "Confirm availability" if the transaction is not confirmed (0)
)} )}
</button> </button>
</div> </div>
@@ -197,7 +240,7 @@ export default function Transactions({ propsShopId, sendParam, deviceType }) {
className={styles.DeclineButton} className={styles.DeclineButton}
onClick={() => handleDecline(transaction.transactionId)} onClick={() => handleDecline(transaction.transactionId)}
> >
decline batalkan
</h5> </h5>
)} )}
</div> </div>

View File

@@ -124,9 +124,6 @@ export default function Transactions({
return ( return (
<div className={styles.Transactions}> <div className={styles.Transactions}>
<div style={{ marginTop: "30px" }}></div>
<h2 className={styles["Transactions-title"]}>Transactions</h2>
{/* <TableCanvas tables={tables} selectedTable={selectedTable} /> */}
<div className={styles.TransactionListContainer}> <div className={styles.TransactionListContainer}>
{transaction && ( {transaction && (
<div <div
@@ -137,6 +134,24 @@ export default function Transactions({
} }
style={{ overflow: "hidden" }} style={{ overflow: "hidden" }}
> >
<div className={styles['receipt-header']}>
<ColorRing className={styles['receipt-logo']} />
<div className={styles['receipt-info']}>
<h3>silahkan bayar ke kasir</h3>
<p>Transaction ID: {transaction.transactionId}</p>
<p>Payment Type: {transaction.payment_type}</p>
</div>
</div>
<div className={styles['dotted-line']}>
<div className={styles['circle-left']} >
</div>
<div className={styles['line']} ></div>
<div className={styles['circle-right']} >
</div>
</div>
<h2 className={styles["Transactions-detail"]}> <h2 className={styles["Transactions-detail"]}>
Transaction ID: {transaction.transactionId} Transaction ID: {transaction.transactionId}
</h2> </h2>
@@ -209,8 +224,7 @@ export default function Transactions({
> >
{transaction.payment_type == 'cash' || isPaymentLoading ? ( {transaction.payment_type == 'cash' || isPaymentLoading ? (
<> <>
{transaction.payment_type == 'cash' && <p>tunggu konfirmasi</p>} {transaction.payment_type == 'cash' && <p>Bayar nanti</p>}
<ColorRing height="50" width="50" color="white" />
</> </>
) : isPaymentOpen ? ( ) : isPaymentOpen ? (
"Claim has paid" // Display "Confirm has paid" if the transaction is confirmed (1) "Claim has paid" // Display "Confirm has paid" if the transaction is confirmed (1)

View File

@@ -1,35 +1,207 @@
import React from "react"; import React, { useRef, useEffect, useState } from "react";
import { ColorRing } from "react-loader-spinner";
import styles from "./Transactions.module.css"; import styles from "./Transactions.module.css";
import { useParams } from "react-router-dom";
import { ColorRing } from "react-loader-spinner";
import {
getTransaction,
confirmTransaction,
declineTransaction,
} from "../helpers/transactionHelpers";
import { getTables } from "../helpers/tableHelper";
import TableCanvas from "../components/TableCanvas";
import { useSearchParams } from "react-router-dom";
export default function Transaction_pending() { export default function Transactions({ propsShopId, sendParam, deviceType, handleMoveToTransaction, shopImg }) {
const containerStyle = { const { shopId, tableId } = useParams();
display: "flex", if (sendParam) sendParam({ shopId, tableId });
justifyContent: "center",
alignItems: "center", const [tables, setTables] = useState([]);
width: "100%", const [selectedTable, setSelectedTable] = useState(null);
height: "100%", // This makes the container stretch to the bottom of the viewport const [isPaymentLoading, setIsPaymentLoading] = useState(false);
backgroundColor: "#000", // Optional: Set a background color if you want to see the color ring clearly const [searchParams] = useSearchParams();
const [transaction, setTransaction] = useState(null);
const noteRef = useRef(null);
useEffect(() => {
const transactionId = searchParams.get("transactionId") || "";
const fetchData = async () => {
try {
const fetchedTransaction = await getTransaction(transactionId);
setTransaction(fetchedTransaction);
console.log(transaction);
} catch (error) {
console.error("Error fetching transaction:", error);
}
};
fetchData();
}, [searchParams]);
useEffect(() => {
const fetchData = async () => {
try {
const fetchedTables = await getTables(shopId || propsShopId);
setTables(fetchedTables);
} catch (error) {
console.error("Error fetching tables:", error);
}
};
fetchData();
}, [shopId || propsShopId]);
const calculateTotalPrice = (detailedTransactions) => {
return detailedTransactions.reduce((total, dt) => {
return total + dt.qty * dt.Item.price;
}, 0);
}; };
const handleConfirm = async (transactionId) => {
if (isPaymentLoading) return;
setIsPaymentLoading(true);
try {
const c = await confirmTransaction(transactionId);
if (c) {
setTransaction({ ...transaction, confirmed: c.confirmed });
}
} catch (error) {
console.error("Error processing payment:", error);
} finally {
setIsPaymentLoading(false);
}
};
const handleDecline = async (transactionId) => {
if (isPaymentLoading) return;
setIsPaymentLoading(true);
try {
const c = await declineTransaction(transactionId);
// if (c) {
// // Update the confirmed status locally
// setTransactions((prevTransactions) =>
// prevTransactions.map((transaction) =>
// transaction.transactionId === transactionId
// ? { ...transaction, confirmed: -1 } // Set to confirmed
// : transaction
// )
// );
// }
} catch (error) {
console.error("Error processing payment:", error);
} finally {
setIsPaymentLoading(false);
}
};
const autoResizeTextArea = (textarea) => {
if (textarea) {
textarea.style.height = "auto"; // Reset height
textarea.style.height = `${textarea.scrollHeight}px`; // Set new height
}
};
useEffect(() => {
if (noteRef.current) {
autoResizeTextArea(noteRef.current);
}
}, [transaction?.notes]);
return ( return (
<div className={styles.Transactions}> <div className={styles.Transactions}>
<div className={containerStyle}>
<div style={{ marginTop: "30px", textAlign: "center" }}> <div className={styles.TransactionListContainer}>
<h2>Transation Pending</h2> {transaction && (
<div style={{ marginTop: "20px" }}> <div
<ColorRing key={transaction.transactionId}
visible={true} className={styles.RoundedRectangle}
height="80" onClick={() =>
width="80" setSelectedTable(transaction.Table || { tableId: 0 })
ariaLabel="blocks-loading" }
wrapperStyle={{}} >
wrapperClass="blocks-wrapper"
colors={["#4fa94d", "#f7c34c", "#ffa53c", "#e34f53", "#d23a8d"]} <div className={styles['receipt-header']}>
/> <ColorRing className={styles['receipt-logo']} />
<p>Waiting for clerk confirmation...</p> <div className={styles['receipt-info']}>
<h3>sedang memeriksa ketersediaan</h3>
<p>Transaction ID: {transaction.transactionId}</p>
<p>Payment Type: {transaction.payment_type}</p>
</div>
</div>
<div className={styles['dotted-line']}>
<div className={styles['circle-left']} onClick={() => handleMoveToTransaction('previous', transaction.transactionId)}>
</div>
<div className={styles['line']} ></div>
<div className={styles['circle-right']} onClick={() => handleMoveToTransaction('next', transaction.transactionId)}>
</div>
</div>
<div className={styles.RibbonBanner}></div>
<ul>
{transaction.DetailedTransactions.map((detail) => (
<li key={detail.detailedTransactionId}>
<span>{detail.Item.name}</span> - {detail.qty} x Rp{" "}
{detail.Item.price}
</li>
))}
</ul>
<h2 className={styles["Transactions-detail"]}>
{transaction.serving_type === "pickup"
? "Ambil sendiri"
: `Diantar ke meja ${transaction.Table ? transaction.Table.tableNo : "N/A"
}`}
</h2>
{transaction.notes != "" && (
<>
<div className={styles.NoteContainer}>
<span>Note :</span>
<span></span>
</div>
<div className={styles.NoteContainer}>
<textarea
className={styles.NoteInput}
value={transaction.notes}
ref={noteRef}
disabled
/>
</div>
</>
)}
<div className={styles.TotalContainer}>
<span>Total:</span>
<span>
Rp {calculateTotalPrice(transaction.DetailedTransactions)}
</span>
</div>
<div className={styles.TotalContainer}>
<button
className={styles.PayButton}
onClick={() => handleConfirm(transaction.transactionId)}
disabled={
transaction.confirmed === -1 ||
transaction.confirmed === 3 ||
isPaymentLoading
} // Disable button if confirmed (1) or declined (-1) or
>
{isPaymentLoading ? (
<ColorRing height="50" width="50" color="white" />
) : transaction.confirmed === 1 ? (
"Konfirmasi telah bayar" // Display "Confirm has paid" if the transaction is confirmed (1)
) : transaction.confirmed === -1 ? (
"Ditolak" // Display "Declined" if the transaction is declined (-1)
) : transaction.confirmed === -2 ? (
"Dibatalkan" // Display "Declined" if the transaction is declined (-1)
) : transaction.confirmed === 2 ? (
"Konfirmasi pesanan siap" // Display "Item ready" if the transaction is ready (2)
) : transaction.confirmed === 3 ? (
"Transaction success" // Display "Item ready" if the transaction is ready (2)
) : (
"Batalkan" // Display "Confirm availability" if the transaction is not confirmed (0)
)}
</button>
</div>
</div> </div>
</div> )}
</div> </div>
</div> </div>
); );

View File

@@ -25,7 +25,7 @@ export default function Transactions({ propsShopId, sendParam, deviceType }) {
const fetchTransactions = async () => { const fetchTransactions = async () => {
try { try {
let response; let response;
response = await getTransactionsFromCafe(shopId || propsShopId, 5); response = await getTransactionsFromCafe(shopId || propsShopId, 5, false);
setTransactions(response); setTransactions(response);
response = await getMyTransactions(shopId || propsShopId, 5); response = await getMyTransactions(shopId || propsShopId, 5);
setMyTransactions(response); setMyTransactions(response);

View File

@@ -1,6 +1,5 @@
.Transactions { .Transactions {
overflow-x: hidden; overflow-x: hidden;
height: 100%;
background-color: white; background-color: white;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -26,7 +25,7 @@
font-family: "Poppins", sans-serif; font-family: "Poppins", sans-serif;
font-weight: 500; font-weight: 500;
font-style: normal; font-style: normal;
font-size: 20px; font-size: 15px;
color: rgba(88, 55, 50, 1); color: rgba(88, 55, 50, 1);
text-align: left; text-align: left;
margin-left: 20px; margin-left: 20px;
@@ -40,17 +39,20 @@
} }
.TotalContainer { .TotalContainer {
display: flex; display: flex
justify-content: space-between; ;
width: 100%; /* Ensures it takes up full width */ justify-content: space-between;
margin: 0 auto; /* width: 100%; */
font-family: "Poppins", sans-serif; margin: 0 auto;
font-weight: 600; font-family: "Poppins", sans-serif;
font-style: normal; font-weight: 600;
font-size: 1.5em; font-style: normal;
padding: 10px; font-size: 15px;
box-sizing: border-box; /* Includes padding in width */ /* padding: 10px; */
margin-bottom: 17px; box-sizing: border-box;
margin-bottom: 17px;
margin-left: 20px;
margin-right: 20px;
} }
.PaymentContainer { .PaymentContainer {
@@ -61,10 +63,9 @@
font-family: "Poppins", sans-serif; font-family: "Poppins", sans-serif;
font-weight: 600; font-weight: 600;
font-style: normal; font-style: normal;
font-size: 1.5em; font-size: 15px;
padding: 10px; padding: 10px;
box-sizing: border-box; /* Includes padding in width */ box-sizing: border-box; /* Includes padding in width */
margin-bottom: 17px;
justify-content: center; justify-content: center;
} }
@@ -72,8 +73,8 @@
font-family: "Poppins", sans-serif; font-family: "Poppins", sans-serif;
font-weight: 500; font-weight: 500;
font-style: normal; font-style: normal;
font-size: 70%; /* Adjusted for better readability */ font-size: 15px; /* Adjusted for better readability */
padding: 12px 24px; /* Added padding for a better look */ padding: 12px 16px; /* Added padding for a better look */
border-radius: 50px; border-radius: 50px;
background-color: rgba(88, 55, 50, 1); background-color: rgba(88, 55, 50, 1);
color: white; color: white;
@@ -87,7 +88,7 @@
font-family: "Poppins", sans-serif; font-family: "Poppins", sans-serif;
font-weight: 500; font-weight: 500;
font-style: normal; font-style: normal;
font-size: 20px; font-size: 15px;
padding: 12px 24px; /* Add some padding for spacing */ padding: 12px 24px; /* Add some padding for spacing */
color: rgba(88, 55, 50, 1); color: rgba(88, 55, 50, 1);
background-color: transparent; /* No background */ background-color: transparent; /* No background */
@@ -96,7 +97,6 @@
cursor: pointer; cursor: pointer;
display: block; /* Center the text horizontally */ display: block; /* Center the text horizontally */
text-align: center; /* Center the text within the button */ text-align: center; /* Center the text within the button */
margin-bottom: 23px; /* Space at the bottom to match the PayButton */
} }
.DeclineButton.active { .DeclineButton.active {
@@ -139,6 +139,7 @@
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
margin-left: 20px; margin-left: 20px;
margin-right: 20px;
font-size: 1em; font-size: 1em;
margin-bottom: 15px; margin-bottom: 15px;
} }
@@ -155,6 +156,7 @@
} }
.RibbonBanner { .RibbonBanner {
pointer-events: none;
position: absolute; position: absolute;
top: 0; top: 0;
width: 200px; width: 200px;
@@ -178,3 +180,95 @@
top: 60px; top: 60px;
left: -15px; left: -15px;
} }
/* Header with logo and receipt info */
.receipt-header {
text-align: center;
}
.receipt-logo {
width: 80px;
height: 80px;
border-radius: 50%; /* Circular logo */
object-fit: cover;
margin-bottom: 10px;
}
.receipt-info h3 {
font-size: 16px;
margin: 5px 0;
}
.receipt-info p {
font-size: 14px;
margin: 2px 0;
}
/* Dotted line with circular cutouts */
.dotted-line {
display: flex;
align-items: center;
justify-content: center;
margin: 15px 0;
}
.dotted-line .line {
border-top: 13px dotted #dbdbdb;
width: 100%;
margin: 0 20px;
}
.dotted-line .circle-left {
left: -25px;
position: absolute;
width: 50px;
height: 50px;
border-radius: 50%;
background-color: #dbdbdb;
display: flex; /* Use flexbox to center the inner circle */
justify-content: center; /* Center horizontally */
align-items: center; /* Center vertically */
}
.dotted-line .circle-right {
right: -25px;
position: absolute;
width: 50px;
height: 50px;
border-radius: 50%;
background-color: #dbdbdb;
display: flex; /* Use flexbox to center the inner circle */
justify-content: center; /* Center horizontally */
align-items: center; /* Center vertically */
}
.inner-circle {
width: 80%;
height: 80%;
border-radius: 50%; /* Make it a circle */
background-color: white; /* Background color of the inner circle */
}
.inner-text {
font-size: 24px; /* Adjust the font size as needed */
color: white; /* Text color */
}
.circle {
position: absolute;
display: inline-block;
width: 24px;
height: 24px;
padding: 4px;
border-radius: 50%;
margin-top: -43px;
margin-left: 43px;
/* Just making it pretty */
background: #38a9e4;
color: white;
font-family: Helvetica, Arial Black, sans;
font-size: 20px;
text-align: center;
}