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

@@ -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>
</>