This commit is contained in:
zadit
2025-02-16 01:19:51 +07:00
parent 422398c65e
commit df0dbafe18
11 changed files with 285 additions and 112 deletions

View File

@@ -612,6 +612,8 @@ function App() {
socket={socket} socket={socket}
totalItemsCount={totalItemsCount} totalItemsCount={totalItemsCount}
deviceType={deviceType} deviceType={deviceType}
shopItems={shopItems}
setShopItems={setShopItems}
/> />
{/* <Footer {/* <Footer
shopId={shopIdentifier} shopId={shopIdentifier}

View File

@@ -60,7 +60,7 @@ const ButtonWithReplica = ({
try { try {
console.log(paymentUrl); console.log(paymentUrl);
const qrv = await decodeQRCodeFromUrl(paymentUrl); const qrv = await decodeQRCodeFromUrl(paymentUrl);
setQRValue('00020101021126760024ID.CO.SPEEDCASH.MERCHANT01189360081530001108500215ID10210011085060303UBE51440014ID.CO.QRIS.WWW0215ID20211101828900303UBE5204829953033605802ID5908DINNCAFE6008SURABAYA61056029462260506S155880105280940703A0163041E7B'); setQRValue(qrv);
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} }

View File

@@ -191,7 +191,8 @@ const Item = ({
alignItems: 'center', alignItems: 'center',
whiteSpace: 'nowrap', whiteSpace: 'nowrap',
borderRadius: '9999px', borderRadius: '9999px',
backgroundImage: 'linear-gradient(to right, #e52535, #eb525e)', backgroundColor: !isAvailable ? 'gray' : 'unset',
backgroundImage: isAvailable && 'linear-gradient(to right, #e52535, #eb525e)',
padding: '0.25rem 0rem', padding: '0.25rem 0rem',
color: 'rgb(255, 255, 255)', color: 'rgb(255, 255, 255)',
fontSize: '0.75rem', fontSize: '0.75rem',

View File

@@ -320,6 +320,7 @@ export const handlePaymentFromGuestDevice = async (
items: items.map((item) => ({ items: items.map((item) => ({
itemId: item.itemId, itemId: item.itemId,
qty: item.qty, qty: item.qty,
price: item.price
})), })),
}; };

View File

@@ -17,7 +17,7 @@ import { getItemsByCafeId } from "../helpers/cartHelpers.js";
import { useNavigationHelpers } from "../helpers/navigationHelpers"; import { useNavigationHelpers } from "../helpers/navigationHelpers";
export default function Invoice({ shopId, table, sendParam, deviceType, socket }) { export default function Invoice({ shopId, table, sendParam, deviceType, socket, shopItems, setShopItems }) {
const { shopIdentifier, tableCode } = useParams(); const { shopIdentifier, tableCode } = useParams();
sendParam({ shopIdentifier, tableCode }); sendParam({ shopIdentifier, tableCode });
@@ -35,24 +35,37 @@ export default function Invoice({ shopId, table, sendParam, deviceType, socket }
const [orderMethod, setOrderMethod] = useState("cash"); const [orderMethod, setOrderMethod] = useState("cash");
const [tableNumber, setTableNumber] = useState(""); const [tableNumber, setTableNumber] = useState("");
const [email, setEmail] = useState(""); const [email, setEmail] = useState("");
const [isLoading, setIsLoading] = useState(true);
useEffect(() => { useEffect(() => {
const fetchCartItems = async () => { const fetchCartItems = async () => {
try { try {
const items = await getCartDetails(shopId); const cart = getItemsByCafeId(shopId);
console.log(items); const itemMap = new Map(cart.map(item => [item.itemId, item.qty]));
// Filter out unavailable items // Step 2: Filter and transform shopItems
const filteredItems = items const filteredItems = shopItems
.map((itemType) => ({ .map(itemType => ({
...itemType, itemTypeId: itemType.itemTypeId,
itemList: itemType.itemList.filter((item) => item.availability), cafeId: itemType.cafeId,
typeName: itemType.name,
itemList: itemType.itemList
.filter(item => itemMap.has(item.itemId)) // Keep only items in getItemsByCafeId
.map(item => ({
itemId: item.itemId,
price: (item.promoPrice ? item.promoPrice : item.price),
name: item.name,
image: item.image,
qty: itemMap.get(item.itemId), // Add qty from getItemsByCafeId
availability: item.availability
}))
})) }))
.filter((itemType) => itemType.itemList.length > 0); // Remove empty itemTypes .filter(itemType => itemType.itemList.length > 0); // Remove empty itemTypes
setCartItems(filteredItems);
console.log(filteredItems) console.log(filteredItems)
// Update local storage by removing unavailable items // Update local storage by removing unavailable items
const updatedLocalStorage = const updatedLocalStorage =
JSON.parse(localStorage.getItem("cart")) || []; JSON.parse(localStorage.getItem("cart")) || [];
@@ -79,7 +92,118 @@ export default function Invoice({ shopId, table, sendParam, deviceType, socket }
return ( return (
total + total +
itemType.itemList.reduce((subtotal, item) => { itemType.itemList.reduce((subtotal, item) => {
return subtotal + item.qty * item.price; return subtotal + item.qty * (item.promoPrice ? item.promoPrice : item.price);
}, 0)
);
}, 0);
setTimeout(function () {
setCartItems(filteredItems);
setTotalPrice(totalPrice);
setIsLoading(false)
}, 100); //delay is in milliseconds
} catch (error) {
console.error("Error fetching cart items:", error);
// Handle error if needed
}
};
fetchCartItems();
const getNewestCartItems = async () => {
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 => {
// Loop through the shopItems and find the corresponding itemId
shopItems.forEach(shopItemType => {
shopItemType.itemList.forEach(shopItem => {
if (shopItem.itemId === item.itemId) {
// Update shopItems with the new data from items (e.g., availability, price)
shopItem.availability = item.availability;
shopItem.price = item.price; // If needed, update other properties like price
shopItem.promoPrice = item.promoPrice;
}
});
});
});
});
// After updating shopItems, set the new state
setShopItems(shopItems);
// Filter out unavailable items
const filteredItems = items
.map((itemType) => ({
...itemType,
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) => {
if (cafe.cafeId === shopId) {
return {
...cafe,
items: cafe.items.map((item) => {
// Find the updated item in filteredItems and set its updated price
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,
availability: updatedItem.availability
};
}
// If no updated item found, return the original item
return item;
}),
};
}
return cafe;
});
const newLocalStoragee = newLocalStorage.map((cafe) => {
if (cafe.cafeId === shopId) {
return {
...cafe,
items: cafe.items.filter((item) =>
filteredItems.some((filtered) =>
filtered.itemList.some(
(i) => i.itemId === item.itemId && i.availability
)
)
),
};
}
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);
}, 0) }, 0)
); );
}, 0); }, 0);
@@ -88,9 +212,10 @@ export default function Invoice({ shopId, table, sendParam, deviceType, socket }
console.error("Error fetching cart items:", error); console.error("Error fetching cart items:", error);
// Handle error if needed // Handle error if needed
} }
}; };
fetchCartItems(); getNewestCartItems();
}, [shopId]); }, [shopId]);
const handlePay = async (isCash) => { const handlePay = async (isCash) => {
@@ -215,22 +340,23 @@ export default function Invoice({ shopId, table, sendParam, deviceType, socket }
<h1 style={{ fontSize: '120%', color: '#8F8787' }}>Tidak ada item di keranjang</h1> <h1 style={{ fontSize: '120%', color: '#8F8787' }}>Tidak ada item di keranjang</h1>
</div> </div>
: :
<> (isLoading ? <></> :
<div className={styles.RoundedRectangle}> <>
{cartItems.map((itemType) => ( <div className={styles.RoundedRectangle}>
<ItemLister {cartItems.map((itemType) => (
shopId={shopId} <ItemLister
forInvoice={true} shopId={shopId}
key={itemType.id} forInvoice={true}
typeName={itemType.typeName} key={itemType.id}
itemList={itemType.itemList} typeName={itemType.typeName}
/> itemList={itemType.itemList}
))} />
))}
{table.tableNo != null && ( {table.tableNo != null && (
<div className={styles.OrderTypeContainer}> <div className={styles.OrderTypeContainer}>
<span htmlFor="orderType">Diantar ke {table.tableNo}</span> <span htmlFor="orderType">Diantar ke {table.tableNo}</span>
{/* <select {/* <select
id="orderType" id="orderType"
value={orderType} value={orderType}
onChange={handleOrderTypeChange} onChange={handleOrderTypeChange}
@@ -241,66 +367,67 @@ export default function Invoice({ shopId, table, sendParam, deviceType, socket }
<option value="pickup">Pickup</option> <option value="pickup">Pickup</option>
{table == null && <option value="serve">Serve</option>} {table == null && <option value="serve">Serve</option>}
</select> */} </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> </div>
)}
{orderType === "serve" && table.length < 1 && ( <div className={styles.NoteContainer}>
<div className={styles.OrderTypeContainer}> <textarea
<span htmlFor="orderType">Serve to:</span> ref={textareaRef}
<input className={styles.NoteInput}
type="text" placeholder="Tambahkan catatan..."
placeholder="Table Number"
value={tableNumber}
onChange={handleTableNumberChange}
className={styles.TableNumberInput}
/> />
</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>
</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>
<div className={styles.NoteContainer}> <span>Rp{totalPrice}</span>
<span>Catatan :</span> </div>
<span></span> )}
</button>
</div>
<div onClick={goToShop} style={{ textAlign: 'center', marginBottom: '20px' }}>Kembali</div>
</div> </div>
<div className={styles.PaymentOptionMargin}></div>
<div className={styles.NoteContainer}> </>
<textarea )
ref={textareaRef}
className={styles.NoteInput}
placeholder="Tambahkan catatan..."
/>
</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>
</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>
<span>Rp{totalPrice}</span>
</div>
)}
</button>
</div>
<div onClick={goToShop} style={{ textAlign: 'center', marginBottom: '20px' }}>Kembali</div>
</div>
<div className={styles.PaymentOptionMargin}></div>
</>
} }
</div> </div>
); );

