This commit is contained in:
zadit
2025-03-15 18:59:44 +07:00
parent 43ad59a1f8
commit a64b999a05
30 changed files with 1818 additions and 391 deletions

2
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,2 @@
{
}

6
package-lock.json generated
View File

@@ -16,6 +16,7 @@
"@testing-library/user-event": "^13.5.0",
"caniuse-lite": "^1.0.30001690",
"crypto-js": "^4.2.0",
"dayjs": "^1.11.13",
"html-to-image": "^1.11.11",
"html2canvas": "^1.4.1",
"jsqr": "^1.4.0",
@@ -7760,6 +7761,11 @@
"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": {
"version": "4.3.5",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz",

View File

@@ -2,7 +2,7 @@
"name": "groovebrew-mockup",
"version": "0.1.0",
"private": true,
"homepage": "https://kedaimaster.com",
"homepage": "https://dev.kedaimaster.com",
"dependencies": {
"@emotion/react": "^11.13.3",
"@emotion/styled": "^11.13.0",
@@ -12,6 +12,7 @@
"@testing-library/user-event": "^13.5.0",
"caniuse-lite": "^1.0.30001690",
"crypto-js": "^4.2.0",
"dayjs": "^1.11.13",
"html-to-image": "^1.11.11",
"html2canvas": "^1.4.1",
"jsqr": "^1.4.0",

View File

@@ -56,6 +56,7 @@ function App() {
const [table, setTable] = useState([]);
const [totalItemsCount, setTotalItemsCount] = useState(0);
const [totalPrice, setTotalPrice] = useState(0);
const [lastTransaction, setLastTransaction] = useState(null);
const [deviceType, setDeviceType] = useState("");
const [shop, setShop] = useState([]);
const [shopItems, setShopItems] = useState([]);
@@ -77,13 +78,23 @@ function App() {
'transaction_failed',
];
useEffect(() => {
const calculateTotalsFromLocalStorage = () => {
const { totalCount, totalPrice } = calculateTotals(shopId);
setTotalItemsCount(totalCount);
setTotalPrice(totalPrice);
// If 'lastTransaction' exists, proceed
const lastTransaction = JSON.parse(localStorage.getItem("lastTransaction"));
console.log(lastTransaction);
if (lastTransaction != null) {
setLastTransaction(lastTransaction);
}
};
useEffect(() => {
calculateTotalsFromLocalStorage();
const handleStorageChange = () => {
@@ -181,13 +192,42 @@ function App() {
console.log("transaction notification");
// Call `setModal` with content and parameters
setModal("transaction_pending", data);
localStorage.setItem('cart', []);
calculateTotalsFromLocalStorage();
});
socket.on("transaction_confirmed", async (data) => {
console.log("transaction notification" + data);
console.log("transaction notification: " + 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) => {
console.log("transaction notification");
setModal("transaction_success", data);
@@ -228,6 +268,7 @@ function App() {
} else {
console.log(data)
setUser(data.data.user);
if(data.data.latestOpenBillTransaction != null) localStorage.setItem('lastTransaction', JSON.stringify(data.data.latestOpenBillTransaction))
if (
data.data.user.password == "unsetunsetunset" &&
localStorage.getItem("settings")
@@ -567,6 +608,7 @@ function App() {
queue={queue}
cartItemsLength={totalItemsCount}
totalPrice={totalPrice}
lastTransaction={lastTransaction}
/>
{/* <Footer
showTable={true}
@@ -607,7 +649,9 @@ function App() {
<>
<Cart
shopId={shopId}
shop={shop}
table={table}
setModal={setModal}
sendParam={handleSetParam}
socket={socket}
totalItemsCount={totalItemsCount}
@@ -649,6 +693,7 @@ function App() {
element={
<>
<Transactions
shop={shop}
shopId={shopId}
sendParam={handleSetParam}
deviceType={deviceType}
@@ -672,6 +717,7 @@ function App() {
<Modal
user={user}
shop={shop}
deviceType={deviceType}
isOpen={isModalOpen}
modalContent={modalContent}
handleMoveToTransaction={handleMoveToTransaction}

View File

@@ -108,7 +108,7 @@
padding-top: 10px;
background-color: transparent;
color: transparent;
width: 21.7%;
width: 178.89px;
}
.downloadQR.active{

View File

@@ -5,6 +5,7 @@ import { useNavigationHelpers } from "../helpers/navigationHelpers";
import Switch from "react-switch";
const HeaderBar = styled.div`
pointer-events: auto;
margin-top:${(props) => (props.HeaderMargin)};
display: flex;
justify-content: space-between;
@@ -306,7 +307,7 @@ const Header = ({
return `${cafeName}'s menu`;
};
return (
<HeaderBar HeaderMargin={HeaderMargin}>
<HeaderBar HeaderMargin={HeaderMargin} >
<Title HeaderSize={HeaderSize}>
{shopName == null
? HeaderText == null

View File

@@ -8,6 +8,7 @@ import CreateTenant from "../pages/CreateTenant"
import TablesPage from "./TablesPage.js";
import PaymentOptions from "./PaymentOptions.js";
import Transaction from "../pages/Transaction";
import Transaction_item from "../pages/Transaction_item";
import Transaction_pending from "../pages/Transaction_pending";
import Transaction_confirmed from "../pages/Transaction_confirmed";
import Transaction_success from "../pages/Transaction_success";
@@ -18,6 +19,7 @@ import MaterialList from "../pages/MaterialList.js";
import MaterialMutationsPage from "../pages/MaterialMutationsPage.js";
import Reports from "../pages/Reports.js";
import NotificationRequest from "../pages/NotificationRequest.js";
import Unavailable from "../pages/Unavailable.js";
import NotificationBlocked from "../pages/NotificationBlocked.js";
import WelcomePageEditor from "../pages/WelcomePageEditor.js";
import GuidePage from "../pages/GuidePage";
@@ -32,7 +34,7 @@ import CreateCoupon from "../pages/CreateCoupon";
import CheckCoupon from "../pages/CheckCoupon";
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 [updateKey, setUpdateKey] = useState(0);
@@ -74,6 +76,7 @@ const Modal = ({ user, shop, isOpen, onClose, modalContent, setModal, handleMove
{modalContent === "edit_account" && <AccountUpdatePage user={user} />}
{modalContent === "reset-password" && <ResetPassword />}
{modalContent === "req_notification" && <NotificationRequest setModal={setModal} />}
{modalContent === "unavailable" && <Unavailable close={handleContentClick} />}
{modalContent === "blocked_notification" && <NotificationBlocked />}
{modalContent === "create_clerk" && <CreateClerk 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" && (
<Transaction propsShopId={shop.cafeId} />
)}
{modalContent === "transaction_pending" && <Transaction_pending />}
{modalContent === "transaction_pending" && <Transaction_pending deviceType={deviceType}/>}
{modalContent === "transaction_item" && <Transaction_item />}
{modalContent === "transaction_confirmed" && (
<Transaction_confirmed paymentUrl={shop.qrPayment} />
<Transaction_confirmed deviceType={deviceType} paymentUrl={shop.qrPayment} />
)}
{modalContent === "payment_claimed" && (
<Payment_claimed paymentUrl={shop.qrPayment} />

View File

@@ -5,6 +5,7 @@ import {
getCafe,
saveCafeDetails,
setConfirmationStatus,
setOpenBillAvailability
} from "../helpers/cafeHelpers";
import Switch from "react-switch"; // Import the Switch component
@@ -15,10 +16,13 @@ const SetPaymentQr = ({ shopId,
const [qrPayment, setQrPayment] = useState();
const [qrCodeDetected, setQrCodeDetected] = useState(false);
const [isNeedConfirmationState, setIsNeedConfirmationState] = useState(0);
const [isQRISavailable, setIsQRISavailable] = useState(0);
const qrPaymentInputRef = useRef(null);
const qrCodeContainerRef = useRef(null);
const [cafe, setCafe] = useState({});
const [isConfig, setIsConfig] = useState(false);
const [isConfigQRIS, setIsConfigQRIS] = useState(false);
const [isOpenBillAvailable, setIsOpenBillAvailable] = useState(false);
useEffect(() => {
const fetchCafe = async () => {
@@ -29,6 +33,8 @@ const SetPaymentQr = ({ shopId,
console.log(response.needsConfirmation);
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]);
setQrSize(response.scale);
} catch (error) {
@@ -40,10 +46,10 @@ const SetPaymentQr = ({ shopId,
// Detect QR code when qrPayment updates
useEffect(() => {
if (qrPayment) {
if (qrPayment && isConfigQRIS) {
detectQRCodeFromContainer();
}
}, [qrPayment]);
}, [qrPayment, isConfigQRIS]);
// Handle file input change
const handleFileChange = (e) => {
@@ -77,41 +83,42 @@ const SetPaymentQr = ({ shopId,
// Save cafe details
const handleSave = async () => {
const qrPaymentFile = qrPaymentInputRef.current.files[0];
let qrPaymentFile;
if(qrPaymentInputRef?.current?.files[0])
qrPaymentFile = qrPaymentInputRef.current.files[0];
const details = {
qrPosition,
qrSize,
qrPaymentFile,
isQRISavailable: isQRISavailable === 1,
isOpenBillAvailable: isOpenBillAvailable === 1,
isNeedConfirmationState: isNeedConfirmationState === 1
};
try {
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);
} catch (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 (
<div style={styles.container}>
<h3 style={styles.title}>Konfigurasi pembayaran</h3>
<div style={styles.switchContainer}>
<p style={styles.uploadMessage}>
Pembayaran QRIS.
</p>
{isConfigQRIS ?
<>
<div
id="qr-code-container"
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}>Unggah</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}>
<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}>
Nyalakan agar kasir memeriksa kembali ketersediaan produk sebelum pelanggan membayar.
</p>
<Switch
onChange={handleChange}
onChange={(checked) => setIsNeedConfirmationState(checked ? 1 : 0)}
checked={isNeedConfirmationState === 1} // Convert to boolean
offColor="#888"
onColor="#4CAF50"
@@ -169,6 +238,11 @@ const SetPaymentQr = ({ shopId,
// Styles
const styles = {
container: {
position: 'relative',
overflowY: 'auto',
overflowX: 'hidden',
maxHeight: '80vh',
width: '100%',
backgroundColor: "white",
padding: "20px",
borderRadius: "8px",
@@ -188,14 +262,24 @@ const styles = {
backgroundSize: "contain",
overflow: "hidden",
margin: "0 auto", // Center the QR code container
marginTop: '10px'
},
uploadMessage: {
fontWeight: 600,
textAlign: "left",
},
qrisConfigButton: {
borderRadius: '15px',
backgroundColor: '#28a745',
width: '144px',
textAlign: 'center',
color: 'white',
lineHeight: '24px',
marginLeft: '14px',
},
uploadButton: {
paddingRight: '10px',
backgroundColor: 'green',
backgroundColor: '#28a745',
borderRadius: '30px',
color: 'white',
fontWeight: 700,

View File

@@ -87,16 +87,15 @@ const SetPaymentQr = ({ shop }) => {
fontsize,
fontcolor,
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
saveCafeDetails(shop.cafeId, details)
.then((response) => {
console.log("Cafe details saved:", response);
if (shop.cafeIdentifyName != cafeIdentifyNameUpdate) {
window.location.href = `/${cafeIdentifyNameUpdate}?modal=edit_tables`;
}
})
.catch((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={{ height: '27.4px', fontSize: '17.6px', fontFamily: 'monospace', fontWeight: '500', color: 'rgba(88, 55, 50, 1)' }}>
<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' />
</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
id="qr-code-container"
style={{
@@ -835,20 +822,20 @@ const SetPaymentQr = ({ shop }) => {
{!isViewingQR ?
<>
<div style={styles.uploadMessage}>
<p>QR identifikasi</p>
<p>QR sticker kedai dan meja</p>
<p>Background</p>
</div>
<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
onClick={() => qrBackgroundInputRef.current.click()} style={styles.changeButton}>Ganti</div>
</div>
<div style={styles.switchContainer}>
{isConfig &&
<div style={{ marginLeft: '15px' }}>
{/* <p style={styles.description} onClick={() => { setIsConfigQR(!isConfigQR); setIsConfigFont(false) }}>
{isConfigQR ? '˅' : '˃'} Konfigurasi QR
</p> */}
<p style={styles.description} onClick={() => { setIsConfigQR(!isConfigQR); setIsConfigFont(false) }}>
{isConfigQR ? '˅' : '˃'} QR
</p>
{isConfigQR && <>
<div style={styles.sliderContainer}>
<label style={styles.label}>
@@ -908,9 +895,9 @@ const SetPaymentQr = ({ shop }) => {
</div>
</>}
{/* <p style={styles.description} onClick={() => { setIsConfigFont(!isConfigFont); setIsConfigQR(false) }}>
{isConfigFont ? '˅' : '˃'} Konfigurasi nomor
</p> */}
<p style={styles.description} onClick={() => { setIsConfigFont(!isConfigFont); setIsConfigQR(false) }}>
{isConfigFont ? '˅' : '˃'} Nomor meja
</p>
{isConfigFont && (
<>
<div style={styles.sliderContainer}>
@@ -989,9 +976,9 @@ const SetPaymentQr = ({ shop }) => {
</div>
}
{/* <p style={styles.description} onClick={() => setIsViewTables(!isViewTables)}>
<p style={styles.description} onClick={() => setIsViewTables(!isViewTables)}>
{isViewTables ? '˅' : '˃'} Daftar meja
</p> */}
</p>
{isViewTables &&
<div style={{ marginTop: '34px', marginLeft: '15px' }}>

View File

@@ -1,5 +1,5 @@
// 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;

View File

@@ -23,6 +23,27 @@ export async function getCafe(cafeId) {
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
export const saveWelcomePageConfig = async (cafeId, details) => {
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
export async function saveCafeDetails(cafeId, details) {
try {
@@ -221,8 +268,12 @@ export async function saveCafeDetails(cafeId, details) {
if (details.qrSize) formData.append("scale", details.qrSize);
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}`, {
method: "PUT",
headers: {

View File

@@ -2,7 +2,7 @@ import API_BASE_URL from "../config.js";
import { getLocalStorage, updateLocalStorage } from "./localStorageHelpers";
import { getItemsByCafeId } from "../helpers/cartHelpers.js";
export async function confirmTransaction(transactionId) {
export async function confirmTransaction(transactionId, notAcceptedItems) {
try {
const token = getLocalStorage("auth");
const response = await fetch(
@@ -13,6 +13,9 @@ export async function confirmTransaction(transactionId) {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({
notAcceptedItems
}),
}
);
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 getAuthToken() {
return localStorage.getItem("auth");
@@ -383,13 +479,6 @@ const getHeaders = (method = "GET") => {
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) => {
const response = await fetch(
@@ -417,12 +506,3 @@ export const getAnalytics = async (filter, selectedCafeId) => {
const response = await fetch(url, getHeaders('POST'));
return response.json();
};
export const getIncome = async (cafeId) => {
const response = await fetch(
`${API_BASE_URL}/transaction/get-income/${cafeId}`,
getHeaders()
);
return response.json();
};

View File

@@ -6,9 +6,9 @@ import reportWebVitals from './reportWebVitals';
const root = ReactDOM.createRoot(document.getElementById('root'));
// Disable console methods
console.log = () => {};
console.warn = () => {};
console.error = () => {};
// console.log = () => {};
// console.warn = () => {};
// console.error = () => {};
root.render(
<React.StrictMode>

View File

@@ -43,7 +43,8 @@ function CafePage({
loading,
queue,
cartItemsLength,
totalPrice
totalPrice,
lastTransaction
}) {
@@ -64,6 +65,7 @@ function CafePage({
const [screenMessage, setScreenMessage] = useState("");
const [isSpotifyNeedLogin, setNeedSpotifyLogin] = useState(true);
const [isExceededDeadline, setIsExceededDeadline] = useState(false);
const [isEditMode, setIsEditMode] = useState(false);
const [filterId, setFilterId] = useState(0);
@@ -135,8 +137,10 @@ function CafePage({
useEffect(() => {
async function fetchData() {
socket.on("joined-room", (response) => {
const { isSpotifyNeedLogin } = response;
const { isSpotifyNeedLogin, isExceededDeadline } = response;
setNeedSpotifyLogin(isSpotifyNeedLogin);
if (isExceededDeadline) setModal('unavailable');
setIsExceededDeadline(isExceededDeadline);
});
}
@@ -206,7 +210,7 @@ function CafePage({
/>
) : (
welcomePageConfig != null && (
<div className="Cafe">
<div className="Cafe" style={{ filter: isExceededDeadline ? 'grayscale(1)' : '', pointerEvents: isExceededDeadline ? 'none' : '' }}>
<body className="App-header">
<Header
HeaderText={"Menu"}
@@ -296,11 +300,15 @@ function CafePage({
))}
{!isEditMode && (user.username || cartItemsLength > 0) &&
<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 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' }}>
{((lastTransaction == null || lastTransaction?.payment_type != 'paylater')) ?
<span style={{ whiteSpace: 'nowrap' }}>Rp{totalPrice}</span>
:
<span style={{ whiteSpace: 'nowrap' }}>Open bill</span>
}
<div style={{ display: 'flex', alignItems: 'center', marginLeft: '5px', width: '20px' }}>
<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>
@@ -310,7 +318,7 @@ function CafePage({
</div>
}
{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', marginLeft: '5px', width: '20px' }}>
<svg viewBox="0 0 512 512">

View File

@@ -4,20 +4,26 @@ import { useParams } from "react-router-dom"; // Changed from useSearchParams to
import { ThreeDots, ColorRing } from "react-loader-spinner";
import ItemLister from "../components/ItemLister";
import { getCartDetails } from "../helpers/itemHelper";
import { getPaymentMethods } from "../helpers/cafeHelpers.js";
import {
handlePaymentFromClerk,
handlePaymentFromGuestSide,
handlePaymentFromGuestDevice,
handleExtendFromGuestDevice,
handleCloseBillFromGuestDevice
} from "../helpers/transactionHelpers";
import { getItemsByCafeId } from "../helpers/cartHelpers.js";
import Dropdown from "./Dropdown.js";
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();
sendParam({ shopIdentifier, tableCode });
@@ -37,6 +43,28 @@ export default function Invoice({ shopId, table, sendParam, deviceType, socket,
const [email, setEmail] = useState("");
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(() => {
const fetchCartItems = async () => {
@@ -166,7 +194,7 @@ export default function Invoice({ shopId, table, sendParam, deviceType, socket,
// Update the price in the local storage item
return {
...item,
price: updatedItem.promoPrice?updatedItem.promoPrice:updatedItem.price,
price: updatedItem.promoPrice ? updatedItem.promoPrice : updatedItem.price,
availability: updatedItem.availability
};
}
@@ -203,7 +231,7 @@ export default function Invoice({ shopId, table, sendParam, deviceType, socket,
return (
total +
itemType.itemList.reduce((subtotal, item) => {
return subtotal + item.qty * (item.promoPrice ? item.promoPrice:item.price);
return subtotal + item.qty * (item.promoPrice ? item.promoPrice : item.price);
}, 0)
);
}, 0);
@@ -218,14 +246,38 @@ export default function Invoice({ shopId, table, sendParam, deviceType, socket,
getNewestCartItems();
}, [shopId]);
const handlePay = async (isCash) => {
const handlePayCloseBill = async (orderMethod) =>{
setIsPaymentLoading(true);
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") {
const pay = await handlePaymentFromClerk(
shopId,
email,
isCash ? "cash" : "cashless",
orderMethod,
orderType,
tableNumber
);
@@ -233,7 +285,7 @@ export default function Invoice({ shopId, table, sendParam, deviceType, socket,
const pay = await handlePaymentFromGuestSide(
shopId,
email,
isCash ? "cash" : "cashless",
orderMethod,
orderType,
tableNumber
);
@@ -241,7 +293,7 @@ export default function Invoice({ shopId, table, sendParam, deviceType, socket,
const socketId = socket.id;
const pay = await handlePaymentFromGuestDevice(
shopId,
isCash ? "cash" : "cashless",
orderMethod,
orderType,
table.tableNo || tableNumber,
textareaRef.current.value,
@@ -278,7 +330,7 @@ export default function Invoice({ shopId, table, sendParam, deviceType, socket,
console.log(localStorage.getItem('cart'))
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
const localStorageCart = JSON.parse(localStorage.getItem('cart'));
@@ -322,12 +374,15 @@ export default function Invoice({ shopId, table, sendParam, deviceType, socket,
const handleEmailChange = (event) => {
setEmail(event.target.value);
};
const transactionData = JSON.parse(localStorage.getItem('lastTransaction'));
return (
<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>
{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={{ width: '50%' }}>
<svg
@@ -342,6 +397,7 @@ export default function Invoice({ shopId, table, sendParam, deviceType, socket,
:
(isLoading ? <></> :
<>
{getItemsByCafeId(shopId).length > 0 &&
<div className={styles.RoundedRectangle}>
{cartItems.map((itemType) => (
<ItemLister
@@ -369,6 +425,7 @@ export default function Invoice({ shopId, table, sendParam, deviceType, socket,
</select> */}
</div>
)}
{orderType === "serve" && table.length < 1 && (
<div className={styles.OrderTypeContainer}>
<span htmlFor="orderType">Serve to:</span>
@@ -395,35 +452,120 @@ export default function Invoice({ shopId, table, sendParam, deviceType, socket,
/>
</div>
</div>
<div className={styles.PaymentOption}>
<div className={styles.TotalContainer}>
<span>Pembayaran</span>
<span>
<select
style={{ borderRadius: '6px', fontSize: '20px' }}
id="orderMethod"
value={orderMethod}
onChange={handleOrderMethodChange}
>
<option value="cash">&nbsp;Tunai</option>
<option value="cashless">&nbsp;Non Tunai&nbsp;</option>
</select>
</span>
}
{transactionData &&
<div className={styles.RoundedRectangle} style={{ backgroundColor: '#c3c3c3', fontSize: '15px', display: 'flex', justifyContent: 'space-between' }}>
{transactionData.payment_type != 'paylater' ?
<>
<div onClick={() => setModal('transaction_item', { transactionId: transactionData.transactionId })} className={styles.AddedLastTransaction}>
Pesanan akan ditambahkan ke transaksi sebelumnya
</div>
<div style={{ display: 'flex', paddingLeft: '25px', paddingRight: '25px' }}>
<button className={styles.PayButton} onClick={() => handlePay(orderMethod == 'cash' ? true : false)}>
<div className={styles.CancelAddedLastTransaction} onClick={() => { window.location.reload(); localStorage.removeItem('lastTransaction') }}>
<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 ? (
<ColorRing height="50" width="50" color="white" />
) : (
<div>
{transactionData ?
<span>Tambahkan</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>
</div>
)}
</button>
</div>
<div onClick={goToShop} style={{ textAlign: 'center', marginBottom: '20px' }}>Kembali</div>
}
</div>
<div className={styles.PaymentOptionMargin}></div>
</>

View File

@@ -46,7 +46,7 @@ const CreateCouponPage = () => {
let encodedCouponCode = encodeURIComponent(encryptedCouponCode);
// 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
setCouponUrl(urlWithCoupon);

162
src/pages/Dropdown.js Normal file
View 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;

View 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;
}

View File

@@ -32,7 +32,7 @@
}
.PaymentOption {
overflow-x: hidden;
overflow: visible;
background-color: white;
display: flex;
flex-direction: column;
@@ -72,7 +72,7 @@
font-size: 1.3em;
padding: 10px 0;
padding-top: 20px;
margin-bottom: 17px;
/* margin-bottom: 17px; */
}
.PayButton {
@@ -98,7 +98,30 @@
padding-left: 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 {
text-align: center;
color: rgba(88, 55, 50, 1);
@@ -178,3 +201,23 @@
resize: none; /* Prevent resizing */
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);
}

View File

@@ -121,8 +121,8 @@ export default function Transactions({ propsShopId, sendParam, deviceType }) {
<ul>
{transaction.DetailedTransactions.map((detail) => (
<li key={detail.detailedTransactionId}>
<span>{detail.Item.name}</span> - {detail.qty} x Rp{" "}
{detail.promoPrice ? detail.promoPrice : detail.price}
<span>{detail.Item.name}</span> - {detail.qty < 1 ? 'tidak tersedia' : `${detail.qty} x Rp
${detail.promoPrice ? detail.promoPrice : detail.price}`}
</li>
))}
</ul>

View File

@@ -144,6 +144,10 @@ const App = ({ forCafe = true, cafeId = -1,
}
};
useEffect(() => {
setCouponList(coupons)
}, [coupons]);
useEffect(() => {
setSelectedCafeId(cafeId)
}, [cafeId]);

View File

@@ -20,8 +20,11 @@ export default function Transactions({ propsShopId, sendParam, deviceType, handl
const [isPaymentLoading, setIsPaymentLoading] = useState(false);
const [searchParams] = useSearchParams();
const [transaction, setTransaction] = useState(null);
const [notAcceptedItems, setNotAcceptedItems] = useState([]);
const noteRef = useRef(null);
const [transactionRefreshKey, setTransactionRefreshKey] = useState(0);
useEffect(() => {
const transactionId = searchParams.get("transactionId") || "";
@@ -60,9 +63,11 @@ export default function Transactions({ propsShopId, sendParam, deviceType, handl
if (isPaymentLoading) return;
setIsPaymentLoading(true);
try {
const c = await confirmTransaction(transactionId);
const c = await confirmTransaction(transactionId, notAcceptedItems);
if (c) {
setTransaction({ ...transaction, confirmed: c.confirmed });
console.log(c)
setTransaction(c);
setTransactionRefreshKey(transactionRefreshKey+1);
}
} catch (error) {
console.error("Error processing payment:", error);
@@ -76,6 +81,11 @@ export default function Transactions({ propsShopId, sendParam, deviceType, handl
setIsPaymentLoading(true);
try {
const c = await declineTransaction(transactionId);
if (c) {
console.log(c)
setTransaction({ ...transaction, confirmed: c.confirmed });
setTransactionRefreshKey(transactionRefreshKey+1);
}
// if (c) {
// // Update the confirmed status locally
// setTransactions((prevTransactions) =>
@@ -107,7 +117,7 @@ export default function Transactions({ propsShopId, sendParam, deviceType, handl
}, [transaction?.notes]);
return (
<div className={styles.Transaction}>
<div key={transactionRefreshKey} className={styles.Transaction}>
<div className={styles.TransactionListContainer}>
{transaction && (
@@ -120,15 +130,17 @@ export default function Transactions({ propsShopId, sendParam, deviceType, handl
>
<div className={styles['receipt-header']}>
<img
src={shopImg} // Placeholder image URL
alt='logo'
className={styles['receipt-logo']}
/>
<div className={styles['receipt-info']}>
<h3>Receipt Information</h3>
<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>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>
@@ -171,14 +183,52 @@ export default function Transactions({ propsShopId, sendParam, deviceType, handl
</div>
</div>
<div className={styles.RibbonBanner}></div>
<ul>
<div>
{transaction.DetailedTransactions.map((detail) => (
<li key={detail.detailedTransactionId}>
<span>{detail.Item.name}</span> - {detail.qty} x Rp{" "}
{detail.promoPrice ? detail.promoPrice : detail.price}
</li>
<div className={styles['itemContainer']} key={detail.detailedTransactionId}>
<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' }}
onClick={() =>
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"]}>
{transaction.serving_type === "pickup"
? "Ambil sendiri"

View File

@@ -37,6 +37,7 @@ export default function Transactions({
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) {
@@ -135,9 +136,36 @@ export default function Transactions({
>
<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']} />
}
<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>Payment Type: {transaction.payment_type}</p>
</div>
@@ -151,30 +179,32 @@ export default function Transactions({
</div>
</div>
<h2 className={styles["Transactions-detail"]}>
Transaction ID: {transaction.transactionId}
</h2>
<h2 className={styles["Transactions-detail"]}>
Payment Type: {transaction.payment_type}
</h2>
<ul>
{(isPaymentOpen
? transaction.DetailedTransactions.slice(0, 2)
: transaction.DetailedTransactions
).map((detail) => (
<li key={detail.detailedTransactionId}>
<span>{detail.Item.name}</span> - {detail.qty} x Rp{" "}
{detail.promoPrice ? detail.promoPrice : detail.price}
<span>{detail.Item.name}</span> - {detail.qty < 1 ? 'tidak tersedia' : `${detail.qty} x Rp
${detail.promoPrice ? detail.promoPrice : detail.price}`}
</li>
))}
</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"]}>
{transaction.serving_type === "pickup"
? "Self pickup"
: `Serve to ${transaction.Table ? transaction.Table.tableNo : "N/A"
? "Ambil sendiri"
: `Diantar ke ${transaction.Table ? transaction.Table.tableNo : "N/A"
}`}
</h2>
{(transaction.notes !== "") && (
<>
<div
@@ -202,6 +232,9 @@ export default function Transactions({
</div>
</>
)}
{transaction.payment_type != 'paylater' ?
<>
<div className={styles.TotalContainer}>
<span>Total:</span>
<span>
@@ -209,30 +242,55 @@ export default function Transactions({
</span>
</div>
<div className={styles.PaymentContainer}>
{transaction.payment_type != 'cash' && transaction.payment_type != 'paylater/cash' &&
<ButtonWithReplica
paymentUrl={paymentUrl}
price={
"Rp" + calculateTotalPrice(transaction.DetailedTransactions)
}
disabled={transaction.payment_type == 'cash' || isPaymentLoading}
disabled={isPaymentLoading}
isPaymentLoading={isPaymentLoading}
handleClick={() => handleConfirm(transaction.transactionId)}
Open={() => setIsPaymentOpen(true)}
isPaymentOpen={isPaymentOpen}
>
{transaction.payment_type == 'cash' || isPaymentLoading ? (
(transaction.payment_type == 'cash' && 'Bayar nanti')
) : isPaymentOpen ? (
(transaction.paymentClaimed ? <>
<div style={{position: 'absolute', left: '15px', top: '9px'}}>
<ColorRing
height="20"
width="20" className={styles['receipt-logo']} /></div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Menunggu konfirmasi</> : "Klaim telah bayar") // Display "Confirm has paid" if the transaction is confirmed (1)
) : (
"Tampilkan QR" // Display "Confirm availability" if the transaction is not confirmed (0)
)}
{
isPaymentLoading ? null : isPaymentOpen ? (
transaction.paymentClaimed ? (
<>
<div style={{ position: 'absolute', left: '15px', top: '9px' }}>
<ColorRing height="20" width="20" className={styles['receipt-logo']} />
</div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Menunggu konfirmasi
</>
) : 'Klaim telah bayar'
) : 'Tampilkan QR'
}
</ButtonWithReplica>
}
</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
className={`${styles.DeclineButton}`}
onClick={() =>
@@ -243,6 +301,17 @@ export default function Transactions({
>
{isPaymentOpen ? "kembali" : "batalkan"}
</h5>
)
}
</>
:
<h5
className={`${styles.DeclineButton}`}
>
Open bill disetujui, silahkan lanjutkan pemesanan anda
</h5>
}
</div>
)}
</div>

View File

@@ -1,28 +1,240 @@
import React from "react";
import { ColorRing } from "react-loader-spinner";
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 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() {
const containerStyle = {
display: "flex",
justifyContent: "center",
alignItems: "center",
width: "100%",
height: "100%", // This makes the container stretch to the bottom of the viewport
backgroundColor: "#000", // Optional: Set a background color if you want to see the color ring clearly
export default function Transactions({
propsShopId,
sendParam,
deviceType,
paymentUrl,
}) {
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);
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 (
<div className={styles.Transaction}>
<div className={containerStyle}>
<div style={{ marginTop: "30px", textAlign: "center" }}>
<h2>pesananmu selesai diproses nih</h2>
<img
className={styles.expression}
src="https://i.imgur.com/nTokiWH.png"
alt="Success"
<div className={styles.TransactionListContainer} style={{ overflow: 'hidden' }}>
{transaction && (
<div
key={transaction.transactionId}
className={styles.RoundedRectangle}
onClick={() =>
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>
</>
)}
{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>
);

View 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>
);
}

View File

@@ -118,6 +118,7 @@ export default function Transactions({ propsShopId, sendParam, deviceType, handl
}
}, [transaction?.notes]);
return (
<div className={styles.Transaction}>
@@ -153,13 +154,27 @@ export default function Transactions({ propsShopId, sendParam, deviceType, handl
</div>
<div className={styles.RibbonBanner}></div>
<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}>
<span>{detail.Item.name}</span> - {detail.qty} x Rp{" "}
{detail.promoPrice ? detail.promoPrice : detail.Item.price}
<span>{detail.Item.name}</span> - {detail.qty < 1 ? 'tidak tersedia' : `${detail.qty} x Rp
${detail.promoPrice ? detail.promoPrice : detail.price}`}
</li>
</>
))}
</ul>
<div
onClick={() => {
localStorage.setItem('lastTransaction', JSON.stringify(transaction));
}} className={styles["addNewItem"]}>Tambah pesanan</div>
<h2 className={styles["Transactions-detail"]}>
{transaction.serving_type === "pickup"
? "Ambil sendiri"
@@ -189,6 +204,7 @@ export default function Transactions({ propsShopId, sendParam, deviceType, handl
Rp {calculateTotalPrice(transaction.DetailedTransactions)}
</span>
</div>
{transaction.payment_type != 'paylater' &&
<div className={styles.TotalContainer}>
<button
className={styles.PayButton}
@@ -201,21 +217,16 @@ export default function Transactions({ propsShopId, sendParam, deviceType, handl
>
{isPaymentLoading ? (
<ColorRing height="50" width="50" color="white" />
) : transaction.confirmed === 1 ? (
"Konfirmasi telah bayar" // Display "Confirm has paid" if the transaction is confirmed (1)
) : transaction.confirmed === -1 ? (
"Ditolak" // Display "Declined" if the transaction is declined (-1)
) : transaction.confirmed === -2 ? (
"Dibatalkan" // Display "Declined" if the transaction is declined (-1)
) : transaction.confirmed === 2 ? (
"Konfirmasi pesanan siap" // Display "Item ready" if the transaction is ready (2)
) : transaction.confirmed === 3 ? (
"Transaction success" // Display "Item ready" if the transaction is ready (2)
) : (
"Batalkan" // Display "Confirm availability" if the transaction is not confirmed (0)
)}
</button>
</div>
}
</div>
)}
</div>

View File

@@ -1,56 +1,240 @@
import React from "react";
import React, { useRef, useEffect, useState } from "react";
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 }) {
// const containerStyle = {
// display: "flex",
// justifyContent: "center",
// alignItems: "center",
// width: "100%",
// height: "100%",
// backgroundColor: "",
// };
export default function Transactions({
propsShopId,
sendParam,
deviceType,
paymentUrl,
}) {
const { shopId, tableId } = useParams();
if (sendParam) sendParam({ shopId, tableId });
const handleNotificationClick = async () => {
const permission = await requestNotificationPermission();
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);
if (permission === "granted") {
console.log("Notification permission granted.");
// Set up notifications or show a success modal
} else if (permission === "denied") {
console.error("Notification permission denied.");
setModal('blocked_notification'); // Show modal for blocked notifications
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 (
<div className={styles.Transaction}>
<div style={{ marginTop: "30px", textAlign: "center" }}>
<h2>Transaction Success</h2>
<img
className={styles.expression}
src="https://i.imgur.com/sgvMI02.png"
alt="Success"
/>
<p style={{ marginTop: "20px", color: "white" }}>
Do you want to get notifications when your item is ready?
</p>
<button
onClick={handleNotificationClick}
<div className={styles.TransactionListContainer} style={{ overflow: 'hidden' }}>
{transaction && (
<div
key={transaction.transactionId}
className={styles.RoundedRectangle}
onClick={() =>
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 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={{
marginTop: "10px",
padding: "10px 20px",
fontSize: "16px",
cursor: "pointer",
backgroundColor: "#4CAF50",
color: "#fff",
border: "none",
borderRadius: "5px",
visibility: transaction.notes == "" ? "hidden" : "visible",
}}
>
yes
</button>
<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>
</>
)}
{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>
);

View File

@@ -11,10 +11,18 @@ import {
import { getTables } from "../helpers/tableHelper";
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();
if (sendParam) sendParam({ shopIdentifier, tableId });
dayjs.extend(utc);
dayjs.extend(timezone);
const [tables, setTables] = useState([]);
const [selectedTable, setSelectedTable] = useState(null);
const [transactions, setTransactions] = useState([]);
@@ -87,6 +95,12 @@ export default function Transactions({ shopId, propsShopId, sendParam, deviceTyp
}, 0);
};
const calculateAllTransactionsTotal = (transactions) => {
return transactions.reduce((grandTotal, transaction) => {
return grandTotal + calculateTotalPrice(transaction.DetailedTransactions);
}, 0);
};
const handleConfirm = async (transactionId) => {
setIsPaymentLoading(true);
try {
@@ -132,7 +146,8 @@ export default function Transactions({ shopId, propsShopId, sendParam, deviceTyp
return (
<div className={styles.Transactions}>
<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>
{/* <TableCanvas tables={tables} selectedTable={selectedTable} /> */}
<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>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 className={styles['dotted-line']}>
@@ -214,8 +230,8 @@ export default function Transactions({ shopId, propsShopId, sendParam, deviceTyp
<ul>
{transaction.DetailedTransactions.map((detail) => (
<li key={detail.detailedTransactionId}>
<span>{detail.Item.name}</span> - {detail.qty} x Rp{" "}
{detail.promoPrice ? detail.promoPrice : detail.price}
<span>{detail.Item.name}</span> - {detail.qty < 1 ? 'tidak tersedia' : `${detail.qty} x Rp
${detail.promoPrice ? detail.promoPrice : detail.price}`}
</li>
))}
</ul>

View File

@@ -85,7 +85,7 @@
font-family: "Plus Jakarta Sans", sans-serif;
font-weight: 500;
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 */
border-radius: 50px;
background-color: rgba(88, 55, 50, 1);
@@ -172,7 +172,7 @@
.RibbonBanner {
pointer-events: none;
position: absolute;
top: 0;
top: -31px;
width: 200px;
height: 200px;
left: -18px;
@@ -372,3 +372,27 @@
background-color: white;
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
View 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>
);
}