Compare commits

..

1 Commits

Author SHA1 Message Date
karyamanswasta
2add1c5090 ok 2025-08-27 08:15:48 +07:00
2 changed files with 219 additions and 212 deletions

View File

@@ -30,6 +30,15 @@ export default function Transactions({ shop, shopId, propsShopId, sendParam, dev
const [loading, setLoading] = useState(false);
// Currency formatter (thousand separators, with Rp prefix)
const formatRp = (value) => `Rp ${new Intl.NumberFormat('id-ID').format(Math.round(Number(value || 0)))}`;
const getStatusClass = (t) => {
if (t.confirmed === 3 || t.is_paid) return styles.statusSuccess;
if (t.confirmed === -1 || t.confirmed === -2) return styles.statusCancelled;
return styles.statusNeutral;
};
useEffect(() => {
setMatchedItems(searchAndAggregateItems(transactions, searchTerm));
}, [searchTerm, transactions]);
@@ -154,7 +163,7 @@ console.log(aggregatedItems.values())
return (
<div className={styles.Transactions}>
<h2 className={styles["Transactions-title"]}>
Transaksi selesai Rp {calculateAllTransactionsTotal(transactions)}
Transaksi selesai {formatRp(calculateAllTransactionsTotal(transactions))}
</h2>
<input
@@ -162,13 +171,13 @@ console.log(aggregatedItems.values())
placeholder="Cari nama item..."
value={searchTerm}
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: '36px', borderRadius: '12px', margin: '6px auto 10px', width: '88%', padding: '0 8px', fontSize: '14px' }}
/>
{/* Existing Transactions List (keep all your JSX below unchanged) */}
<div className={styles.TransactionListContainer} style={{ padding: '0 8px' }}>
<div className={styles.TransactionListContainer}>
{matchedItems.length > 0 && matchedItems.map(item => (
<div
@@ -183,7 +192,7 @@ console.log(aggregatedItems.values())
</ul>
<div className={styles.TotalContainer}>
<span>Total:</span>
<span>Rp {item.totalPrice}</span>
<span>{formatRp(item.totalPrice)}</span>
</div>
</div>
))}
@@ -191,94 +200,60 @@ console.log(aggregatedItems.values())
transactions.map((transaction) => (
<div
key={transaction.transactionId}
className={styles.RoundedRectangle}
style={{ overflow: 'hidden' }}
className={`${styles.RoundedRectangle} ${!transaction.is_paid ? styles.unpaid : ''}`}
>
<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 ? (
<div style={{ display: 'flex', justifyContent: 'center', margin: '16px 0px' }}>
<svg
style={{ width: '60px', transform: 'Rotate(45deg)' }}
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"
/>
) : (transaction.confirmed === -1 && !transaction.is_paid) || (transaction.confirmed === -2 && !transaction.is_paid) ? (
<div style={{ display: 'flex', justifyContent: 'center', margin: '12px 0' }}>
<svg width="60" height="60" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M18.3 5.7a1 1 0 0 0-1.4 0L12 10.6 7.1 5.7A1 1 0 0 0 5.7 7.1L10.6 12l-4.9 4.9a1 1 0 1 0 1.4 1.4L12 13.4l4.9 4.9a1 1 0 0 0 1.4-1.4L13.4 12l4.9-4.9a1 1 0 0 0 0-1.4z" fill="#E45454"/>
</svg>
</div>
) : transaction.confirmed === 2 && !transaction.is_paid ? (
<ColorRing className={styles['receipt-logo']} />
) : transaction.confirmed === 3 || transaction.is_paid ? (
<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>
<div style={{ display: 'flex', justifyContent: 'center', margin: '12px 0' }}>
<svg width="60" height="60" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M9 16.2 4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4z" fill="#54B265"/>
</svg>
</div>
) : (
<ColorRing className={styles['receipt-logo']} />
)}
<div className={styles['receipt-info']}>
{deviceType == 'clerk' ?
<h3>{transaction.confirmed === 1 && !transaction.is_paid ? (
"Silahkan Cek Pembayaran"
) : transaction.confirmed === -1 && !transaction.is_paid ? (
"Dibatalkan Oleh Kasir"
) : transaction.confirmed === -2 && !transaction.is_paid ? (
"Dibatalkan Oleh Pelanggan"
) : transaction.confirmed === 2 && !transaction.is_paid ? (
"Sedang Diproses"
) : transaction.confirmed === 3 || transaction.is_paid ? (
"Transaksi Sukses"
) : (
"Silahkan Cek Ketersediaan"
)}</h3>
:
<h3>{transaction.confirmed === 1 ? (
(transaction.payment_type == 'cash' ? 'Silahkan Bayar Ke Kasir' : "Silahkan Bayar Dengan QRIS")
) : transaction.confirmed === -1 ? (
"Dibatalkan Oleh Kasir"
) : transaction.confirmed === -2 ? (
"Dibatalkan Oleh Pelanggan"
) : transaction.confirmed === 2 ? (
"Sedang diproses"
) : transaction.confirmed === 3 ? (
"Transaksi Sukses"
) : (
"Memeriksa Ketersediaan "
)}</h3>}
{deviceType == 'clerk' ?
<h3 className={getStatusClass(transaction)}>{transaction.confirmed === 1 && !transaction.is_paid ? (
"Silahkan Cek Pembayaran"
) : transaction.confirmed === -1 && !transaction.is_paid ? (
"Dibatalkan Oleh Kasir"
) : transaction.confirmed === -2 && !transaction.is_paid ? (
"Dibatalkan Oleh Pelanggan"
) : transaction.confirmed === 2 && !transaction.is_paid ? (
"Sedang Diproses"
) : transaction.confirmed === 3 || transaction.is_paid ? (
"Transaksi Sukses"
) : (
"Silahkan Cek Ketersediaan"
)}</h3>
:
<h3 className={getStatusClass(transaction)}>{transaction.confirmed === 1 ? (
(transaction.payment_type == 'cash' ? 'Silahkan Bayar Ke Kasir' : "Silahkan Bayar Dengan QRIS")
) : transaction.confirmed === -1 ? (
"Dibatalkan Oleh Kasir"
) : transaction.confirmed === -2 ? (
"Dibatalkan Oleh Pelanggan"
) : transaction.confirmed === 2 ? (
"Sedang diproses"
) : transaction.confirmed === 3 ? (
"Transaksi Sukses"
) : (
"Memeriksa Ketersediaan "
)}</h3>}
<p>Transaction ID: {transaction.transactionId}</p>
<p>Payment Type: {transaction.payment_type}</p>
@@ -304,12 +279,11 @@ console.log(aggregatedItems.values())
<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}`}
<span>{detail.Item.name}</span> - {detail.qty < 1 ? 'tidak tersedia' : `${detail.qty} x ${formatRp(detail.promoPrice ? detail.promoPrice : detail.price)}`}
</li>
))}
</ul>
{!transaction.is_paid && transaction.confirmed > -1 &&
{!transaction.is_paid && transaction.confirmed > -1 && (
<div
onClick={() => {
localStorage.setItem('lastTransaction', JSON.stringify(transaction));
@@ -322,12 +296,11 @@ console.log(aggregatedItems.values())
>
Tambah pesanan
</div>
}
)}
<h2 className={styles["Transactions-detail"]}>
{transaction.serving_type === "pickup"
? "Self pickup"
: `Serve to ${transaction.Table ? transaction.Table.tableNo : "N/A"
}`}
: `Serve to ${transaction.Table ? transaction.Table.tableNo : "N/A"}`}
</h2>
{transaction.notes && (
@@ -350,38 +323,44 @@ console.log(aggregatedItems.values())
<div className={styles.TotalContainer}>
<span>Total:</span>
<span>
Rp {calculateTotalPrice(transaction.DetailedTransactions)}
{formatRp(calculateTotalPrice(transaction.DetailedTransactions))}
</span>
</div>
<div className={styles.TotalContainer}>
{(deviceType == 'clerk' && !transaction.is_paid && (transaction.confirmed == 0 || transaction.confirmed == 1 || transaction.confirmed == 2)) &&
<button
className={styles.PayButton}
onClick={() => handleConfirm(transaction.transactionId)}
disabled={isPaymentLoading}
>
{
isPaymentLoading ? (
<ColorRing height="50" width="50" color="white" />
) : transaction.confirmed === 1 ? (
"Konfirmasi Telah Bayar"
) : transaction.confirmed === 2 ? (
"Confirm item is ready"
) : (
"Confirm availability"
)
}
</button>
}
{(deviceType == 'clerk' && !transaction.is_paid && (transaction.confirmed == 0 || transaction.confirmed == 1 || transaction.confirmed == 2)) && (
<div className={styles.ActionRow}>
<button
className={styles.PayButton}
onClick={() => handleConfirm(transaction.transactionId)}
disabled={isPaymentLoading}
>
{
isPaymentLoading ? (
<ColorRing height="28" width="28" color="white" />
) : transaction.confirmed === 1 ? (
"Konfirmasi"
) : transaction.confirmed === 2 ? (
"Confirm item is ready"
) : (
"Confirm availability"
)
}
</button>
<button
className={styles.DeclineButton}
onClick={() => handleDecline(transaction.transactionId)}
disabled={isPaymentLoading}
>
{isPaymentLoading ? '...' : 'Batalkan'}
</button>
</div>
)}
{deviceType == 'guestDevice' && transaction.confirmed < 2 && transaction.payment_type != 'cash' && transaction.payment_type != 'paylater/cash' &&
<ButtonWithReplica
paymentUrl={paymentUrl}
price={
"Rp" + calculateTotalPrice(transaction.DetailedTransactions)
}
price={formatRp(calculateTotalPrice(transaction.DetailedTransactions))}
disabled={isPaymentLoading}
isPaymentLoading={isPaymentLoading}
handleClick={() => handleConfirm(transaction.transactionId)}
@@ -404,40 +383,30 @@ console.log(aggregatedItems.values())
}
</div>
{deviceType == 'guestDevice' && transaction.confirmed >= 0 && transaction.confirmed < 2 && 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.confirmed >= 0 && transaction.confirmed < 2 && transaction.payment_type != 'paylater/cash' && transaction.payment_type != 'paylater/cashless' || isPaymentOpen) &&
<h5
className={`${styles.DeclineButton}`}
onClick={() =>
isPaymentOpen
? setIsPaymentOpen(false)
: handleDecline(transaction.transactionId)
{deviceType == 'guestDevice' && (
transaction.confirmed >= 0 && transaction.confirmed < 2 && transaction.payment_type == 'cash' ? (
<button
className={styles.DeclineButton}
onClick={() => handleDecline(transaction.transactionId)}
disabled={
transaction.confirmed === -1 ||
transaction.confirmed === 3 ||
isPaymentLoading
}
>
{isPaymentOpen ? "kembali" : "batalkan"}
</h5>
{isPaymentLoading ? '...' : 'Batalkan'}
</button>
) : (
(transaction.confirmed >= 0 && transaction.confirmed < 2 && (transaction.payment_type != 'paylater/cash' && transaction.payment_type != 'paylater/cashless' || isPaymentOpen)) && (
<button
className={styles.DeclineButton}
onClick={() => isPaymentOpen ? setIsPaymentOpen(false) : handleDecline(transaction.transactionId)}
>
{isPaymentOpen ? 'Kembali' : 'Batalkan'}
</button>
)
)
}
)}
</div>
))}
</div>

View File

@@ -13,41 +13,43 @@
}
.Transactions {
overflow-x: hidden;
background-color: white;
background-color: #f5f7f6;
display: flex;
flex-direction: column;
justify-content: flex-start;
font-size: calc(10px + 2vmin);
color: rgba(88, 55, 50, 1);
background-color: #e9e9e9;
color: rgba(40, 40, 40, 1);
}
.Transactions-title {
font-family: "Plus Jakarta Sans", sans-serif;
font-weight: 500;
font-weight: 700;
font-style: normal;
font-size: 6vw;
color: black;
font-size: clamp(18px, 3.6vw, 24px);
color: var(--brand-sage, #6B8F71);
text-align: left;
margin-left: 20px;
margin-top: 57px;
margin: 16px 16px 8px 16px;
}
.Transactions-detail {
font-family: "Plus Jakarta Sans", sans-serif;
font-weight: 500;
font-style: normal;
font-size: 15px;
color: rgba(88, 55, 50, 1);
font-size: 13px;
color: #555;
text-align: left;
margin-left: 20px;
margin-top: 17px;
margin-left: 16px;
margin-top: 12px;
}
.TransactionListContainer {
overflow-y: auto; /* Enables vertical scrolling */
background-color: #dbdbdb;
background-color: transparent;
overflow: visible;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 12px;
padding: 8px 12px 16px;
}
.TotalContainer {
@@ -57,14 +59,14 @@
/* width: 100%; */
margin: 0 auto;
font-family: "Plus Jakarta Sans", sans-serif;
font-weight: 600;
font-weight: 700;
font-style: normal;
font-size: 15px;
font-size: 13px;
/* padding: 10px; */
box-sizing: border-box;
margin-bottom: 17px;
margin-left: 20px;
margin-right: 20px;
margin-bottom: 12px;
margin-left: 16px;
margin-right: 16px;
}
.PaymentContainer {
@@ -83,61 +85,66 @@
.PayButton {
font-family: "Plus Jakarta Sans", sans-serif;
font-weight: 500;
font-weight: 600;
font-style: normal;
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);
font-size: 12px;
padding: 10px 14px;
border-radius: 10px; /* square rounded */
background-color: var(--brand-primary, #73a585);
color: white;
border: none;
margin: 0 auto;
cursor: pointer;
display: block; /* Centering the button */
display: inline-flex;
align-items: center;
justify-content: center;
text-align: center;
}
.DeclineButton {
font-family: "Plus Jakarta Sans", sans-serif;
z-index: 201;
position: relative;
font-weight: 500;
font-weight: 600;
font-style: normal;
font-size: 15px;
padding: 12px 24px; /* Add some padding for spacing */
color: rgba(88, 55, 50, 1);
background-color: transparent; /* No background */
border: none; /* No border */
margin: 0 auto; /* Center horizontally */
font-size: 12px;
padding: 10px 14px;
color: #444;
background-color: #f0f0f0;
border: 1px solid #e0e0e0;
border-radius: 10px; /* square rounded */
cursor: pointer;
display: block; /* Center the text horizontally */
text-align: center; /* Center the text within the button */
display: inline-flex;
align-items: center;
justify-content: center;
}
.DeclineButton.active {
position: relative;
z-index: 201;
font-family: "Plus Jakarta Sans", sans-serif;
font-weight: 500;
font-style: normal;
font-size: 20px;
padding: 12px 24px; /* Add some padding for spacing */
color: rgba(88, 55, 50, 1);
background-color: transparent; /* No background */
border: none; /* No border */
margin: 0 auto; /* Center horizontally */
cursor: pointer;
display: block; /* Center the text horizontally */
text-align: center; /* Center the text within the button */
margin-bottom: 23px; /* Space at the bottom to match the PayButton */
.DeclineButton.active { opacity: 0.9; }
/* Row for primary/secondary action buttons */
.ActionRow {
display: flex;
gap: 8px;
width: 100%;
justify-content: center;
}
.RoundedRectangle {
position: relative;
border-radius: 20px;
padding: 15px; /* Adjusted for better spacing */
margin: 12px;
background-color: #f9f9f9;
border-radius: 16px;
padding: 12px 12px 10px;
margin: 0;
background-color: #ffffff;
border: 1px solid var(--brand-sage-100, #E9F3ED);
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.06);
display: flex;
flex-direction: column;
min-height: 280px; /* base height, grows with content */
}
/* Unpaid tickets: taller to keep content inside ticket design */
.unpaid {
min-height: 380px;
}
/* Self pickup accent: subtle brand accent */
/* removed pickup accent and badge per request */
.expression {
width: 100%;
}
@@ -201,20 +208,28 @@
}
.receipt-logo {
width: 80px;
height: 80px;
width: 60px;
height: 60px;
border-radius: 50%; /* Circular logo */
object-fit: cover;
margin-bottom: 10px;
margin-bottom: 8px;
}
.receipt-info h3 {
font-size: 16px;
margin: 5px 0;
font-size: 13px;
font-weight: 700;
color: var(--brand-sage, #6B8F71);
margin: 4px 0 2px;
}
/* Status-colors override */
.receipt-info h3.statusNeutral { color: var(--brand-sage, #6B8F71); }
.receipt-info h3.statusSuccess { color: #54B265; }
.receipt-info h3.statusCancelled { color: #E45454; }
.receipt-info p {
font-size: 14px;
font-size: 12px;
color: #666;
margin: 2px 0;
}
/* Dotted line with circular cutouts */
@@ -222,39 +237,61 @@
display: flex;
align-items: center;
justify-content: center;
margin: 15px 0;
margin: 12px 0 10px;
}
.dotted-line .line {
border-top: 13px dotted #dbdbdb;
border-top: 10px dotted #e9e9e9;
width: 100%;
margin: 0 18px;
}
.dotted-line .circle-left {
left: -25px;
left: -18px;
position: absolute;
width: 50px;
height: 50px;
width: 36px;
height: 36px;
border-radius: 50%;
background-color: #dbdbdb;
background-color: #e9e9e9;
display: flex; /* Use flexbox to center the inner circle */
justify-content: center; /* Center horizontally */
align-items: center; /* Center vertically */
}
.dotted-line .circle-right {
right: -25px;
right: -18px;
position: absolute;
width: 50px;
height: 50px;
width: 36px;
height: 36px;
border-radius: 50%;
background-color: #dbdbdb;
background-color: #e9e9e9;
display: flex; /* Use flexbox to center the inner circle */
justify-content: center; /* Center horizontally */
align-items: center; /* Center vertically */
}
/* No scroll body for items per request */
/* Compact list text */
.RoundedRectangle ul {
list-style: none;
padding: 0 12px 6px;
margin: 0;
}
.RoundedRectangle ul li {
font-size: 12px;
color: #333;
padding: 3px 0;
}
/* Grid density tweaks */
@media (min-width: 640px) {
.TransactionListContainer { gap: 14px; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }
}
@media (min-width: 1024px) {
.TransactionListContainer { gap: 16px; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }
}
.inner-circle {
width: 80%;
height: 80%;
@@ -389,10 +426,11 @@
.addNewItem{
width: 100%;
height: 27px;
background-color: rgb(115, 165, 133);
border-radius: 11px;
height: 28px;
background-color: var(--brand-primary, rgb(115, 165, 133));
border-radius: 10px; /* square rounded */
text-align: center;
color: white;
line-height: 27px;
line-height: 28px;
font-size: 12px; /* compact */
}