View File

@@ -51,7 +51,7 @@ export default function Transactions({ propsShopId, sendParam, deviceType }) {
const calculateTotalPrice = (detailedTransactions) => { const calculateTotalPrice = (detailedTransactions) => {
return detailedTransactions.reduce((total, dt) => { return detailedTransactions.reduce((total, dt) => {
return total + dt.qty * dt.Item.price; return total + dt.qty * (dt.promoPrice ? dt.promoPrice : dt.price);
}, 0); }, 0);
}; };
@@ -122,7 +122,7 @@ export default function Transactions({ propsShopId, sendParam, deviceType }) {
{transaction.DetailedTransactions.map((detail) => ( {transaction.DetailedTransactions.map((detail) => (
<li key={detail.detailedTransactionId}> <li key={detail.detailedTransactionId}>
<span>{detail.Item.name}</span> - {detail.qty} x Rp{" "} <span>{detail.Item.name}</span> - {detail.qty} x Rp{" "}
{detail.Item.price} {detail.promoPrice ? detail.promoPrice : detail.price}
</li> </li>
))} ))}
</ul> </ul>

View File

@@ -567,11 +567,20 @@ const App = ({ forCafe = true, cafeId = -1,
/> />
) )
} }
{selectedCafeId != -1 && (
<RoundedRectangle
title={"Total promo"}
loading={loading}
value={'Rp'+analytics?.currentTotals.totalPromoSpend}
// width="calc(50% - 10px)"
onClick={() => window.location.href = window.location.origin + '/' + otherCafes.find(item => item.cafeId === selectedCafeId).cafeIdentifyName}
/>
)}
{!forCafe && selectedCafeId != -1 && selectedCafeId != 0 && ( {!forCafe && selectedCafeId != -1 && selectedCafeId != 0 && (
<RoundedRectangle <RoundedRectangle
title={"Kunjungi bisnis"} title={"Kunjungi bisnis"}
loading={loading} loading={loading}
width="calc(100% - 10px)" // width="calc(100% - 10px)"
onClick={() => window.location.href = window.location.origin + '/' + otherCafes.find(item => item.cafeId === selectedCafeId).cafeIdentifyName} onClick={() => window.location.href = window.location.origin + '/' + otherCafes.find(item => item.cafeId === selectedCafeId).cafeIdentifyName}
/> />
)} )}

