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

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' }}>
<span style={{ whiteSpace: 'nowrap' }}>Rp{totalPrice}</span>
{((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 () => {
@@ -115,7 +143,7 @@ export default function Invoice({ shopId, table, sendParam, deviceType, socket,
try {
// Fetch items from the cart details (latest state)
const items = await getCartDetails(shopId);
// Loop through each item type in the items from the cart details
items.forEach(itemType => {
itemType.itemList.forEach(item => {
@@ -132,10 +160,10 @@ export default function Invoice({ shopId, table, sendParam, deviceType, socket,
});
});
});
// After updating shopItems, set the new state
setShopItems(shopItems);
// Filter out unavailable items
const filteredItems = items
.map((itemType) => ({
@@ -143,13 +171,13 @@ export default function Invoice({ shopId, table, sendParam, deviceType, socket,
itemList: itemType.itemList.filter((item) => item.availability),
}))
.filter((itemType) => itemType.itemList.length > 0); // Remove empty itemTypes
setIsLoading(true);
setTimeout(function () {
setCartItems(filteredItems);
setIsLoading(false);
}, 100); // delay is in milliseconds
// Update local storage by removing unavailable items and updating prices
const updatedLocalStorage = JSON.parse(localStorage.getItem("cart")) || [];
const newLocalStorage = updatedLocalStorage.map((cafe) => {
@@ -161,16 +189,16 @@ export default function Invoice({ shopId, table, sendParam, deviceType, socket,
const updatedItem = filteredItems
.flatMap((itemType) => itemType.itemList)
.find((filtered) => filtered.itemId === item.itemId);
if (updatedItem) {
// 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
};
}
// If no updated item found, return the original item
return item;
}),
@@ -178,7 +206,7 @@ export default function Invoice({ shopId, table, sendParam, deviceType, socket,
}
return cafe;
});
const newLocalStoragee = newLocalStorage.map((cafe) => {
if (cafe.cafeId === shopId) {
return {
@@ -195,15 +223,15 @@ export default function Invoice({ shopId, table, sendParam, deviceType, socket,
return cafe;
});
localStorage.setItem("cart", JSON.stringify(newLocalStoragee));
window.dispatchEvent(new Event("localStorageUpdated"));
// Calculate total price based on filtered cart items
const totalPrice = filteredItems.reduce((total, itemType) => {
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);
@@ -212,42 +240,66 @@ export default function Invoice({ shopId, table, sendParam, deviceType, socket,
console.error("Error fetching cart items:", error);
// Handle error if needed
}
};
getNewestCartItems();
}, [shopId]);
const handlePay = async (isCash) => {
const handlePayCloseBill = async (orderMethod) =>{
setIsPaymentLoading(true);
console.log("tipe" + deviceType);
if (deviceType == "clerk") {
const pay = await handlePaymentFromClerk(
shopId,
email,
isCash ? "cash" : "cashless",
orderType,
tableNumber
);
} else if (deviceType == "guestSide") {
const pay = await handlePaymentFromGuestSide(
shopId,
email,
isCash ? "cash" : "cashless",
orderType,
tableNumber
);
} else if (deviceType == "guestDevice") {
if (transactionData) {
const socketId = socket.id;
const pay = await handlePaymentFromGuestDevice(
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,
isCash ? "cash" : "cashless",
orderType,
table.tableNo || tableNumber,
transactionData.transactionId,
textareaRef.current.value,
socketId
);
}
else
if (deviceType == "clerk") {
const pay = await handlePaymentFromClerk(
shopId,
email,
orderMethod,
orderType,
tableNumber
);
} else if (deviceType == "guestSide") {
const pay = await handlePaymentFromGuestSide(
shopId,
email,
orderMethod,
orderType,
tableNumber
);
} else if (deviceType == "guestDevice") {
const socketId = socket.id;
const pay = await handlePaymentFromGuestDevice(
shopId,
orderMethod,
orderType,
table.tableNo || tableNumber,
textareaRef.current.value,
socketId
);
}
console.log("transaction from " + deviceType + "success");
setIsPaymentLoading(false);
@@ -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,21 +397,22 @@ export default function Invoice({ shopId, table, sendParam, deviceType, socket,
:
(isLoading ? <></> :
<>
<div className={styles.RoundedRectangle}>
{cartItems.map((itemType) => (
<ItemLister
shopId={shopId}
forInvoice={true}
key={itemType.id}
typeName={itemType.typeName}
itemList={itemType.itemList}
/>
))}
{getItemsByCafeId(shopId).length > 0 &&
<div className={styles.RoundedRectangle}>
{cartItems.map((itemType) => (
<ItemLister
shopId={shopId}
forInvoice={true}
key={itemType.id}
typeName={itemType.typeName}
itemList={itemType.itemList}
/>
))}
{table.tableNo != null && (
<div className={styles.OrderTypeContainer}>
<span htmlFor="orderType">Diantar ke {table.tableNo}</span>
{/* <select
{table.tableNo != null && (
<div className={styles.OrderTypeContainer}>
<span htmlFor="orderType">Diantar ke {table.tableNo}</span>
{/* <select
id="orderType"
value={orderType}
onChange={handleOrderTypeChange}
@@ -367,63 +423,149 @@ export default function Invoice({ shopId, table, sendParam, deviceType, socket,
<option value="pickup">Pickup</option>
{table == null && <option value="serve">Serve</option>}
</select> */}
</div>
)}
{orderType === "serve" && table.length < 1 && (
<div className={styles.OrderTypeContainer}>
<span htmlFor="orderType">Serve to:</span>
<input
type="text"
placeholder="Table Number"
value={tableNumber}
onChange={handleTableNumberChange}
className={styles.TableNumberInput}
/>
</div>
)}
<div className={styles.NoteContainer}>
<span>Catatan :</span>
<span></span>
</div>
)}
{orderType === "serve" && table.length < 1 && (
<div className={styles.OrderTypeContainer}>
<span htmlFor="orderType">Serve to:</span>
<input
type="text"
placeholder="Table Number"
value={tableNumber}
onChange={handleTableNumberChange}
className={styles.TableNumberInput}
<div className={styles.NoteContainer}>
<textarea
ref={textareaRef}
className={styles.NoteInput}
placeholder="Tambahkan catatan..."
/>
</div>
)}
<div className={styles.NoteContainer}>
<span>Catatan :</span>
<span></span>
</div>
}
<div className={styles.NoteContainer}>
<textarea
ref={textareaRef}
className={styles.NoteInput}
placeholder="Tambahkan catatan..."
/>
{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 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>
}
<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>
{paymentMethods != null && <Dropdown setDropdownKey={() => setDropdownKey(dropdownKey + 1)} paymentMethods={paymentMethods} onChange={handleOrderMethodChange} />}
</span>
</div>
<div style={{ display: 'flex', paddingLeft: '25px', paddingRight: '25px' }}>
<button className={styles.PayButton} onClick={() => handlePay(orderMethod == 'cash' ? true : false)}>
{isPaymentLoading ? (
<ColorRing height="50" width="50" color="white" />
) : (
<div>
<span>Pesan</span>
{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{totalPrice}</span>
</div>
)}
</button>
</div>
<div onClick={goToShop} style={{ textAlign: 'center', marginBottom: '20px' }}>Kembali</div>
<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>
<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>
@@ -147,9 +159,9 @@ export default function Transactions({ propsShopId, sendParam, deviceType, handl
<path d="M 0.198 14.001 C -0.192 14.392 -0.088 11.275 0.1 11.088 L 7.293 4.293 C 7.684 3.902 8.316 3.902 8.707 4.293 L 15.833 11.258 C 16.021 11.445 16.203 14.644 15.812 14.253 L 8 6.414 L 0.198 14.001 Z" fill="#dbdbdb"></path>
</g>
</svg>
</div>
<div className={styles.circle}>1</div>
</div>
</div>
<div className={styles.circle}>1</div>
</div>
<div className={styles['line']} ></div>
<div className={styles['circle-right']} onClick={() => handleMoveToTransaction('next', transaction.transactionId)}>
<div className={styles["inner-circle"]}>
@@ -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']}>
<ColorRing className={styles['receipt-logo']} />
{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,47 +232,86 @@ export default function Transactions({
</div>
</>
)}
<div className={styles.TotalContainer}>
<span>Total:</span>
<span>
Rp {calculateTotalPrice(transaction.DetailedTransactions)}
</span>
</div>
<div className={styles.PaymentContainer}>
<ButtonWithReplica
paymentUrl={paymentUrl}
price={
"Rp" + calculateTotalPrice(transaction.DetailedTransactions)
}
disabled={transaction.payment_type == 'cash' || isPaymentLoading}
isPaymentLoading={isPaymentLoading}
handleClick={() => handleConfirm(transaction.transactionId)}
Open={() => setIsPaymentOpen(true)}
isPaymentOpen={isPaymentOpen}
{transaction.payment_type != 'paylater' ?
<>
<div className={styles.TotalContainer}>
<span>Total:</span>
<span>
Rp {calculateTotalPrice(transaction.DetailedTransactions)}
</span>
</div>
<div className={styles.PaymentContainer}>
{transaction.payment_type != 'cash' && transaction.payment_type != 'paylater/cash' &&
<ButtonWithReplica
paymentUrl={paymentUrl}
price={
"Rp" + calculateTotalPrice(transaction.DetailedTransactions)
}
disabled={isPaymentLoading}
isPaymentLoading={isPaymentLoading}
handleClick={() => handleConfirm(transaction.transactionId)}
Open={() => setIsPaymentOpen(true)}
isPaymentOpen={isPaymentOpen}
>
{
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={() =>
isPaymentOpen
? setIsPaymentOpen(false)
: handleDecline(transaction.transactionId)
}
>
{isPaymentOpen ? "kembali" : "batalkan"}
</h5>
)
}
</>
:
<h5
className={`${styles.DeclineButton}`}
>
{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)
)}
</ButtonWithReplica>
</div>
<h5
className={`${styles.DeclineButton}`}
onClick={() =>
isPaymentOpen
? setIsPaymentOpen(false)
: handleDecline(transaction.transactionId)
}
>
{isPaymentOpen ? "kembali" : "batalkan"}
</h5>
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>
<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}>
@@ -138,9 +139,9 @@ export default function Transactions({ propsShopId, sendParam, deviceType, handl
<p>ID Transaksi: {transaction.transactionId}</p>
<p>Pembayaran: {transaction.payment_type}</p>
<p>{transaction.serving_type === "pickup"
? "Ambil sendiri"
: `Diantar ke ${transaction.Table ? transaction.Table.tableNo : "N/A"
}`}</p>
? "Ambil sendiri"
: `Diantar ke ${transaction.Table ? transaction.Table.tableNo : "N/A"
}`}</p>
</div>
</div>
@@ -153,13 +154,27 @@ export default function Transactions({ propsShopId, sendParam, deviceType, handl
</div>
<div className={styles.RibbonBanner}></div>
<ul>
{transaction.DetailedTransactions.map((detail) => (
<li key={detail.detailedTransactionId}>
<span>{detail.Item.name}</span> - {detail.qty} x Rp{" "}
{detail.promoPrice ? detail.promoPrice : detail.Item.price}
</li>
{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 < 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,57 +1,241 @@
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}
style={{
marginTop: "10px",
padding: "10px 20px",
fontSize: "16px",
cursor: "pointer",
backgroundColor: "#4CAF50",
color: "#fff",
border: "none",
borderRadius: "5px",
}}
<div className={styles.TransactionListContainer} style={{ overflow: 'hidden' }}>
{transaction && (
<div
key={transaction.transactionId}
className={styles.RoundedRectangle}
onClick={() =>
setSelectedTable(transaction.Table || { tableId: 0 })
}
>
yes
</button>
</div>
<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={{
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

@@ -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([]);
@@ -86,6 +94,12 @@ export default function Transactions({ shopId, propsShopId, sendParam, deviceTyp
return total + dt.qty * (dt.promoPrice ? dt.promoPrice : dt.price);
}, 0);
};
const calculateAllTransactionsTotal = (transactions) => {
return transactions.reduce((grandTotal, transaction) => {
return grandTotal + calculateTotalPrice(transaction.DetailedTransactions);
}, 0);
};
const handleConfirm = async (transactionId) => {
setIsPaymentLoading(true);
@@ -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;
@@ -371,4 +371,28 @@
margin: 3px;
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>
);
}