This commit is contained in:
Vassshhh
2025-08-27 21:27:08 +07:00
parent 4fa272875f
commit dcf0455772
12 changed files with 219 additions and 101 deletions

View File

@@ -377,7 +377,7 @@ function App() {
setGuestSides(connectedGuestSides.sessionDatas);
console.log("getting guest side");
setDeviceType("clerk");
checkNotifications();
// checkNotifications();
} else {
setDeviceType("guestDevice");
}

View File

@@ -29,10 +29,51 @@ const Item = ({
const [itemDescription, setItemDescription] = useState(initialDescription);
const fileInputRef = useRef(null);
const formatToRupiah = (value) => {
if (typeof value !== "number") return value;
return value.toLocaleString("id-ID");
// const stringValue = (value || '').toString();
// // Ambil hanya angka
// const cleaned = stringValue.replace(/[^0-9]/g, '');
// // Pastikan cleaned bukan kosong
// if (!cleaned) return 'Rp0';
// // Ubah ke number dan kalikan 1000
// const number = Number(cleaned) * 1000;
// // Kalau gagal parsing, fallback
// if (isNaN(number)) return 'Rp0';
// // Format rupiah tanpa desimal
// return number.toLocaleString('id-ID', {
// style: 'currency',
// currency: 'IDR',
// minimumFractionDigits: 0,
// maximumFractionDigits: 0
// });
};
const rupiahFormat = (angka, prefix = "Rp. ") => {
let number_string = angka.toString().replace(/[^,\d]/g, '');
let split = number_string.split(',');
let sisa = split[0].length % 3;
let rupiah = split[0].substr(0, sisa);
let ribuan = split[0].substr(sisa).match(/\d{3}/gi);
// tambahkan titik jika yang di input sudah menjadi angka ribuan
if(ribuan){
let separator = sisa ? '.' : '';
rupiah += separator + ribuan.join('.');
}
rupiah = split[1] != undefined ? rupiah + ',' + split[1] : rupiah;
return rupiah ? prefix + rupiah : "";
}
useEffect(() => {
console.log(imageUrl);
console.log(selectedImage);
@@ -305,7 +346,9 @@ const formatToRupiah = (value) => {
))}
{forInvoice && (
<p className={styles.itemPriceInvoice}>Rp {itemQty * (promoPrice > 0? formatToRupiah(promoPrice) : formatToRupiah(itemPrice))}</p>
<p className={styles.itemPriceInvoice}>Rp {formatToRupiah(itemQty * (promoPrice > 0? promoPrice : itemPrice))}</p>
// <p className={styles.itemPriceInvoice}>{itemQty * (promoPrice > 0? rupiahFormat(promoPrice) : rupiahFormat(itemPrice))}</p>
)}
</div>
{forCart && (

View File

@@ -119,7 +119,7 @@
.TotalContainer {
display: flex;
justify-content: space-between;
width: 80vw;
/* width: 80vw; */
margin: 0 auto;
font-family: "Plus Jakarta Sans", sans-serif;
font-weight: 600;

View File

@@ -217,7 +217,8 @@ export const handlePaymentFromClerk = async (
user_email,
payment_type,
serving_type,
tableNo
tableNo,
notes
) => {
try {
const token = getLocalStorage("auth");
@@ -245,6 +246,7 @@ export const handlePaymentFromClerk = async (
serving_type,
tableNo,
transactions: structuredItems,
notes
}),
}
);

View File

@@ -429,7 +429,7 @@ function CafePage({
}
/>
))}
{!isEditMode && !isTablet && (user.username || cartItemsLength > 0) && (
{!isEditMode && (user.username || cartItemsLength > 0) && (
<div
style={{
marginTop: "10px",
@@ -442,7 +442,7 @@ function CafePage({
textAlign: "center",
}}
>
{(lastTransaction != null || cartItemsLength > 0) && (
{(!isTablet &&(lastTransaction != null || cartItemsLength > 0)) && (
<div
onClick={goToCart}
style={{

View File

@@ -22,6 +22,11 @@ import { getItemsByCafeId } from "../helpers/cartHelpers.js";
import Dropdown from "./Dropdown.js";
import { useNavigationHelpers } from "../helpers/navigationHelpers";
const formatToRupiah = (value) => {
if (typeof value !== "number") return value;
return value.toLocaleString("id-ID");
};
export default function Invoice({
shopId,
setModal,
@@ -292,8 +297,11 @@ export default function Invoice({
email,
orderMethod,
orderType,
tableNumber
tableNumber,
textareaRef.current.value
);
if (pay) window.location.reload();
} else if (deviceType == "guestSide") {
const pay = await handlePaymentFromGuestSide(
shopId,
@@ -302,6 +310,8 @@ export default function Invoice({
orderType,
tableNumber
);
if (pay) window.location.reload();
} else if (deviceType == "guestDevice") {
const socketId = socket.id;
const pay = await handlePaymentFromGuestDevice(
@@ -312,6 +322,7 @@ export default function Invoice({
textareaRef.current.value,
socketId
);
if (pay) window.location.reload();
}
console.log("transaction from " + deviceType + "success");
@@ -598,7 +609,7 @@ export default function Invoice({
<span>Pesan</span>
)}
<span>Rp{totalPrice}</span>
<span>Rp{formatToRupiah(totalPrice)}</span>
</div>
)}
</button>
@@ -654,17 +665,19 @@ export default function Invoice({
<span>
Rp
{transactionData.DetailedTransactions.reduce(
(total, transaction) => {
return (
total +
(transaction.promoPrice == 0 ||
transaction.promoPrice == null
? transaction.price * transaction.qty
: transaction.promoPrice * transaction.qty)
);
},
0
{formatToRupiah(
transactionData.DetailedTransactions.reduce(
(total, transaction) => {
return (
total +
(transaction.promoPrice == 0 ||
transaction.promoPrice == null
? transaction.price * transaction.qty
: transaction.promoPrice * transaction.qty)
);
},
0
)
)}
</span>
</div>
@@ -694,7 +707,7 @@ export default function Invoice({
<span>Pesan</span>
)}
<span>Rp{totalPrice}</span>
<span>Rp{formatToRupiah(totalPrice)}</span>
</div>
)}
</button>

View File

@@ -69,7 +69,7 @@
.TotalContainer {
display: flex;
justify-content: space-between;
width: 80vw;
/* width: 80vw; */
margin: 0 auto;
font-family: "Plus Jakarta Sans", sans-serif;
font-weight: 600;
@@ -466,7 +466,7 @@
.TotalContainer {
display: flex;
justify-content: space-between;
width: 80vw;
/* width: 80vw; */
margin: 0 auto;
font-family: "Plus Jakarta Sans", sans-serif;
font-weight: 600;

View File

@@ -67,7 +67,7 @@ export default function Transactions({ propsShopId, sendParam, deviceType, handl
if (c) {
console.log(c)
setTransaction(c);
setTransactionRefreshKey(transactionRefreshKey+1);
setTransactionRefreshKey(transactionRefreshKey + 1);
}
} catch (error) {
console.error("Error processing payment:", error);
@@ -84,7 +84,7 @@ export default function Transactions({ propsShopId, sendParam, deviceType, handl
if (c) {
console.log(c)
setTransaction({ ...transaction, confirmed: c.confirmed });
setTransactionRefreshKey(transactionRefreshKey+1);
setTransactionRefreshKey(transactionRefreshKey + 1);
}
// if (c) {
// // Update the confirmed status locally
@@ -115,6 +115,9 @@ export default function Transactions({ propsShopId, sendParam, deviceType, handl
autoResizeTextArea(noteRef.current);
}
}, [transaction?.notes]);
const handlePrint = () => {
window.print();
};
return (
<div key={transactionRefreshKey} className={styles.Transaction}>
@@ -128,18 +131,17 @@ export default function Transactions({ propsShopId, sendParam, deviceType, handl
setSelectedTable(transaction.Table || { tableId: 0 })
}
>
<div className={styles['receipt-header']}>
<div className={styles['receipt-info']}>
<h3>{transaction.payment_type == 'cash' ? 'Tunai' : transaction.payment_type == 'cashless' ? 'Non tunai' : transaction.payment_type == 'paylater' ? 'Open bill' :'Close bill' }</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>
{
transaction.payment_type == 'paylater/cash' ?
<p>Cash</p>
:
(transaction.payment_type == 'paylater/cashless' &&
<p>Non tunai</p>
)
<p>Cash</p>
:
(transaction.payment_type == 'paylater/cashless' &&
<p>Non tunai</p>
)
}
</div>
</div>
@@ -161,8 +163,8 @@ export default function Transactions({ propsShopId, sendParam, deviceType, handl
</svg>
</div>
{depth > 0 &&
<div className={styles.circle}>{depth}</div>
}
<div className={styles.circle}>{depth}</div>
}
</div>
<div className={styles['line']} ></div>
<div className={styles['circle-right']} onClick={() => handleMoveToTransaction('next', transaction.transactionId)}>
@@ -188,7 +190,7 @@ export default function Transactions({ propsShopId, sendParam, deviceType, handl
<div>
{transaction.DetailedTransactions.map((detail) => (
<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' }}
<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)
@@ -225,27 +227,27 @@ export default function Transactions({ propsShopId, sendParam, deviceType, handl
overflow: 'hidden',
textOverflow: 'ellipsis'
}}>
{detail.Item.name}</span> - {notAcceptedItems.includes(detail.detailedTransactionId) || detail.qty < 1 ? 'tidak tersedia' : `${detail.qty} x Rp
{detail.Item.name}</span> - {notAcceptedItems.includes(detail.detailedTransactionId) || detail.qty < 1 ? 'tidak tersedia' : `${detail.qty} x Rp
${detail.promoPrice ? detail.promoPrice : detail.price}`}
</div>
))}
</div>
{!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);
{!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>
}
// Dispatch the custom event
window.dispatchEvent(new Event("localStorageUpdated"));
}}
className={styles["addNewItem"]}
>
Tambah pesanan
</div>
}
<h2 className={styles["Transactions-detail"]}>
{transaction.serving_type === "pickup"
@@ -311,9 +313,55 @@ export default function Transactions({ propsShopId, sendParam, deviceType, handl
batalkan
</h5>
)}
{transaction.confirmed > 1 && (
<h5
className={styles.DeclineButton}
onClick={() => handlePrint()}
>
Cetak struk
</h5>
)}
</div>
)}
</div>
{transaction &&
<div id="print-section" className={styles.printContainer}>
<div className="receipt">
<h2 className="center-text">Struk Pembayaran</h2>
<hr />
<p>ID Transaksi: {transaction.transactionId}</p>
<p>Metode: {transaction.payment_type === 'cash' ? 'Tunai' :
transaction.payment_type === 'cashless' ? 'Non Tunai' :
'Open Bill'}</p>
<p>Meja: {transaction.Table?.tableNo || '-'}</p>
<hr />
{transaction.DetailedTransactions.map((detail) => (
<div key={detail.detailedTransactionId} className="item-line">
<p>{detail.Item.name}</p>
<p>{detail.qty} x Rp{detail.promoPrice || detail.price}</p>
</div>
))}
<hr />
<p className="total">Total: Rp {calculateTotalPrice(transaction.DetailedTransactions)}</p>
{transaction.notes && (
<>
<hr />
<p>Catatan:</p>
<p>{transaction.notes}</p>
</>
)}
<hr />
<p className="center-text">Terima Kasih!</p>
</div>
</div>
}
</div>
);
}

View File

@@ -198,7 +198,7 @@ console.log(aggregatedItems.values())
<div className={styles['receipt-header']}>
{transaction.confirmed === 1 ? (
<ColorRing className={styles['receipt-logo']} />
) : transaction.confirmed === -1 && !transaction.is_paid || transaction.confirmed === -2 && !transaction.is_paid ? (
) : (transaction.confirmed == -1 && !transaction.is_paid) || (transaction.confirmed === -2 && !transaction.is_paid) ? (
<div style={{ display: 'flex', justifyContent: 'center', margin: '16px 0px' }}>
<svg
style={{ width: '60px', transform: 'Rotate(45deg)' }}

View File

@@ -395,4 +395,56 @@
text-align: center;
color: white;
line-height: 27px;
}
}
/* Transactions.module.css */
.printContainer {
display: none; /* Hidden in normal view */
}
/* Print-specific styles */
@media print {
.Transaction {
display: none; /* Hide everything else when printing */
}
.printContainer {
display: block !important;
position: static;
background: white;
color: black;
font-family: 'Courier New', Courier, monospace;
padding: 20px;
}
.receipt {
width: 100%;
max-width: 400px;
margin: 0 auto;
}
.center-text {
text-align: center;
font-weight: bold;
}
.item-line {
display: flex;
justify-content: space-between;
margin-bottom: 8px;
}
.total {
text-align: right;
font-size: 1.2em;
font-weight: bold;
margin-top: 20px;
}
button, .DeclineButton, .addNewItem {
display: none !important;
}
}