Compare commits

...

4 Commits

Author SHA1 Message Date
karyamanswasta
2add1c5090 ok 2025-08-27 08:15:48 +07:00
karyamanswasta
3fbe0bb870 ok 2025-08-27 07:41:31 +07:00
karyamanswasta
dae7fb9221 ok 2025-08-27 07:12:39 +07:00
karyamanswasta
6ed982d6ef ok 2025-08-27 07:00:14 +07:00
16 changed files with 557 additions and 383 deletions

View File

@@ -36,7 +36,7 @@ body {
height: 100%; height: 100%;
width: 100%; width: 100%;
background-image: url(https://i.ibb.co.com/F4FMw1jz/testuseonly.png); background-image: url(https://i.ibb.co.com/F4FMw1jz/testuseonly.png);
z-index: 1000; z-index: 0; /* align with item lister */
filter: opacity(0.04); filter: opacity(0.04);
pointer-events: none; pointer-events: none;
} }
@@ -67,15 +67,136 @@ body {
/* Ensure sticky cart bar stays above item overlays */ /* Ensure sticky cart bar stays above item overlays */
.StickyCartBar { .StickyCartBar {
z-index: 100 !important; /* Menurunkan z-index agar tidak menutupi material list */ z-index: 120 !important; /* above content, below modal */
position: fixed; position: fixed;
bottom: 0;
left: 0; left: 0;
right: 0; right: 0;
background-color: white; bottom: 24px;
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1); background: transparent;
padding: 15px 20px; box-shadow: none;
border-radius: 20px 20px 0 0; pointer-events: none; /* only children capture clicks */
display: flex;
justify-content: center;
align-items: center;
padding: 0 12px;
}
/* Intro slide/fade when bar first appears */
.StickyCartBar.intro { animation: bar-in 450ms ease-out; }
@keyframes bar-in {
0% { opacity: 0; transform: translateY(12px); }
100% { opacity: 1; transform: translateY(0); }
}
/* Floating cart buttons inside sticky bar */
.StickyCartBar .cartBtn {
pointer-events: auto;
display: flex;
align-items: center;
justify-content: space-between;
height: 44px;
padding: 0 14px;
border-radius: 999px;
border: none;
background: rgba(115,165,133,0.96);
color: #fff;
backdrop-filter: blur(3px);
box-shadow: 0 12px 28px rgba(115,165,133,0.35);
width: clamp(200px, 60vw, 420px);
transition: transform 0.15s ease, box-shadow 0.15s ease, background 0.2s ease;
position: relative;
}
.StickyCartBar .cartBtn:hover { box-shadow: 0 14px 32px rgba(115,165,133,0.4); transform: translateY(-1px); }
.StickyCartBar .cartBtn:active { transform: translateY(0); }
.StickyCartBar .cartBtn:focus-visible { outline: 3px solid rgba(115,165,133,0.4); outline-offset: 2px; }
/* Bump + ping animation when items are added */
.StickyCartBar .cartBtn.bump { animation: cart-bump 300ms ease; }
.StickyCartBar .cartBtn.bump::after {
content: '';
position: absolute;
inset: 0;
border-radius: inherit;
box-shadow: 0 0 0 0 rgba(115,165,133,0.35);
animation: cart-ping 600ms ease-out;
}
@keyframes cart-bump {
0% { transform: scale(1); }
10% { transform: scale(0.98); }
30% { transform: scale(1.04); }
50% { transform: scale(1.02); }
100% { transform: scale(1); }
}
@keyframes cart-ping {
0% { box-shadow: 0 0 0 0 rgba(115,165,133,0.35); }
100% { box-shadow: 0 0 0 14px rgba(115,165,133,0); }
}
.StickyCartBar .historyBtn {
pointer-events: auto;
display: flex;
align-items: center;
justify-content: center;
width: 48px;
height: 44px;
margin-left: 8px;
border-radius: 999px;
border: none;
background: rgba(115,165,133,0.96);
color: #fff;
backdrop-filter: blur(3px);
box-shadow: 0 12px 28px rgba(115,165,133,0.35);
transition: transform 0.15s ease, box-shadow 0.15s ease, background 0.2s ease;
}
.StickyCartBar .historyBtn:hover { box-shadow: 0 14px 32px rgba(115,165,133,0.4); transform: translateY(-1px); }
.StickyCartBar .historyBtn:active { transform: translateY(0); }
.StickyCartBar .historyBtn:focus-visible { outline: 3px solid rgba(115,165,133,0.4); outline-offset: 2px; }
/* Subtle pulse to draw attention on new transaction */
.StickyCartBar .historyBtn.pulse { animation: btn-pulse 900ms ease-out; }
@keyframes btn-pulse {
0% { box-shadow: 0 12px 28px rgba(115,165,133,0.35); }
60% { box-shadow: 0 18px 38px rgba(115,165,133,0.5); }
100% { box-shadow: 0 12px 28px rgba(115,165,133,0.35); }
}
.StickyCartBar .summary {
display: flex;
align-items: center;
gap: 8px;
font-weight: 700;
}
.StickyCartBar .value { white-space: nowrap; font-weight: 800; }
.StickyCartBar .icon { display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 22px; margin-left: 8px; }
.StickyCartBar .icon { position: relative; }
.StickyCartBar .icon .badge {
position: absolute;
top: -6px;
right: -6px;
width: 18px;
height: 18px;
border-radius: 50%;
background: #ff6b6b;
color: #fff;
font-size: 11px;
font-weight: 800;
line-height: 18px;
text-align: center;
padding: 0;
box-shadow: 0 2px 6px rgba(255,107,107,0.35);
}
.StickyCartBar .icon .badge.pop { animation: badge-pop 280ms ease-out; }
@keyframes badge-pop {
0% { transform: scale(0.8); }
50% { transform: scale(1.2); }
100% { transform: scale(1); }
}
@media (max-width: 420px) {
.StickyCartBar { bottom: 16px; }
.StickyCartBar .cartBtn { height: 46px; }
.StickyCartBar .historyBtn { height: 46px; width: 48px; }
} }
.App-link { .App-link {

View File

@@ -23,7 +23,7 @@ const HeaderBar = styled.div`
padding: 12px 14px; padding: 12px 14px;
color: black; color: black;
background-color: #ffffff; background-color: #ffffff;
z-index: 200; z-index: ${(props) => (props.zIndexLevel !== undefined ? props.zIndexLevel : 200)};
border: 1px solid #00000000; border: 1px solid #00000000;
margin: 20px 12px; margin: 20px 12px;
border-radius: 13px; border-radius: 13px;
@@ -148,7 +148,7 @@ const Rectangle = styled.div`
width: 240px; width: 240px;
max-height: 75vh; max-height: 75vh;
background-color: white; background-color: white;
z-index: 198; z-index: ${(props) => (props.baseZIndex !== undefined ? props.baseZIndex : 198)};
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12); box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
border: 1px solid #f0f0f0; border: 1px solid #f0f0f0;
border-radius: 12px; border-radius: 12px;
@@ -276,7 +276,9 @@ const Header = ({
removeConnectedGuestSides, removeConnectedGuestSides,
setIsEditMode, setIsEditMode,
isEditMode, isEditMode,
HeaderMargin = '25px' HeaderMargin = '25px',
zIndexLevel,
rectZIndex
}) => { }) => {
const { goToLogin, goToGuestSideLogin, goToAdminCafes } = const { goToLogin, goToGuestSideLogin, goToAdminCafes } =
useNavigationHelpers(shopId, tableCode); useNavigationHelpers(shopId, tableCode);
@@ -354,7 +356,7 @@ const Header = ({
}; };
return ( return (
<HeaderBarbackground shopName={shopName}> <HeaderBarbackground shopName={shopName}>
<HeaderBar HeaderMargin={HeaderMargin} shopName={shopName}> <HeaderBar HeaderMargin={HeaderMargin} shopName={shopName} zIndexLevel={zIndexLevel}>
<LeftGroup> <LeftGroup>
<CafeAvatar <CafeAvatar
src={shopImage && !shopImage.includes('undefined') ? shopImage : "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcS-DjX_bGBax4NL14ULvkAdU4FP3FKoWXWu5w&s"} src={shopImage && !shopImage.includes('undefined') ? shopImage : "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcS-DjX_bGBax4NL14ULvkAdU4FP3FKoWXWu5w&s"}
@@ -378,7 +380,7 @@ const Header = ({
<HamburgerIcon /> <HamburgerIcon />
</HamburgerButton> </HamburgerButton>
{showRectangle && ( {showRectangle && (
<Rectangle ref={rectangleRef} animate={animate}> <Rectangle ref={rectangleRef} animate={animate} baseZIndex={rectZIndex !== undefined ? rectZIndex : zIndexLevel}>
<ChildContainer> <ChildContainer>
{guestSideOfClerk && guestSideOfClerk.clerkUsername && ( {guestSideOfClerk && guestSideOfClerk.clerkUsername && (
<Child hasChildren> <Child hasChildren>

View File

@@ -5,6 +5,7 @@ const Item = ({
forCart, forCart,
forInvoice, forInvoice,
portrait, portrait,
hideDetails,
name: initialName, name: initialName,
description: initialDescription, description: initialDescription,
price: initialPrice, price: initialPrice,
@@ -147,75 +148,14 @@ const Item = ({
)} )}
</div> </div>
)} )}
<div className={styles.itemDetails}>
{forInvoice &&
<div className={styles.plusNegative2} onClick={onRemoveClick}>
</div>
}
{/* Title under image for portrait, non-overlay */}
{portrait && null}
{!portrait && (
<div style={{ marginRight: forInvoice ? 10 : 0 }}>
<h3 className={styles.title} style={{ width: forInvoice ? 160 : 'auto' }}>{displayName}</h3>
{initialDescription && !forInvoice && (
<p className={styles.desc}>{initialDescription}</p>
)}
{!forInvoice && (
<div className={styles.priceRow}>
{promoPrice && promoPrice != 0 && promoPrice != '' ? (
<>
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
<span className={`${styles.itemPriceList} ${styles.promo}`}>Rp {formatCurrency(promoPrice)}</span>
<span className={`${styles.itemPriceList} ${styles.original}`}>Rp {formatCurrency(initialPrice)}</span>
</div>
</>
) : (
<span className={styles.itemPriceList}>Rp {formatCurrency(initialPrice)}</span>
)}
</div>
)}
</div>
)}
{forInvoice && ( {forInvoice && (
<> <div className={styles.itemDetails}>
<div className={styles.plusNegative2} onClick={onRemoveClick}></div>
<p className={styles.multiplySymbol}>x</p> <p className={styles.multiplySymbol}>x</p>
<p className={styles.qtyInvoice}>{itemQty}</p> <p className={styles.qtyInvoice}>{itemQty}</p>
</>
)}
{!forInvoice && (
portrait ? (
(blank || isBeingEdit) ? (
<div className={styles.itemQty}>
<button className={styles.addButton} style={{ backgroundColor: '#ffffff', color: 'var(--brand-sage, #6B8F71)', borderColor: 'var(--brand-sage, #6B8F71)', minWidth: 90, height: 36, fontSize: 14 }} onClick={isBeingEdit ? handleUpdate : handleCreate}>
{isBeingEdit ? 'Simpan' : 'Buat'}
</button>
</div>
) : null
) : (
!isBeingEdit ? (
<div className={styles.itemQty}>
<div className={styles.qtyGroup}>
<button className={styles.qtyBtn} onClick={handleNegativeClick} aria-label="Kurangi" style={{ width: 36, height: 36, fontSize: 18 }}>-</button>
<span className={styles.qtyVal}>{itemQty}</span>
<button className={styles.qtyBtn} onClick={handlePlusClick} aria-label="Tambah" style={{ width: 36, height: 36, fontSize: 18 }}>+</button>
</div>
</div>
) : (
<div className={styles.itemQty}>
<button className={styles.addButton} style={{ backgroundColor: '#ffffff', color: 'var(--brand-sage, #6B8F71)', borderColor: 'var(--brand-sage, #6B8F71)', width: 130, height: 36, fontSize: 14 }} onClick={isBeingEdit ? handleUpdate : handleCreate}>
{isBeingEdit ? 'Simpan' : 'Buat'}
</button>
</div>
)
)
)}
{forInvoice && (
<p className={styles.itemPriceInvoice}>Rp {formatCurrency(itemQty * (promoPrice > 0 ? promoPrice : itemPrice))}</p> <p className={styles.itemPriceInvoice}>Rp {formatCurrency(itemQty * (promoPrice > 0 ? promoPrice : itemPrice))}</p>
)}
</div> </div>
)}
{forCart && ( {forCart && (
<div className={styles.remove} onClick={handleRemoveClick}> <div className={styles.remove} onClick={handleRemoveClick}>

View File

@@ -20,10 +20,7 @@
position: relative; position: relative;
} }
.item:hover { .item:hover { /* remove hover effect on list items */ }
box-shadow: 0 4px 12px rgba(0,0,0,0.12);
transform: translateY(-2px);
}
/* Portrait variant for cafe page grid */ /* Portrait variant for cafe page grid */
.itemPortrait { .itemPortrait {
@@ -726,10 +723,7 @@
transition: all 0.2s ease; transition: all 0.2s ease;
} }
.item-card:hover { .item-card:hover { /* no hover */ }
box-shadow: 0 4px 16px rgba(0,0,0,0.12);
transform: translateY(-2px);
}
/* List variant */ /* List variant */
.item-list-item { .item-list-item {
@@ -743,6 +737,4 @@
transition: all 0.2s ease; transition: all 0.2s ease;
} }
.item-list-item:hover { .item-list-item:hover { /* no hover */ }
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}

View File

@@ -1,4 +1,5 @@
import React, { useState, useEffect, useRef } from "react"; import React, { useState, useEffect, useRef } from "react";
import { createPortal } from "react-dom";
import styles from "./Modal.module.css"; import styles from "./Modal.module.css";
import { getImageUrl } from "../helpers/itemHelper.js"; import { getImageUrl } from "../helpers/itemHelper.js";
@@ -106,7 +107,7 @@ const ItemConfig = ({
} }
}; };
return ( return createPortal(
<div onClick={handleOverlayClick} className={styles.modalOverlay}> <div onClick={handleOverlayClick} className={styles.modalOverlay}>
<div onClick={handleContentClick} className={styles.modalContent}> <div onClick={handleContentClick} className={styles.modalContent}>
<div className={styles.imageSection}> <div className={styles.imageSection}>
@@ -208,7 +209,8 @@ const ItemConfig = ({
</div> </div>
</div> </div>
</div> </div>
</div> </div>,
document.body
); );
}; };

View File

@@ -8,7 +8,7 @@
bottom: 0; bottom: 0;
display: flex; display: flex;
flex-direction: column-reverse; flex-direction: column-reverse;
z-index: 200; z-index: 2147483600 !important; /* above watermark and page layers */
background-color: rgba(0, 0, 0, 0.38); /* #00000061 */ background-color: rgba(0, 0, 0, 0.38); /* #00000061 */
} }

View File

@@ -88,7 +88,7 @@ const ItemLister = ({
availability: true, availability: true,
selectedImage: null, selectedImage: null,
})); }));
const displayItems = showGrid ? [...items, ...dummyItems] : items; const displayItems = items; // no dummy items on cafe page
const handlePlusClick = (itemId) => { const handlePlusClick = (itemId) => {
const updatedItems = items.map((item) => { const updatedItems = items.map((item) => {
@@ -913,7 +913,7 @@ const ItemLister = ({
cancelEdit={() => toggleAddNewItem()} cancelEdit={() => toggleAddNewItem()}
handleCreateItem={onCreateItem} handleCreateItem={onCreateItem}
/> />
<Item blank={true} handleCreateItem={onCreateItem} /> <Item blank={true} handleCreateItem={onCreateItem} hideDetails={!showGrid} />
</> </>
)} )}
</> </>
@@ -977,6 +977,7 @@ const ItemLister = ({
qty={item.qty} qty={item.qty}
imageUrl={item.image} imageUrl={item.image}
imageFile={item.selectedImage} imageFile={item.selectedImage}
hideDetails={!showGrid}
onPlusClick={() => handlePlusClick(item.itemId)} onPlusClick={() => handlePlusClick(item.itemId)}
onNegativeClick={() => handleNegativeClick(item.itemId)} onNegativeClick={() => handleNegativeClick(item.itemId)}
onRemoveClick={() => handleRemoveClick(item.itemId)} onRemoveClick={() => handleRemoveClick(item.itemId)}
@@ -991,7 +992,7 @@ const ItemLister = ({
) : null; ) : null;
})} })}
{(showGrid ? [...items, ...dummyItems] : items).map((item, indexx) => { {items.map((item, indexx) => {
return !forCart || (forCart && item.qty > 0) ? ( return !forCart || (forCart && item.qty > 0) ? (
<div <div
key={item.itemId}> key={item.itemId}>
@@ -1061,10 +1062,11 @@ const ItemLister = ({
<Item <Item
key={item.itemId} key={item.itemId}
last={index === indexTotal - 1 && indexx === (showGrid ? (items.length + dummyItems.length) : items.length) - 1} last={index === indexTotal - 1 && indexx === items.length - 1}
forCart={forCart} forCart={forCart}
forInvoice={forInvoice} forInvoice={forInvoice}
portrait={showGrid} portrait={showGrid}
hideDetails={!showGrid}
name={item.name} name={item.name}
price={item.price} price={item.price}
promoPrice={item.promoPrice} promoPrice={item.promoPrice}

View File

@@ -10,7 +10,7 @@
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05); box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
margin-bottom: 20px; margin-bottom: 20px;
position: relative; position: relative;
z-index: 150; /* Memastikan berada di bawah material list */ z-index: 0 !important; /* ensure behind modal overlay */
} }
.fullscreen { .fullscreen {
@@ -20,7 +20,7 @@
left: 0; left: 0;
right: 0; right: 0;
background-color: white; background-color: white;
z-index: 1000; z-index: 100; /* keep above page, below modal overlays (>=200) */
overflow-y: auto; overflow-y: auto;
} }
@@ -97,7 +97,7 @@
margin-bottom: 20px; margin-bottom: 20px;
box-shadow: 0 1px 3px rgba(0,0,0,0.05); box-shadow: 0 1px 3px rgba(0,0,0,0.05);
position: relative; position: relative;
z-index: 151; z-index: 0 !important; /* ensure behind modal overlay */
} }
.titleActions { .titleActions {
@@ -120,7 +120,7 @@
transition: all 0.2s ease; transition: all 0.2s ease;
box-shadow: 0 1px 3px rgba(0,0,0,0.05); box-shadow: 0 1px 3px rgba(0,0,0,0.05);
position: relative; position: relative;
z-index: 152; z-index: 0 !important; /* ensure behind modal overlay */
} }
.iconBtn:disabled { .iconBtn:disabled {
opacity: 0.5; opacity: 0.5;
@@ -169,26 +169,50 @@
} }
.add-item-button { .add-item-button {
margin-top: 16px; margin: 0; /* follow parent gap for top/bottom spacing */
padding: 12px 20px; display: inline-block;
font-size: 16px; width: 275px; /* requested size */
background-color: #359d42d1; height: 275px; /* requested size */
color: #fff; padding: 0; /* match image padding (none) */
border: none; border: none;
border-radius: 10px; background: transparent; /* card drawn via ::before */
color: transparent; /* no visible text */
border-radius: 12px; /* match portrait image radius */
cursor: pointer; cursor: pointer;
transition: all 0.2s ease; transition: none;
box-shadow: 0 2px 6px rgba(53, 157, 66, 0.2); box-shadow: none;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
position: relative; position: relative;
z-index: 151; z-index: 1; /* behind modal overlay */
} }
.add-item-button::before {
content: "";
position: absolute;
inset: 0; /* fill container */
border-radius: 12px; /* match portrait image radius */
background:
/* vertical bar */
linear-gradient(var(--brand-sage, #6B8F71), var(--brand-sage, #6B8F71)) center/5px 20% no-repeat,
/* horizontal bar */
linear-gradient(var(--brand-sage, #6B8F71), var(--brand-sage, #6B8F71)) center/20% 5px no-repeat,
/* base */
var(--brand-sage-50, #F0F6F2);
border: 2px dashed var(--brand-sage, #6B8F71);
box-shadow: 0 4px 12px rgba(0,0,0,0.10);
}
.add-item-button:focus-visible {
outline: 2px solid var(--brand-sage, #6B8F71);
outline-offset: 2px;
border-radius: 12px;
}
/* Overlay text inside the left tile area */
.add-item-button::after { content: none; }
.add-item-button:hover { .add-item-button:hover {
background-color: #2d8a39; background-color: #ffffff;
box-shadow: 0 4px 10px rgba(53, 157, 66, 0.3); box-shadow: none;
} }
.item-list { .item-list {
@@ -196,7 +220,20 @@
flex-direction: column; flex-direction: column;
gap: 16px; gap: 16px;
position: relative; position: relative;
z-index: 150; z-index: 0 !important; /* ensure behind modal overlay */
}
/* Generic switch row styling reused in multiple spots */
.switch-container {
display: inline-flex;
align-items: center;
gap: 8px;
}
.switch-label {
font-size: 13px;
font-weight: 600;
color: #333;
} }
/* Grid layout for portrait cards on cafe page */ /* Grid layout for portrait cards on cafe page */
@@ -234,33 +271,62 @@
position: relative; position: relative;
z-index: 150; z-index: 150;
} }
.itemWrapper:hover { .itemWrapper:hover { /* remove hover effect */ }
box-shadow: 0 4px 12px rgba(0,0,0,0.12);
transform: translateY(-2px);
}
.editModeLayout { .editModeLayout {
border-radius: 12px 12px 0 0; /* Turn full-width bar into subtle corner controls over image */
position: absolute; position: absolute;
z-index: 155; /* Memastikan berada di atas itemWrapper */ inset: 0;
background-color: #000000cc; z-index: 155; /* above itemWrapper */
width: 100%; pointer-events: none; /* only children are interactive */
top: 0;
left: 0;
right: 0;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
font-size: 14px;
padding: 12px 16px;
color: white;
} }
.editModeLayout h3 { /* Left badge: availability switch + label */
margin: 0; .editModeLayout > div:first-child {
font-weight: 500; position: absolute;
font-size: 14px; top: 16px;
left: 16px;
display: inline-flex;
align-items: center;
gap: 8px;
background: rgba(0, 0, 0, 0.55);
color: #fff;
border-radius: 999px;
padding: 6px 10px;
pointer-events: auto;
backdrop-filter: blur(2px);
}
/* Right badge: edit/unmark button */
.editModeLayout > div:last-child {
position: absolute;
top: 16px;
right: 16px;
display: inline-flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
background: rgba(0, 0, 0, 0.55);
color: #fff;
border-radius: 999px;
pointer-events: auto;
cursor: pointer;
transition: background 0.2s ease, transform 0.2s ease;
backdrop-filter: blur(2px);
}
.editModeLayout > div:last-child:hover {
background: rgba(0, 0, 0, 0.7);
transform: translateY(-1px);
}
/* When label exists, keep it compact and readable */
.editModeLayout .switch-label {
font-size: 12px;
font-weight: 600;
color: #fff;
white-space: nowrap;
} }
.PaymentOption { .PaymentOption {

View File

@@ -97,7 +97,7 @@
top: 0; top: 0;
right: 0; right: 0;
background-color: white; background-color: white;
z-index: 300; z-index: 0; /* align with item lister */
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }

View File

@@ -1,4 +1,5 @@
import React, {useState, useEffect} from "react"; import React, {useState, useEffect} from "react";
import { createPortal } from "react-dom";
import styles from "./Modal.module.css"; import styles from "./Modal.module.css";
import AccountUpdatePage from "../components/AccountUpdatePage.js"; import AccountUpdatePage from "../components/AccountUpdatePage.js";
@@ -75,7 +76,7 @@ const Modal = ({ user, shop, isOpen, onClose, modalContent, deviceType, setModal
event.stopPropagation(); event.stopPropagation();
}; };
if(modalContent == '') handleOverlayClick(); if(modalContent == '') handleOverlayClick();
return ( return createPortal(
<div key={updateKey} onClick={handleOverlayClick} className={styles.modalOverlay}> <div key={updateKey} onClick={handleOverlayClick} className={styles.modalOverlay}>
<div className={`${styles.modalContent} ${(modalContent === 'edit_tables' || modalContent === 'payment_option' || modalContent === 'create_clerk') ? styles.modalContentWide : ''}`} onClick={handleContentClick}> <div className={`${styles.modalContent} ${(modalContent === 'edit_tables' || modalContent === 'payment_option' || modalContent === 'create_clerk') ? styles.modalContentWide : ''}`} onClick={handleContentClick}>
@@ -132,7 +133,8 @@ const Modal = ({ user, shop, isOpen, onClose, modalContent, deviceType, setModal
{modalContent === "message" && <Message handleYes={onModalYesFunction} handleNo={handleNo}/>} {modalContent === "message" && <Message handleYes={onModalYesFunction} handleNo={handleNo}/>}
{modalContent === "player-prompt" && <PlayerPrompt cafeId={shop.cafeId} setModal={setModal} handleClose={handleOverlayClick} welcomePageConfig={shop.welcomePageConfig}/>} {modalContent === "player-prompt" && <PlayerPrompt cafeId={shop.cafeId} setModal={setModal} handleClose={handleOverlayClick} welcomePageConfig={shop.welcomePageConfig}/>}
</div> </div>
</div> </div>,
document.body
); );
}; };

View File

@@ -8,7 +8,7 @@
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
z-index: 9999; z-index: 2147483647 !important; /* ensure above any app layers */
padding: 20px; padding: 20px;
} }

View File

@@ -31,14 +31,14 @@
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.7); text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.7);
border-radius: 0 0 15px 15px; border-radius: 0 0 15px 15px;
z-index: 1; z-index: 0; /* align with item lister */
} }
.current-name { .current-name {
white-space: nowrap; white-space: nowrap;
pointer-events: none; pointer-events: none;
position: relative; position: relative;
z-index: 2; z-index: 1;
text-align: left; text-align: left;
margin: 35px 30px; margin: 35px 30px;
font-size: 16px; font-size: 16px;
@@ -70,7 +70,7 @@
.current-artist { .current-artist {
pointer-events: none; pointer-events: none;
position: relative; position: relative;
z-index: 2; z-index: 1;
text-align: left; text-align: left;
margin: -32px 30px; margin: -32px 30px;
font-size: 18px; font-size: 18px;
@@ -83,7 +83,7 @@
.progress-container { .progress-container {
pointer-events: none; pointer-events: none;
position: relative; position: relative;
z-index: 2; z-index: 1;
text-align: left; text-align: left;
margin: 12px 30px; margin: 12px 30px;
} }
@@ -318,4 +318,3 @@
.search-button.clicked { .search-button.clicked {
background-color: #d0c7b3; /* The color when clicked */ background-color: #d0c7b3; /* The color when clicked */
} }

View File

@@ -1,5 +1,5 @@
.watermark { .watermark {
z-index: 5; z-index: 0; /* align with item lister */
margin-top: 30px; margin-top: 30px;
background-color: rgb(222, 237, 100); background-color: rgb(222, 237, 100);
font-weight: 700; font-weight: 700;
@@ -28,7 +28,7 @@
/* Media query for desktop */ /* Media query for desktop */
@media (min-width: 768px) { /* Adjust the min-width as needed */ @media (min-width: 768px) { /* Adjust the min-width as needed */
.watermark { .watermark {
z-index: 5; z-index: 0; /* align with item lister */
margin-top: 30px; margin-top: 30px;
background-color: rgb(222, 237, 100); background-color: rgb(222, 237, 100);
font-weight: 700; font-weight: 700;

View File

@@ -1,6 +1,6 @@
// src/CafePage.js // src/CafePage.js
import React, { useState, useEffect } from "react"; import React, { useState, useEffect, useRef } from "react";
import { import {
useParams, useParams,
useSearchParams, useSearchParams,
@@ -77,6 +77,40 @@ function CafePage({
const [config, setConfig] = useState({}); const [config, setConfig] = useState({});
const [beingEditedType, setBeingEditedType] = useState(0); const [beingEditedType, setBeingEditedType] = useState(0);
// Sticky floating cart bar animations and visibility
const [cartBump, setCartBump] = useState(false);
const [barIntro, setBarIntro] = useState(false);
const [historyPulse, setHistoryPulse] = useState(false);
const prevShowRef = useRef(false);
const prevTxnRef = useRef(null);
const showBar = !isEditMode && (user.username || cartItemsLength > 0);
useEffect(() => {
if (cartItemsLength > 0) {
setCartBump(true);
const t = setTimeout(() => setCartBump(false), 450);
return () => clearTimeout(t);
}
}, [cartItemsLength]);
useEffect(() => {
if (showBar && !prevShowRef.current) {
setBarIntro(true);
const t = setTimeout(() => setBarIntro(false), 500);
prevShowRef.current = true;
return () => clearTimeout(t);
}
if (!showBar) prevShowRef.current = false;
}, [showBar]);
useEffect(() => {
if (lastTransaction && lastTransaction !== prevTxnRef.current) {
setHistoryPulse(true);
const t = setTimeout(() => setHistoryPulse(false), 900);
prevTxnRef.current = lastTransaction;
return () => clearTimeout(t);
}
}, [lastTransaction]);
// const checkWelcomePageConfig = () => { // const checkWelcomePageConfig = () => {
// const parsedConfig = JSON.parse(welcomePageConfig); // const parsedConfig = JSON.parse(welcomePageConfig);
@@ -246,6 +280,8 @@ function CafePage({
removeConnectedGuestSides={removeConnectedGuestSides} removeConnectedGuestSides={removeConnectedGuestSides}
setIsEditMode={(e) => setIsEditMode(e)} setIsEditMode={(e) => setIsEditMode(e)}
isEditMode={isEditMode} isEditMode={isEditMode}
zIndexLevel={9000}
rectZIndex={9000}
/> />
<MusicPlayer <MusicPlayer
socket={socket} socket={socket}
@@ -319,29 +355,33 @@ function CafePage({
} }
/> />
))} ))}
{!isEditMode && (user.username || cartItemsLength > 0) && {showBar &&
<div className="StickyCartBar" style={{ marginTop: '10px', height: '40px', position: 'sticky', bottom: '40px', display: 'flex', justifyContent: 'center', alignItems: 'center', textAlign: 'center' }}> <div className={`StickyCartBar${barIntro ? ' intro' : ''}`}>
{(lastTransaction != null || cartItemsLength > 0) && {(lastTransaction != null || cartItemsLength > 0) &&
<div onClick={goToCart} style={{ backgroundColor: '#73a585', width: user.username ? '55vw' : '70vw', height: '40px', borderRadius: '30px', display: 'flex', justifyContent: 'space-between', padding: '0 20px' }}> <div role="button" tabIndex={0} aria-label="Buka keranjang" onKeyDown={(e)=>{ if(e.key==='Enter'||e.key===' ') { e.preventDefault(); goToCart(); } }} onClick={goToCart} className={`cartBtn${cartBump ? ' bump' : ''}`}>
<div style={{ display: 'flex', flexWrap: 'wrap', alignContent: 'center' }}>{lastTransaction != null && '+'}{cartItemsLength} item</div> <div className="summary">
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'flex-end', width: '130px' }}> {(lastTransaction != null) && <span>+</span>}
<span>{cartItemsLength} item</span>
</div>
<div className="summary" style={{ gap: 6 }}>
{((lastTransaction == null || lastTransaction?.payment_type != 'paylater')) ? {((lastTransaction == null || lastTransaction?.payment_type != 'paylater')) ?
<span style={{ whiteSpace: 'nowrap' }}>Rp{totalPrice}</span> <span className="value">{`Rp ${Number(totalPrice || 0).toLocaleString('id-ID')}`}</span>
: :
<span style={{ whiteSpace: 'nowrap' }}>Open bill</span> <span className="value">Open bill</span>
} }
<div style={{ display: 'flex', alignItems: 'center', marginLeft: '5px', width: '20px' }}> <div className="icon">
<svg viewBox="0 0 34 34" style={{ fill: 'white', marginTop: '4px' }}> <svg viewBox="0 0 34 34" style={{ fill: 'white', marginTop: '4px' }}>
<path d="M9.79175 24.75C8.09591 24.75 6.72383 26.1375 6.72383 27.8333C6.72383 29.5292 8.09591 30.9167 9.79175 30.9167C11.4876 30.9167 12.8751 29.5292 12.8751 27.8333C12.8751 26.1375 11.4876 24.75 9.79175 24.75ZM0.541748 0.0833435V3.16668H3.62508L9.17508 14.8679L7.09383 18.645C6.84717 19.0767 6.70842 19.5854 6.70842 20.125C6.70842 21.8208 8.09591 23.2083 9.79175 23.2083H28.2917V20.125H10.4392C10.2234 20.125 10.0538 19.9554 10.0538 19.7396L10.1001 19.5546L11.4876 17.0417H22.973C24.1292 17.0417 25.1467 16.4096 25.6709 15.4538L31.1901 5.44834C31.3134 5.23251 31.3751 4.97043 31.3751 4.70834C31.3751 3.86043 30.6813 3.16668 29.8334 3.16668H7.03217L5.583 0.0833435H0.541748ZM25.2084 24.75C23.5126 24.75 22.1405 26.1375 22.1405 27.8333C22.1405 29.5292 23.5126 30.9167 25.2084 30.9167C26.9042 30.9167 28.2917 29.5292 28.2917 27.8333C28.2917 26.1375 26.9042 24.75 25.2084 24.75Z"></path> <path d="M9.79175 24.75C8.09591 24.75 6.72383 26.1375 6.72383 27.8333C6.72383 29.5292 8.09591 30.9167 9.79175 30.9167C11.4876 30.9167 12.8751 29.5292 12.8751 27.8333C12.8751 26.1375 11.4876 24.75 9.79175 24.75ZM0.541748 0.0833435V3.16668H3.62508L9.17508 14.8679L7.09383 18.645C6.84717 19.0767 6.70842 19.5854 6.70842 20.125C6.70842 21.8208 8.09591 23.2083 9.79175 23.2083H28.2917V20.125H10.4392C10.2234 20.125 10.0538 19.9554 10.0538 19.7396L10.1001 19.5546L11.4876 17.0417H22.973C24.1292 17.0417 25.1467 16.4096 25.6709 15.4538L31.1901 5.44834C31.3134 5.23251 31.3751 4.97043 31.3751 4.70834C31.3751 3.86043 30.6813 3.16668 29.8334 3.16668H7.03217L5.583 0.0833435H0.541748ZM25.2084 24.75C23.5126 24.75 22.1405 26.1375 22.1405 27.8333C22.1405 29.5292 23.5126 30.9167 25.2084 30.9167C26.9042 30.9167 28.2917 29.5292 28.2917 27.8333C28.2917 26.1375 26.9042 24.75 25.2084 24.75Z"></path>
</svg> </svg>
{cartItemsLength > 0 && <span className={`badge${cartBump ? ' pop' : ''}`}>{cartItemsLength > 9 ? '9+' : cartItemsLength}</span>}
</div> </div>
</div> </div>
</div> </div>
} }
{user.username && {user.username &&
<div onClick={goToTransactions} style={{ backgroundColor: '#73a585', width: '15vw', height: '40px', borderRadius: '30px', display: 'flex', justifyContent: 'center', marginLeft: (lastTransaction != null || cartItemsLength > 0) ? '6px' : '0px' }}> <div role="button" tabIndex={0} aria-label="Lihat riwayat" onKeyDown={(e)=>{ if(e.key==='Enter'||e.key===' ') { e.preventDefault(); goToTransactions(); } }} onClick={goToTransactions} className={`historyBtn${historyPulse ? ' pulse' : ''}`}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', width: '38px', marginRight: '5px' }}> <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', width: 38, marginRight: 6 }}>
<div style={{ display: 'flex', alignItems: 'center', marginLeft: '5px', width: '20px' }}> <div className="icon">
<svg viewBox="0 0 512 512"> <svg viewBox="0 0 512 512">
<g <g
transform="translate(0 460) scale(0.09 -0.09)" transform="translate(0 460) scale(0.09 -0.09)"
@@ -389,3 +429,4 @@ function CafePage({
} }
export default CafePage; export default CafePage;

View File

@@ -30,6 +30,15 @@ export default function Transactions({ shop, shopId, propsShopId, sendParam, dev
const [loading, setLoading] = useState(false); 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(() => { useEffect(() => {
setMatchedItems(searchAndAggregateItems(transactions, searchTerm)); setMatchedItems(searchAndAggregateItems(transactions, searchTerm));
}, [searchTerm, transactions]); }, [searchTerm, transactions]);
@@ -154,7 +163,7 @@ console.log(aggregatedItems.values())
return ( return (
<div className={styles.Transactions}> <div className={styles.Transactions}>
<h2 className={styles["Transactions-title"]}> <h2 className={styles["Transactions-title"]}>
Transaksi selesai Rp {calculateAllTransactionsTotal(transactions)} Transaksi selesai {formatRp(calculateAllTransactionsTotal(transactions))}
</h2> </h2>
<input <input
@@ -162,13 +171,13 @@ console.log(aggregatedItems.values())
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: '36px', borderRadius: '12px', margin: '6px auto 10px', width: '88%', padding: '0 8px', fontSize: '14px' }}
/> />
{/* 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}>
{matchedItems.length > 0 && matchedItems.map(item => ( {matchedItems.length > 0 && matchedItems.map(item => (
<div <div
@@ -183,7 +192,7 @@ console.log(aggregatedItems.values())
</ul> </ul>
<div className={styles.TotalContainer}> <div className={styles.TotalContainer}>
<span>Total:</span> <span>Total:</span>
<span>Rp {item.totalPrice}</span> <span>{formatRp(item.totalPrice)}</span>
</div> </div>
</div> </div>
))} ))}
@@ -191,68 +200,34 @@ console.log(aggregatedItems.values())
transactions.map((transaction) => ( transactions.map((transaction) => (
<div <div
key={transaction.transactionId} key={transaction.transactionId}
className={styles.RoundedRectangle} className={`${styles.RoundedRectangle} ${!transaction.is_paid ? styles.unpaid : ''}`}
style={{ overflow: 'hidden' }}
> >
<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.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' }}> <div style={{ display: 'flex', justifyContent: 'center', margin: '12px 0' }}>
<svg <svg width="60" height="60" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
style={{ width: '60px', transform: 'Rotate(45deg)' }} <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"/>
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> </svg>
</div> </div>
) : transaction.confirmed === 2 && !transaction.is_paid ? ( ) : transaction.confirmed === 2 && !transaction.is_paid ? (
<ColorRing className={styles['receipt-logo']} /> <ColorRing className={styles['receipt-logo']} />
) : transaction.confirmed === 3 || transaction.is_paid ? ( ) : transaction.confirmed === 3 || transaction.is_paid ? (
<div> <div style={{ display: 'flex', justifyContent: 'center', margin: '12px 0' }}>
<svg <svg width="60" height="60" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
height="60px" <path d="M9 16.2 4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4z" fill="#54B265"/>
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>
</svg> </svg>
</div> </div>
) : ( ) : (
<ColorRing className={styles['receipt-logo']} /> <ColorRing className={styles['receipt-logo']} />
)} )}
<div className={styles['receipt-info']}> <div className={styles['receipt-info']}>
{deviceType == 'clerk' ? {deviceType == 'clerk' ?
<h3>{transaction.confirmed === 1 && !transaction.is_paid ? ( <h3 className={getStatusClass(transaction)}>{transaction.confirmed === 1 && !transaction.is_paid ? (
"Silahkan Cek Pembayaran" "Silahkan Cek Pembayaran"
) : transaction.confirmed === -1 && !transaction.is_paid ? ( ) : transaction.confirmed === -1 && !transaction.is_paid ? (
"Dibatalkan Oleh Kasir" "Dibatalkan Oleh Kasir"
@@ -266,7 +241,7 @@ console.log(aggregatedItems.values())
"Silahkan Cek Ketersediaan" "Silahkan Cek Ketersediaan"
)}</h3> )}</h3>
: :
<h3>{transaction.confirmed === 1 ? ( <h3 className={getStatusClass(transaction)}>{transaction.confirmed === 1 ? (
(transaction.payment_type == 'cash' ? 'Silahkan Bayar Ke Kasir' : "Silahkan Bayar Dengan QRIS") (transaction.payment_type == 'cash' ? 'Silahkan Bayar Ke Kasir' : "Silahkan Bayar Dengan QRIS")
) : transaction.confirmed === -1 ? ( ) : transaction.confirmed === -1 ? (
"Dibatalkan Oleh Kasir" "Dibatalkan Oleh Kasir"
@@ -304,12 +279,11 @@ console.log(aggregatedItems.values())
<ul> <ul>
{transaction.DetailedTransactions.map((detail) => ( {transaction.DetailedTransactions.map((detail) => (
<li key={detail.detailedTransactionId}> <li key={detail.detailedTransactionId}>
<span>{detail.Item.name}</span> - {detail.qty < 1 ? 'tidak tersedia' : `${detail.qty} x Rp <span>{detail.Item.name}</span> - {detail.qty < 1 ? 'tidak tersedia' : `${detail.qty} x ${formatRp(detail.promoPrice ? detail.promoPrice : detail.price)}`}
${detail.promoPrice ? detail.promoPrice : detail.price}`}
</li> </li>
))} ))}
</ul> </ul>
{!transaction.is_paid && transaction.confirmed > -1 && {!transaction.is_paid && transaction.confirmed > -1 && (
<div <div
onClick={() => { onClick={() => {
localStorage.setItem('lastTransaction', JSON.stringify(transaction)); localStorage.setItem('lastTransaction', JSON.stringify(transaction));
@@ -322,12 +296,11 @@ console.log(aggregatedItems.values())
> >
Tambah pesanan Tambah pesanan
</div> </div>
} )}
<h2 className={styles["Transactions-detail"]}> <h2 className={styles["Transactions-detail"]}>
{transaction.serving_type === "pickup" {transaction.serving_type === "pickup"
? "Self pickup" ? "Self pickup"
: `Serve to ${transaction.Table ? transaction.Table.tableNo : "N/A" : `Serve to ${transaction.Table ? transaction.Table.tableNo : "N/A"}`}
}`}
</h2> </h2>
{transaction.notes && ( {transaction.notes && (
@@ -350,12 +323,13 @@ console.log(aggregatedItems.values())
<div className={styles.TotalContainer}> <div className={styles.TotalContainer}>
<span>Total:</span> <span>Total:</span>
<span> <span>
Rp {calculateTotalPrice(transaction.DetailedTransactions)} {formatRp(calculateTotalPrice(transaction.DetailedTransactions))}
</span> </span>
</div> </div>
<div className={styles.TotalContainer}> <div className={styles.TotalContainer}>
{(deviceType == 'clerk' && !transaction.is_paid && (transaction.confirmed == 0 || transaction.confirmed == 1 || transaction.confirmed == 2)) && {(deviceType == 'clerk' && !transaction.is_paid && (transaction.confirmed == 0 || transaction.confirmed == 1 || transaction.confirmed == 2)) && (
<div className={styles.ActionRow}>
<button <button
className={styles.PayButton} className={styles.PayButton}
onClick={() => handleConfirm(transaction.transactionId)} onClick={() => handleConfirm(transaction.transactionId)}
@@ -363,25 +337,30 @@ console.log(aggregatedItems.values())
> >
{ {
isPaymentLoading ? ( isPaymentLoading ? (
<ColorRing height="50" width="50" color="white" /> <ColorRing height="28" width="28" color="white" />
) : transaction.confirmed === 1 ? ( ) : transaction.confirmed === 1 ? (
"Konfirmasi Telah Bayar" "Konfirmasi"
) : transaction.confirmed === 2 ? ( ) : transaction.confirmed === 2 ? (
"Confirm item is ready" "Confirm item is ready"
) : ( ) : (
"Confirm availability" "Confirm availability"
) )
} }
</button> </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' && {deviceType == 'guestDevice' && transaction.confirmed < 2 && transaction.payment_type != 'cash' && transaction.payment_type != 'paylater/cash' &&
<ButtonWithReplica <ButtonWithReplica
paymentUrl={paymentUrl} paymentUrl={paymentUrl}
price={ price={formatRp(calculateTotalPrice(transaction.DetailedTransactions))}
"Rp" + calculateTotalPrice(transaction.DetailedTransactions)
}
disabled={isPaymentLoading} disabled={isPaymentLoading}
isPaymentLoading={isPaymentLoading} isPaymentLoading={isPaymentLoading}
handleClick={() => handleConfirm(transaction.transactionId)} handleClick={() => handleConfirm(transaction.transactionId)}
@@ -404,40 +383,30 @@ console.log(aggregatedItems.values())
} }
</div> </div>
{deviceType == 'guestDevice' && transaction.confirmed >= 0 && transaction.confirmed < 2 && transaction.payment_type == 'cash' ? {deviceType == 'guestDevice' && (
transaction.confirmed >= 0 && transaction.confirmed < 2 && transaction.payment_type == 'cash' ? (
<button <button
className={styles.PayButton} className={styles.DeclineButton}
onClick={() => handleDecline(transaction.transactionId)} onClick={() => handleDecline(transaction.transactionId)}
disabled={ disabled={
transaction.confirmed === -1 || transaction.confirmed === -1 ||
transaction.confirmed === 3 || transaction.confirmed === 3 ||
isPaymentLoading isPaymentLoading
} // Disable button if confirmed (1) or declined (-1) or }
> >
{isPaymentLoading ? ( {isPaymentLoading ? '...' : 'Batalkan'}
<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> </button>
: ) : (
((transaction.confirmed >= 0 && transaction.confirmed < 2 && transaction.payment_type != 'paylater/cash' && transaction.payment_type != 'paylater/cashless' || isPaymentOpen) && (transaction.confirmed >= 0 && transaction.confirmed < 2 && (transaction.payment_type != 'paylater/cash' && transaction.payment_type != 'paylater/cashless' || isPaymentOpen)) && (
<h5 <button
className={`${styles.DeclineButton}`} className={styles.DeclineButton}
onClick={() => onClick={() => isPaymentOpen ? setIsPaymentOpen(false) : handleDecline(transaction.transactionId)}
isPaymentOpen
? setIsPaymentOpen(false)
: handleDecline(transaction.transactionId)
}
> >
{isPaymentOpen ? "kembali" : "batalkan"} {isPaymentOpen ? 'Kembali' : 'Batalkan'}
</h5> </button>
) )
} )
)}
</div> </div>
))} ))}
</div> </div>

View File

@@ -13,41 +13,43 @@
} }
.Transactions { .Transactions {
overflow-x: hidden; overflow-x: hidden;
background-color: white; background-color: #f5f7f6;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: flex-start; justify-content: flex-start;
font-size: calc(10px + 2vmin); font-size: calc(10px + 2vmin);
color: rgba(88, 55, 50, 1); color: rgba(40, 40, 40, 1);
background-color: #e9e9e9;
} }
.Transactions-title { .Transactions-title {
font-family: "Plus Jakarta Sans", sans-serif; font-family: "Plus Jakarta Sans", sans-serif;
font-weight: 500; font-weight: 700;
font-style: normal; font-style: normal;
font-size: 6vw; font-size: clamp(18px, 3.6vw, 24px);
color: black; color: var(--brand-sage, #6B8F71);
text-align: left; text-align: left;
margin-left: 20px; margin: 16px 16px 8px 16px;
margin-top: 57px;
} }
.Transactions-detail { .Transactions-detail {
font-family: "Plus Jakarta Sans", sans-serif; font-family: "Plus Jakarta Sans", sans-serif;
font-weight: 500; font-weight: 500;
font-style: normal; font-style: normal;
font-size: 15px; font-size: 13px;
color: rgba(88, 55, 50, 1); color: #555;
text-align: left; text-align: left;
margin-left: 20px; margin-left: 16px;
margin-top: 17px; margin-top: 12px;
} }
.TransactionListContainer { .TransactionListContainer {
overflow-y: auto; /* Enables vertical scrolling */ overflow-y: auto; /* Enables vertical scrolling */
background-color: #dbdbdb; background-color: transparent;
overflow: visible; overflow: visible;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 12px;
padding: 8px 12px 16px;
} }
.TotalContainer { .TotalContainer {
@@ -57,14 +59,14 @@
/* width: 100%; */ /* width: 100%; */
margin: 0 auto; margin: 0 auto;
font-family: "Plus Jakarta Sans", sans-serif; font-family: "Plus Jakarta Sans", sans-serif;
font-weight: 600; font-weight: 700;
font-style: normal; font-style: normal;
font-size: 15px; font-size: 13px;
/* padding: 10px; */ /* padding: 10px; */
box-sizing: border-box; box-sizing: border-box;
margin-bottom: 17px; margin-bottom: 12px;
margin-left: 20px; margin-left: 16px;
margin-right: 20px; margin-right: 16px;
} }
.PaymentContainer { .PaymentContainer {
@@ -83,61 +85,66 @@
.PayButton { .PayButton {
font-family: "Plus Jakarta Sans", sans-serif; font-family: "Plus Jakarta Sans", sans-serif;
font-weight: 500; font-weight: 600;
font-style: normal; font-style: normal;
font-size: 12px; /* Adjusted for better readability */ font-size: 12px;
padding: 12px 16px; /* Added padding for a better look */ padding: 10px 14px;
border-radius: 50px; border-radius: 10px; /* square rounded */
background-color: rgba(88, 55, 50, 1); background-color: var(--brand-primary, #73a585);
color: white; color: white;
border: none; border: none;
margin: 0 auto;
cursor: pointer; cursor: pointer;
display: block; /* Centering the button */ display: inline-flex;
align-items: center;
justify-content: center;
text-align: center; text-align: center;
} }
.DeclineButton { .DeclineButton {
font-family: "Plus Jakarta Sans", sans-serif; font-family: "Plus Jakarta Sans", sans-serif;
z-index: 201; font-weight: 600;
position: relative;
font-weight: 500;
font-style: normal; font-style: normal;
font-size: 15px; font-size: 12px;
padding: 12px 24px; /* Add some padding for spacing */ padding: 10px 14px;
color: rgba(88, 55, 50, 1); color: #444;
background-color: transparent; /* No background */ background-color: #f0f0f0;
border: none; /* No border */ border: 1px solid #e0e0e0;
margin: 0 auto; /* Center horizontally */ border-radius: 10px; /* square rounded */
cursor: pointer; cursor: pointer;
display: block; /* Center the text horizontally */ display: inline-flex;
text-align: center; /* Center the text within the button */ align-items: center;
justify-content: center;
} }
.DeclineButton.active { .DeclineButton.active { opacity: 0.9; }
position: relative;
z-index: 201; /* Row for primary/secondary action buttons */
font-family: "Plus Jakarta Sans", sans-serif; .ActionRow {
font-weight: 500; display: flex;
font-style: normal; gap: 8px;
font-size: 20px; width: 100%;
padding: 12px 24px; /* Add some padding for spacing */ justify-content: center;
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 */
} }
.RoundedRectangle { .RoundedRectangle {
position: relative; position: relative;
border-radius: 20px; border-radius: 16px;
padding: 15px; /* Adjusted for better spacing */ padding: 12px 12px 10px;
margin: 12px; margin: 0;
background-color: #f9f9f9; 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 { .expression {
width: 100%; width: 100%;
} }
@@ -201,20 +208,28 @@
} }
.receipt-logo { .receipt-logo {
width: 80px; width: 60px;
height: 80px; height: 60px;
border-radius: 50%; /* Circular logo */ border-radius: 50%; /* Circular logo */
object-fit: cover; object-fit: cover;
margin-bottom: 10px; margin-bottom: 8px;
} }
.receipt-info h3 { .receipt-info h3 {
font-size: 16px; font-size: 13px;
margin: 5px 0; 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 { .receipt-info p {
font-size: 14px; font-size: 12px;
color: #666;
margin: 2px 0; margin: 2px 0;
} }
/* Dotted line with circular cutouts */ /* Dotted line with circular cutouts */
@@ -222,39 +237,61 @@
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
margin: 15px 0; margin: 12px 0 10px;
} }
.dotted-line .line { .dotted-line .line {
border-top: 13px dotted #dbdbdb; border-top: 10px dotted #e9e9e9;
width: 100%; width: 100%;
margin: 0 18px; margin: 0 18px;
} }
.dotted-line .circle-left { .dotted-line .circle-left {
left: -25px; left: -18px;
position: absolute; position: absolute;
width: 50px; width: 36px;
height: 50px; height: 36px;
border-radius: 50%; border-radius: 50%;
background-color: #dbdbdb; background-color: #e9e9e9;
display: flex; /* Use flexbox to center the inner circle */ display: flex; /* Use flexbox to center the inner circle */
justify-content: center; /* Center horizontally */ justify-content: center; /* Center horizontally */
align-items: center; /* Center vertically */ align-items: center; /* Center vertically */
} }
.dotted-line .circle-right { .dotted-line .circle-right {
right: -25px; right: -18px;
position: absolute; position: absolute;
width: 50px; width: 36px;
height: 50px; height: 36px;
border-radius: 50%; border-radius: 50%;
background-color: #dbdbdb; background-color: #e9e9e9;
display: flex; /* Use flexbox to center the inner circle */ display: flex; /* Use flexbox to center the inner circle */
justify-content: center; /* Center horizontally */ justify-content: center; /* Center horizontally */
align-items: center; /* Center vertically */ 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 { .inner-circle {
width: 80%; width: 80%;
height: 80%; height: 80%;
@@ -389,10 +426,11 @@
.addNewItem{ .addNewItem{
width: 100%; width: 100%;
height: 27px; height: 28px;
background-color: rgb(115, 165, 133); background-color: var(--brand-primary, rgb(115, 165, 133));
border-radius: 11px; border-radius: 10px; /* square rounded */
text-align: center; text-align: center;
color: white; color: white;
line-height: 27px; line-height: 28px;
font-size: 12px; /* compact */
} }