View File

@@ -52,7 +52,7 @@ export default function Transactions({ propsShopId, sendParam, deviceType, handl
const calculateTotalPrice = (detailedTransactions) => { const calculateTotalPrice = (detailedTransactions) => {
return detailedTransactions.reduce((total, dt) => { return detailedTransactions.reduce((total, dt) => {
return total + dt.qty * dt.Item.price; return total + dt.qty * (dt.promoPrice ? dt.promoPrice : dt.price);
}, 0); }, 0);
}; };
@@ -175,7 +175,7 @@ export default function Transactions({ propsShopId, sendParam, deviceType, handl
{transaction.DetailedTransactions.map((detail) => ( {transaction.DetailedTransactions.map((detail) => (
<li key={detail.detailedTransactionId}> <li key={detail.detailedTransactionId}>
<span>{detail.Item.name}</span> - {detail.qty} x Rp{" "} <span>{detail.Item.name}</span> - {detail.qty} x Rp{" "}
{detail.Item.price} {detail.promoPrice ? detail.promoPrice : detail.price}
</li> </li>
))} ))}
</ul> </ul>

View File

@@ -73,7 +73,7 @@ export default function Transactions({
const calculateTotalPrice = (detailedTransactions) => { const calculateTotalPrice = (detailedTransactions) => {
return detailedTransactions.reduce((total, dt) => { return detailedTransactions.reduce((total, dt) => {
return total + dt.qty * dt.Item.price; return total + dt.qty * (dt.promoPrice ? dt.promoPrice : dt.price);
}, 0); }, 0);
}; };
@@ -124,7 +124,7 @@ export default function Transactions({
return ( return (
<div className={styles.Transaction}> <div className={styles.Transaction}>
<div className={styles.TransactionListContainer}> <div className={styles.TransactionListContainer} style={{ overflow: 'hidden' }}>
{transaction && ( {transaction && (
<div <div
key={transaction.transactionId} key={transaction.transactionId}
@@ -164,7 +164,7 @@ export default function Transactions({
).map((detail) => ( ).map((detail) => (
<li key={detail.detailedTransactionId}> <li key={detail.detailedTransactionId}>
<span>{detail.Item.name}</span> - {detail.qty} x Rp{" "} <span>{detail.Item.name}</span> - {detail.qty} x Rp{" "}
{detail.Item.price} {detail.promoPrice ? detail.promoPrice : detail.price}
</li> </li>
))} ))}
</ul> </ul>
@@ -172,9 +172,8 @@ export default function Transactions({
<h2 className={styles["Transactions-detail"]}> <h2 className={styles["Transactions-detail"]}>
{transaction.serving_type === "pickup" {transaction.serving_type === "pickup"
? "Self pickup" ? "Self pickup"
: `Serve to ${ : `Serve to ${transaction.Table ? transaction.Table.tableNo : "N/A"
transaction.Table ? transaction.Table.tableNo : "N/A" }`}
}`}
</h2> </h2>
{(transaction.notes !== "") && ( {(transaction.notes !== "") && (
<> <>
@@ -222,13 +221,15 @@ export default function Transactions({
isPaymentOpen={isPaymentOpen} isPaymentOpen={isPaymentOpen}
> >
{transaction.payment_type == 'cash' || isPaymentLoading ? ( {transaction.payment_type == 'cash' || isPaymentLoading ? (
<> (transaction.payment_type == 'cash' && 'Bayar nanti')
{transaction.payment_type == 'cash' && <p>Bayar nanti</p>}
</>
) : isPaymentOpen ? ( ) : isPaymentOpen ? (
"Claim has paid" // Display "Confirm has paid" if the transaction is confirmed (1) (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)
) : ( ) : (
"Show payment" // Display "Confirm availability" if the transaction is not confirmed (0) "Tampilkan QR" // Display "Confirm availability" if the transaction is not confirmed (0)
)} )}
</ButtonWithReplica> </ButtonWithReplica>
</div> </div>

View File

@@ -64,7 +64,7 @@ export default function Transactions({ propsShopId, sendParam, deviceType, handl
const calculateTotalPrice = (detailedTransactions) => { const calculateTotalPrice = (detailedTransactions) => {
return detailedTransactions.reduce((total, dt) => { return detailedTransactions.reduce((total, dt) => {
return total + dt.qty * dt.Item.price; return total + dt.qty * (dt.promoPrice ? dt.promoPrice : dt.price);
}, 0); }, 0);
}; };
@@ -156,7 +156,7 @@ export default function Transactions({ propsShopId, sendParam, deviceType, handl
{transaction.DetailedTransactions.map((detail) => ( {transaction.DetailedTransactions.map((detail) => (
<li key={detail.detailedTransactionId}> <li key={detail.detailedTransactionId}>
<span>{detail.Item.name}</span> - {detail.qty} x Rp{" "} <span>{detail.Item.name}</span> - {detail.qty} x Rp{" "}
{detail.Item.price} {detail.promoPrice ? detail.promoPrice : detail.Item.price}
</li> </li>
))} ))}
</ul> </ul>

View File

@@ -26,9 +26,40 @@ export default function Transactions({ shopId, propsShopId, sendParam, deviceTyp
try { try {
let response; let response;
response = await getTransactionsFromCafe(shopId || propsShopId, 5, false); response = await getTransactionsFromCafe(shopId || propsShopId, 5, false);
setTransactions(response); console.log(response)
if(response) {
setTransactions(response);
return;
}
} catch (error) {
console.error("Error fetching transactions:", error);
}
try {
let response;
response = await getMyTransactions(shopId || propsShopId, 5); response = await getMyTransactions(shopId || propsShopId, 5);
setMyTransactions(response); console.log(response)
const combinedTransactions = [];
response.forEach(cafe => {
const { cafeId, name: cafeName, transactions } = cafe;
transactions.forEach(transaction => {
const newTransaction = {
...transaction,
cafeId,
cafeName,
DetailedTransactions: transaction.detailedTransactions // Rename here
};
delete newTransaction.detailedTransactions; // Remove the old key
combinedTransactions.push(newTransaction);
});
});
// combinedTransactions now contains all transactions with cafe info and renamed key
console.log(combinedTransactions)
// combinedTransactions now contains all transactions with cafe info
setTransactions(combinedTransactions);
} catch (error) { } catch (error) {
console.error("Error fetching transactions:", error); console.error("Error fetching transactions:", error);
} }
@@ -52,7 +83,7 @@ export default function Transactions({ shopId, propsShopId, sendParam, deviceTyp
const calculateTotalPrice = (detailedTransactions) => { const calculateTotalPrice = (detailedTransactions) => {
return detailedTransactions.reduce((total, dt) => { return detailedTransactions.reduce((total, dt) => {
return total + dt.qty * dt.Item.price; return total + dt.qty * (dt.promoPrice ? dt.promoPrice : dt.price);
}, 0); }, 0);
}; };
@@ -118,7 +149,7 @@ export default function Transactions({ shopId, propsShopId, sendParam, deviceTyp
<div className={styles['receipt-header']}> <div className={styles['receipt-header']}>
{transaction.confirmed === 1 ? ( {transaction.confirmed === 1 ? (
<ColorRing className={styles['receipt-logo']} /> <ColorRing className={styles['receipt-logo']} />
) : transaction.confirmed === -1 || transaction.confirmed === -2 ? ( ) : transaction.confirmed === -1 || transaction.confirmed === -2 ? (
<div style={{ display: 'flex', justifyContent: 'center' }}> <div style={{ display: 'flex', justifyContent: 'center' }}>
<svg <svg
style={{ width: '60px', transform: 'Rotate(45deg)' }} style={{ width: '60px', transform: 'Rotate(45deg)' }}
@@ -184,7 +215,7 @@ export default function Transactions({ shopId, propsShopId, sendParam, deviceTyp
{transaction.DetailedTransactions.map((detail) => ( {transaction.DetailedTransactions.map((detail) => (
<li key={detail.detailedTransactionId}> <li key={detail.detailedTransactionId}>
<span>{detail.Item.name}</span> - {detail.qty} x Rp{" "} <span>{detail.Item.name}</span> - {detail.qty} x Rp{" "}
{detail.Item.price} {detail.promoPrice ? detail.promoPrice : detail.price}
</li> </li>
))} ))}
</ul> </ul>
@@ -195,7 +226,7 @@ export default function Transactions({ shopId, propsShopId, sendParam, deviceTyp
}`} }`}
</h2> </h2>
{transaction.notes != "" && ( {transaction.notes && (
<> <>
<div className={styles.NoteContainer}> <div className={styles.NoteContainer}>
<span>Note :</span> <span>Note :</span>
@@ -211,6 +242,7 @@ export default function Transactions({ shopId, propsShopId, sendParam, deviceTyp
</div> </div>
</> </>
)} )}
<div className={styles.TotalContainer}> <div className={styles.TotalContainer}>
<span>Total:</span> <span>Total:</span>
<span> <span>