This commit is contained in:
everythingonblack
2025-05-20 17:47:43 +07:00
parent b726ae6919
commit df7c4f737c
7 changed files with 119 additions and 81 deletions

View File

@@ -28,6 +28,15 @@
Learn how to configure a non-root public URL by running `npm run build`. Learn how to configure a non-root public URL by running `npm run build`.
--> -->
<title>KedaiMaster</title> <title>KedaiMaster</title>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-2SKSCVFB2N"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){ dataLayer.push(arguments); }
gtag('js', new Date());
gtag('config', 'G-2SKSCVFB2N');
</script>
</head> </head>
<body> <body>
<noscript>You need to enable JavaScript to run this app.</noscript> <noscript>You need to enable JavaScript to run this app.</noscript>

View File

@@ -3,12 +3,10 @@
@import url("https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@200;300;400;500;600;700;800&ital,wght@0,200..800;1,200..800&display=swap"); @import url("https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@200;300;400;500;600;700;800&ital,wght@0,200..800;1,200..800&display=swap");
html, html,
body { body {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */ scrollbar-width: none; /* Firefox */
} }
.App { .App {
/* overflow-x: hidden; */
} }
.Cafe { .Cafe {

View File

@@ -135,6 +135,7 @@ function App() {
return; return;
} }
setModal('transaction_confirmed',{transactionId: lastTransaction.transactionId})
const myLastTransaction = await checkIsMyTransaction(lastTransaction.transactionId); const myLastTransaction = await checkIsMyTransaction(lastTransaction.transactionId);
console.log(myLastTransaction) console.log(myLastTransaction)
if (myLastTransaction.isMyTransaction) { if (myLastTransaction.isMyTransaction) {
@@ -771,6 +772,7 @@ function App() {
sendParam={handleSetParam} sendParam={handleSetParam}
deviceType={deviceType} deviceType={deviceType}
paymentUrl={shop.qrPayment} paymentUrl={shop.qrPayment}
setModal={setModal}
/> />
{/* <Footer {/* <Footer
shopId={shopIdentifier} shopId={shopIdentifier}

View File

@@ -77,17 +77,28 @@ function CafePage({
const [beingEditedType, setBeingEditedType] = useState(0); const [beingEditedType, setBeingEditedType] = useState(0);
const checkWelcomePageConfig = () => { // const checkWelcomePageConfig = () => {
const parsedConfig = JSON.parse(welcomePageConfig); // const parsedConfig = JSON.parse(welcomePageConfig);
if (parsedConfig.isWelcomePageActive == "true") { // if (parsedConfig.isWelcomePageActive == "true") {
const clicked = sessionStorage.getItem("getStartedClicked"); // const clicked = sessionStorage.getItem("getStartedClicked");
if (!clicked) { // if (!clicked) {
sessionStorage.setItem("getStartedClicked", true); // sessionStorage.setItem("getStartedClicked", true);
document.body.style.overflow = "hidden"; // document.body.style.overflow = "hidden";
setIsStarted(true); // setIsStarted(true);
} // }
// }
// };
useEffect(() => {
if (window.gtag && shopIdentifier) {
window.gtag('event', 'page_view', {
page_title: `Cafe - ${shopIdentifier}`,
page_location: window.location.href,
page_path: `/` + shopIdentifier,
shop_id: shopId || null, // opsional jika kamu mau track ID juga
});
} }
}; }, [shopIdentifier]);
useEffect(() => { useEffect(() => {
if (welcomePageConfig) { if (welcomePageConfig) {
@@ -100,7 +111,7 @@ function CafePage({
isActive: parsedConfig.isWelcomePageActive === "true", isActive: parsedConfig.isWelcomePageActive === "true",
}); });
} }
checkWelcomePageConfig(); // checkWelcomePageConfig();
}, [welcomePageConfig]); }, [welcomePageConfig]);

View File

@@ -82,7 +82,7 @@ const RoundedRectangle = ({
}; };
const percentageStyle = { const percentageStyle = {
fontSize: "16px", fontSize: "14px",
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
textAlign: "right", textAlign: "right",
@@ -282,11 +282,11 @@ const App = ({ forCafe = true, cafeId = -1,
if (amount >= 1_000_000_000) { if (amount >= 1_000_000_000) {
// Format for billions // Format for billions
const billions = amount / 1_000_000_000; const billions = amount / 1_000_000_000;
return billions.toFixed(0) + "b"; // No decimal places for billions return billions.toFixed(0) + "m"; // No decimal places for billions
} else if (amount >= 1_000_000) { } else if (amount >= 1_000_000) {
// Format for millions // Format for millions
const millions = amount / 1_000_000; const millions = amount / 1_000_000;
return millions.toFixed(2).replace(/\.00$/, "") + "m"; // Two decimal places, remove trailing '.00' return millions.toFixed(2).replace(/\.00$/, "") + "jt"; // Two decimal places, remove trailing '.00'
} else if (amount >= 1_000) { } else if (amount >= 1_000) {
// Format for thousands // Format for thousands
const thousands = amount / 1_000; const thousands = amount / 1_000;

View File

@@ -192,7 +192,7 @@ export default function Transactions({
))} ))}
</ul> </ul>
{transaction.payment_type != 'paylater/cash' && transaction.payment_type != 'paylater/cashless' && {(transaction.payment_type != 'paylater/cash' && transaction.payment_type != 'paylater/cashless') &&
<div <div
onClick={() => { onClick={() => {
localStorage.setItem('lastTransaction', JSON.stringify(transaction)); localStorage.setItem('lastTransaction', JSON.stringify(transaction));

View File

@@ -18,7 +18,7 @@ import ButtonWithReplica from "../components/ButtonWithReplica";
dayjs.extend(utc); dayjs.extend(utc);
dayjs.extend(timezone); dayjs.extend(timezone);
export default function Transactions({ shop, shopId, propsShopId, sendParam, deviceType, paymentUrl }) { export default function Transactions({ shop, shopId, propsShopId, sendParam, deviceType, paymentUrl, setModal }) {
const { shopIdentifier, tableId } = useParams(); const { shopIdentifier, tableId } = useParams();
if (sendParam) sendParam({ shopIdentifier, tableId }); if (sendParam) sendParam({ shopIdentifier, tableId });
@@ -26,13 +26,13 @@ export default function Transactions({ shop, shopId, propsShopId, sendParam, dev
const [isPaymentLoading, setIsPaymentLoading] = useState(false); const [isPaymentLoading, setIsPaymentLoading] = useState(false);
const [isPaymentOpen, setIsPaymentOpen] = useState(false); const [isPaymentOpen, setIsPaymentOpen] = useState(false);
const [searchTerm, setSearchTerm] = useState(''); const [searchTerm, setSearchTerm] = useState('');
const [matchedItems, setMatchedItems] = useState([]); const [matchedItems, setMatchedItems] = useState([]);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
useEffect(() => { useEffect(() => {
setMatchedItems(searchAndAggregateItems(transactions, searchTerm)); setMatchedItems(searchAndAggregateItems(transactions, searchTerm));
}, [searchTerm, transactions]); }, [searchTerm, transactions]);
useEffect(() => { useEffect(() => {
@@ -40,9 +40,13 @@ useEffect(() => {
try { try {
setLoading(true); setLoading(true);
let response = await getTransactionsFromCafe(shopId || propsShopId, 5, false); let response = await getTransactionsFromCafe(shopId || propsShopId, 5, false);
setLoading(false); setLoading(false);
if (response) setTransactions(response); if (response) setTransactions(response);
// response = await getMyTransactions(shopId || propsShopId, 5);
// setMyTransactions(response);
} catch (error) { } catch (error) {
console.error("Error fetching transactions:", error); console.error("Error fetching transactions:", error);
} }
@@ -62,38 +66,38 @@ useEffect(() => {
return grandTotal + calculateTotalPrice(transaction.DetailedTransactions); return grandTotal + calculateTotalPrice(transaction.DetailedTransactions);
}, 0); }, 0);
}; };
const searchAndAggregateItems = (transactions, searchTerm) => { const searchAndAggregateItems = (transactions, searchTerm) => {
if (!searchTerm.trim()) return []; if (!searchTerm.trim()) return [];
const normalizedTerm = searchTerm.trim().toLowerCase(); const normalizedTerm = searchTerm.trim().toLowerCase();
const aggregatedItems = new Map(); const aggregatedItems = new Map();
transactions.forEach(transaction => { transactions.forEach(transaction => {
transaction.DetailedTransactions.forEach(detail => { transaction.DetailedTransactions.forEach(detail => {
const itemName = detail.Item.name; const itemName = detail.Item.name;
const itemNameLower = itemName.toLowerCase(); const itemNameLower = itemName.toLowerCase();
if (itemNameLower.includes(normalizedTerm)) { if (itemNameLower.includes(normalizedTerm)) {
const key = detail.itemId; const key = detail.itemId;
if (!aggregatedItems.has(key)) { if (!aggregatedItems.has(key)) {
aggregatedItems.set(key, { aggregatedItems.set(key, {
itemId: detail.itemId, itemId: detail.itemId,
name: itemName, name: itemName,
totalQty: 0, totalQty: 0,
totalPrice: 0, totalPrice: 0,
}); });
}
const current = aggregatedItems.get(key);
current.totalQty += detail.qty;
current.totalPrice += detail.qty * (detail.promoPrice || detail.price);
} }
});
const current = aggregatedItems.get(key);
current.totalQty += detail.qty;
current.totalPrice += detail.qty * (detail.promoPrice || detail.price);
}
}); });
});
return Array.from(aggregatedItems.values()); return Array.from(aggregatedItems.values());
}; };
const handleConfirm = async (transactionId) => { const handleConfirm = async (transactionId) => {
@@ -103,7 +107,7 @@ const searchAndAggregateItems = (transactions, searchTerm) => {
if (result) { if (result) {
setTransactions(prev => setTransactions(prev =>
prev.map(t => prev.map(t =>
t.transactionId === transactionId ? { ...t, confirmed: 1 } : t t.transactionId === transactionId ? result : t
) )
); );
} }
@@ -148,35 +152,35 @@ const searchAndAggregateItems = (transactions, searchTerm) => {
</h2> </h2>
<input <input
type="text" type="text"
placeholder="Cari nama item..." placeholder="Cari nama item..."
value={searchTerm} value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)} onChange={(e) => setSearchTerm(e.target.value)}
style={{ border:'0px',height: '42px',borderRadius: '15px', margin: '7px auto 10px', width: '88%', paddingLeft: '8px' }} style={{ border: '0px', height: '42px', borderRadius: '15px', margin: '7px auto 10px', width: '88%', paddingLeft: '8px' }}
/> />
{/* Existing Transactions List (keep all your JSX below unchanged) */} {/* Existing Transactions List (keep all your JSX below unchanged) */}
<div className={styles.TransactionListContainer} style={{ padding: '0 8px' }}> <div className={styles.TransactionListContainer} style={{ padding: '0 8px' }}>
{matchedItems.length > 0 && matchedItems.map(item => ( {matchedItems.length > 0 && matchedItems.map(item => (
<div <div
key={item.itemId} key={item.itemId}
className={styles.RoundedRectangle} className={styles.RoundedRectangle}
style={{ overflow: "hidden" }} style={{ overflow: "hidden" }}
> >
<ul> <ul>
<li> <li>
<strong>{item.name}</strong> x {item.totalQty} <strong>{item.name}</strong> x {item.totalQty}
</li> </li>
</ul> </ul>
<div className={styles.TotalContainer}> <div className={styles.TotalContainer}>
<span>Total:</span> <span>Total:</span>
<span>Rp {item.totalPrice}</span> <span>Rp {item.totalPrice}</span>
</div> </div>
</div> </div>
))} ))}
{transactions && {transactions &&
transactions.map((transaction) => ( transactions.map((transaction) => (
<div <div
@@ -188,7 +192,7 @@ const searchAndAggregateItems = (transactions, searchTerm) => {
<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.is_paid || transaction.confirmed === -2 && !transaction.is_paid ? (
<div style={{ display: 'flex', justifyContent: 'center', margin: '16px 0px' }}> <div style={{ display: 'flex', justifyContent: 'center', margin: '16px 0px' }}>
<svg <svg
style={{ width: '60px', transform: 'Rotate(45deg)' }} style={{ width: '60px', transform: 'Rotate(45deg)' }}
@@ -205,9 +209,9 @@ const searchAndAggregateItems = (transactions, searchTerm) => {
/> />
</svg> </svg>
</div> </div>
) : transaction.confirmed === 2 ? ( ) : transaction.confirmed === 2 && !transaction.is_paid ? (
<ColorRing className={styles['receipt-logo']} /> <ColorRing className={styles['receipt-logo']} />
) : transaction.confirmed === 3 ? ( ) : transaction.confirmed === 3 || transaction.is_paid ? (
<div> <div>
<svg <svg
height="60px" height="60px"
@@ -242,15 +246,15 @@ const searchAndAggregateItems = (transactions, searchTerm) => {
<div className={styles['receipt-info']}> <div className={styles['receipt-info']}>
{deviceType == 'clerk' ? {deviceType == 'clerk' ?
<h3>{transaction.confirmed === 1 ? ( <h3>{transaction.confirmed === 1 && !transaction.is_paid ? (
"Silahkan Cek Pembayaran" "Silahkan Cek Pembayaran"
) : transaction.confirmed === -1 ? ( ) : transaction.confirmed === -1 && !transaction.is_paid ? (
"Dibatalkan Oleh Kasir" "Dibatalkan Oleh Kasir"
) : transaction.confirmed === -2 ? ( ) : transaction.confirmed === -2 && !transaction.is_paid ? (
"Dibatalkan Oleh Pelanggan" "Dibatalkan Oleh Pelanggan"
) : transaction.confirmed === 2 ? ( ) : transaction.confirmed === 2 && !transaction.is_paid ? (
"Sedang Diproses" "Sedang Diproses"
) : transaction.confirmed === 3 ? ( ) : transaction.confirmed === 3 || transaction.is_paid ? (
"Transaksi Sukses" "Transaksi Sukses"
) : ( ) : (
"Silahkan Cek Ketersediaan" "Silahkan Cek Ketersediaan"
@@ -299,6 +303,20 @@ const searchAndAggregateItems = (transactions, searchTerm) => {
</li> </li>
))} ))}
</ul> </ul>
{!transaction.is_paid && transaction.confirmed > -1 &&
<div
onClick={() => {
localStorage.setItem('lastTransaction', JSON.stringify(transaction));
setModal("message", { captMessage: 'Silahkan tambahkan pesanan', descMessage: 'Pembayaran akan ditambahkan ke transaksi sebelumnya.' }, null, null);
// Dispatch the custom event
window.dispatchEvent(new Event("localStorageUpdated"));
}}
className={styles["addNewItem"]}
>
Tambah pesanan
</div>
}
<h2 className={styles["Transactions-detail"]}> <h2 className={styles["Transactions-detail"]}>
{transaction.serving_type === "pickup" {transaction.serving_type === "pickup"
? "Self pickup" ? "Self pickup"
@@ -331,7 +349,7 @@ const searchAndAggregateItems = (transactions, searchTerm) => {
</div> </div>
<div className={styles.TotalContainer}> <div className={styles.TotalContainer}>
{(deviceType == 'clerk' && (transaction.confirmed == 0 || transaction.confirmed == 1 || transaction.confirmed == 2)) && {(deviceType == 'clerk' && !transaction.is_paid && (transaction.confirmed == 0 || transaction.confirmed == 1 || transaction.confirmed == 2)) &&
<button <button
className={styles.PayButton} className={styles.PayButton}
onClick={() => handleConfirm(transaction.transactionId)} onClick={() => handleConfirm(transaction.transactionId)}