ok
This commit is contained in:
2
.vscode/settings.json
vendored
Normal file
2
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
{
|
||||||
|
}
|
||||||
6
package-lock.json
generated
6
package-lock.json
generated
@@ -16,6 +16,7 @@
|
|||||||
"@testing-library/user-event": "^13.5.0",
|
"@testing-library/user-event": "^13.5.0",
|
||||||
"caniuse-lite": "^1.0.30001690",
|
"caniuse-lite": "^1.0.30001690",
|
||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
|
"dayjs": "^1.11.13",
|
||||||
"html-to-image": "^1.11.11",
|
"html-to-image": "^1.11.11",
|
||||||
"html2canvas": "^1.4.1",
|
"html2canvas": "^1.4.1",
|
||||||
"jsqr": "^1.4.0",
|
"jsqr": "^1.4.0",
|
||||||
@@ -7760,6 +7761,11 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/dayjs": {
|
||||||
|
"version": "1.11.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz",
|
||||||
|
"integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg=="
|
||||||
|
},
|
||||||
"node_modules/debug": {
|
"node_modules/debug": {
|
||||||
"version": "4.3.5",
|
"version": "4.3.5",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz",
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"name": "groovebrew-mockup",
|
"name": "groovebrew-mockup",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"homepage": "https://kedaimaster.com",
|
"homepage": "https://dev.kedaimaster.com",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emotion/react": "^11.13.3",
|
"@emotion/react": "^11.13.3",
|
||||||
"@emotion/styled": "^11.13.0",
|
"@emotion/styled": "^11.13.0",
|
||||||
@@ -12,6 +12,7 @@
|
|||||||
"@testing-library/user-event": "^13.5.0",
|
"@testing-library/user-event": "^13.5.0",
|
||||||
"caniuse-lite": "^1.0.30001690",
|
"caniuse-lite": "^1.0.30001690",
|
||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
|
"dayjs": "^1.11.13",
|
||||||
"html-to-image": "^1.11.11",
|
"html-to-image": "^1.11.11",
|
||||||
"html2canvas": "^1.4.1",
|
"html2canvas": "^1.4.1",
|
||||||
"jsqr": "^1.4.0",
|
"jsqr": "^1.4.0",
|
||||||
|
|||||||
50
src/App.js
50
src/App.js
@@ -56,6 +56,7 @@ function App() {
|
|||||||
const [table, setTable] = useState([]);
|
const [table, setTable] = useState([]);
|
||||||
const [totalItemsCount, setTotalItemsCount] = useState(0);
|
const [totalItemsCount, setTotalItemsCount] = useState(0);
|
||||||
const [totalPrice, setTotalPrice] = useState(0);
|
const [totalPrice, setTotalPrice] = useState(0);
|
||||||
|
const [lastTransaction, setLastTransaction] = useState(null);
|
||||||
const [deviceType, setDeviceType] = useState("");
|
const [deviceType, setDeviceType] = useState("");
|
||||||
const [shop, setShop] = useState([]);
|
const [shop, setShop] = useState([]);
|
||||||
const [shopItems, setShopItems] = useState([]);
|
const [shopItems, setShopItems] = useState([]);
|
||||||
@@ -77,13 +78,23 @@ function App() {
|
|||||||
'transaction_failed',
|
'transaction_failed',
|
||||||
];
|
];
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const calculateTotalsFromLocalStorage = () => {
|
const calculateTotalsFromLocalStorage = () => {
|
||||||
const { totalCount, totalPrice } = calculateTotals(shopId);
|
const { totalCount, totalPrice } = calculateTotals(shopId);
|
||||||
setTotalItemsCount(totalCount);
|
setTotalItemsCount(totalCount);
|
||||||
setTotalPrice(totalPrice);
|
setTotalPrice(totalPrice);
|
||||||
|
|
||||||
|
|
||||||
|
// If 'lastTransaction' exists, proceed
|
||||||
|
const lastTransaction = JSON.parse(localStorage.getItem("lastTransaction"));
|
||||||
|
console.log(lastTransaction);
|
||||||
|
|
||||||
|
if (lastTransaction != null) {
|
||||||
|
setLastTransaction(lastTransaction);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
|
||||||
calculateTotalsFromLocalStorage();
|
calculateTotalsFromLocalStorage();
|
||||||
|
|
||||||
const handleStorageChange = () => {
|
const handleStorageChange = () => {
|
||||||
@@ -181,13 +192,42 @@ function App() {
|
|||||||
console.log("transaction notification");
|
console.log("transaction notification");
|
||||||
// Call `setModal` with content and parameters
|
// Call `setModal` with content and parameters
|
||||||
setModal("transaction_pending", data);
|
setModal("transaction_pending", data);
|
||||||
|
|
||||||
|
localStorage.setItem('cart', []);
|
||||||
|
|
||||||
|
calculateTotalsFromLocalStorage();
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("transaction_confirmed", async (data) => {
|
socket.on("transaction_confirmed", async (data) => {
|
||||||
console.log("transaction notification" + data);
|
console.log("transaction notification: " + data);
|
||||||
setModal("transaction_confirmed", data);
|
setModal("transaction_confirmed", data);
|
||||||
|
|
||||||
|
localStorage.setItem('cart', []);
|
||||||
|
|
||||||
|
const startTime = Date.now(); // Capture the start time
|
||||||
|
const timeout = 10000; // 10 seconds timeout in milliseconds
|
||||||
|
|
||||||
|
calculateTotalsFromLocalStorage();
|
||||||
|
|
||||||
|
while (localStorage.getItem("lastTransaction") === null) {
|
||||||
|
if (Date.now() - startTime > timeout) {
|
||||||
|
return; // Exit the function and don't proceed further
|
||||||
|
}
|
||||||
|
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// If 'lastTransaction' exists, proceed
|
||||||
|
const lastTransaction = JSON.parse(localStorage.getItem("lastTransaction"));
|
||||||
|
console.log(lastTransaction);
|
||||||
|
|
||||||
|
if (lastTransaction != null) {
|
||||||
|
setLastTransaction(lastTransaction);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
socket.on("transaction_success", async (data) => {
|
socket.on("transaction_success", async (data) => {
|
||||||
console.log("transaction notification");
|
console.log("transaction notification");
|
||||||
setModal("transaction_success", data);
|
setModal("transaction_success", data);
|
||||||
@@ -228,6 +268,7 @@ function App() {
|
|||||||
} else {
|
} else {
|
||||||
console.log(data)
|
console.log(data)
|
||||||
setUser(data.data.user);
|
setUser(data.data.user);
|
||||||
|
if(data.data.latestOpenBillTransaction != null) localStorage.setItem('lastTransaction', JSON.stringify(data.data.latestOpenBillTransaction))
|
||||||
if (
|
if (
|
||||||
data.data.user.password == "unsetunsetunset" &&
|
data.data.user.password == "unsetunsetunset" &&
|
||||||
localStorage.getItem("settings")
|
localStorage.getItem("settings")
|
||||||
@@ -567,6 +608,7 @@ function App() {
|
|||||||
queue={queue}
|
queue={queue}
|
||||||
cartItemsLength={totalItemsCount}
|
cartItemsLength={totalItemsCount}
|
||||||
totalPrice={totalPrice}
|
totalPrice={totalPrice}
|
||||||
|
lastTransaction={lastTransaction}
|
||||||
/>
|
/>
|
||||||
{/* <Footer
|
{/* <Footer
|
||||||
showTable={true}
|
showTable={true}
|
||||||
@@ -607,7 +649,9 @@ function App() {
|
|||||||
<>
|
<>
|
||||||
<Cart
|
<Cart
|
||||||
shopId={shopId}
|
shopId={shopId}
|
||||||
|
shop={shop}
|
||||||
table={table}
|
table={table}
|
||||||
|
setModal={setModal}
|
||||||
sendParam={handleSetParam}
|
sendParam={handleSetParam}
|
||||||
socket={socket}
|
socket={socket}
|
||||||
totalItemsCount={totalItemsCount}
|
totalItemsCount={totalItemsCount}
|
||||||
@@ -649,6 +693,7 @@ function App() {
|
|||||||
element={
|
element={
|
||||||
<>
|
<>
|
||||||
<Transactions
|
<Transactions
|
||||||
|
shop={shop}
|
||||||
shopId={shopId}
|
shopId={shopId}
|
||||||
sendParam={handleSetParam}
|
sendParam={handleSetParam}
|
||||||
deviceType={deviceType}
|
deviceType={deviceType}
|
||||||
@@ -672,6 +717,7 @@ function App() {
|
|||||||
<Modal
|
<Modal
|
||||||
user={user}
|
user={user}
|
||||||
shop={shop}
|
shop={shop}
|
||||||
|
deviceType={deviceType}
|
||||||
isOpen={isModalOpen}
|
isOpen={isModalOpen}
|
||||||
modalContent={modalContent}
|
modalContent={modalContent}
|
||||||
handleMoveToTransaction={handleMoveToTransaction}
|
handleMoveToTransaction={handleMoveToTransaction}
|
||||||
|
|||||||
@@ -108,7 +108,7 @@
|
|||||||
padding-top: 10px;
|
padding-top: 10px;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
color: transparent;
|
color: transparent;
|
||||||
width: 21.7%;
|
width: 178.89px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.downloadQR.active{
|
.downloadQR.active{
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { useNavigationHelpers } from "../helpers/navigationHelpers";
|
|||||||
import Switch from "react-switch";
|
import Switch from "react-switch";
|
||||||
|
|
||||||
const HeaderBar = styled.div`
|
const HeaderBar = styled.div`
|
||||||
|
pointer-events: auto;
|
||||||
margin-top:${(props) => (props.HeaderMargin)};
|
margin-top:${(props) => (props.HeaderMargin)};
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import CreateTenant from "../pages/CreateTenant"
|
|||||||
import TablesPage from "./TablesPage.js";
|
import TablesPage from "./TablesPage.js";
|
||||||
import PaymentOptions from "./PaymentOptions.js";
|
import PaymentOptions from "./PaymentOptions.js";
|
||||||
import Transaction from "../pages/Transaction";
|
import Transaction from "../pages/Transaction";
|
||||||
|
import Transaction_item from "../pages/Transaction_item";
|
||||||
import Transaction_pending from "../pages/Transaction_pending";
|
import Transaction_pending from "../pages/Transaction_pending";
|
||||||
import Transaction_confirmed from "../pages/Transaction_confirmed";
|
import Transaction_confirmed from "../pages/Transaction_confirmed";
|
||||||
import Transaction_success from "../pages/Transaction_success";
|
import Transaction_success from "../pages/Transaction_success";
|
||||||
@@ -18,6 +19,7 @@ import MaterialList from "../pages/MaterialList.js";
|
|||||||
import MaterialMutationsPage from "../pages/MaterialMutationsPage.js";
|
import MaterialMutationsPage from "../pages/MaterialMutationsPage.js";
|
||||||
import Reports from "../pages/Reports.js";
|
import Reports from "../pages/Reports.js";
|
||||||
import NotificationRequest from "../pages/NotificationRequest.js";
|
import NotificationRequest from "../pages/NotificationRequest.js";
|
||||||
|
import Unavailable from "../pages/Unavailable.js";
|
||||||
import NotificationBlocked from "../pages/NotificationBlocked.js";
|
import NotificationBlocked from "../pages/NotificationBlocked.js";
|
||||||
import WelcomePageEditor from "../pages/WelcomePageEditor.js";
|
import WelcomePageEditor from "../pages/WelcomePageEditor.js";
|
||||||
import GuidePage from "../pages/GuidePage";
|
import GuidePage from "../pages/GuidePage";
|
||||||
@@ -32,7 +34,7 @@ import CreateCoupon from "../pages/CreateCoupon";
|
|||||||
import CheckCoupon from "../pages/CheckCoupon";
|
import CheckCoupon from "../pages/CheckCoupon";
|
||||||
import CreateUserWithCoupon from "../pages/CreateUserWithCoupon";
|
import CreateUserWithCoupon from "../pages/CreateUserWithCoupon";
|
||||||
|
|
||||||
const Modal = ({ user, shop, isOpen, onClose, modalContent, setModal, handleMoveToTransaction,welcomePageConfig, onModalCloseFunction }) => {
|
const Modal = ({ user, shop, isOpen, onClose, modalContent, deviceType, setModal, handleMoveToTransaction,welcomePageConfig, onModalCloseFunction }) => {
|
||||||
|
|
||||||
const [shopImg, setShopImg] = useState('');
|
const [shopImg, setShopImg] = useState('');
|
||||||
const [updateKey, setUpdateKey] = useState(0);
|
const [updateKey, setUpdateKey] = useState(0);
|
||||||
@@ -74,6 +76,7 @@ const Modal = ({ user, shop, isOpen, onClose, modalContent, setModal, handleMove
|
|||||||
{modalContent === "edit_account" && <AccountUpdatePage user={user} />}
|
{modalContent === "edit_account" && <AccountUpdatePage user={user} />}
|
||||||
{modalContent === "reset-password" && <ResetPassword />}
|
{modalContent === "reset-password" && <ResetPassword />}
|
||||||
{modalContent === "req_notification" && <NotificationRequest setModal={setModal} />}
|
{modalContent === "req_notification" && <NotificationRequest setModal={setModal} />}
|
||||||
|
{modalContent === "unavailable" && <Unavailable close={handleContentClick} />}
|
||||||
{modalContent === "blocked_notification" && <NotificationBlocked />}
|
{modalContent === "blocked_notification" && <NotificationBlocked />}
|
||||||
{modalContent === "create_clerk" && <CreateClerk shopId={shop.cafeId} />}
|
{modalContent === "create_clerk" && <CreateClerk shopId={shop.cafeId} />}
|
||||||
{modalContent === "create_kedai" && <CreateCafe shopId={shop.cafeId} />}
|
{modalContent === "create_kedai" && <CreateCafe shopId={shop.cafeId} />}
|
||||||
@@ -85,9 +88,10 @@ const Modal = ({ user, shop, isOpen, onClose, modalContent, setModal, handleMove
|
|||||||
{modalContent === "transaction_canceled" && (
|
{modalContent === "transaction_canceled" && (
|
||||||
<Transaction propsShopId={shop.cafeId} />
|
<Transaction propsShopId={shop.cafeId} />
|
||||||
)}
|
)}
|
||||||
{modalContent === "transaction_pending" && <Transaction_pending />}
|
{modalContent === "transaction_pending" && <Transaction_pending deviceType={deviceType}/>}
|
||||||
|
{modalContent === "transaction_item" && <Transaction_item />}
|
||||||
{modalContent === "transaction_confirmed" && (
|
{modalContent === "transaction_confirmed" && (
|
||||||
<Transaction_confirmed paymentUrl={shop.qrPayment} />
|
<Transaction_confirmed deviceType={deviceType} paymentUrl={shop.qrPayment} />
|
||||||
)}
|
)}
|
||||||
{modalContent === "payment_claimed" && (
|
{modalContent === "payment_claimed" && (
|
||||||
<Payment_claimed paymentUrl={shop.qrPayment} />
|
<Payment_claimed paymentUrl={shop.qrPayment} />
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
getCafe,
|
getCafe,
|
||||||
saveCafeDetails,
|
saveCafeDetails,
|
||||||
setConfirmationStatus,
|
setConfirmationStatus,
|
||||||
|
setOpenBillAvailability
|
||||||
} from "../helpers/cafeHelpers";
|
} from "../helpers/cafeHelpers";
|
||||||
import Switch from "react-switch"; // Import the Switch component
|
import Switch from "react-switch"; // Import the Switch component
|
||||||
|
|
||||||
@@ -15,10 +16,13 @@ const SetPaymentQr = ({ shopId,
|
|||||||
const [qrPayment, setQrPayment] = useState();
|
const [qrPayment, setQrPayment] = useState();
|
||||||
const [qrCodeDetected, setQrCodeDetected] = useState(false);
|
const [qrCodeDetected, setQrCodeDetected] = useState(false);
|
||||||
const [isNeedConfirmationState, setIsNeedConfirmationState] = useState(0);
|
const [isNeedConfirmationState, setIsNeedConfirmationState] = useState(0);
|
||||||
|
const [isQRISavailable, setIsQRISavailable] = useState(0);
|
||||||
const qrPaymentInputRef = useRef(null);
|
const qrPaymentInputRef = useRef(null);
|
||||||
const qrCodeContainerRef = useRef(null);
|
const qrCodeContainerRef = useRef(null);
|
||||||
const [cafe, setCafe] = useState({});
|
const [cafe, setCafe] = useState({});
|
||||||
const [isConfig, setIsConfig] = useState(false);
|
|
||||||
|
const [isConfigQRIS, setIsConfigQRIS] = useState(false);
|
||||||
|
const [isOpenBillAvailable, setIsOpenBillAvailable] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchCafe = async () => {
|
const fetchCafe = async () => {
|
||||||
@@ -29,6 +33,8 @@ const SetPaymentQr = ({ shopId,
|
|||||||
console.log(response.needsConfirmation);
|
console.log(response.needsConfirmation);
|
||||||
|
|
||||||
setIsNeedConfirmationState(response.needsConfirmation === true ? 1 : 0); // Set state based on fetched data
|
setIsNeedConfirmationState(response.needsConfirmation === true ? 1 : 0); // Set state based on fetched data
|
||||||
|
setIsQRISavailable(response.isQRISavailable === true ? 1 : 0); // Set state based on fetched data
|
||||||
|
setIsOpenBillAvailable(response.isOpenBillAvailable === true ? 1 : 0); // Set state based on fetched data
|
||||||
setQrPosition([response.xposition, response.yposition]);
|
setQrPosition([response.xposition, response.yposition]);
|
||||||
setQrSize(response.scale);
|
setQrSize(response.scale);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -40,10 +46,10 @@ const SetPaymentQr = ({ shopId,
|
|||||||
|
|
||||||
// Detect QR code when qrPayment updates
|
// Detect QR code when qrPayment updates
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (qrPayment) {
|
if (qrPayment && isConfigQRIS) {
|
||||||
detectQRCodeFromContainer();
|
detectQRCodeFromContainer();
|
||||||
}
|
}
|
||||||
}, [qrPayment]);
|
}, [qrPayment, isConfigQRIS]);
|
||||||
|
|
||||||
// Handle file input change
|
// Handle file input change
|
||||||
const handleFileChange = (e) => {
|
const handleFileChange = (e) => {
|
||||||
@@ -77,41 +83,42 @@ const SetPaymentQr = ({ shopId,
|
|||||||
|
|
||||||
// Save cafe details
|
// Save cafe details
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
const qrPaymentFile = qrPaymentInputRef.current.files[0];
|
let qrPaymentFile;
|
||||||
|
if(qrPaymentInputRef?.current?.files[0])
|
||||||
|
qrPaymentFile = qrPaymentInputRef.current.files[0];
|
||||||
const details = {
|
const details = {
|
||||||
qrPosition,
|
qrPosition,
|
||||||
qrSize,
|
qrSize,
|
||||||
qrPaymentFile,
|
qrPaymentFile,
|
||||||
|
isQRISavailable: isQRISavailable === 1,
|
||||||
|
isOpenBillAvailable: isOpenBillAvailable === 1,
|
||||||
|
isNeedConfirmationState: isNeedConfirmationState === 1
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await saveCafeDetails(cafe.cafeId, details);
|
const response = await saveCafeDetails(cafe.cafeId, details);
|
||||||
|
|
||||||
|
setIsNeedConfirmationState(response.needsConfirmation ? 1 : 0); // Update state after saving
|
||||||
|
setIsQRISavailable(response.isQRISavailable ? 1 : 0); // Update state after saving
|
||||||
|
setIsOpenBillAvailable(response.isOpenBillAvailable ? 1 : 0); // Update state after saving
|
||||||
|
|
||||||
console.log("Cafe details saved:", response);
|
console.log("Cafe details saved:", response);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error saving cafe details:", error);
|
console.error("Error saving cafe details:", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await setConfirmationStatus(cafe.cafeId, isNeedConfirmationState === 1);
|
|
||||||
setIsNeedConfirmationState(response.needsConfirmation ? 1 : 0); // Update state after saving
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
setIsNeedConfirmationState(cafe.needsConfirmation ? 1 : 0); // Fallback to initial value
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Handle Switch toggle
|
|
||||||
const handleChange = (checked) => {
|
|
||||||
setIsNeedConfirmationState(checked ? 1 : 0); // Toggle state based on the switch
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleError = () => {
|
|
||||||
setQrPayment('https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSPsNr0TPq8dHT3nBwDQ6OHQQTrqzVFoeBOmuWfgyErrLbJi6f6CnnYhpNHEvkJ_2X-kyI&usqp=CAU'); // Set your fallback image here
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={styles.container}>
|
<div style={styles.container}>
|
||||||
<h3 style={styles.title}>Konfigurasi pembayaran</h3>
|
<h3 style={styles.title}>Konfigurasi pembayaran</h3>
|
||||||
|
|
||||||
|
<div style={styles.switchContainer}>
|
||||||
|
<p style={styles.uploadMessage}>
|
||||||
|
Pembayaran QRIS.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{isConfigQRIS ?
|
||||||
|
<>
|
||||||
<div
|
<div
|
||||||
id="qr-code-container"
|
id="qr-code-container"
|
||||||
ref={qrCodeContainerRef}
|
ref={qrCodeContainerRef}
|
||||||
@@ -141,12 +148,74 @@ const SetPaymentQr = ({ shopId,
|
|||||||
onClick={() => qrPaymentInputRef.current.click()} style={styles.uploadButton}>Ganti</div> : <div
|
onClick={() => qrPaymentInputRef.current.click()} style={styles.uploadButton}>Ganti</div> : <div
|
||||||
onClick={() => qrPaymentInputRef.current.click()} style={styles.uploadButton}>Unggah</div>}
|
onClick={() => qrPaymentInputRef.current.click()} style={styles.uploadButton}>Unggah</div>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div onClick={() => setIsConfigQRIS(false)}
|
||||||
|
|
||||||
|
style={{
|
||||||
|
...styles.qrisConfigButton,
|
||||||
|
width: '100%',
|
||||||
|
marginLeft: "0",
|
||||||
|
}}
|
||||||
|
>Terapkan</div>
|
||||||
|
</>
|
||||||
|
:
|
||||||
|
<>
|
||||||
|
<p style={styles.description}>
|
||||||
|
Aktifkan fitur agar pelanggan dapat menggunakan opsi pembayaran QRIS, namun kasir anda perlu memeriksa rekening untuk memastikan pembayaran.
|
||||||
|
</p>
|
||||||
|
<div style={{ display: 'flex' }}>
|
||||||
|
<Switch
|
||||||
|
onChange={(checked) => setIsQRISavailable(checked ? 1 : 0)}
|
||||||
|
checked={isQRISavailable === 1} // Convert to boolean
|
||||||
|
offColor="#888"
|
||||||
|
onColor="#4CAF50"
|
||||||
|
uncheckedIcon={false}
|
||||||
|
checkedIcon={false}
|
||||||
|
height={25}
|
||||||
|
width={50}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
onClick={() => setIsConfigQRIS(true)}
|
||||||
|
style={{
|
||||||
|
...styles.qrisConfigButton,
|
||||||
|
backgroundColor: isQRISavailable == 1 ? styles.qrisConfigButton.backgroundColor : 'gray',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Konfigurasi QRIS
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
<div style={styles.switchContainer}>
|
<div style={styles.switchContainer}>
|
||||||
|
<p style={styles.uploadMessage}>
|
||||||
|
Open bill
|
||||||
|
</p>
|
||||||
|
<p style={styles.description}>
|
||||||
|
Aktifkan fitur agar pelanggan dapat menambahkan pesanan selama sesi berlangsung tanpa perlu melakukan transaksi baru dan hanya membayar di akhir.
|
||||||
|
</p>
|
||||||
|
<Switch
|
||||||
|
onChange={(checked) => setIsOpenBillAvailable(checked ? 1 : 0)}
|
||||||
|
checked={isOpenBillAvailable === 1} // Convert to boolean
|
||||||
|
offColor="#888"
|
||||||
|
onColor="#4CAF50"
|
||||||
|
uncheckedIcon={false}
|
||||||
|
checkedIcon={false}
|
||||||
|
height={25}
|
||||||
|
width={50}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={styles.switchContainer}>
|
||||||
|
<p style={styles.uploadMessage}>
|
||||||
|
Pengecekan ganda
|
||||||
|
</p>
|
||||||
<p style={styles.description}>
|
<p style={styles.description}>
|
||||||
Nyalakan agar kasir memeriksa kembali ketersediaan produk sebelum pelanggan membayar.
|
Nyalakan agar kasir memeriksa kembali ketersediaan produk sebelum pelanggan membayar.
|
||||||
</p>
|
</p>
|
||||||
<Switch
|
<Switch
|
||||||
onChange={handleChange}
|
onChange={(checked) => setIsNeedConfirmationState(checked ? 1 : 0)}
|
||||||
checked={isNeedConfirmationState === 1} // Convert to boolean
|
checked={isNeedConfirmationState === 1} // Convert to boolean
|
||||||
offColor="#888"
|
offColor="#888"
|
||||||
onColor="#4CAF50"
|
onColor="#4CAF50"
|
||||||
@@ -169,6 +238,11 @@ const SetPaymentQr = ({ shopId,
|
|||||||
// Styles
|
// Styles
|
||||||
const styles = {
|
const styles = {
|
||||||
container: {
|
container: {
|
||||||
|
position: 'relative',
|
||||||
|
overflowY: 'auto',
|
||||||
|
overflowX: 'hidden',
|
||||||
|
maxHeight: '80vh',
|
||||||
|
width: '100%',
|
||||||
backgroundColor: "white",
|
backgroundColor: "white",
|
||||||
padding: "20px",
|
padding: "20px",
|
||||||
borderRadius: "8px",
|
borderRadius: "8px",
|
||||||
@@ -188,14 +262,24 @@ const styles = {
|
|||||||
backgroundSize: "contain",
|
backgroundSize: "contain",
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
margin: "0 auto", // Center the QR code container
|
margin: "0 auto", // Center the QR code container
|
||||||
|
marginTop: '10px'
|
||||||
},
|
},
|
||||||
uploadMessage: {
|
uploadMessage: {
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
textAlign: "left",
|
textAlign: "left",
|
||||||
},
|
},
|
||||||
|
qrisConfigButton: {
|
||||||
|
borderRadius: '15px',
|
||||||
|
backgroundColor: '#28a745',
|
||||||
|
width: '144px',
|
||||||
|
textAlign: 'center',
|
||||||
|
color: 'white',
|
||||||
|
lineHeight: '24px',
|
||||||
|
marginLeft: '14px',
|
||||||
|
},
|
||||||
uploadButton: {
|
uploadButton: {
|
||||||
paddingRight: '10px',
|
paddingRight: '10px',
|
||||||
backgroundColor: 'green',
|
backgroundColor: '#28a745',
|
||||||
borderRadius: '30px',
|
borderRadius: '30px',
|
||||||
color: 'white',
|
color: 'white',
|
||||||
fontWeight: 700,
|
fontWeight: 700,
|
||||||
|
|||||||
@@ -87,16 +87,15 @@ const SetPaymentQr = ({ shop }) => {
|
|||||||
fontsize,
|
fontsize,
|
||||||
fontcolor,
|
fontcolor,
|
||||||
fontPosition: initialFontPos,
|
fontPosition: initialFontPos,
|
||||||
cafeIdentifyName: shop.cafeIdentifyName != cafeIdentifyNameUpdate ? cafeIdentifyNameUpdate : null
|
cafeIdentifyName: shop.cafeIdentifyName != cafeIdentifyNameUpdate ? cafeIdentifyNameUpdate : null,
|
||||||
|
name: shop.name != inputValue ? inputValue : null
|
||||||
};
|
};
|
||||||
|
|
||||||
// Call saveCafeDetails function with the updated details object
|
// Call saveCafeDetails function with the updated details object
|
||||||
saveCafeDetails(shop.cafeId, details)
|
saveCafeDetails(shop.cafeId, details)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
console.log("Cafe details saved:", response);
|
console.log("Cafe details saved:", response);
|
||||||
if (shop.cafeIdentifyName != cafeIdentifyNameUpdate) {
|
|
||||||
window.location.href = `/${cafeIdentifyNameUpdate}?modal=edit_tables`;
|
window.location.href = `/${cafeIdentifyNameUpdate}?modal=edit_tables`;
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error("Error saving cafe details:", error);
|
console.error("Error saving cafe details:", error);
|
||||||
@@ -748,7 +747,7 @@ const SetPaymentQr = ({ shop }) => {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div style={{ height: 110, position: 'relative', scale: '80%', transformOrigin: 'left', left: 0, width: '125%', border: '1px solid black', borderRadius: '8px' }}>
|
<div style={{ height: 110, position: 'relative', scale: '80%', transformOrigin: 'left', left: 0, width: '125%', border: '1px solid black', borderRadius: '8px', marginBottom: '10px' }}>
|
||||||
<div style={{ padding: '16px 12px', height: '84px', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
<div style={{ padding: '16px 12px', height: '84px', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||||
<div style={{ height: '27.4px', fontSize: '17.6px', fontFamily: 'monospace', fontWeight: '500', color: 'rgba(88, 55, 50, 1)' }}>
|
<div style={{ height: '27.4px', fontSize: '17.6px', fontFamily: 'monospace', fontWeight: '500', color: 'rgba(88, 55, 50, 1)' }}>
|
||||||
<input
|
<input
|
||||||
@@ -767,18 +766,6 @@ const SetPaymentQr = ({ shop }) => {
|
|||||||
<img style={{ width: '48px', height: '48px' }} src='https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcS-DjX_bGBax4NL14ULvkAdU4FP3FKoWXWu5w&s' />
|
<img style={{ width: '48px', height: '48px' }} src='https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcS-DjX_bGBax4NL14ULvkAdU4FP3FKoWXWu5w&s' />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<>
|
|
||||||
<div style={styles.uploadMessage}>
|
|
||||||
<p>Nama kedai</p>
|
|
||||||
</div>
|
|
||||||
<div style={styles.resultMessage}>
|
|
||||||
<p>-----------------</p>
|
|
||||||
<div
|
|
||||||
onClick={() => { setIsConfigCafeIdentityName(true); cafeIdentifyNameRef.current && cafeIdentifyNameRef.current.focus(); }} style={styles.changeButton}>Ganti
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
<div
|
<div
|
||||||
id="qr-code-container"
|
id="qr-code-container"
|
||||||
style={{
|
style={{
|
||||||
@@ -835,20 +822,20 @@ const SetPaymentQr = ({ shop }) => {
|
|||||||
{!isViewingQR ?
|
{!isViewingQR ?
|
||||||
<>
|
<>
|
||||||
<div style={styles.uploadMessage}>
|
<div style={styles.uploadMessage}>
|
||||||
<p>QR identifikasi</p>
|
<p>QR sticker kedai dan meja</p>
|
||||||
<p>Background</p>
|
<p>Background</p>
|
||||||
</div>
|
</div>
|
||||||
<div style={styles.resultMessage}>
|
<div style={styles.resultMessage}>
|
||||||
<p onClick={() => { setIsConfig(!isConfig); setIsConfigQR(!isConfigQR); setIsConfigFont(false) }}> {isConfig ? '˅' : '˃'} Konfigurasi QR</p>
|
<p onClick={() => { setIsConfig(!isConfig); setIsConfigQR(!isConfigQR); setIsConfigFont(false) }}> {isConfig ? '˅' : '˃'} Konfigurasi</p>
|
||||||
<div
|
<div
|
||||||
onClick={() => qrBackgroundInputRef.current.click()} style={styles.changeButton}>Ganti</div>
|
onClick={() => qrBackgroundInputRef.current.click()} style={styles.changeButton}>Ganti</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={styles.switchContainer}>
|
<div style={styles.switchContainer}>
|
||||||
{isConfig &&
|
{isConfig &&
|
||||||
<div style={{ marginLeft: '15px' }}>
|
<div style={{ marginLeft: '15px' }}>
|
||||||
{/* <p style={styles.description} onClick={() => { setIsConfigQR(!isConfigQR); setIsConfigFont(false) }}>
|
<p style={styles.description} onClick={() => { setIsConfigQR(!isConfigQR); setIsConfigFont(false) }}>
|
||||||
{isConfigQR ? '˅' : '˃'} Konfigurasi QR
|
{isConfigQR ? '˅' : '˃'} QR
|
||||||
</p> */}
|
</p>
|
||||||
{isConfigQR && <>
|
{isConfigQR && <>
|
||||||
<div style={styles.sliderContainer}>
|
<div style={styles.sliderContainer}>
|
||||||
<label style={styles.label}>
|
<label style={styles.label}>
|
||||||
@@ -908,9 +895,9 @@ const SetPaymentQr = ({ shop }) => {
|
|||||||
</div>
|
</div>
|
||||||
</>}
|
</>}
|
||||||
|
|
||||||
{/* <p style={styles.description} onClick={() => { setIsConfigFont(!isConfigFont); setIsConfigQR(false) }}>
|
<p style={styles.description} onClick={() => { setIsConfigFont(!isConfigFont); setIsConfigQR(false) }}>
|
||||||
{isConfigFont ? '˅' : '˃'} Konfigurasi nomor
|
{isConfigFont ? '˅' : '˃'} Nomor meja
|
||||||
</p> */}
|
</p>
|
||||||
{isConfigFont && (
|
{isConfigFont && (
|
||||||
<>
|
<>
|
||||||
<div style={styles.sliderContainer}>
|
<div style={styles.sliderContainer}>
|
||||||
@@ -989,9 +976,9 @@ const SetPaymentQr = ({ shop }) => {
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
{/* <p style={styles.description} onClick={() => setIsViewTables(!isViewTables)}>
|
<p style={styles.description} onClick={() => setIsViewTables(!isViewTables)}>
|
||||||
{isViewTables ? '˅' : '˃'} Daftar meja
|
{isViewTables ? '˅' : '˃'} Daftar meja
|
||||||
</p> */}
|
</p>
|
||||||
|
|
||||||
{isViewTables &&
|
{isViewTables &&
|
||||||
<div style={{ marginTop: '34px', marginLeft: '15px' }}>
|
<div style={{ marginTop: '34px', marginLeft: '15px' }}>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// src/config.js
|
// src/config.js
|
||||||
|
|
||||||
const API_BASE_URL = 'https://api.kedaimaster.com';
|
const API_BASE_URL = 'https://dev.api.kedaimaster.com';
|
||||||
|
|
||||||
export default API_BASE_URL;
|
export default API_BASE_URL;
|
||||||
|
|||||||
@@ -23,6 +23,27 @@ export async function getCafe(cafeId) {
|
|||||||
console.error("Error:", error);
|
console.error("Error:", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export async function getPaymentMethods(cafeId) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/cafe/get-payment-methods/` + cafeId, {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const payments = await response.json();
|
||||||
|
return payments;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
// api.js
|
// api.js
|
||||||
export const saveWelcomePageConfig = async (cafeId, details) => {
|
export const saveWelcomePageConfig = async (cafeId, details) => {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
@@ -182,6 +203,32 @@ export async function setConfirmationStatus(cafeId, isNeedConfirmation) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function setOpenBillAvailability(cafeId, isOpenBillAvailable) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
`${API_BASE_URL}/cafe/openbill-availability/` + cafeId,
|
||||||
|
{
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Authorization: `Bearer ${getAuthToken()}`,
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ isOpenBillAvailable: isOpenBillAvailable }),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
console.log(data);
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to update item type:", error);
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
// helpers/cafeHelpers.js
|
// helpers/cafeHelpers.js
|
||||||
export async function saveCafeDetails(cafeId, details) {
|
export async function saveCafeDetails(cafeId, details) {
|
||||||
try {
|
try {
|
||||||
@@ -221,8 +268,12 @@ export async function saveCafeDetails(cafeId, details) {
|
|||||||
if (details.qrSize) formData.append("scale", details.qrSize);
|
if (details.qrSize) formData.append("scale", details.qrSize);
|
||||||
|
|
||||||
if (details.cafeIdentifyName) formData.append("cafeIdentifyName", details.cafeIdentifyName);
|
if (details.cafeIdentifyName) formData.append("cafeIdentifyName", details.cafeIdentifyName);
|
||||||
console.log(details.cafeIdentifyName)
|
if (details.name) formData.append("name", details.name);
|
||||||
|
|
||||||
|
if (details.isQRISavailable !== undefined)formData.append("isQRISavailable", details.isQRISavailable)
|
||||||
|
if (details.isOpenBillAvailable !== undefined)formData.append("isOpenBillAvailable", details.isOpenBillAvailable)
|
||||||
|
if (details.isNeedConfirmationState !== undefined)formData.append("isNeedConfirmationState", details.isNeedConfirmationState)
|
||||||
|
console.log(details)
|
||||||
const response = await fetch(`${API_BASE_URL}/cafe/set-cafe/${cafeId}`, {
|
const response = await fetch(`${API_BASE_URL}/cafe/set-cafe/${cafeId}`, {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
headers: {
|
headers: {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import API_BASE_URL from "../config.js";
|
|||||||
import { getLocalStorage, updateLocalStorage } from "./localStorageHelpers";
|
import { getLocalStorage, updateLocalStorage } from "./localStorageHelpers";
|
||||||
import { getItemsByCafeId } from "../helpers/cartHelpers.js";
|
import { getItemsByCafeId } from "../helpers/cartHelpers.js";
|
||||||
|
|
||||||
export async function confirmTransaction(transactionId) {
|
export async function confirmTransaction(transactionId, notAcceptedItems) {
|
||||||
try {
|
try {
|
||||||
const token = getLocalStorage("auth");
|
const token = getLocalStorage("auth");
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
@@ -13,6 +13,9 @@ export async function confirmTransaction(transactionId) {
|
|||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
Authorization: `Bearer ${token}`,
|
Authorization: `Bearer ${token}`,
|
||||||
},
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
notAcceptedItems
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
@@ -363,6 +366,99 @@ export const handlePaymentFromGuestDevice = async (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const handleCloseBillFromGuestDevice = async (
|
||||||
|
transactionId,
|
||||||
|
orderMethod,
|
||||||
|
socketId
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
const token = getLocalStorage("auth");
|
||||||
|
|
||||||
|
const response = await fetch(
|
||||||
|
API_BASE_URL + "/transaction/closeBillFromGuestDevice/" + transactionId,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
socketId,
|
||||||
|
orderMethod
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
// Handle success response
|
||||||
|
console.log("Transaction successful!");
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
// Handle error response
|
||||||
|
console.error("Transaction failed:", response.statusText);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error sending transaction:", error);
|
||||||
|
// Handle network or other errors
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const handleExtendFromGuestDevice = async (
|
||||||
|
shopId,
|
||||||
|
transactionId,
|
||||||
|
notes,
|
||||||
|
socketId
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
const token = getLocalStorage("auth");
|
||||||
|
const items = getItemsByCafeId(shopId);
|
||||||
|
|
||||||
|
const structuredItems = {
|
||||||
|
items: items.map((item) => ({
|
||||||
|
itemId: item.itemId,
|
||||||
|
qty: item.qty,
|
||||||
|
price: item.price
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log(items);
|
||||||
|
const response = await fetch(
|
||||||
|
API_BASE_URL + "/transaction/extentFromGuestDevice/" + transactionId,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
transactions: structuredItems,
|
||||||
|
notes: notes,
|
||||||
|
socketId,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
// Handle success response
|
||||||
|
console.log("Transaction successful!");
|
||||||
|
const data = await response.json();
|
||||||
|
if (data.newUser) updateLocalStorage("auth", data.auth);
|
||||||
|
// Optionally return response data or handle further actions upon success
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
// Handle error response
|
||||||
|
console.error("Transaction failed:", response.statusText);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error sending transaction:", error);
|
||||||
|
// Handle network or other errors
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Function to retrieve the authentication token from localStorage
|
// Function to retrieve the authentication token from localStorage
|
||||||
function getAuthToken() {
|
function getAuthToken() {
|
||||||
return localStorage.getItem("auth");
|
return localStorage.getItem("auth");
|
||||||
@@ -383,13 +479,6 @@ const getHeaders = (method = "GET") => {
|
|||||||
headers,
|
headers,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
export const getFavourite = async (cafeId) => {
|
|
||||||
const response = await fetch(
|
|
||||||
`${API_BASE_URL}/transaction/get-favourite/${cafeId}`,
|
|
||||||
getHeaders()
|
|
||||||
);
|
|
||||||
return response.json();
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getReports = async (cafeId, filter) => {
|
export const getReports = async (cafeId, filter) => {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
@@ -417,12 +506,3 @@ export const getAnalytics = async (filter, selectedCafeId) => {
|
|||||||
const response = await fetch(url, getHeaders('POST'));
|
const response = await fetch(url, getHeaders('POST'));
|
||||||
return response.json();
|
return response.json();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export const getIncome = async (cafeId) => {
|
|
||||||
const response = await fetch(
|
|
||||||
`${API_BASE_URL}/transaction/get-income/${cafeId}`,
|
|
||||||
getHeaders()
|
|
||||||
);
|
|
||||||
return response.json();
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ import reportWebVitals from './reportWebVitals';
|
|||||||
|
|
||||||
const root = ReactDOM.createRoot(document.getElementById('root'));
|
const root = ReactDOM.createRoot(document.getElementById('root'));
|
||||||
// Disable console methods
|
// Disable console methods
|
||||||
console.log = () => {};
|
// console.log = () => {};
|
||||||
console.warn = () => {};
|
// console.warn = () => {};
|
||||||
console.error = () => {};
|
// console.error = () => {};
|
||||||
|
|
||||||
root.render(
|
root.render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
|
|||||||
@@ -43,7 +43,8 @@ function CafePage({
|
|||||||
loading,
|
loading,
|
||||||
queue,
|
queue,
|
||||||
cartItemsLength,
|
cartItemsLength,
|
||||||
totalPrice
|
totalPrice,
|
||||||
|
lastTransaction
|
||||||
}) {
|
}) {
|
||||||
|
|
||||||
|
|
||||||
@@ -64,6 +65,7 @@ function CafePage({
|
|||||||
const [screenMessage, setScreenMessage] = useState("");
|
const [screenMessage, setScreenMessage] = useState("");
|
||||||
|
|
||||||
const [isSpotifyNeedLogin, setNeedSpotifyLogin] = useState(true);
|
const [isSpotifyNeedLogin, setNeedSpotifyLogin] = useState(true);
|
||||||
|
const [isExceededDeadline, setIsExceededDeadline] = useState(false);
|
||||||
|
|
||||||
const [isEditMode, setIsEditMode] = useState(false);
|
const [isEditMode, setIsEditMode] = useState(false);
|
||||||
const [filterId, setFilterId] = useState(0);
|
const [filterId, setFilterId] = useState(0);
|
||||||
@@ -135,8 +137,10 @@ function CafePage({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function fetchData() {
|
async function fetchData() {
|
||||||
socket.on("joined-room", (response) => {
|
socket.on("joined-room", (response) => {
|
||||||
const { isSpotifyNeedLogin } = response;
|
const { isSpotifyNeedLogin, isExceededDeadline } = response;
|
||||||
setNeedSpotifyLogin(isSpotifyNeedLogin);
|
setNeedSpotifyLogin(isSpotifyNeedLogin);
|
||||||
|
if (isExceededDeadline) setModal('unavailable');
|
||||||
|
setIsExceededDeadline(isExceededDeadline);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,7 +210,7 @@ function CafePage({
|
|||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
welcomePageConfig != null && (
|
welcomePageConfig != null && (
|
||||||
<div className="Cafe">
|
<div className="Cafe" style={{ filter: isExceededDeadline ? 'grayscale(1)' : '', pointerEvents: isExceededDeadline ? 'none' : '' }}>
|
||||||
<body className="App-header">
|
<body className="App-header">
|
||||||
<Header
|
<Header
|
||||||
HeaderText={"Menu"}
|
HeaderText={"Menu"}
|
||||||
@@ -296,11 +300,15 @@ function CafePage({
|
|||||||
))}
|
))}
|
||||||
{!isEditMode && (user.username || cartItemsLength > 0) &&
|
{!isEditMode && (user.username || cartItemsLength > 0) &&
|
||||||
<div style={{ marginTop: '10px', height: '40px', position: 'sticky', bottom: '40px', display: 'flex', justifyContent: 'center', alignItems: 'center', textAlign: 'center' }}>
|
<div style={{ marginTop: '10px', height: '40px', position: 'sticky', bottom: '40px', display: 'flex', justifyContent: 'center', alignItems: 'center', textAlign: 'center' }}>
|
||||||
{cartItemsLength > 0 &&
|
{(lastTransaction != null || cartItemsLength > 0) &&
|
||||||
<div onClick={goToCart} style={{ backgroundColor: '#73a585', width: user.username ? '55vw' : '70vw', height: '40px', borderRadius: '30px', display: 'flex', justifyContent: 'space-between', padding: '0 20px' }}>
|
<div onClick={goToCart} style={{ backgroundColor: '#73a585', width: user.username ? '55vw' : '70vw', height: '40px', borderRadius: '30px', display: 'flex', justifyContent: 'space-between', padding: '0 20px' }}>
|
||||||
<div style={{ display: 'flex', flexWrap: 'wrap', alignContent: 'center' }}>{cartItemsLength} item</div>
|
<div style={{ display: 'flex', flexWrap: 'wrap', alignContent: 'center' }}>{lastTransaction != null && '+'}{cartItemsLength} item</div>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'flex-end', width: '130px' }}>
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'flex-end', width: '130px' }}>
|
||||||
|
{((lastTransaction == null || lastTransaction?.payment_type != 'paylater')) ?
|
||||||
<span style={{ whiteSpace: 'nowrap' }}>Rp{totalPrice}</span>
|
<span style={{ whiteSpace: 'nowrap' }}>Rp{totalPrice}</span>
|
||||||
|
:
|
||||||
|
<span style={{ whiteSpace: 'nowrap' }}>Open bill</span>
|
||||||
|
}
|
||||||
<div style={{ display: 'flex', alignItems: 'center', marginLeft: '5px', width: '20px' }}>
|
<div style={{ display: 'flex', alignItems: 'center', marginLeft: '5px', width: '20px' }}>
|
||||||
<svg viewBox="0 0 34 34" style={{ fill: 'white', marginTop: '4px' }}>
|
<svg viewBox="0 0 34 34" style={{ fill: 'white', marginTop: '4px' }}>
|
||||||
<path d="M9.79175 24.75C8.09591 24.75 6.72383 26.1375 6.72383 27.8333C6.72383 29.5292 8.09591 30.9167 9.79175 30.9167C11.4876 30.9167 12.8751 29.5292 12.8751 27.8333C12.8751 26.1375 11.4876 24.75 9.79175 24.75ZM0.541748 0.0833435V3.16668H3.62508L9.17508 14.8679L7.09383 18.645C6.84717 19.0767 6.70842 19.5854 6.70842 20.125C6.70842 21.8208 8.09591 23.2083 9.79175 23.2083H28.2917V20.125H10.4392C10.2234 20.125 10.0538 19.9554 10.0538 19.7396L10.1001 19.5546L11.4876 17.0417H22.973C24.1292 17.0417 25.1467 16.4096 25.6709 15.4538L31.1901 5.44834C31.3134 5.23251 31.3751 4.97043 31.3751 4.70834C31.3751 3.86043 30.6813 3.16668 29.8334 3.16668H7.03217L5.583 0.0833435H0.541748ZM25.2084 24.75C23.5126 24.75 22.1405 26.1375 22.1405 27.8333C22.1405 29.5292 23.5126 30.9167 25.2084 30.9167C26.9042 30.9167 28.2917 29.5292 28.2917 27.8333C28.2917 26.1375 26.9042 24.75 25.2084 24.75Z"></path>
|
<path d="M9.79175 24.75C8.09591 24.75 6.72383 26.1375 6.72383 27.8333C6.72383 29.5292 8.09591 30.9167 9.79175 30.9167C11.4876 30.9167 12.8751 29.5292 12.8751 27.8333C12.8751 26.1375 11.4876 24.75 9.79175 24.75ZM0.541748 0.0833435V3.16668H3.62508L9.17508 14.8679L7.09383 18.645C6.84717 19.0767 6.70842 19.5854 6.70842 20.125C6.70842 21.8208 8.09591 23.2083 9.79175 23.2083H28.2917V20.125H10.4392C10.2234 20.125 10.0538 19.9554 10.0538 19.7396L10.1001 19.5546L11.4876 17.0417H22.973C24.1292 17.0417 25.1467 16.4096 25.6709 15.4538L31.1901 5.44834C31.3134 5.23251 31.3751 4.97043 31.3751 4.70834C31.3751 3.86043 30.6813 3.16668 29.8334 3.16668H7.03217L5.583 0.0833435H0.541748ZM25.2084 24.75C23.5126 24.75 22.1405 26.1375 22.1405 27.8333C22.1405 29.5292 23.5126 30.9167 25.2084 30.9167C26.9042 30.9167 28.2917 29.5292 28.2917 27.8333C28.2917 26.1375 26.9042 24.75 25.2084 24.75Z"></path>
|
||||||
@@ -310,7 +318,7 @@ function CafePage({
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
{user.username &&
|
{user.username &&
|
||||||
<div onClick={goToTransactions} style={{ backgroundColor: '#73a585', width: '15vw', height: '40px', borderRadius: '30px', display: 'flex', justifyContent: 'center', marginLeft: cartItemsLength > 0 ? '6px' : '0px' }}>
|
<div onClick={goToTransactions} style={{ backgroundColor: '#73a585', width: '15vw', height: '40px', borderRadius: '30px', display: 'flex', justifyContent: 'center', marginLeft: lastTransaction != null || cartItemsLength > 0 ? '6px' : '0px' }}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', width: '38px', marginRight: '5px' }}>
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', width: '38px', marginRight: '5px' }}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', marginLeft: '5px', width: '20px' }}>
|
<div style={{ display: 'flex', alignItems: 'center', marginLeft: '5px', width: '20px' }}>
|
||||||
<svg viewBox="0 0 512 512">
|
<svg viewBox="0 0 512 512">
|
||||||
|
|||||||
@@ -4,20 +4,26 @@ import { useParams } from "react-router-dom"; // Changed from useSearchParams to
|
|||||||
import { ThreeDots, ColorRing } from "react-loader-spinner";
|
import { ThreeDots, ColorRing } from "react-loader-spinner";
|
||||||
|
|
||||||
import ItemLister from "../components/ItemLister";
|
import ItemLister from "../components/ItemLister";
|
||||||
|
|
||||||
import { getCartDetails } from "../helpers/itemHelper";
|
import { getCartDetails } from "../helpers/itemHelper";
|
||||||
|
|
||||||
|
import { getPaymentMethods } from "../helpers/cafeHelpers.js";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
handlePaymentFromClerk,
|
handlePaymentFromClerk,
|
||||||
handlePaymentFromGuestSide,
|
handlePaymentFromGuestSide,
|
||||||
handlePaymentFromGuestDevice,
|
handlePaymentFromGuestDevice,
|
||||||
|
handleExtendFromGuestDevice,
|
||||||
|
handleCloseBillFromGuestDevice
|
||||||
} from "../helpers/transactionHelpers";
|
} from "../helpers/transactionHelpers";
|
||||||
|
|
||||||
import { getItemsByCafeId } from "../helpers/cartHelpers.js";
|
import { getItemsByCafeId } from "../helpers/cartHelpers.js";
|
||||||
|
|
||||||
|
import Dropdown from "./Dropdown.js";
|
||||||
import { useNavigationHelpers } from "../helpers/navigationHelpers";
|
import { useNavigationHelpers } from "../helpers/navigationHelpers";
|
||||||
|
|
||||||
|
|
||||||
export default function Invoice({ shopId, table, sendParam, deviceType, socket, shopItems, setShopItems }) {
|
export default function Invoice({ shopId, setModal, table, sendParam, deviceType, socket, shopItems, setShopItems }) {
|
||||||
const { shopIdentifier, tableCode } = useParams();
|
const { shopIdentifier, tableCode } = useParams();
|
||||||
|
|
||||||
sendParam({ shopIdentifier, tableCode });
|
sendParam({ shopIdentifier, tableCode });
|
||||||
@@ -37,6 +43,28 @@ export default function Invoice({ shopId, table, sendParam, deviceType, socket,
|
|||||||
const [email, setEmail] = useState("");
|
const [email, setEmail] = useState("");
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
|
||||||
|
const [dropdownKey, setDropdownKey] = useState(0);
|
||||||
|
|
||||||
|
const [paymentMethods, setPaymentMethods] = useState(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchPaymentMethods = async () => {
|
||||||
|
try {
|
||||||
|
const methods = await getPaymentMethods(shopId);
|
||||||
|
console.log(methods)
|
||||||
|
const lastTransaction = JSON.parse(localStorage.getItem('lastTransaction'));
|
||||||
|
if (lastTransaction?.payment_type == 'paylater') methods.isOpenBillAvailable = false;
|
||||||
|
setPaymentMethods(methods)
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (shopId) {
|
||||||
|
fetchPaymentMethods();
|
||||||
|
}
|
||||||
|
}, [shopId, dropdownKey]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchCartItems = async () => {
|
const fetchCartItems = async () => {
|
||||||
@@ -218,14 +246,38 @@ export default function Invoice({ shopId, table, sendParam, deviceType, socket,
|
|||||||
getNewestCartItems();
|
getNewestCartItems();
|
||||||
}, [shopId]);
|
}, [shopId]);
|
||||||
|
|
||||||
const handlePay = async (isCash) => {
|
const handlePayCloseBill = async (orderMethod) =>{
|
||||||
setIsPaymentLoading(true);
|
setIsPaymentLoading(true);
|
||||||
console.log("tipe" + deviceType);
|
console.log("tipe" + deviceType);
|
||||||
|
if (transactionData) {
|
||||||
|
const socketId = socket.id;
|
||||||
|
const pay = await handleCloseBillFromGuestDevice(
|
||||||
|
transactionData.transactionId,
|
||||||
|
orderMethod,
|
||||||
|
socketId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handlePay = async (orderMethod) => {
|
||||||
|
setIsPaymentLoading(true);
|
||||||
|
console.log("tipe" + deviceType);
|
||||||
|
if (transactionData) {
|
||||||
|
const socketId = socket.id;
|
||||||
|
const pay = await handleExtendFromGuestDevice(
|
||||||
|
shopId,
|
||||||
|
transactionData.transactionId,
|
||||||
|
textareaRef.current.value,
|
||||||
|
socketId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
|
||||||
if (deviceType == "clerk") {
|
if (deviceType == "clerk") {
|
||||||
const pay = await handlePaymentFromClerk(
|
const pay = await handlePaymentFromClerk(
|
||||||
shopId,
|
shopId,
|
||||||
email,
|
email,
|
||||||
isCash ? "cash" : "cashless",
|
orderMethod,
|
||||||
orderType,
|
orderType,
|
||||||
tableNumber
|
tableNumber
|
||||||
);
|
);
|
||||||
@@ -233,7 +285,7 @@ export default function Invoice({ shopId, table, sendParam, deviceType, socket,
|
|||||||
const pay = await handlePaymentFromGuestSide(
|
const pay = await handlePaymentFromGuestSide(
|
||||||
shopId,
|
shopId,
|
||||||
email,
|
email,
|
||||||
isCash ? "cash" : "cashless",
|
orderMethod,
|
||||||
orderType,
|
orderType,
|
||||||
tableNumber
|
tableNumber
|
||||||
);
|
);
|
||||||
@@ -241,7 +293,7 @@ export default function Invoice({ shopId, table, sendParam, deviceType, socket,
|
|||||||
const socketId = socket.id;
|
const socketId = socket.id;
|
||||||
const pay = await handlePaymentFromGuestDevice(
|
const pay = await handlePaymentFromGuestDevice(
|
||||||
shopId,
|
shopId,
|
||||||
isCash ? "cash" : "cashless",
|
orderMethod,
|
||||||
orderType,
|
orderType,
|
||||||
table.tableNo || tableNumber,
|
table.tableNo || tableNumber,
|
||||||
textareaRef.current.value,
|
textareaRef.current.value,
|
||||||
@@ -278,7 +330,7 @@ export default function Invoice({ shopId, table, sendParam, deviceType, socket,
|
|||||||
console.log(localStorage.getItem('cart'))
|
console.log(localStorage.getItem('cart'))
|
||||||
console.log(cartItems)
|
console.log(cartItems)
|
||||||
|
|
||||||
if (localStorage.getItem('cart') == "[]") return;
|
if (localStorage.getItem('cart') == null || localStorage.getItem('cart') == '' || localStorage.getItem('cart') == '[]') return;
|
||||||
|
|
||||||
// Parse the local storage cart
|
// Parse the local storage cart
|
||||||
const localStorageCart = JSON.parse(localStorage.getItem('cart'));
|
const localStorageCart = JSON.parse(localStorage.getItem('cart'));
|
||||||
@@ -322,12 +374,15 @@ export default function Invoice({ shopId, table, sendParam, deviceType, socket,
|
|||||||
const handleEmailChange = (event) => {
|
const handleEmailChange = (event) => {
|
||||||
setEmail(event.target.value);
|
setEmail(event.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const transactionData = JSON.parse(localStorage.getItem('lastTransaction'));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.Invoice} style={{ height: (getItemsByCafeId(shopId).length > 0 ? '' : '100vh'), minHeight: (getItemsByCafeId(shopId).length > 0 ? '100vh' : '') }}>
|
<div className={styles.Invoice} style={{ height: (getItemsByCafeId(shopId).length > 0 ? '' : '100vh'), minHeight: (getItemsByCafeId(shopId).length > 0 ? '100vh' : '') }}>
|
||||||
|
|
||||||
<div onClick={goToShop} style={{ marginLeft: '22px', marginTop: '49px', marginRight: '10px', display: 'flex', flexWrap: 'nowrap', alignItems: 'center', fontSize: '25px' }} ><svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 512 512"><path d="M48,256c0,114.87,93.13,208,208,208s208-93.13,208-208S370.87,48,256,48,48,141.13,48,256Zm212.65-91.36a16,16,0,0,1,.09,22.63L208.42,240H342a16,16,0,0,1,0,32H208.42l52.32,52.73A16,16,0,1,1,238,347.27l-79.39-80a16,16,0,0,1,0-22.54l79.39-80A16,16,0,0,1,260.65,164.64Z" /></svg>Keranjang</div>
|
<div onClick={goToShop} style={{ marginLeft: '22px', marginTop: '49px', marginRight: '10px', display: 'flex', flexWrap: 'nowrap', alignItems: 'center', fontSize: '25px' }} ><svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 512 512"><path d="M48,256c0,114.87,93.13,208,208,208s208-93.13,208-208S370.87,48,256,48,48,141.13,48,256Zm212.65-91.36a16,16,0,0,1,.09,22.63L208.42,240H342a16,16,0,0,1,0,32H208.42l52.32,52.73A16,16,0,1,1,238,347.27l-79.39-80a16,16,0,0,1,0-22.54l79.39-80A16,16,0,0,1,260.65,164.64Z" /></svg>Keranjang</div>
|
||||||
|
|
||||||
{getItemsByCafeId(shopId) < 1 ?
|
{(transactionData == null && getItemsByCafeId(shopId).length < 1) ?
|
||||||
<div style={{ height: '75vh', display: 'flex', justifyContent: 'center', flexDirection: 'column', alignContent: 'center', alignItems: 'center' }}>
|
<div style={{ height: '75vh', display: 'flex', justifyContent: 'center', flexDirection: 'column', alignContent: 'center', alignItems: 'center' }}>
|
||||||
<div style={{ width: '50%' }}>
|
<div style={{ width: '50%' }}>
|
||||||
<svg
|
<svg
|
||||||
@@ -342,6 +397,7 @@ export default function Invoice({ shopId, table, sendParam, deviceType, socket,
|
|||||||
:
|
:
|
||||||
(isLoading ? <></> :
|
(isLoading ? <></> :
|
||||||
<>
|
<>
|
||||||
|
{getItemsByCafeId(shopId).length > 0 &&
|
||||||
<div className={styles.RoundedRectangle}>
|
<div className={styles.RoundedRectangle}>
|
||||||
{cartItems.map((itemType) => (
|
{cartItems.map((itemType) => (
|
||||||
<ItemLister
|
<ItemLister
|
||||||
@@ -369,6 +425,7 @@ export default function Invoice({ shopId, table, sendParam, deviceType, socket,
|
|||||||
</select> */}
|
</select> */}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{orderType === "serve" && table.length < 1 && (
|
{orderType === "serve" && table.length < 1 && (
|
||||||
<div className={styles.OrderTypeContainer}>
|
<div className={styles.OrderTypeContainer}>
|
||||||
<span htmlFor="orderType">Serve to:</span>
|
<span htmlFor="orderType">Serve to:</span>
|
||||||
@@ -395,35 +452,120 @@ export default function Invoice({ shopId, table, sendParam, deviceType, socket,
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.PaymentOption}>
|
}
|
||||||
<div className={styles.TotalContainer}>
|
|
||||||
<span>Pembayaran</span>
|
{transactionData &&
|
||||||
<span>
|
<div className={styles.RoundedRectangle} style={{ backgroundColor: '#c3c3c3', fontSize: '15px', display: 'flex', justifyContent: 'space-between' }}>
|
||||||
<select
|
{transactionData.payment_type != 'paylater' ?
|
||||||
style={{ borderRadius: '6px', fontSize: '20px' }}
|
<>
|
||||||
id="orderMethod"
|
<div onClick={() => setModal('transaction_item', { transactionId: transactionData.transactionId })} className={styles.AddedLastTransaction}>
|
||||||
value={orderMethod}
|
Pesanan akan ditambahkan ke transaksi sebelumnya
|
||||||
onChange={handleOrderMethodChange}
|
|
||||||
>
|
|
||||||
<option value="cash"> Tunai</option>
|
|
||||||
<option value="cashless"> Non Tunai </option>
|
|
||||||
</select>
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', paddingLeft: '25px', paddingRight: '25px' }}>
|
<div className={styles.CancelAddedLastTransaction} onClick={() => { window.location.reload(); localStorage.removeItem('lastTransaction') }}>
|
||||||
<button className={styles.PayButton} onClick={() => handlePay(orderMethod == 'cash' ? true : false)}>
|
<svg
|
||||||
|
style={{ width: '40px', height: '40px' }}
|
||||||
|
className={styles['plusNegative2']}
|
||||||
|
clipRule="evenodd"
|
||||||
|
fillRule="evenodd"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeMiterlimit="2"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="m12.002 2c5.518 0 9.998 4.48 9.998 9.998 0 5.517-4.48 9.997-9.998 9.997-5.517 0-9.997-4.48-9.997-9.997 0-5.518 4.48-9.998 9.997-9.998zm0 1.5c-4.69 0-8.497 3.808-8.497 8.498s3.807 8.497 8.497 8.497 8.498-3.807 8.498-8.497-3.808-8.498-8.498-8.498zm-.747 7.75h-3.5c-.414 0-.75.336-.75.75s.336.75.75.75h3.5v3.5c0 .414.336.75.75.75s.75-.336.75-.75v-3.5h3.5c.414 0 .75-.336.75-.75s-.336-.75-.75-.75h-3.5v-3.5c0-.414-.336-.75-.75-.75s-.75.336-.75.75z"
|
||||||
|
fillRule="nonzero"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
:
|
||||||
|
|
||||||
|
<div className={styles.AddedLastTransaction}>
|
||||||
|
<div>
|
||||||
|
Open bill
|
||||||
|
<div onClick={() => setModal('transaction_item', { transactionId: transactionData.transactionId })}>
|
||||||
|
Lihat tagihan
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{getItemsByCafeId(shopId).length > 0 ?
|
||||||
|
|
||||||
|
<button className={styles.PayButton3} onClick={() => handlePay(orderMethod)}>
|
||||||
{isPaymentLoading ? (
|
{isPaymentLoading ? (
|
||||||
<ColorRing height="50" width="50" color="white" />
|
<ColorRing height="50" width="50" color="white" />
|
||||||
) : (
|
) : (
|
||||||
<div>
|
<div>
|
||||||
|
{transactionData ?
|
||||||
|
|
||||||
|
<span>Tambahkan</span>
|
||||||
|
:
|
||||||
<span>Pesan</span>
|
<span>Pesan</span>
|
||||||
|
}
|
||||||
|
|
||||||
|
<span>Rp{totalPrice}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
:
|
||||||
|
|
||||||
|
<button className={styles.PayButton3} style={{ backgroundColor: 'rgb(42 145 24)', letterSpacing: '1px' }} onClick={goToShop}>
|
||||||
|
<div>
|
||||||
|
<span>Tambahkan item lain</span>
|
||||||
|
</div>
|
||||||
|
</button>}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<div className={styles.PaymentOption}>
|
||||||
|
<div className={styles.TotalContainer}>
|
||||||
|
<span>Pembayaran</span>
|
||||||
|
<span>
|
||||||
|
{paymentMethods != null && <Dropdown setDropdownKey={() => setDropdownKey(dropdownKey + 1)} paymentMethods={paymentMethods} onChange={handleOrderMethodChange} />}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{transactionData && transactionData.payment_type === 'paylater' ?
|
||||||
|
<div style={{ display: 'flex', paddingLeft: '25px', paddingRight: '25px', marginTop: '17px' }}>
|
||||||
|
<button className={styles.PayButton} onClick={() => handlePayCloseBill(orderMethod)}>
|
||||||
|
{isPaymentLoading ? (
|
||||||
|
<ColorRing height="50" width="50" color="white" />
|
||||||
|
) : (
|
||||||
|
<div>
|
||||||
|
<span>Tutup bill</span>
|
||||||
|
|
||||||
|
<span>Rp{
|
||||||
|
transactionData.DetailedTransactions.reduce((total, transaction) => {
|
||||||
|
return total + (transaction.promoPrice == 0 || transaction.promoPrice == null
|
||||||
|
? transaction.price * transaction.qty
|
||||||
|
: transaction.promoPrice * transaction.qty);
|
||||||
|
}, 0)
|
||||||
|
}</span>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
:
|
||||||
|
|
||||||
|
<div style={{ display: 'flex', paddingLeft: '25px', paddingRight: '25px', marginTop: '17px' }}>
|
||||||
|
<button className={styles.PayButton} onClick={() => handlePay(orderMethod)}>
|
||||||
|
{isPaymentLoading ? (
|
||||||
|
<ColorRing height="50" width="50" color="white" />
|
||||||
|
) : (
|
||||||
|
<div>
|
||||||
|
{transactionData ?
|
||||||
|
|
||||||
|
<span>Tambahkan</span>
|
||||||
|
:
|
||||||
|
<span>Pesan</span>
|
||||||
|
}
|
||||||
|
|
||||||
<span>Rp{totalPrice}</span>
|
<span>Rp{totalPrice}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div onClick={goToShop} style={{ textAlign: 'center', marginBottom: '20px' }}>Kembali</div>
|
}
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.PaymentOptionMargin}></div>
|
<div className={styles.PaymentOptionMargin}></div>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ const CreateCouponPage = () => {
|
|||||||
let encodedCouponCode = encodeURIComponent(encryptedCouponCode);
|
let encodedCouponCode = encodeURIComponent(encryptedCouponCode);
|
||||||
|
|
||||||
// Construct the URL with the encoded coupon code as a query parameter
|
// Construct the URL with the encoded coupon code as a query parameter
|
||||||
const urlWithCoupon = `https://coupon.kedaimaster.com/coupon?c=${encodedCouponCode}`;
|
const urlWithCoupon = `https://dev.coupon.kedaimaster.com/coupon?c=${encodedCouponCode}`;
|
||||||
|
|
||||||
// Optionally, set the URL to use with the coupon
|
// Optionally, set the URL to use with the coupon
|
||||||
setCouponUrl(urlWithCoupon);
|
setCouponUrl(urlWithCoupon);
|
||||||
|
|||||||
162
src/pages/Dropdown.js
Normal file
162
src/pages/Dropdown.js
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
import React, { useState, useRef, useEffect } from "react";
|
||||||
|
import styles from "./Dropdown.module.css"; // Import the CSS Module
|
||||||
|
|
||||||
|
const icons = {
|
||||||
|
cash: (
|
||||||
|
<svg
|
||||||
|
fill="#000000"
|
||||||
|
viewBox="0 0 96 96"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="20px"
|
||||||
|
height="20px"
|
||||||
|
>
|
||||||
|
<g id="SVGRepo_bgCarrier" stroke-width="0"></g>
|
||||||
|
<g
|
||||||
|
id="SVGRepo_tracerCarrier"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
></g>
|
||||||
|
<g id="SVGRepo_iconCarrier">
|
||||||
|
{" "}
|
||||||
|
<title></title>{" "}
|
||||||
|
<g>
|
||||||
|
{" "}
|
||||||
|
<path d="M90,12H6a5.9966,5.9966,0,0,0-6,6V78a5.9966,5.9966,0,0,0,6,6H90a5.9966,5.9966,0,0,0,6-6V18A5.9966,5.9966,0,0,0,90,12ZM24,72A12.0119,12.0119,0,0,0,12,60V36A12.0119,12.0119,0,0,0,24,24H72A12.0119,12.0119,0,0,0,84,36V60A12.0119,12.0119,0,0,0,72,72Z"></path>{" "}
|
||||||
|
<path d="M48,36A12,12,0,1,0,60,48,12.0119,12.0119,0,0,0,48,36Z"></path>{" "}
|
||||||
|
</g>{" "}
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
cashless: (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlSpace="preserve"
|
||||||
|
width="20px"
|
||||||
|
height="20px"
|
||||||
|
viewBox="0 0 21000 7750"
|
||||||
|
xmlnsXlink="http://www.w3.org/1999/xlink"
|
||||||
|
>
|
||||||
|
<defs>
|
||||||
|
<style type="text/css">
|
||||||
|
{`
|
||||||
|
.fil0 { fill: black; fill-rule: nonzero; }
|
||||||
|
`}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<g id="__x0023_Layer_x0020_1">
|
||||||
|
<metadata id="CorelCorpID_0Corel-Layer" />
|
||||||
|
<path
|
||||||
|
className="fil0"
|
||||||
|
d="M20140 4750l0 -667 0 -1333 -2000 0 -1333 0 0 -667 3333 0 0 -1333 -3333 0 -2000 0 0 1333 0 667 0 1333 2000 0 1333 0 0 667 -3333 0 0 1333 3333 0 2000 0 0 -1333zm527 -417l0 2167c0,44 -18,87 -49,118 -31,31 -74,49 -118,49l-2167 0 0 333 2500 0c44,0 87,-18 118,-49 31,-31 49,-74 49,-118l0 -2500 -333 0zm-18000 -4333l-2500 0c-44,0 -87,18 -118,49 -31,31 -49,74 -49,118l0 2500 333 0 0 -2167c0,-44 18,-87 49,-118 31,-31 74,-49 118,-49l2167 0 0 -333zm2140 7750l1333 0 0 -3000 -1333 0 0 3000zm1167 -7000l-3167 0 0 1333 2000 0 0 2000 1333 0 0 -3167c0,-44 -18,-87 -49,-118 -31,-31 -74,-49 -118,-49zm-3833 0l-1167 0c-44,0 -87,18 -118,49 -31,31 -49,74 -49,118l0 5000c0,44 18,87 49,118 31,31 74,49 118,49l3167 0 0 -1333 -2000 0 0 -4000zm667 3333l1333 0 0 -1333 -1333 0 0 1333zm333 -1000l0 0 667 0 0 667 -667 0 0 -667zm3667 -2333l0 1333 4000 0 0 667 -2667 0 -1333 0 0 1333 0 2000 1333 0 0 -1980 2000 1980 2000 0 -2087 -2000 753 0 1333 0 0 -1333 0 -667 0 -1333 -1333 0 -4000 0zm6000 5333l1333 0 0 -5333 -1333 0 0 5333z"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
paylater: (
|
||||||
|
<svg
|
||||||
|
fill="black"
|
||||||
|
height="20px"
|
||||||
|
width="20px"
|
||||||
|
version="1.1"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 409.221 409.221"
|
||||||
|
xmlnsXlink="http://www.w3.org/1999/xlink"
|
||||||
|
enableBackground="new 0 0 409.221 409.221"
|
||||||
|
>
|
||||||
|
<g id="SVGRepo_bgCarrier" strokeWidth="0"></g>
|
||||||
|
<g
|
||||||
|
id="SVGRepo_tracerCarrier"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
></g>
|
||||||
|
<g id="SVGRepo_iconCarrier">
|
||||||
|
<path d="m115.921,208.425c0-5.523 4.477-10 10-10h56.421c5.523,0 10,4.477 10,10s-4.477,10-10,10h-56.421c-5.523,0-10-4.477-10-10zm10,54.838h56.421c5.523,0 10-4.477 10-10s-4.477-10-10-10h-56.421c-5.523,0-10,4.477-10,10s4.477,10 10,10zm32.2-159.536c0-19.248 20.421-34.326 46.49-34.326 26.068,0 46.488,15.078 46.488,34.326s-20.42,34.326-46.488,34.326c-26.069,0-46.49-15.078-46.49-34.326zm20,0c0,6.762 11.329,14.326 26.49,14.326 15.16,0 26.488-7.563 26.488-14.326s-11.328-14.326-26.488-14.326c-15.162-1.42109e-14-26.49,7.564-26.49,14.326zm-52.2,81.176h56.421c5.523,0 10-4.477 10-10s-4.477-10-10-10h-56.421c-5.523,0-10,4.477-10,10s4.477,10 10,10zm157.381,155.665h-32.818c-5.522,0-10,4.477-10,10s4.478,10 10,10h32.818c5.522,0 10-4.477 10-10s-4.478-10-10-10zm59.627-330.568v389.221c0,5.523-4.478,10-10,10h-256.637c-5.523,0-10-4.477-10-10v-389.221c0-5.523 4.477-10 10-10h36.662c5.523,0 10,4.477 10,10v14.327h16.662v-14.327c0-5.523 4.477-10 10-10h36.663c5.523,0 10,4.477 10,10v14.327h16.663v-14.327c0-5.523 4.478-10 10-10h36.662c5.522,0 10,4.477 10,10v14.327h16.663v-14.327c0-5.523 4.478-10 10-10h36.661c5.523,0 10.001,4.477 10.001,10zm-20,10h-16.661v14.327c0,5.523-4.478,10-10,10h-36.663c-5.522,0-10-4.477-10-10v-14.327h-16.662v14.327c0,5.523-4.478,10-10,10h-36.663c-5.523,0-10-4.477-10-10v-14.327h-16.663v14.327c0,5.523-4.477,10-10,10h-36.662c-5.523,0-10-4.477-10-10v-14.327h-16.663v369.221h236.637v-369.221zm-39.627,178.425h-32.818c-5.522,0-10,4.477-10,10s4.478,10 10,10h32.818c5.522,0 10-4.477 10-10s-4.478-10-10-10zm0,44.838h-32.818c-5.522,0-10,4.477-10,10s4.478,10 10,10h32.818c5.522,0 10-4.477 10-10s-4.478-10-10-10zm0,48.653h-157.381c-5.523,0-10,4.477-10,10s4.477,10 10,10h157.381c5.522,0 10-4.477 10-10s-4.478-10-10-10z"></path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
const Dropdown = ({ onChange, paymentMethods, setDropdownKey }) => {
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
const [selectedOption, setSelectedOption] = useState("cash");
|
||||||
|
const dropdownRef = useRef(null);
|
||||||
|
|
||||||
|
const toggleDropdown = () => {
|
||||||
|
setDropdownKey();
|
||||||
|
setIsOpen(!isOpen);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClickOutside = (event) => {
|
||||||
|
if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
|
||||||
|
setIsOpen(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
document.addEventListener("mousedown", handleClickOutside);
|
||||||
|
return () => document.removeEventListener("mousedown", handleClickOutside);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleOptionClick = (value) => {
|
||||||
|
setSelectedOption(value);
|
||||||
|
setIsOpen(false);
|
||||||
|
|
||||||
|
if (onChange) onChange({ target: { value } });
|
||||||
|
};
|
||||||
|
|
||||||
|
const options = [
|
||||||
|
{ value: "cash", label: "Tunai", icon: icons.cash },
|
||||||
|
{ value: "cashless", label: "Non tunai", icon: icons.cashless },
|
||||||
|
{ value: "paylater", label: "Open bill", icon: icons.paylater },
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.dropdown} ref={dropdownRef}>
|
||||||
|
<div className={styles.dropdownButton} onClick={toggleDropdown}>
|
||||||
|
<a>
|
||||||
|
<div style={{ display: 'flex' }}>
|
||||||
|
<div className={styles.svg}>
|
||||||
|
{icons[selectedOption]}
|
||||||
|
</div>
|
||||||
|
{options.find((option) => option.value === selectedOption)?.label}
|
||||||
|
</div>
|
||||||
|
<span
|
||||||
|
className={styles.arrow}
|
||||||
|
style={{
|
||||||
|
color: 'black'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isOpen ? "▲" : "▼"}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{isOpen && (
|
||||||
|
<div className={styles.dropdownContent}>
|
||||||
|
{options
|
||||||
|
.filter((option) => {
|
||||||
|
// Filter out 'cashless' option if QRIS is not available
|
||||||
|
if (option.value === 'cashless' && !paymentMethods.isQRISavailable) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Filter out 'paylater' option if Open Bill is not available
|
||||||
|
if (option.value === 'paylater' && !paymentMethods.isOpenBillAvailable) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.map((option) => (
|
||||||
|
<a onClick={() => handleOptionClick(option.value)} key={option.value}>
|
||||||
|
<div className={styles.svg}>
|
||||||
|
{option.icon}
|
||||||
|
</div>
|
||||||
|
{option.label}
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Dropdown;
|
||||||
60
src/pages/Dropdown.module.css
Normal file
60
src/pages/Dropdown.module.css
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
.dropdown {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdownButton {
|
||||||
|
background-color: #fff;
|
||||||
|
width: 150px;
|
||||||
|
color: #000;
|
||||||
|
border: 1px solid #000;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 17px;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdownButton a {
|
||||||
|
color: #000;
|
||||||
|
padding: 8px 16px;
|
||||||
|
text-decoration: none;
|
||||||
|
display: block;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
line-height: 19px;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdownButton i {
|
||||||
|
margin-left: 8px;
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdownContent {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 105%;
|
||||||
|
left: 0;
|
||||||
|
background: #fff;
|
||||||
|
width: 150px;
|
||||||
|
border-radius: 6px;
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||||
|
border: 1px solid #000;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdownContent a {
|
||||||
|
color: #000;
|
||||||
|
padding: 8px 16px;
|
||||||
|
text-decoration: none;
|
||||||
|
display: block;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
border-bottom: 1px solid #ddd;
|
||||||
|
display: flex;
|
||||||
|
line-height: 19px;
|
||||||
|
font-size: 17px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.svg {
|
||||||
|
width: 27px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.PaymentOption {
|
.PaymentOption {
|
||||||
overflow-x: hidden;
|
overflow: visible;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -72,7 +72,7 @@
|
|||||||
font-size: 1.3em;
|
font-size: 1.3em;
|
||||||
padding: 10px 0;
|
padding: 10px 0;
|
||||||
padding-top: 20px;
|
padding-top: 20px;
|
||||||
margin-bottom: 17px;
|
/* margin-bottom: 17px; */
|
||||||
}
|
}
|
||||||
|
|
||||||
.PayButton {
|
.PayButton {
|
||||||
@@ -98,7 +98,30 @@
|
|||||||
padding-left: 20px;
|
padding-left: 20px;
|
||||||
padding-right: 20px;
|
padding-right: 20px;
|
||||||
}
|
}
|
||||||
|
.PayButton3{
|
||||||
|
font-family: "Plus Jakarta Sans", sans-serif;
|
||||||
|
font-weight: 500;
|
||||||
|
font-style: normal;
|
||||||
|
font-size: 20px;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 50px;
|
||||||
|
background-color: rgba(88, 55, 50, 1);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
margin: 0px auto;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.PayButton3 div{
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding-left: 20px;
|
||||||
|
padding-right: 20px;
|
||||||
|
}
|
||||||
.Pay2Button {
|
.Pay2Button {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: rgba(88, 55, 50, 1);
|
color: rgba(88, 55, 50, 1);
|
||||||
@@ -178,3 +201,23 @@
|
|||||||
resize: none; /* Prevent resizing */
|
resize: none; /* Prevent resizing */
|
||||||
overflow-wrap: break-word; /* Ensure text wraps */
|
overflow-wrap: break-word; /* Ensure text wraps */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.AddedLastTransaction{
|
||||||
|
width: 100%;
|
||||||
|
font-size: 1em;
|
||||||
|
padding: 10px 20px;
|
||||||
|
margin-bottom: 7px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.AddedLastTransaction div{
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
.CancelAddedLastTransaction{
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
margin-right: 30px;
|
||||||
|
margin-top: 10px;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
}
|
||||||
@@ -121,8 +121,8 @@ export default function Transactions({ propsShopId, sendParam, deviceType }) {
|
|||||||
<ul>
|
<ul>
|
||||||
{transaction.DetailedTransactions.map((detail) => (
|
{transaction.DetailedTransactions.map((detail) => (
|
||||||
<li key={detail.detailedTransactionId}>
|
<li key={detail.detailedTransactionId}>
|
||||||
<span>{detail.Item.name}</span> - {detail.qty} x Rp{" "}
|
<span>{detail.Item.name}</span> - {detail.qty < 1 ? 'tidak tersedia' : `${detail.qty} x Rp
|
||||||
{detail.promoPrice ? detail.promoPrice : detail.price}
|
${detail.promoPrice ? detail.promoPrice : detail.price}`}
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -144,6 +144,10 @@ const App = ({ forCafe = true, cafeId = -1,
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setCouponList(coupons)
|
||||||
|
}, [coupons]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSelectedCafeId(cafeId)
|
setSelectedCafeId(cafeId)
|
||||||
}, [cafeId]);
|
}, [cafeId]);
|
||||||
|
|||||||
@@ -20,8 +20,11 @@ export default function Transactions({ propsShopId, sendParam, deviceType, handl
|
|||||||
const [isPaymentLoading, setIsPaymentLoading] = useState(false);
|
const [isPaymentLoading, setIsPaymentLoading] = useState(false);
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const [transaction, setTransaction] = useState(null);
|
const [transaction, setTransaction] = useState(null);
|
||||||
|
const [notAcceptedItems, setNotAcceptedItems] = useState([]);
|
||||||
const noteRef = useRef(null);
|
const noteRef = useRef(null);
|
||||||
|
|
||||||
|
const [transactionRefreshKey, setTransactionRefreshKey] = useState(0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const transactionId = searchParams.get("transactionId") || "";
|
const transactionId = searchParams.get("transactionId") || "";
|
||||||
|
|
||||||
@@ -60,9 +63,11 @@ export default function Transactions({ propsShopId, sendParam, deviceType, handl
|
|||||||
if (isPaymentLoading) return;
|
if (isPaymentLoading) return;
|
||||||
setIsPaymentLoading(true);
|
setIsPaymentLoading(true);
|
||||||
try {
|
try {
|
||||||
const c = await confirmTransaction(transactionId);
|
const c = await confirmTransaction(transactionId, notAcceptedItems);
|
||||||
if (c) {
|
if (c) {
|
||||||
setTransaction({ ...transaction, confirmed: c.confirmed });
|
console.log(c)
|
||||||
|
setTransaction(c);
|
||||||
|
setTransactionRefreshKey(transactionRefreshKey+1);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error processing payment:", error);
|
console.error("Error processing payment:", error);
|
||||||
@@ -76,6 +81,11 @@ export default function Transactions({ propsShopId, sendParam, deviceType, handl
|
|||||||
setIsPaymentLoading(true);
|
setIsPaymentLoading(true);
|
||||||
try {
|
try {
|
||||||
const c = await declineTransaction(transactionId);
|
const c = await declineTransaction(transactionId);
|
||||||
|
if (c) {
|
||||||
|
console.log(c)
|
||||||
|
setTransaction({ ...transaction, confirmed: c.confirmed });
|
||||||
|
setTransactionRefreshKey(transactionRefreshKey+1);
|
||||||
|
}
|
||||||
// if (c) {
|
// if (c) {
|
||||||
// // Update the confirmed status locally
|
// // Update the confirmed status locally
|
||||||
// setTransactions((prevTransactions) =>
|
// setTransactions((prevTransactions) =>
|
||||||
@@ -107,7 +117,7 @@ export default function Transactions({ propsShopId, sendParam, deviceType, handl
|
|||||||
}, [transaction?.notes]);
|
}, [transaction?.notes]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.Transaction}>
|
<div key={transactionRefreshKey} className={styles.Transaction}>
|
||||||
|
|
||||||
<div className={styles.TransactionListContainer}>
|
<div className={styles.TransactionListContainer}>
|
||||||
{transaction && (
|
{transaction && (
|
||||||
@@ -120,15 +130,17 @@ export default function Transactions({ propsShopId, sendParam, deviceType, handl
|
|||||||
>
|
>
|
||||||
|
|
||||||
<div className={styles['receipt-header']}>
|
<div className={styles['receipt-header']}>
|
||||||
<img
|
|
||||||
src={shopImg} // Placeholder image URL
|
|
||||||
alt='logo'
|
|
||||||
className={styles['receipt-logo']}
|
|
||||||
/>
|
|
||||||
<div className={styles['receipt-info']}>
|
<div className={styles['receipt-info']}>
|
||||||
<h3>Receipt Information</h3>
|
<h3>{transaction.payment_type == 'cash' ? 'Tunai' : transaction.payment_type == 'cashless' ? 'Non tunai' : transaction.payment_type == 'paylater' ? 'Open bill' :'Close bill' }</h3>
|
||||||
<p>Transaction ID: {transaction.transactionId}</p>
|
<p>Transaction ID: {transaction.transactionId}</p>
|
||||||
<p>Payment Type: {transaction.payment_type}</p>
|
{
|
||||||
|
transaction.payment_type == 'paylater/cash' ?
|
||||||
|
<p>Cash</p>
|
||||||
|
:
|
||||||
|
(transaction.payment_type == 'paylater/cashless' &&
|
||||||
|
<p>Non tunai</p>
|
||||||
|
)
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -171,14 +183,52 @@ export default function Transactions({ propsShopId, sendParam, deviceType, handl
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.RibbonBanner}></div>
|
<div className={styles.RibbonBanner}></div>
|
||||||
<ul>
|
<div>
|
||||||
{transaction.DetailedTransactions.map((detail) => (
|
{transaction.DetailedTransactions.map((detail) => (
|
||||||
<li key={detail.detailedTransactionId}>
|
<div className={styles['itemContainer']} key={detail.detailedTransactionId}>
|
||||||
<span>{detail.Item.name}</span> - {detail.qty} x Rp{" "}
|
<div className={styles['plusNegative']} style={{ visibility: transaction.confirmed >= 0 && detail.acceptedStatus == 0 ? 'visible' : 'hidden', margin: transaction.confirmed >= 0 && detail.acceptedStatus == 0 ? '4px 10px 0px 10px' : '4px 0px 0px 0px' }}
|
||||||
{detail.promoPrice ? detail.promoPrice : detail.price}
|
onClick={() =>
|
||||||
</li>
|
setNotAcceptedItems(prevState =>
|
||||||
|
prevState.includes(detail.detailedTransactionId)
|
||||||
|
? prevState.filter(item => item !== detail.detailedTransactionId)
|
||||||
|
: [...prevState, detail.detailedTransactionId]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{!notAcceptedItems.includes(detail.detailedTransactionId) ?
|
||||||
|
<svg
|
||||||
|
className={styles['plusNegative2']}
|
||||||
|
clipRule="evenodd"
|
||||||
|
fillRule="evenodd"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeMiterlimit="2"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="m12.002 2c5.518 0 9.998 4.48 9.998 9.998 0 5.517-4.48 9.997-9.998 9.997-5.517 0-9.997-4.48-9.997-9.997 0-5.518 4.48-9.998 9.997-9.998zm0 1.5c-4.69 0-8.497 3.808-8.497 8.498s3.807 8.497 8.497 8.497 8.498-3.807 8.498-8.497-3.808-8.498-8.498-8.498zm-.747 7.75h-3.5c-.414 0-.75.336-.75.75s.336.75.75.75h3.5v3.5c0 .414.336.75.75.75s.75-.336.75-.75v-3.5h3.5c.414 0 .75-.336.75-.75s-.336-.75-.75-.75h-3.5v-3.5c0-.414-.336-.75-.75-.75s-.75.336-.75.75z"
|
||||||
|
fillRule="nonzero"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
:
|
||||||
|
<svg className={styles['plusNegative2']}
|
||||||
|
viewBox="0 0 50 50" xmlns="http://www.w3.org/2000/svg"><path d="M25 38c-5.1 0-9.7-3-11.8-7.6l1.8-.8c1.8 3.9 5.7 6.4 10 6.4 6.1 0 11-4.9 11-11s-4.9-11-11-11c-4.6 0-8.5 2.8-10.1 7.3l-1.9-.7c1.9-5.2 6.6-8.6 12-8.6 7.2 0 13 5.8 13 13s-5.8 13-13 13z" /><path d="M20 22h-8v-8h2v6h6z" /></svg>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span style={{
|
||||||
|
display: 'inline-block',
|
||||||
|
maxWidth: '100px', // Adjust the max-width as needed
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis'
|
||||||
|
}}>
|
||||||
|
{detail.Item.name}</span> - {notAcceptedItems.includes(detail.detailedTransactionId) || detail.qty < 1 ? 'tidak tersedia' : `${detail.qty} x Rp
|
||||||
|
${detail.promoPrice ? detail.promoPrice : detail.price}`}
|
||||||
|
|
||||||
|
</div>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</div>
|
||||||
<h2 className={styles["Transactions-detail"]}>
|
<h2 className={styles["Transactions-detail"]}>
|
||||||
{transaction.serving_type === "pickup"
|
{transaction.serving_type === "pickup"
|
||||||
? "Ambil sendiri"
|
? "Ambil sendiri"
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ export default function Transactions({
|
|||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
try {
|
try {
|
||||||
const fetchedTransaction = await getTransaction(transactionId);
|
const fetchedTransaction = await getTransaction(transactionId);
|
||||||
|
if (fetchedTransaction.payment_type == 'paylater' && deviceType == 'guestDevice') localStorage.setItem('lastTransaction', JSON.stringify(fetchedTransaction));
|
||||||
setTransaction(fetchedTransaction);
|
setTransaction(fetchedTransaction);
|
||||||
console.log(fetchedTransaction); // Log the fetched transaction
|
console.log(fetchedTransaction); // Log the fetched transaction
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -135,9 +136,36 @@ export default function Transactions({
|
|||||||
>
|
>
|
||||||
|
|
||||||
<div className={styles['receipt-header']}>
|
<div className={styles['receipt-header']}>
|
||||||
|
{transaction.payment_type == 'paylater' ?
|
||||||
|
<div>
|
||||||
|
<svg
|
||||||
|
height="60px"
|
||||||
|
width="60px"
|
||||||
|
version="1.1"
|
||||||
|
id="Layer_1"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlnsXlink="http://www.w3.org/1999/xlink"
|
||||||
|
viewBox="0 0 506.4 506.4"
|
||||||
|
xmlSpace="preserve"
|
||||||
|
fill="#000000"
|
||||||
|
>
|
||||||
|
<g id="SVGRepo_bgCarrier" strokeWidth="0"></g>
|
||||||
|
<g id="SVGRepo_tracerCarrier" strokeLinecap="round" strokeLinejoin="round"></g>
|
||||||
|
<g id="SVGRepo_iconCarrier">
|
||||||
|
<circle style={{ fill: '#54B265' }} cx="253.2" cy="253.2" r="249.2" />
|
||||||
|
<path
|
||||||
|
style={{ fill: '#F4EFEF' }}
|
||||||
|
d="M372.8,200.4l-11.2-11.2c-4.4-4.4-12-4.4-16.4,0L232,302.4l-69.6-69.6c-4.4-4.4-12-4.4-16.4,0 L134.4,244c-4.4,4.4-4.4,12,0,16.4l89.2,89.2c4.4,4.4,12,4.4,16.4,0l0,0l0,0l10.4-10.4l0.8-0.8l121.6-121.6 C377.2,212.4,377.2,205.2,372.8,200.4z"
|
||||||
|
></path>
|
||||||
|
<path d="M253.2,506.4C113.6,506.4,0,392.8,0,253.2S113.6,0,253.2,0s253.2,113.6,253.2,253.2S392.8,506.4,253.2,506.4z M253.2,8 C118,8,8,118,8,253.2s110,245.2,245.2,245.2s245.2-110,245.2-245.2S388.4,8,253.2,8z"></path>
|
||||||
|
<path d="M231.6,357.2c-4,0-8-1.6-11.2-4.4l-89.2-89.2c-6-6-6-16,0-22l11.6-11.6c6-6,16.4-6,22,0l66.8,66.8L342,186.4 c2.8-2.8,6.8-4.4,11.2-4.4c4,0,8,1.6,11.2,4.4l11.2,11.2l0,0c6,6,6,16,0,22L242.8,352.4C239.6,355.6,235.6,357.2,231.6,357.2z M154,233.6c-2,0-4,0.8-5.6,2.4l-11.6,11.6c-2.8,2.8-2.8,8,0,10.8l89.2,89.2c2.8,2.8,8,2.8,10.8,0l132.8-132.8c2.8-2.8,2.8-8,0-10.8 l-11.2-11.2c-2.8-2.8-8-2.8-10.8,0L234.4,306c-1.6,1.6-4,1.6-5.6,0l-69.6-69.6C158,234.4,156,233.6,154,233.6z"></path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</div> :
|
||||||
<ColorRing className={styles['receipt-logo']} />
|
<ColorRing className={styles['receipt-logo']} />
|
||||||
|
}
|
||||||
<div className={styles['receipt-info']}>
|
<div className={styles['receipt-info']}>
|
||||||
{transaction.payment_type == 'cashless' ? <h3>silahkan bayar dengan QRIS</h3> : <h3>silahkan bayar ke kasir</h3>}
|
{transaction.payment_type == 'cashless' || transaction.payment_type == 'paylater/cashless' ? <h3>silahkan bayar dengan QRIS</h3> : transaction.payment_type == 'cash' || transaction.payment_type == 'paylater/cash' ? <h3>silahkan bayar ke kasir</h3> : transaction.DetailedTransactions.some(transaction => transaction.additionalNumber > 0) ? <h3>Item sukses ditambahkan</h3> : <h3>open bill disetujui</h3>}
|
||||||
<p>Transaction ID: {transaction.transactionId}</p>
|
<p>Transaction ID: {transaction.transactionId}</p>
|
||||||
<p>Payment Type: {transaction.payment_type}</p>
|
<p>Payment Type: {transaction.payment_type}</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -151,30 +179,32 @@ export default function Transactions({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2 className={styles["Transactions-detail"]}>
|
|
||||||
Transaction ID: {transaction.transactionId}
|
|
||||||
</h2>
|
|
||||||
<h2 className={styles["Transactions-detail"]}>
|
|
||||||
Payment Type: {transaction.payment_type}
|
|
||||||
</h2>
|
|
||||||
<ul>
|
<ul>
|
||||||
{(isPaymentOpen
|
{(isPaymentOpen
|
||||||
? transaction.DetailedTransactions.slice(0, 2)
|
? transaction.DetailedTransactions.slice(0, 2)
|
||||||
: transaction.DetailedTransactions
|
: transaction.DetailedTransactions
|
||||||
).map((detail) => (
|
).map((detail) => (
|
||||||
<li key={detail.detailedTransactionId}>
|
<li key={detail.detailedTransactionId}>
|
||||||
<span>{detail.Item.name}</span> - {detail.qty} x Rp{" "}
|
<span>{detail.Item.name}</span> - {detail.qty < 1 ? 'tidak tersedia' : `${detail.qty} x Rp
|
||||||
{detail.promoPrice ? detail.promoPrice : detail.price}
|
${detail.promoPrice ? detail.promoPrice : detail.price}`}
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
{transaction.payment_type != 'paylater/cash' && transaction.payment_type != 'paylater/cashless' &&
|
||||||
|
<div
|
||||||
|
onClick={() => {
|
||||||
|
localStorage.setItem('lastTransaction', JSON.stringify(transaction));
|
||||||
|
}} className={styles["addNewItem"]}>Tambah pesanan</div>
|
||||||
|
}
|
||||||
|
|
||||||
<h2 className={styles["Transactions-detail"]}>
|
<h2 className={styles["Transactions-detail"]}>
|
||||||
{transaction.serving_type === "pickup"
|
{transaction.serving_type === "pickup"
|
||||||
? "Self pickup"
|
? "Ambil sendiri"
|
||||||
: `Serve to ${transaction.Table ? transaction.Table.tableNo : "N/A"
|
: `Diantar ke ${transaction.Table ? transaction.Table.tableNo : "N/A"
|
||||||
}`}
|
}`}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
{(transaction.notes !== "") && (
|
{(transaction.notes !== "") && (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
@@ -202,6 +232,9 @@ export default function Transactions({
|
|||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{transaction.payment_type != 'paylater' ?
|
||||||
|
<>
|
||||||
<div className={styles.TotalContainer}>
|
<div className={styles.TotalContainer}>
|
||||||
<span>Total:</span>
|
<span>Total:</span>
|
||||||
<span>
|
<span>
|
||||||
@@ -209,30 +242,55 @@ export default function Transactions({
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.PaymentContainer}>
|
<div className={styles.PaymentContainer}>
|
||||||
|
{transaction.payment_type != 'cash' && transaction.payment_type != 'paylater/cash' &&
|
||||||
<ButtonWithReplica
|
<ButtonWithReplica
|
||||||
paymentUrl={paymentUrl}
|
paymentUrl={paymentUrl}
|
||||||
price={
|
price={
|
||||||
"Rp" + calculateTotalPrice(transaction.DetailedTransactions)
|
"Rp" + calculateTotalPrice(transaction.DetailedTransactions)
|
||||||
}
|
}
|
||||||
disabled={transaction.payment_type == 'cash' || isPaymentLoading}
|
disabled={isPaymentLoading}
|
||||||
isPaymentLoading={isPaymentLoading}
|
isPaymentLoading={isPaymentLoading}
|
||||||
handleClick={() => handleConfirm(transaction.transactionId)}
|
handleClick={() => handleConfirm(transaction.transactionId)}
|
||||||
Open={() => setIsPaymentOpen(true)}
|
Open={() => setIsPaymentOpen(true)}
|
||||||
isPaymentOpen={isPaymentOpen}
|
isPaymentOpen={isPaymentOpen}
|
||||||
>
|
>
|
||||||
{transaction.payment_type == 'cash' || isPaymentLoading ? (
|
{
|
||||||
(transaction.payment_type == 'cash' && 'Bayar nanti')
|
isPaymentLoading ? null : isPaymentOpen ? (
|
||||||
) : isPaymentOpen ? (
|
transaction.paymentClaimed ? (
|
||||||
(transaction.paymentClaimed ? <>
|
<>
|
||||||
<div style={{ position: 'absolute', left: '15px', top: '9px' }}>
|
<div style={{ position: 'absolute', left: '15px', top: '9px' }}>
|
||||||
<ColorRing
|
<ColorRing height="20" width="20" className={styles['receipt-logo']} />
|
||||||
height="20"
|
</div> Menunggu konfirmasi
|
||||||
width="20" className={styles['receipt-logo']} /></div> Menunggu konfirmasi</> : "Klaim telah bayar") // Display "Confirm has paid" if the transaction is confirmed (1)
|
</>
|
||||||
) : (
|
) : 'Klaim telah bayar'
|
||||||
"Tampilkan QR" // Display "Confirm availability" if the transaction is not confirmed (0)
|
) : 'Tampilkan QR'
|
||||||
)}
|
}
|
||||||
|
|
||||||
</ButtonWithReplica>
|
</ButtonWithReplica>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
|
{transaction.payment_type == 'cash' ?
|
||||||
|
<button
|
||||||
|
className={styles.PayButton}
|
||||||
|
onClick={() => handleDecline(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 ? (
|
||||||
|
"Ditolak" // Display "Declined" if the transaction is declined (-1)
|
||||||
|
) : transaction.confirmed === -2 ? (
|
||||||
|
"Dibatalkan" // Display "Declined" if the transaction is declined (-1)
|
||||||
|
) : (
|
||||||
|
"Batalkan" // Display "Confirm availability" if the transaction is not confirmed (0)
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
:
|
||||||
|
(transaction.payment_type != 'paylater/cash' && transaction.payment_type != 'paylater/cashless' || isPaymentOpen &&
|
||||||
<h5
|
<h5
|
||||||
className={`${styles.DeclineButton}`}
|
className={`${styles.DeclineButton}`}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
@@ -243,6 +301,17 @@ export default function Transactions({
|
|||||||
>
|
>
|
||||||
{isPaymentOpen ? "kembali" : "batalkan"}
|
{isPaymentOpen ? "kembali" : "batalkan"}
|
||||||
</h5>
|
</h5>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
:
|
||||||
|
|
||||||
|
<h5
|
||||||
|
className={`${styles.DeclineButton}`}
|
||||||
|
>
|
||||||
|
Open bill disetujui, silahkan lanjutkan pemesanan anda
|
||||||
|
</h5>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,28 +1,240 @@
|
|||||||
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 ButtonWithReplica from "../components/ButtonWithReplica";
|
||||||
|
import {
|
||||||
|
getTransaction,
|
||||||
|
confirmTransaction,
|
||||||
|
handleClaimHasPaid,
|
||||||
|
declineTransaction,
|
||||||
|
cancelTransaction,
|
||||||
|
} 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({
|
||||||
const containerStyle = {
|
propsShopId,
|
||||||
display: "flex",
|
sendParam,
|
||||||
justifyContent: "center",
|
deviceType,
|
||||||
alignItems: "center",
|
paymentUrl,
|
||||||
width: "100%",
|
}) {
|
||||||
height: "100%", // This makes the container stretch to the bottom of the viewport
|
const { shopId, tableId } = useParams();
|
||||||
backgroundColor: "#000", // Optional: Set a background color if you want to see the color ring clearly
|
if (sendParam) sendParam({ shopId, tableId });
|
||||||
|
|
||||||
|
const [tables, setTables] = useState([]);
|
||||||
|
const [selectedTable, setSelectedTable] = useState(null);
|
||||||
|
const [isPaymentLoading, setIsPaymentLoading] = useState(false);
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
const [transaction, setTransaction] = useState(null);
|
||||||
|
const noteRef = useRef(null);
|
||||||
|
const [isPaymentOpen, setIsPaymentOpen] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const transactionId = searchParams.get("transactionId") || "";
|
||||||
|
|
||||||
|
const fetchData = async () => {
|
||||||
|
try {
|
||||||
|
const fetchedTransaction = await getTransaction(transactionId);
|
||||||
|
if (fetchedTransaction.payment_type == 'paylater' && deviceType == 'guestDevice') localStorage.setItem('lastTransaction', JSON.stringify(fetchedTransaction));
|
||||||
|
setTransaction(fetchedTransaction);
|
||||||
|
console.log(fetchedTransaction); // Log the fetched transaction
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching transaction:", error);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const waitForLocalStorage = async () => {
|
||||||
|
while (localStorage.getItem("auth") === null) {
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const initialize = async () => {
|
||||||
|
await waitForLocalStorage();
|
||||||
|
await fetchData();
|
||||||
|
};
|
||||||
|
|
||||||
|
initialize();
|
||||||
|
}, [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.promoPrice ? dt.promoPrice : dt.price);
|
||||||
|
}, 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConfirm = async (transactionId) => {
|
||||||
|
if (isPaymentLoading) return;
|
||||||
|
setIsPaymentLoading(true);
|
||||||
|
try {
|
||||||
|
const c = await handleClaimHasPaid(transactionId);
|
||||||
|
if (c) {
|
||||||
|
setTransaction({
|
||||||
|
...transaction,
|
||||||
|
confirmed: c.confirmed,
|
||||||
|
paymentClaimed: true,
|
||||||
|
});
|
||||||
|
console.log(transaction);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error processing payment:", error);
|
||||||
|
} finally {
|
||||||
|
setIsPaymentLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDecline = async (transactionId) => {
|
||||||
|
if (isPaymentLoading) return;
|
||||||
|
setIsPaymentLoading(true);
|
||||||
|
try {
|
||||||
|
const c = await cancelTransaction(transactionId);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error processing payment:", error);
|
||||||
|
} finally {
|
||||||
|
setIsPaymentLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const autoResizeTextArea = (textarea) => {
|
||||||
|
if (textarea) {
|
||||||
|
textarea.style.height = "auto"; // Reset height
|
||||||
|
textarea.style.height = `${textarea.scrollHeight}px`; // Set new height
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (noteRef.current) {
|
||||||
|
autoResizeTextArea(noteRef.current);
|
||||||
|
}
|
||||||
|
}, [transaction?.notes]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.Transaction}>
|
<div className={styles.Transaction}>
|
||||||
<div className={containerStyle}>
|
<div className={styles.TransactionListContainer} style={{ overflow: 'hidden' }}>
|
||||||
<div style={{ marginTop: "30px", textAlign: "center" }}>
|
{transaction && (
|
||||||
<h2>pesananmu selesai diproses nih</h2>
|
<div
|
||||||
<img
|
key={transaction.transactionId}
|
||||||
className={styles.expression}
|
className={styles.RoundedRectangle}
|
||||||
src="https://i.imgur.com/nTokiWH.png"
|
onClick={() =>
|
||||||
alt="Success"
|
setSelectedTable(transaction.Table || { tableId: 0 })
|
||||||
|
}
|
||||||
|
>
|
||||||
|
|
||||||
|
<div className={styles['receipt-header']}>
|
||||||
|
<div>
|
||||||
|
<svg
|
||||||
|
height="60px"
|
||||||
|
width="60px"
|
||||||
|
version="1.1"
|
||||||
|
id="Layer_1"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlnsXlink="http://www.w3.org/1999/xlink"
|
||||||
|
viewBox="0 0 506.4 506.4"
|
||||||
|
xmlSpace="preserve"
|
||||||
|
fill="#000000"
|
||||||
|
style={{marginTop: '12px', marginBottom: '12px'}}
|
||||||
|
>
|
||||||
|
<g id="SVGRepo_bgCarrier" strokeWidth="0"></g>
|
||||||
|
<g id="SVGRepo_tracerCarrier" strokeLinecap="round" strokeLinejoin="round"></g>
|
||||||
|
<g id="SVGRepo_iconCarrier">
|
||||||
|
<circle style={{ fill: '#54B265' }} cx="253.2" cy="253.2" r="249.2" />
|
||||||
|
<path
|
||||||
|
style={{ fill: '#F4EFEF' }}
|
||||||
|
d="M372.8,200.4l-11.2-11.2c-4.4-4.4-12-4.4-16.4,0L232,302.4l-69.6-69.6c-4.4-4.4-12-4.4-16.4,0 L134.4,244c-4.4,4.4-4.4,12,0,16.4l89.2,89.2c4.4,4.4,12,4.4,16.4,0l0,0l0,0l10.4-10.4l0.8-0.8l121.6-121.6 C377.2,212.4,377.2,205.2,372.8,200.4z"
|
||||||
|
></path>
|
||||||
|
<path d="M253.2,506.4C113.6,506.4,0,392.8,0,253.2S113.6,0,253.2,0s253.2,113.6,253.2,253.2S392.8,506.4,253.2,506.4z M253.2,8 C118,8,8,118,8,253.2s110,245.2,245.2,245.2s245.2-110,245.2-245.2S388.4,8,253.2,8z"></path>
|
||||||
|
<path d="M231.6,357.2c-4,0-8-1.6-11.2-4.4l-89.2-89.2c-6-6-6-16,0-22l11.6-11.6c6-6,16.4-6,22,0l66.8,66.8L342,186.4 c2.8-2.8,6.8-4.4,11.2-4.4c4,0,8,1.6,11.2,4.4l11.2,11.2l0,0c6,6,6,16,0,22L242.8,352.4C239.6,355.6,235.6,357.2,231.6,357.2z M154,233.6c-2,0-4,0.8-5.6,2.4l-11.6,11.6c-2.8,2.8-2.8,8,0,10.8l89.2,89.2c2.8,2.8,8,2.8,10.8,0l132.8-132.8c2.8-2.8,2.8-8,0-10.8 l-11.2-11.2c-2.8-2.8-8-2.8-10.8,0L234.4,306c-1.6,1.6-4,1.6-5.6,0l-69.6-69.6C158,234.4,156,233.6,154,233.6z"></path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles['receipt-info']}>
|
||||||
|
<h3>Transaksi Selesai</h3>
|
||||||
|
<p>Transaction ID: {transaction.transactionId}</p>
|
||||||
|
<p>Payment Type: {transaction.payment_type}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
{(isPaymentOpen
|
||||||
|
? transaction.DetailedTransactions.slice(0, 2)
|
||||||
|
: transaction.DetailedTransactions
|
||||||
|
).map((detail) => (
|
||||||
|
<li key={detail.detailedTransactionId}>
|
||||||
|
<span>{detail.Item.name}</span> - {detail.qty < 1 ? 'tidak tersedia' : `${detail.qty} x Rp
|
||||||
|
${detail.promoPrice ? detail.promoPrice : detail.price}`}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2 className={styles["Transactions-detail"]}>
|
||||||
|
{transaction.serving_type === "pickup"
|
||||||
|
? "Ambil sendiri"
|
||||||
|
: `Diantar ke ${transaction.Table ? transaction.Table.tableNo : "N/A"
|
||||||
|
}`}
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
{(transaction.notes !== "") && (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
className={styles.NoteContainer}
|
||||||
|
style={{
|
||||||
|
visibility: transaction.notes == "" ? "hidden" : "visible",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span>Note :</span>
|
||||||
|
<span></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={styles.NoteContainer}
|
||||||
|
style={{
|
||||||
|
visibility: transaction.notes == "" ? "hidden" : "visible",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<textarea
|
||||||
|
className={styles.NoteInput}
|
||||||
|
value={transaction.notes}
|
||||||
|
ref={noteRef}
|
||||||
|
disabled
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{transaction.payment_type != 'paylater' ?
|
||||||
|
<>
|
||||||
|
<div className={styles.TotalContainer}>
|
||||||
|
<span>Total:</span>
|
||||||
|
<span>
|
||||||
|
Rp {calculateTotalPrice(transaction.DetailedTransactions)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
:
|
||||||
|
|
||||||
|
<h5
|
||||||
|
className={`${styles.DeclineButton}`}
|
||||||
|
>
|
||||||
|
Open bill disetujui, silahkan lanjutkan pemesanan anda
|
||||||
|
</h5>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
139
src/pages/Transaction_item.js
Normal file
139
src/pages/Transaction_item.js
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
import React, { useRef, useEffect, useState } from "react";
|
||||||
|
import styles from "./Transactions.module.css";
|
||||||
|
import { useParams } from "react-router-dom";
|
||||||
|
import { ColorRing } from "react-loader-spinner";
|
||||||
|
import {
|
||||||
|
getTransaction,
|
||||||
|
confirmTransaction,
|
||||||
|
cancelTransaction,
|
||||||
|
} from "../helpers/transactionHelpers";
|
||||||
|
import { getTables } from "../helpers/tableHelper";
|
||||||
|
import TableCanvas from "../components/TableCanvas";
|
||||||
|
import { useSearchParams } from "react-router-dom";
|
||||||
|
|
||||||
|
export default function Transactions({ propsShopId, sendParam, deviceType, handleMoveToTransaction, shopImg }) {
|
||||||
|
const { shopId, tableId } = useParams();
|
||||||
|
if (sendParam) sendParam({ shopId, tableId });
|
||||||
|
|
||||||
|
const [tables, setTables] = useState([]);
|
||||||
|
const [selectedTable, setSelectedTable] = useState(null);
|
||||||
|
const [isPaymentLoading, setIsPaymentLoading] = useState(false);
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
const [transaction, setTransaction] = useState(null);
|
||||||
|
const noteRef = useRef(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const transactionId = searchParams.get("transactionId") || "";
|
||||||
|
|
||||||
|
const fetchData = async () => {
|
||||||
|
try {
|
||||||
|
const fetchedTransaction = await getTransaction(transactionId);
|
||||||
|
setTransaction(fetchedTransaction);
|
||||||
|
console.log(fetchedTransaction); // Log the fetched transaction
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching transaction:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const waitForLocalStorage = async () => {
|
||||||
|
while (localStorage.getItem("auth") === null) {
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const initialize = async () => {
|
||||||
|
await waitForLocalStorage();
|
||||||
|
await fetchData();
|
||||||
|
};
|
||||||
|
|
||||||
|
initialize();
|
||||||
|
}, [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.promoPrice ? dt.promoPrice : dt.price);
|
||||||
|
}, 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<div className={styles.Transaction}>
|
||||||
|
|
||||||
|
<div className={styles.TransactionListContainer}>
|
||||||
|
{transaction && (
|
||||||
|
<div
|
||||||
|
key={transaction.transactionId}
|
||||||
|
className={styles.RoundedRectangle}
|
||||||
|
onClick={() =>
|
||||||
|
setSelectedTable(transaction.Table || { tableId: 0 })
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<ul>
|
||||||
|
{transaction.DetailedTransactions.map((detail) => (
|
||||||
|
<li key={detail.detailedTransactionId}>
|
||||||
|
<span>{detail.Item.name}</span> - {detail.qty < 1 ? 'tidak tersedia' : `${detail.qty} x Rp
|
||||||
|
${detail.promoPrice ? detail.promoPrice : detail.price}`}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2 className={styles["Transactions-detail"]}>
|
||||||
|
{transaction.serving_type === "pickup"
|
||||||
|
? "Ambil sendiri"
|
||||||
|
: `Diantar ke ${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>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -118,6 +118,7 @@ export default function Transactions({ propsShopId, sendParam, deviceType, handl
|
|||||||
}
|
}
|
||||||
}, [transaction?.notes]);
|
}, [transaction?.notes]);
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.Transaction}>
|
<div className={styles.Transaction}>
|
||||||
|
|
||||||
@@ -153,13 +154,27 @@ export default function Transactions({ propsShopId, sendParam, deviceType, handl
|
|||||||
</div>
|
</div>
|
||||||
<div className={styles.RibbonBanner}></div>
|
<div className={styles.RibbonBanner}></div>
|
||||||
<ul>
|
<ul>
|
||||||
{transaction.DetailedTransactions.map((detail) => (
|
{transaction.DetailedTransactions.map((detail, index) => (
|
||||||
|
<>
|
||||||
|
{detail.additionalNumber > transaction.DetailedTransactions[index - 1]?.additionalNumber &&
|
||||||
|
|
||||||
|
<div style={{marginTop: '10px'}} key={detail.detailedTransactionId}>
|
||||||
|
tambah -----
|
||||||
|
</div>
|
||||||
|
}
|
||||||
<li key={detail.detailedTransactionId}>
|
<li key={detail.detailedTransactionId}>
|
||||||
<span>{detail.Item.name}</span> - {detail.qty} x Rp{" "}
|
<span>{detail.Item.name}</span> - {detail.qty < 1 ? 'tidak tersedia' : `${detail.qty} x Rp
|
||||||
{detail.promoPrice ? detail.promoPrice : detail.Item.price}
|
${detail.promoPrice ? detail.promoPrice : detail.price}`}
|
||||||
</li>
|
</li>
|
||||||
|
</>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
<div
|
||||||
|
onClick={() => {
|
||||||
|
localStorage.setItem('lastTransaction', JSON.stringify(transaction));
|
||||||
|
}} className={styles["addNewItem"]}>Tambah pesanan</div>
|
||||||
|
|
||||||
<h2 className={styles["Transactions-detail"]}>
|
<h2 className={styles["Transactions-detail"]}>
|
||||||
{transaction.serving_type === "pickup"
|
{transaction.serving_type === "pickup"
|
||||||
? "Ambil sendiri"
|
? "Ambil sendiri"
|
||||||
@@ -189,6 +204,7 @@ export default function Transactions({ propsShopId, sendParam, deviceType, handl
|
|||||||
Rp {calculateTotalPrice(transaction.DetailedTransactions)}
|
Rp {calculateTotalPrice(transaction.DetailedTransactions)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
{transaction.payment_type != 'paylater' &&
|
||||||
<div className={styles.TotalContainer}>
|
<div className={styles.TotalContainer}>
|
||||||
<button
|
<button
|
||||||
className={styles.PayButton}
|
className={styles.PayButton}
|
||||||
@@ -201,21 +217,16 @@ export default function Transactions({ propsShopId, sendParam, deviceType, handl
|
|||||||
>
|
>
|
||||||
{isPaymentLoading ? (
|
{isPaymentLoading ? (
|
||||||
<ColorRing height="50" width="50" color="white" />
|
<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 ? (
|
) : transaction.confirmed === -1 ? (
|
||||||
"Ditolak" // Display "Declined" if the transaction is declined (-1)
|
"Ditolak" // Display "Declined" if the transaction is declined (-1)
|
||||||
) : transaction.confirmed === -2 ? (
|
) : transaction.confirmed === -2 ? (
|
||||||
"Dibatalkan" // Display "Declined" if the transaction is declined (-1)
|
"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)
|
"Batalkan" // Display "Confirm availability" if the transaction is not confirmed (0)
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,56 +1,240 @@
|
|||||||
import React from "react";
|
import React, { useRef, useEffect, useState } from "react";
|
||||||
import styles from "./Transactions.module.css";
|
import styles from "./Transactions.module.css";
|
||||||
import { requestNotificationPermission } from '../services/notificationService'; // Import the notification service
|
import { useParams } from "react-router-dom";
|
||||||
|
import { ColorRing } from "react-loader-spinner";
|
||||||
|
import ButtonWithReplica from "../components/ButtonWithReplica";
|
||||||
|
import {
|
||||||
|
getTransaction,
|
||||||
|
confirmTransaction,
|
||||||
|
handleClaimHasPaid,
|
||||||
|
declineTransaction,
|
||||||
|
cancelTransaction,
|
||||||
|
} from "../helpers/transactionHelpers";
|
||||||
|
import { getTables } from "../helpers/tableHelper";
|
||||||
|
import TableCanvas from "../components/TableCanvas";
|
||||||
|
import { useSearchParams } from "react-router-dom";
|
||||||
|
|
||||||
export default function Transaction_pending({ setModal }) {
|
export default function Transactions({
|
||||||
// const containerStyle = {
|
propsShopId,
|
||||||
// display: "flex",
|
sendParam,
|
||||||
// justifyContent: "center",
|
deviceType,
|
||||||
// alignItems: "center",
|
paymentUrl,
|
||||||
// width: "100%",
|
}) {
|
||||||
// height: "100%",
|
const { shopId, tableId } = useParams();
|
||||||
// backgroundColor: "",
|
if (sendParam) sendParam({ shopId, tableId });
|
||||||
// };
|
|
||||||
|
|
||||||
const handleNotificationClick = async () => {
|
const [tables, setTables] = useState([]);
|
||||||
const permission = await requestNotificationPermission();
|
const [selectedTable, setSelectedTable] = useState(null);
|
||||||
|
const [isPaymentLoading, setIsPaymentLoading] = useState(false);
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
const [transaction, setTransaction] = useState(null);
|
||||||
|
const noteRef = useRef(null);
|
||||||
|
const [isPaymentOpen, setIsPaymentOpen] = useState(false);
|
||||||
|
|
||||||
if (permission === "granted") {
|
useEffect(() => {
|
||||||
console.log("Notification permission granted.");
|
const transactionId = searchParams.get("transactionId") || "";
|
||||||
// Set up notifications or show a success modal
|
|
||||||
} else if (permission === "denied") {
|
const fetchData = async () => {
|
||||||
console.error("Notification permission denied.");
|
try {
|
||||||
setModal('blocked_notification'); // Show modal for blocked notifications
|
const fetchedTransaction = await getTransaction(transactionId);
|
||||||
|
if (fetchedTransaction.payment_type == 'paylater' && deviceType == 'guestDevice') localStorage.setItem('lastTransaction', JSON.stringify(fetchedTransaction));
|
||||||
|
setTransaction(fetchedTransaction);
|
||||||
|
console.log(fetchedTransaction); // Log the fetched transaction
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching transaction:", error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const waitForLocalStorage = async () => {
|
||||||
|
while (localStorage.getItem("auth") === null) {
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const initialize = async () => {
|
||||||
|
await waitForLocalStorage();
|
||||||
|
await fetchData();
|
||||||
|
};
|
||||||
|
|
||||||
|
initialize();
|
||||||
|
}, [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.promoPrice ? dt.promoPrice : dt.price);
|
||||||
|
}, 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConfirm = async (transactionId) => {
|
||||||
|
if (isPaymentLoading) return;
|
||||||
|
setIsPaymentLoading(true);
|
||||||
|
try {
|
||||||
|
const c = await handleClaimHasPaid(transactionId);
|
||||||
|
if (c) {
|
||||||
|
setTransaction({
|
||||||
|
...transaction,
|
||||||
|
confirmed: c.confirmed,
|
||||||
|
paymentClaimed: true,
|
||||||
|
});
|
||||||
|
console.log(transaction);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error processing payment:", error);
|
||||||
|
} finally {
|
||||||
|
setIsPaymentLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDecline = async (transactionId) => {
|
||||||
|
if (isPaymentLoading) return;
|
||||||
|
setIsPaymentLoading(true);
|
||||||
|
try {
|
||||||
|
const c = await cancelTransaction(transactionId);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error processing payment:", error);
|
||||||
|
} finally {
|
||||||
|
setIsPaymentLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const autoResizeTextArea = (textarea) => {
|
||||||
|
if (textarea) {
|
||||||
|
textarea.style.height = "auto"; // Reset height
|
||||||
|
textarea.style.height = `${textarea.scrollHeight}px`; // Set new height
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (noteRef.current) {
|
||||||
|
autoResizeTextArea(noteRef.current);
|
||||||
|
}
|
||||||
|
}, [transaction?.notes]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.Transaction}>
|
<div className={styles.Transaction}>
|
||||||
<div style={{ marginTop: "30px", textAlign: "center" }}>
|
<div className={styles.TransactionListContainer} style={{ overflow: 'hidden' }}>
|
||||||
<h2>Transaction Success</h2>
|
{transaction && (
|
||||||
<img
|
<div
|
||||||
className={styles.expression}
|
key={transaction.transactionId}
|
||||||
src="https://i.imgur.com/sgvMI02.png"
|
className={styles.RoundedRectangle}
|
||||||
alt="Success"
|
onClick={() =>
|
||||||
/>
|
setSelectedTable(transaction.Table || { tableId: 0 })
|
||||||
<p style={{ marginTop: "20px", color: "white" }}>
|
}
|
||||||
Do you want to get notifications when your item is ready?
|
>
|
||||||
</p>
|
|
||||||
<button
|
<div className={styles['receipt-header']}>
|
||||||
onClick={handleNotificationClick}
|
<div>
|
||||||
|
<svg
|
||||||
|
height="60px"
|
||||||
|
width="60px"
|
||||||
|
version="1.1"
|
||||||
|
id="Layer_1"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlnsXlink="http://www.w3.org/1999/xlink"
|
||||||
|
viewBox="0 0 506.4 506.4"
|
||||||
|
xmlSpace="preserve"
|
||||||
|
fill="#000000"
|
||||||
|
style={{marginTop: '12px', marginBottom: '12px'}}
|
||||||
|
>
|
||||||
|
<g id="SVGRepo_bgCarrier" strokeWidth="0"></g>
|
||||||
|
<g id="SVGRepo_tracerCarrier" strokeLinecap="round" strokeLinejoin="round"></g>
|
||||||
|
<g id="SVGRepo_iconCarrier">
|
||||||
|
<circle style={{ fill: '#54B265' }} cx="253.2" cy="253.2" r="249.2" />
|
||||||
|
<path
|
||||||
|
style={{ fill: '#F4EFEF' }}
|
||||||
|
d="M372.8,200.4l-11.2-11.2c-4.4-4.4-12-4.4-16.4,0L232,302.4l-69.6-69.6c-4.4-4.4-12-4.4-16.4,0 L134.4,244c-4.4,4.4-4.4,12,0,16.4l89.2,89.2c4.4,4.4,12,4.4,16.4,0l0,0l0,0l10.4-10.4l0.8-0.8l121.6-121.6 C377.2,212.4,377.2,205.2,372.8,200.4z"
|
||||||
|
></path>
|
||||||
|
<path d="M253.2,506.4C113.6,506.4,0,392.8,0,253.2S113.6,0,253.2,0s253.2,113.6,253.2,253.2S392.8,506.4,253.2,506.4z M253.2,8 C118,8,8,118,8,253.2s110,245.2,245.2,245.2s245.2-110,245.2-245.2S388.4,8,253.2,8z"></path>
|
||||||
|
<path d="M231.6,357.2c-4,0-8-1.6-11.2-4.4l-89.2-89.2c-6-6-6-16,0-22l11.6-11.6c6-6,16.4-6,22,0l66.8,66.8L342,186.4 c2.8-2.8,6.8-4.4,11.2-4.4c4,0,8,1.6,11.2,4.4l11.2,11.2l0,0c6,6,6,16,0,22L242.8,352.4C239.6,355.6,235.6,357.2,231.6,357.2z M154,233.6c-2,0-4,0.8-5.6,2.4l-11.6,11.6c-2.8,2.8-2.8,8,0,10.8l89.2,89.2c2.8,2.8,8,2.8,10.8,0l132.8-132.8c2.8-2.8,2.8-8,0-10.8 l-11.2-11.2c-2.8-2.8-8-2.8-10.8,0L234.4,306c-1.6,1.6-4,1.6-5.6,0l-69.6-69.6C158,234.4,156,233.6,154,233.6z"></path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles['receipt-info']}>
|
||||||
|
<h3>Transaksi Berhasil</h3>
|
||||||
|
<p>Transaction ID: {transaction.transactionId}</p>
|
||||||
|
<p>Payment Type: {transaction.payment_type}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
{(isPaymentOpen
|
||||||
|
? transaction.DetailedTransactions.slice(0, 2)
|
||||||
|
: transaction.DetailedTransactions
|
||||||
|
).map((detail) => (
|
||||||
|
<li key={detail.detailedTransactionId}>
|
||||||
|
<span>{detail.Item.name}</span> - {detail.qty < 1 ? 'tidak tersedia' : `${detail.qty} x Rp
|
||||||
|
${detail.promoPrice ? detail.promoPrice : detail.price}`}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2 className={styles["Transactions-detail"]}>
|
||||||
|
{transaction.serving_type === "pickup"
|
||||||
|
? "Ambil sendiri"
|
||||||
|
: `Diantar ke ${transaction.Table ? transaction.Table.tableNo : "N/A"
|
||||||
|
}`}
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
{(transaction.notes !== "") && (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
className={styles.NoteContainer}
|
||||||
style={{
|
style={{
|
||||||
marginTop: "10px",
|
visibility: transaction.notes == "" ? "hidden" : "visible",
|
||||||
padding: "10px 20px",
|
|
||||||
fontSize: "16px",
|
|
||||||
cursor: "pointer",
|
|
||||||
backgroundColor: "#4CAF50",
|
|
||||||
color: "#fff",
|
|
||||||
border: "none",
|
|
||||||
borderRadius: "5px",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
yes
|
<span>Note :</span>
|
||||||
</button>
|
<span></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={styles.NoteContainer}
|
||||||
|
style={{
|
||||||
|
visibility: transaction.notes == "" ? "hidden" : "visible",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<textarea
|
||||||
|
className={styles.NoteInput}
|
||||||
|
value={transaction.notes}
|
||||||
|
ref={noteRef}
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{transaction.payment_type != 'paylater' ?
|
||||||
|
<>
|
||||||
|
<div className={styles.TotalContainer}>
|
||||||
|
<span>Total:</span>
|
||||||
|
<span>
|
||||||
|
Rp {calculateTotalPrice(transaction.DetailedTransactions)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
:
|
||||||
|
|
||||||
|
<h5
|
||||||
|
className={`${styles.DeclineButton}`}
|
||||||
|
>
|
||||||
|
Open bill disetujui, silahkan lanjutkan pemesanan anda
|
||||||
|
</h5>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -11,10 +11,18 @@ import {
|
|||||||
import { getTables } from "../helpers/tableHelper";
|
import { getTables } from "../helpers/tableHelper";
|
||||||
import TableCanvas from "../components/TableCanvas";
|
import TableCanvas from "../components/TableCanvas";
|
||||||
|
|
||||||
export default function Transactions({ shopId, propsShopId, sendParam, deviceType }) {
|
import dayjs from 'dayjs';
|
||||||
|
import utc from 'dayjs/plugin/utc';
|
||||||
|
import timezone from 'dayjs/plugin/timezone';
|
||||||
|
|
||||||
|
|
||||||
|
export default function Transactions({ shop, shopId, propsShopId, sendParam, deviceType }) {
|
||||||
const { shopIdentifier, tableId } = useParams();
|
const { shopIdentifier, tableId } = useParams();
|
||||||
if (sendParam) sendParam({ shopIdentifier, tableId });
|
if (sendParam) sendParam({ shopIdentifier, tableId });
|
||||||
|
|
||||||
|
dayjs.extend(utc);
|
||||||
|
dayjs.extend(timezone);
|
||||||
|
|
||||||
const [tables, setTables] = useState([]);
|
const [tables, setTables] = useState([]);
|
||||||
const [selectedTable, setSelectedTable] = useState(null);
|
const [selectedTable, setSelectedTable] = useState(null);
|
||||||
const [transactions, setTransactions] = useState([]);
|
const [transactions, setTransactions] = useState([]);
|
||||||
@@ -87,6 +95,12 @@ export default function Transactions({ shopId, propsShopId, sendParam, deviceTyp
|
|||||||
}, 0);
|
}, 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const calculateAllTransactionsTotal = (transactions) => {
|
||||||
|
return transactions.reduce((grandTotal, transaction) => {
|
||||||
|
return grandTotal + calculateTotalPrice(transaction.DetailedTransactions);
|
||||||
|
}, 0);
|
||||||
|
};
|
||||||
|
|
||||||
const handleConfirm = async (transactionId) => {
|
const handleConfirm = async (transactionId) => {
|
||||||
setIsPaymentLoading(true);
|
setIsPaymentLoading(true);
|
||||||
try {
|
try {
|
||||||
@@ -132,7 +146,8 @@ export default function Transactions({ shopId, propsShopId, sendParam, deviceTyp
|
|||||||
return (
|
return (
|
||||||
<div className={styles.Transactions}>
|
<div className={styles.Transactions}>
|
||||||
<div style={{ marginTop: "30px" }}></div>
|
<div style={{ marginTop: "30px" }}></div>
|
||||||
<h2 className={styles["Transactions-title"]}>Daftar transaksi</h2>
|
<h2 className={styles["Transactions-title"]}>Daftar transaksi
|
||||||
|
Rp {calculateAllTransactionsTotal(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} style={{ padding: '0 20px 0 20px' }}>
|
<div className={styles.TransactionListContainer} style={{ padding: '0 20px 0 20px' }}>
|
||||||
@@ -193,6 +208,7 @@ export default function Transactions({ shopId, propsShopId, sendParam, deviceTyp
|
|||||||
|
|
||||||
<p>Transaction ID: {transaction.transactionId}</p>
|
<p>Transaction ID: {transaction.transactionId}</p>
|
||||||
<p>Payment Type: {transaction.payment_type}</p>
|
<p>Payment Type: {transaction.payment_type}</p>
|
||||||
|
<p>{dayjs.utc(transaction.createdAt).tz(shop.timezone).format('YYYY-MM-DD HH:mm:ss')}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles['dotted-line']}>
|
<div className={styles['dotted-line']}>
|
||||||
@@ -214,8 +230,8 @@ export default function Transactions({ shopId, propsShopId, sendParam, deviceTyp
|
|||||||
<ul>
|
<ul>
|
||||||
{transaction.DetailedTransactions.map((detail) => (
|
{transaction.DetailedTransactions.map((detail) => (
|
||||||
<li key={detail.detailedTransactionId}>
|
<li key={detail.detailedTransactionId}>
|
||||||
<span>{detail.Item.name}</span> - {detail.qty} x Rp{" "}
|
<span>{detail.Item.name}</span> - {detail.qty < 1 ? 'tidak tersedia' : `${detail.qty} x Rp
|
||||||
{detail.promoPrice ? detail.promoPrice : detail.price}
|
${detail.promoPrice ? detail.promoPrice : detail.price}`}
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -85,7 +85,7 @@
|
|||||||
font-family: "Plus Jakarta Sans", sans-serif;
|
font-family: "Plus Jakarta Sans", sans-serif;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-size: 15px; /* Adjusted for better readability */
|
font-size: 12px; /* Adjusted for better readability */
|
||||||
padding: 12px 16px; /* 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);
|
||||||
@@ -172,7 +172,7 @@
|
|||||||
.RibbonBanner {
|
.RibbonBanner {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: -31px;
|
||||||
width: 200px;
|
width: 200px;
|
||||||
height: 200px;
|
height: 200px;
|
||||||
left: -18px;
|
left: -18px;
|
||||||
@@ -372,3 +372,27 @@
|
|||||||
background-color: white;
|
background-color: white;
|
||||||
border: 1px solid black;
|
border: 1px solid black;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.itemContainer {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.plusNegative {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
margin: 4px 10px 0px 10px;
|
||||||
|
}
|
||||||
|
.plusNegative2 {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.addNewItem{
|
||||||
|
width: 100%;
|
||||||
|
height: 27px;
|
||||||
|
background-color: rgb(115, 165, 133);
|
||||||
|
border-radius: 11px;
|
||||||
|
text-align: center;
|
||||||
|
color: white;
|
||||||
|
line-height: 27px;
|
||||||
|
}
|
||||||
41
src/pages/Unavailable.js
Normal file
41
src/pages/Unavailable.js
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import React from "react";
|
||||||
|
import styles from "./Transactions.module.css";
|
||||||
|
import { requestNotificationPermission } from '../services/notificationService'; // Import the notification service
|
||||||
|
|
||||||
|
export default function Transaction_pending({ close }) {
|
||||||
|
// const containerStyle = {
|
||||||
|
// display: "flex",
|
||||||
|
// justifyContent: "center",
|
||||||
|
// alignItems: "center",
|
||||||
|
// width: "100%",
|
||||||
|
// height: "100%",
|
||||||
|
// backgroundColor: "",
|
||||||
|
// };
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.Transaction}>
|
||||||
|
<div style={{ textAlign: "center", padding: '10px' }}>
|
||||||
|
|
||||||
|
<h2>Kedai ini sedang tidak tersedia saat ini.</h2>
|
||||||
|
<p style={{ marginTop: "20px", color: "black" }}>
|
||||||
|
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
onClick={close}
|
||||||
|
style={{
|
||||||
|
marginTop: "10px",
|
||||||
|
padding: "10px 20px",
|
||||||
|
fontSize: "16px",
|
||||||
|
cursor: "pointer",
|
||||||
|
backgroundColor: "#4CAF50",
|
||||||
|
color: "#fff",
|
||||||
|
border: "none",
|
||||||
|
borderRadius: "20px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Tutup
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user