ok
This commit is contained in:
10
src/App.css
10
src/App.css
@@ -1,6 +1,14 @@
|
||||
@import url("https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap");
|
||||
@import url("https://fonts.googleapis.com/css2?family=Aboreto&family=Rubik+Doodle+Shadow&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");
|
||||
:root {
|
||||
--brand-primary: #73a585; /* general brand (e.g., music player) */
|
||||
--brand-sage: #6B8F71; /* sage green for active category */
|
||||
--brand-sage-50: #F0F6F2; /* very light hover bg */
|
||||
--brand-sage-100: #E9F3ED; /* light hover bg */
|
||||
--brand-sage-hover: #7FAE7D; /* hover for filled buttons */
|
||||
--brand-sage-muted: #CFD8D3; /* disabled button */
|
||||
}
|
||||
html,
|
||||
body {
|
||||
scrollbar-width: none; /* Firefox */
|
||||
@@ -54,6 +62,8 @@ body {
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* removed two-column layout; reverted to single column */
|
||||
|
||||
.App-link {
|
||||
color: #61dafb;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useLocation } from "react-router-dom";
|
||||
import { useNavigationHelpers } from "../helpers/navigationHelpers";
|
||||
import Switch from "react-switch";
|
||||
|
||||
// Restore original gradient background for header container when shopName exists
|
||||
const HeaderBarbackground = styled.div`
|
||||
${({ shopName }) =>
|
||||
shopName &&
|
||||
@@ -19,7 +20,7 @@ const HeaderBar = styled.div`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20px 15px;
|
||||
padding: 12px 14px;
|
||||
color: black;
|
||||
background-color: #ffffff;
|
||||
z-index: 200;
|
||||
@@ -33,30 +34,15 @@ const HeaderBar = styled.div`
|
||||
const Title = styled.h2`
|
||||
margin: 0;
|
||||
font-family: "Plus Jakarta Sans", sans-serif;
|
||||
font-weight: 500;
|
||||
font-weight: 600;
|
||||
font-style: normal;
|
||||
font-size:${(props) => (props.HeaderSize)};
|
||||
color: rgba(88, 55, 50, 1);
|
||||
text-transform: uppercase;
|
||||
color: rgba(45, 45, 45, 1);
|
||||
`;
|
||||
|
||||
const ProfileName = styled.h2`
|
||||
position: absolute;
|
||||
font-family: "Plus Jakarta Sans", sans-serif;
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
font-size: 30px;
|
||||
z-index: 199;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
animation: ${(props) => {
|
||||
if (props.animate === "grow") return gg;
|
||||
if (props.animate === "shrink") return ss;
|
||||
return nn;
|
||||
}}
|
||||
0.5s forwards;
|
||||
text-align: left;
|
||||
`;
|
||||
// SubTitle removed per redesign (no subtitle below cafe name)
|
||||
|
||||
// Deprecated the animated ProfileName in favor of a cleaner layout
|
||||
|
||||
const nn = keyframes`
|
||||
0% {
|
||||
@@ -103,22 +89,17 @@ const ss = keyframes`
|
||||
}
|
||||
`;
|
||||
|
||||
const ProfileImage = styled.img`
|
||||
position: relative;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 50%;
|
||||
object-fit: contain;
|
||||
cursor: pointer;
|
||||
z-index: 199;
|
||||
animation: ${(props) => {
|
||||
if (props.animate === "grow") return g;
|
||||
if (props.animate === "shrink") return s;
|
||||
return "none";
|
||||
}}
|
||||
0.5s forwards;
|
||||
const CafeAvatar = styled.img`
|
||||
width: clamp(32px, 5vw, 56px);
|
||||
height: clamp(32px, 5vw, 56px);
|
||||
border-radius: 8px;
|
||||
object-fit: cover;
|
||||
background: #f2f2f2;
|
||||
margin-left: 8px; /* extra left padding so it’s not too tight */
|
||||
`;
|
||||
|
||||
// User initial avatar removed; only cafe image is shown on the left
|
||||
|
||||
const g = keyframes`
|
||||
0% {
|
||||
top: 0px;
|
||||
@@ -149,62 +130,43 @@ const s = keyframes`
|
||||
}
|
||||
`;
|
||||
|
||||
/* Replace bubble-like animation with subtle fade/slide */
|
||||
const grow = keyframes`
|
||||
0% {
|
||||
right: 12px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-top-left-radius: 50%;
|
||||
border-bottom-left-radius: 50%;
|
||||
}
|
||||
100% {
|
||||
right: 28px;
|
||||
width: 300px;
|
||||
border-top-left-radius: 15px;
|
||||
border-bottom-left-radius: 15px;
|
||||
}
|
||||
0% { opacity: 0; transform: translateY(-6px) scale(0.98); }
|
||||
100% { opacity: 1; transform: translateY(0) scale(1); }
|
||||
`;
|
||||
|
||||
const shrink = keyframes`
|
||||
0% {
|
||||
right: 12px;
|
||||
width: 300px;
|
||||
height: auto;
|
||||
border-radius: 20px;
|
||||
}
|
||||
100% {
|
||||
right: 28px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
0% { opacity: 1; transform: translateY(0) scale(1); }
|
||||
100% { opacity: 0; transform: translateY(-6px) scale(0.98); }
|
||||
`;
|
||||
const Rectangle = styled.div`
|
||||
overflow-y: hidden;
|
||||
overflow-y: auto;
|
||||
position: absolute;
|
||||
top: 39px;
|
||||
right: 12px;
|
||||
width: 200px;
|
||||
max-height: 87vh; /* or another appropriate value */
|
||||
top: calc(100% + 8px);
|
||||
right: 0;
|
||||
width: 240px;
|
||||
max-height: 75vh;
|
||||
background-color: white;
|
||||
z-index: 198;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||
animation: ${(props) => (props.animate === "grow" ? grow : shrink)} 0.5s
|
||||
forwards;
|
||||
padding: 10px;
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
|
||||
border: 1px solid #f0f0f0;
|
||||
border-radius: 12px;
|
||||
animation: ${(props) => (props.animate === "grow" ? grow : shrink)} 0.2s forwards;
|
||||
padding: 10px 12px;
|
||||
box-sizing: border-box;
|
||||
overflow-x: hidden;
|
||||
font-size: 14px;
|
||||
color: #393939;
|
||||
backdrop-filter: blur(2px);
|
||||
`;
|
||||
|
||||
const ChildContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-end;
|
||||
flex-wrap: wrap;
|
||||
padding-top: 70px;
|
||||
align-items: stretch;
|
||||
flex-wrap: nowrap;
|
||||
`;
|
||||
|
||||
const ChildWrapper = styled.div`
|
||||
@@ -213,25 +175,88 @@ const ChildWrapper = styled.div`
|
||||
width: 100%;
|
||||
`;
|
||||
const Child = styled.div`
|
||||
width: 100%;
|
||||
height: 36px;
|
||||
font-family: "Plus Jakarta Sans", sans-serif;
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
|
||||
width: 100%;
|
||||
min-height: 36px;
|
||||
font-family: "Plus Jakarta Sans", sans-serif;
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
padding: ${(props) => (props.hasChildren ? '8px 0 4px' : '8px 4px')};
|
||||
${(props) =>
|
||||
props.hasChildren
|
||||
? `
|
||||
margin-top: 14px;
|
||||
border-top: 0.5px solid #a5a5a5;
|
||||
margin-top: 10px;
|
||||
border-top: 0.5px solid #e9e9e9;
|
||||
height: auto;
|
||||
`
|
||||
: `
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
cursor: pointer;
|
||||
`}
|
||||
`;
|
||||
|
||||
const LeftGroup = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
`;
|
||||
|
||||
const CenterGroup = styled.div`
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
pointer-events: none; /* so clicks pass through center when needed */
|
||||
`;
|
||||
|
||||
const CafeInfo = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
`;
|
||||
|
||||
const RightGroup = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
`;
|
||||
|
||||
const CategoryLabel = styled.div`
|
||||
font-family: "Plus Jakarta Sans", sans-serif;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
color: #6B8F71;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
padding: 6px 2px;
|
||||
pointer-events: none;
|
||||
`;
|
||||
|
||||
const HamburgerButton = styled.button`
|
||||
width: clamp(32px, 4.5vw, 52px);
|
||||
height: clamp(32px, 4.5vw, 52px);
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e5e5e5;
|
||||
background: #ffffff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
& > svg {
|
||||
width: 60%;
|
||||
height: 60%;
|
||||
}
|
||||
`;
|
||||
|
||||
const HamburgerIcon = () => (
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="3" y="6" width="18" height="2" rx="1" fill="#2d2d2d"/>
|
||||
<rect x="3" y="11" width="18" height="2" rx="1" fill="#2d2d2d"/>
|
||||
<rect x="3" y="16" width="18" height="2" rx="1" fill="#2d2d2d"/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
|
||||
const Header = ({
|
||||
HeaderText,
|
||||
@@ -261,10 +286,10 @@ const Header = ({
|
||||
const [guestSideOf, setGuestSideOf] = useState(null);
|
||||
const location = useLocation();
|
||||
|
||||
const handleImageClick = () => {
|
||||
const toggleMenu = () => {
|
||||
if (showRectangle) {
|
||||
setAnimate("shrink");
|
||||
setTimeout(() => setShowRectangle(false), 500);
|
||||
setTimeout(() => setShowRectangle(false), 200);
|
||||
} else {
|
||||
setAnimate("grow");
|
||||
setShowRectangle(true);
|
||||
@@ -274,15 +299,14 @@ const Header = ({
|
||||
const handleClickOutside = (event) => {
|
||||
if (rectangleRef.current && !rectangleRef.current.contains(event.target)) {
|
||||
setAnimate("shrink");
|
||||
setTimeout(() => setShowRectangle(false), 500);
|
||||
rectangleRef.current.style.overflow = "hidden";
|
||||
setTimeout(() => setShowRectangle(false), 200);
|
||||
}
|
||||
};
|
||||
|
||||
const handleScroll = () => {
|
||||
if (showRectangle) {
|
||||
setAnimate("shrink");
|
||||
setTimeout(() => setShowRectangle(false), 500);
|
||||
setTimeout(() => setShowRectangle(false), 200);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -321,38 +345,48 @@ const Header = ({
|
||||
// Otherwise, use the possessive function
|
||||
return `${cafeName}'s menu`;
|
||||
};
|
||||
return (
|
||||
|
||||
const formatCafeName = (name) => {
|
||||
if (!name) return name;
|
||||
return name
|
||||
.toLowerCase()
|
||||
.replace(/\b\w/g, (c) => c.toUpperCase());
|
||||
};
|
||||
return (
|
||||
<HeaderBarbackground shopName={shopName}>
|
||||
<HeaderBar HeaderMargin={HeaderMargin} shopName={shopName}>
|
||||
<Title HeaderSize={HeaderSize}>
|
||||
{shopName == null
|
||||
? HeaderText == null
|
||||
? "kedaimaster"
|
||||
: HeaderText
|
||||
: shopName}
|
||||
</Title>
|
||||
<div style={{ visibility: showProfile ? "visible" : "hidden" }}>
|
||||
<ProfileImage
|
||||
src={shopImage && !shopImage.includes('undefined') ? shopImage || 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcS-DjX_bGBax4NL14ULvkAdU4FP3FKoWXWu5w&s' : "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcS-DjX_bGBax4NL14ULvkAdU4FP3FKoWXWu5w&s"}
|
||||
alt="Profile"
|
||||
onClick={user.username !== undefined ? handleImageClick : null}
|
||||
animate={showRectangle && animate}
|
||||
/>
|
||||
<ProfileName animate={showRectangle && animate}>
|
||||
{showProfile && user.username !== undefined ? user.username : "guest"}
|
||||
</ProfileName>
|
||||
{showRectangle && (
|
||||
<Rectangle ref={rectangleRef} animate={animate}>
|
||||
<ChildContainer>
|
||||
<HeaderBar HeaderMargin={HeaderMargin} shopName={shopName}>
|
||||
<LeftGroup>
|
||||
<CafeAvatar
|
||||
src={shopImage && !shopImage.includes('undefined') ? shopImage : "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcS-DjX_bGBax4NL14ULvkAdU4FP3FKoWXWu5w&s"}
|
||||
alt="Cafe"
|
||||
/>
|
||||
</LeftGroup>
|
||||
<CenterGroup>
|
||||
<Title HeaderSize={HeaderSize}>
|
||||
{formatCafeName(
|
||||
shopName == null
|
||||
? HeaderText == null
|
||||
? "kedaimaster"
|
||||
: HeaderText
|
||||
: shopName
|
||||
)}
|
||||
</Title>
|
||||
</CenterGroup>
|
||||
|
||||
<RightGroup style={{ visibility: showProfile ? "visible" : "hidden", position: 'relative' }}>
|
||||
<HamburgerButton onClick={toggleMenu} aria-label="Open menu">
|
||||
<HamburgerIcon />
|
||||
</HamburgerButton>
|
||||
{showRectangle && (
|
||||
<Rectangle ref={rectangleRef} animate={animate}>
|
||||
<ChildContainer>
|
||||
{guestSideOfClerk && guestSideOfClerk.clerkUsername && (
|
||||
<Child hasChildren>
|
||||
this is the guest side of {guestSideOfClerk.clerkUsername}
|
||||
</Child>
|
||||
)}
|
||||
{user.username !== undefined && (
|
||||
<Child onClick={() => setModal("edit_account")}>
|
||||
Kelola akun
|
||||
</Child>
|
||||
<CategoryLabel>Kelola akun</CategoryLabel>
|
||||
)}
|
||||
{user.roleId == 0 && (
|
||||
<Child onClick={()=>setModal('create_coupon', {})}>Buat Voucher</Child>)}
|
||||
@@ -364,9 +398,9 @@ const Header = ({
|
||||
user.roleId === 1 && (
|
||||
<>
|
||||
<Child hasChildren>
|
||||
<Child>
|
||||
{shopName}
|
||||
</Child>
|
||||
<CategoryLabel>
|
||||
{formatCafeName(shopName)}
|
||||
</CategoryLabel>
|
||||
<Child>
|
||||
Mode pengembangan
|
||||
<Switch
|
||||
@@ -381,7 +415,7 @@ const Header = ({
|
||||
</Child>
|
||||
|
||||
<Child hasChildren>
|
||||
<Child>Konfigurasi</Child>
|
||||
<CategoryLabel>Konfigurasi</CategoryLabel>
|
||||
<Child onClick={() => setModal("welcome_config")}>
|
||||
Desain kafe
|
||||
</Child>
|
||||
@@ -393,7 +427,7 @@ const Header = ({
|
||||
</Child>
|
||||
</Child>
|
||||
<Child hasChildren>
|
||||
<Child>Kasir</Child>
|
||||
<CategoryLabel>Kasir</CategoryLabel>
|
||||
<Child onClick={() => setModal("create_clerk")}>
|
||||
+ Tambah
|
||||
</Child>
|
||||
@@ -420,7 +454,7 @@ const Header = ({
|
||||
user.cafeId == shopId &&
|
||||
user.roleId === 2 && (
|
||||
<Child hasChildren>
|
||||
<Child>{shopName}</Child>
|
||||
<CategoryLabel>{formatCafeName(shopName)}</CategoryLabel>
|
||||
|
||||
<Child>
|
||||
Mode pengembangan
|
||||
@@ -435,7 +469,7 @@ const Header = ({
|
||||
</Child>
|
||||
|
||||
<Child hasChildren>
|
||||
<Child>Konfigurasi</Child>
|
||||
<CategoryLabel>Konfigurasi</CategoryLabel>
|
||||
<Child onClick={() => setModal("welcome_config")}>
|
||||
Desain kafe
|
||||
</Child>
|
||||
@@ -478,11 +512,12 @@ const Header = ({
|
||||
{user.username !== undefined && (
|
||||
<Child hasChildren ><Child onClick={isLogout}>Logout</Child></Child>
|
||||
)}
|
||||
</ChildContainer>
|
||||
</Rectangle>
|
||||
)}
|
||||
</div>
|
||||
</HeaderBar></HeaderBarbackground>
|
||||
</ChildContainer>
|
||||
</Rectangle>
|
||||
)}
|
||||
</RightGroup>
|
||||
</HeaderBar>
|
||||
</HeaderBarbackground>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -74,6 +74,11 @@ const Item = ({
|
||||
}
|
||||
};
|
||||
|
||||
const formatCurrency = (value) => {
|
||||
const num = Number(value) || 0;
|
||||
return num.toLocaleString('id-ID');
|
||||
};
|
||||
|
||||
const handlePriceChange = (event) => {
|
||||
setItemPrice(event.target.value);
|
||||
};
|
||||
@@ -93,23 +98,17 @@ const Item = ({
|
||||
<div className={`${!last && !forInvoice ? styles.notLast : ""}`}>
|
||||
<div className={`${styles.item} ${forInvoice ? styles.itemInvoice : ""} `}>
|
||||
{!forInvoice && (
|
||||
// <div className={styles.imageContainer}>
|
||||
<img
|
||||
src={
|
||||
previewUrl
|
||||
}
|
||||
src={previewUrl}
|
||||
onError={({ currentTarget }) => {
|
||||
currentTarget.onerror = null; // prevents looping
|
||||
currentTarget.onerror = null;
|
||||
currentTarget.src =
|
||||
"https://png.pngtree.com/png-vector/20221125/ourmid/pngtree-no-image-available-icon-flatvector-illustration-pic-design-profile-vector-png-image_40966566.jpg";
|
||||
}}
|
||||
alt={itemName}
|
||||
style={{
|
||||
filter: !isAvailable ? "grayscale(100%)" : "none",
|
||||
}}
|
||||
style={{ filter: !isAvailable ? "grayscale(100%)" : "none" }}
|
||||
className={styles.imageContainer}
|
||||
/>
|
||||
// </div>
|
||||
)}
|
||||
<div className={styles.itemDetails}>
|
||||
{forInvoice &&
|
||||
@@ -141,168 +140,63 @@ const Item = ({
|
||||
onChange={handleNameChange}
|
||||
disabled={!blank && !isBeingEdit}
|
||||
/> */}
|
||||
<h3 style={{
|
||||
textTransform: 'capitalize',
|
||||
margin: `${forInvoice ? '13px 0px 10px 10px' : '5px 0px 10px 10px'}`,
|
||||
fontSize: '16px',
|
||||
display: '-webkit-box',
|
||||
WebkitBoxOrient: 'vertical',
|
||||
overflow: 'hidden',
|
||||
WebkitLineClamp: 2,
|
||||
textOverflow: 'ellipsis',
|
||||
width: `${forInvoice? '160px' : 'unset'}`
|
||||
}}>
|
||||
{itemName}
|
||||
</h3>
|
||||
<div style={{ marginRight: forInvoice ? 10 : 0 }}>
|
||||
<h3 className={styles.title} style={{ width: forInvoice ? 160 : 'auto' }}>{itemName}</h3>
|
||||
{!forInvoice && (
|
||||
<div className={styles.priceRow}>
|
||||
{promoPrice && promoPrice != 0 && promoPrice != '' ? (
|
||||
<>
|
||||
<div className={styles.promoBadge} style={{ background: !isAvailable ? 'gray' : undefined }}>
|
||||
Promo {(((initialPrice - promoPrice) / initialPrice) * 100).toFixed(0)}%
|
||||
</div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<span className={styles.priceNow}>Rp {formatCurrency(promoPrice)}</span>
|
||||
<span className={styles.priceOld}>Rp {formatCurrency(initialPrice)}</span>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<span className={styles.priceNow}>Rp {formatCurrency(initialPrice)}</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{forInvoice && (
|
||||
<>
|
||||
<p className={styles.multiplySymbol}>x</p>
|
||||
<p className={styles.qtyInvoice}>{itemQty}</p>
|
||||
</>
|
||||
)}
|
||||
|
||||
{!forInvoice && (
|
||||
// <input
|
||||
// className={`${styles.itemPrice} ${
|
||||
// isBeingEdit || blank ? styles.blank : styles.notblank
|
||||
// } ${!isAvailable ? styles.disabled : ""}`}
|
||||
// value={itemPrice}
|
||||
// placeholder="Harga"
|
||||
// onChange={handlePriceChange}
|
||||
// disabled={!blank && !isBeingEdit}
|
||||
// />
|
||||
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
alignItems: 'center',
|
||||
color: 'rgb(28, 29, 29)',
|
||||
fontSize: '0.875rem',
|
||||
fontWeight: 600,
|
||||
lineHeight: '1rem',
|
||||
marginLeft: 10
|
||||
}}>
|
||||
{promoPrice && promoPrice != 0 && promoPrice != '' ?
|
||||
<>
|
||||
<div style={{
|
||||
position: 'relative',
|
||||
marginTop: '0.125rem',
|
||||
display: 'flex',
|
||||
width: '87px',
|
||||
alignItems: 'center',
|
||||
whiteSpace: 'nowrap',
|
||||
borderRadius: '9999px',
|
||||
backgroundColor: !isAvailable ? 'gray' : 'unset',
|
||||
backgroundImage: isAvailable && 'linear-gradient(to right, #e52535, #fe6d78)',
|
||||
padding: '0.25rem 0rem',
|
||||
color: 'rgb(255, 255, 255)',
|
||||
fontSize: '0.75rem',
|
||||
fontWeight: 600,
|
||||
lineHeight: '1rem',
|
||||
justifyContent: 'center'
|
||||
}}>
|
||||
Promo {(((initialPrice - promoPrice) / initialPrice) * 100).toFixed(0)}%
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex' }}>
|
||||
<span style={{
|
||||
marginLeft: '1rem',
|
||||
marginRight: '0.5rem',
|
||||
marginTop: '0.125rem'
|
||||
}}>{promoPrice}</span>
|
||||
<span style={{
|
||||
marginTop: '0.125rem',
|
||||
color: 'rgb(114, 114, 114)',
|
||||
textDecoration: 'line-through'
|
||||
}}>{initialPrice}</span>
|
||||
</div>
|
||||
</>
|
||||
:
|
||||
<>
|
||||
|
||||
<div style={{ display: 'flex' }}>
|
||||
<span style={{
|
||||
marginRight: '0.5rem',
|
||||
marginTop: '0.125rem'
|
||||
}}>{initialPrice}</span>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!forInvoice &&
|
||||
(!isBeingEdit && itemQty != 0 ? (
|
||||
!isBeingEdit && itemQty > 0 ? (
|
||||
<div className={styles.itemQty}>
|
||||
<svg
|
||||
className={styles.plusNegative}
|
||||
onClick={handleNegativeClick}
|
||||
clipRule="evenodd"
|
||||
fillRule="evenodd"
|
||||
strokeLinejoin="round"
|
||||
strokeMiterlimit="2"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="m12.002 2.005c5.518 0 9.998 4.48 9.998 9.997 0 5.518-4.48 9.998-9.998 9.998-5.517 0-9.997-4.48-9.997-9.998 0-5.517 4.48-9.997 9.997-9.997zm0 1.5c-4.69 0-8.497 3.807-8.497 8.497s3.807 8.498 8.497 8.498 8.498-3.808 8.498-8.498-3.808-8.497-8.498-8.497zm4.253 7.75h-8.5c-.414 0-.75.336-.75.75s.336.75.75.75h8.5c.414 0 .75-.336.75-.75s-.336-.75-.75-.75z"
|
||||
fillRule="nonzero"
|
||||
/>
|
||||
</svg>
|
||||
{!blank && !isBeingEdit ? (
|
||||
<p className={styles.itemQtyValue}>{itemQty}</p>
|
||||
) : (
|
||||
<input
|
||||
className={styles.itemQtyInput}
|
||||
value={itemQty}
|
||||
onChange={handleQtyChange}
|
||||
disabled={!blank && !isBeingEdit}
|
||||
/>
|
||||
)}
|
||||
<svg
|
||||
className={styles.plusNegative}
|
||||
onClick={handlePlusClick}
|
||||
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>
|
||||
<div className={styles.qtyGroup}>
|
||||
<button className={styles.qtyBtn} onClick={handleNegativeClick} aria-label="Kurangi">-</button>
|
||||
{!blank && !isBeingEdit ? (
|
||||
<span className={styles.qtyVal}>{itemQty}</span>
|
||||
) : (
|
||||
<input className={styles.itemQtyInput} value={itemQty} onChange={handleQtyChange} disabled={!blank && !isBeingEdit} />
|
||||
)}
|
||||
<button className={styles.qtyBtn} onClick={handlePlusClick} aria-label="Tambah">+</button>
|
||||
</div>
|
||||
</div>
|
||||
) : !blank && !isBeingEdit ? (
|
||||
<div className={styles.itemQty}>
|
||||
<button
|
||||
className={styles.addButton}
|
||||
style={{ backgroundColor: !isAvailable ? "" : "inherit", border: `2px solid ${isAvailable ? 'inherit' : 'gray'}`, color: `${isAvailable ? '#a8c7a9' : 'gray'}` }}
|
||||
onClick={handlePlusClick}
|
||||
disabled={!isAvailable} // Optionally disable the button if not available
|
||||
>
|
||||
<button className={styles.addButton} onClick={handlePlusClick} disabled={!isAvailable}>
|
||||
Pesan
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div className={styles.itemQty}>
|
||||
<button
|
||||
className={styles.addButton}
|
||||
style={{
|
||||
backgroundColor: "white",
|
||||
width: "150px",
|
||||
color: '#a8c7a9'
|
||||
}}
|
||||
onClick={isBeingEdit ? handleUpdate : handleCreate}
|
||||
>
|
||||
{isBeingEdit ? "Simpan" : "Buat"}
|
||||
<button className={styles.addButton} style={{ backgroundColor: '#ffffff', color: 'var(--brand-sage, #6B8F71)', borderColor: 'var(--brand-sage, #6B8F71)', width: 150 }} onClick={isBeingEdit ? handleUpdate : handleCreate}>
|
||||
{isBeingEdit ? 'Simpan' : 'Buat'}
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
)
|
||||
)}
|
||||
|
||||
{forInvoice && (
|
||||
<p className={styles.itemPriceInvoice}>Rp {itemQty * (promoPrice > 0? promoPrice : itemPrice)}</p>
|
||||
<p className={styles.itemPriceInvoice}>Rp {formatCurrency(itemQty * (promoPrice > 0 ? promoPrice : itemPrice))}</p>
|
||||
)}
|
||||
</div>
|
||||
{forCart && (
|
||||
@@ -316,18 +210,11 @@ const Item = ({
|
||||
</button>
|
||||
)} */}
|
||||
</div>
|
||||
{itemDescription && itemDescription != 'undefined' && itemDescription?.length &&
|
||||
{itemDescription && itemDescription != 'undefined' && itemDescription?.length ? (
|
||||
<div>
|
||||
<p style={{
|
||||
maxHeight: '34px',
|
||||
display: '-webkit-box',
|
||||
WebkitBoxOrient: 'vertical',
|
||||
overflow: 'hidden',
|
||||
WebkitLineClamp: 2,
|
||||
textOverflow: 'ellipsis', color: '#5f5f5f', fontSize: '14px', padding: '5px', margin: 0
|
||||
}}>{itemDescription}</p>
|
||||
<p className={styles.desc} style={{ padding: '4px 6px', margin: 0 }}>{itemDescription}</p>
|
||||
</div>
|
||||
}
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -6,19 +6,20 @@
|
||||
|
||||
.item {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding-left: 5px;
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
color: rgba(88, 55, 50, 1);
|
||||
font-size: 32px;
|
||||
box-sizing: border-box; /* Include padding and border in the element's total width */
|
||||
width: 100%; /* Ensure the item does not exceed the parent's width */
|
||||
overflow: hidden; /* Prevent internal overflow */
|
||||
padding-top: 10px;
|
||||
margin-bottom: 5px;
|
||||
width: 100%;
|
||||
gap: 10px;
|
||||
padding: 8px 10px;
|
||||
margin: 6px 0;
|
||||
border: 1px solid #e3ece6;
|
||||
border-radius: 12px;
|
||||
background: var(--brand-sage-50, #F0F6F2);
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.03);
|
||||
box-sizing: border-box;
|
||||
transition: box-shadow 0.2s ease, border-color 0.2s ease, background-color 0.2s ease;
|
||||
}
|
||||
.item:hover { box-shadow: 0 4px 10px rgba(0,0,0,0.08); border-color: #d9e6de; }
|
||||
|
||||
.item:not(.itemInvoice) {
|
||||
/* border-top: 2px solid #00000017; */
|
||||
@@ -50,9 +51,9 @@
|
||||
|
||||
.imageContainer {
|
||||
position: relative;
|
||||
width: 26vw;
|
||||
height: 26vw;
|
||||
border-radius: 12px;
|
||||
width: clamp(68px, 18vw, 96px);
|
||||
height: clamp(68px, 18vw, 96px);
|
||||
border-radius: 10px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
@@ -84,11 +85,15 @@
|
||||
.itemDetails {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
flex-grow: 1;
|
||||
justify-content: center;
|
||||
align-items: stretch;
|
||||
gap: 6px;
|
||||
margin-left: 6px;
|
||||
margin-right: 6px;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.infoRow { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
|
||||
|
||||
.itemInvoiceDetails {
|
||||
display: flex;
|
||||
@@ -127,17 +132,7 @@
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.itemPrice {
|
||||
font-family: "Plus Jakarta Sans", sans-serif;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
width: calc(100% - 15px); /* Adjust the width to prevent overflow */
|
||||
font-size: 3.3vw;
|
||||
/* margin-bottom: 35px; */
|
||||
margin-left: 5px;
|
||||
color: #3a3a3a;
|
||||
background-color: transparent;
|
||||
}
|
||||
.itemPrice { display: none; }
|
||||
|
||||
.itemPriceInvoice {
|
||||
font-family: "Plus Jakarta Sans", sans-serif;
|
||||
@@ -153,11 +148,9 @@
|
||||
.itemQty {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 0.9rem;
|
||||
margin-left: 5px;
|
||||
color: #a8c7a9;
|
||||
fill: #a8c7a9;
|
||||
height: 40px;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
min-height: 32px;
|
||||
}
|
||||
|
||||
.itemQtyValue {
|
||||
@@ -183,19 +176,21 @@
|
||||
}
|
||||
|
||||
.addButton {
|
||||
background-color: #ffffff;
|
||||
border: 2px solid #a8c7a9;
|
||||
/* border: none; */
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
width: 87px;
|
||||
height: 32px;
|
||||
margin-left: 5px;
|
||||
margin-top: 5px;
|
||||
border-radius: 20px;
|
||||
background-color: var(--brand-sage, #6B8F71);
|
||||
border: 1px solid var(--brand-sage, #6B8F71);
|
||||
color: #ffffff;
|
||||
display: inline-block;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
min-width: 84px;
|
||||
height: 34px;
|
||||
padding: 0 14px;
|
||||
border-radius: 10px; /* square rounded corner */
|
||||
box-shadow: 0 1px 2px rgba(0,0,0,0.08);
|
||||
}
|
||||
.addButton:hover { background-color: var(--brand-sage-hover, #7FAE7D); border-color: var(--brand-sage-hover, #7FAE7D); }
|
||||
.addButton:disabled { background-color: var(--brand-sage-muted, #CFD8D3); border-color: var(--brand-sage-muted, #CFD8D3); cursor: default; }
|
||||
.grayscale {
|
||||
filter: grayscale(100%);
|
||||
}
|
||||
@@ -204,9 +199,8 @@
|
||||
color: gray;
|
||||
}
|
||||
.plusNegative {
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
margin: 2.5px 0 -0.5px 0px;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.plusNegative2 {
|
||||
@@ -224,6 +218,91 @@
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
/* New elements for clean cafe item card */
|
||||
.title {
|
||||
font-family: "Plus Jakarta Sans", sans-serif;
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
color: #2d2d2d;
|
||||
margin: 0 0 2px 0;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
-webkit-line-clamp: 2;
|
||||
text-transform: capitalize;
|
||||
text-align: left;
|
||||
flex: 1; min-width: 0;
|
||||
}
|
||||
|
||||
/* Responsive type scale for title and price */
|
||||
@media (min-width: 600px) {
|
||||
.title { font-size: 17px; }
|
||||
.priceNow { font-size: 15px; }
|
||||
}
|
||||
@media (min-width: 992px) {
|
||||
.title { font-size: 18px; }
|
||||
.priceNow { font-size: 16px; }
|
||||
}
|
||||
.desc {
|
||||
color: #5f5f5f;
|
||||
font-size: 12px;
|
||||
line-height: 1.25;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
-webkit-line-clamp: 2;
|
||||
}
|
||||
.priceRow { display: inline-flex; align-items: center; gap: 8px; }
|
||||
.promoBadge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 2px 8px;
|
||||
height: 20px;
|
||||
border-radius: 999px;
|
||||
background: linear-gradient(to right, #e52535, #fe6d78);
|
||||
color: #fff;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
}
|
||||
.priceNow {
|
||||
color: #1c1d1d;
|
||||
font-weight: 700;
|
||||
font-size: 14px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.priceOld {
|
||||
color: #727272;
|
||||
font-size: 12px;
|
||||
text-decoration: line-through;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.qtyGroup {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
border: 1px solid #e6e6e6;
|
||||
border-radius: 10px; /* square rounded corners */
|
||||
height: 32px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.qtyBtn {
|
||||
width: 34px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #fff;
|
||||
color: var(--brand-sage, #6B8F71);
|
||||
border: none;
|
||||
}
|
||||
.qtyBtn:hover { background: var(--brand-sage-50, #F0F6F2); }
|
||||
.qtyVal {
|
||||
min-width: 28px;
|
||||
text-align: center;
|
||||
font-weight: 700;
|
||||
color: #2d2d2d;
|
||||
}
|
||||
|
||||
.itemInvoice .itemDetails {
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
import ItemType from "./ItemType.js";
|
||||
import { createItemType, updateItemDeletionStatus } from "../helpers/itemHelper.js";
|
||||
import ItemConfig from "./ItemConfig.js"
|
||||
import { ArrowUp, ArrowDown, Pencil } from 'lucide-react';
|
||||
|
||||
const ItemLister = ({
|
||||
index,
|
||||
@@ -618,87 +619,31 @@ const ItemLister = ({
|
||||
disabled={!isEdit}
|
||||
/>
|
||||
{isEditMode && !isEdit && (
|
||||
<>
|
||||
<div
|
||||
style={{
|
||||
width: '32px',
|
||||
height: '32px', // Add a height to the div
|
||||
display: 'flex', // Use flexbox
|
||||
justifyContent: 'center', // Center horizontally
|
||||
alignItems: 'center', // Center vertically
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
onClick={index == 0 ? null : () => moveItemTypeUp(itemTypeId)} // Move onClick here for the whole div
|
||||
<div className={styles.titleActions}>
|
||||
<button
|
||||
className={styles.iconBtn}
|
||||
onClick={() => index === 0 ? null : moveItemTypeUp(itemTypeId)}
|
||||
disabled={index === 0}
|
||||
aria-label="Naikkan kategori"
|
||||
>
|
||||
<svg
|
||||
viewBox="0 0 16 16"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="#000000"
|
||||
style={{ width: '100%', height: '100%' }} // Ensure SVG fits the div
|
||||
>
|
||||
<g id="SVGRepo_bgCarrier" strokeWidth="0"></g>
|
||||
<g id="SVGRepo_tracerCarrier" strokeLinecap="round" strokeLinejoin="round"></g>
|
||||
<g id="SVGRepo_iconCarrier">
|
||||
<path d="m 1 11 c 0 -0.265625 0.105469 -0.519531 0.292969 -0.707031 l 6 -6 c 0.390625 -0.390625 1.023437 -0.390625 1.414062 0 l 6 6 c 0.1875 0.1875 0.292969 0.441406 0.292969 0.707031 s -0.105469 0.519531 -0.292969 0.707031 c -0.390625 0.390625 -1.023437 0.390625 -1.414062 0 l -5.292969 -5.292969 l -5.292969 5.292969 c -0.390625 0.390625 -1.023437 0.390625 -1.414062 0 c -0.1875 -0.1875 -0.292969 -0.441406 -0.292969 -0.707031 z m 0 0" fill={index === 0 ? "gray" : "#2e3436"}></path>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
width: '32px',
|
||||
height: '32px', // Add a height to the div
|
||||
display: 'flex', // Use flexbox
|
||||
justifyContent: 'center', // Center horizontally
|
||||
alignItems: 'center', // Center vertically
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
onClick={index == indexTotal - 1 ? null : () => moveItemTypeDown(itemTypeId)} // Move onClick here for the whole div
|
||||
<ArrowUp size={18} />
|
||||
</button>
|
||||
<button
|
||||
className={styles.iconBtn}
|
||||
onClick={() => index === indexTotal - 1 ? null : moveItemTypeDown(itemTypeId)}
|
||||
disabled={index === indexTotal - 1}
|
||||
aria-label="Turunkan kategori"
|
||||
>
|
||||
<svg
|
||||
viewBox="0 0 16 16"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="#000000"
|
||||
style={{ width: '100%', height: '100%' }} // Ensure SVG fits the div
|
||||
>
|
||||
<g id="SVGRepo_bgCarrier" strokeWidth="0"></g>
|
||||
<g id="SVGRepo_tracerCarrier" strokeLinecap="round" strokeLinejoin="round"></g>
|
||||
<g id="SVGRepo_iconCarrier">
|
||||
<path d="m 1 5 c 0 -0.265625 0.105469 -0.519531 0.292969 -0.707031 c 0.390625 -0.390625 1.023437 -0.390625 1.414062 0 l 5.292969 5.292969 l 5.292969 -5.292969 c 0.390625 -0.390625 1.023437 -0.390625 1.414062 0 c 0.1875 0.1875 0.292969 0.441406 0.292969 0.707031 s -0.105469 0.519531 -0.292969 0.707031 l -6 6 c -0.390625 0.390625 -1.023437 0.390625 -1.414062 0 l -6 -6 c -0.1875 -0.1875 -0.292969 -0.441406 -0.292969 -0.707031 z m 0 0" fill={index === indexTotal - 1 ? "gray" : "#2e3436"}></path>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
width: '32px',
|
||||
height: '32px', // Add a height to the div
|
||||
display: 'flex', // Use flexbox
|
||||
justifyContent: 'center', // Center horizontally
|
||||
alignItems: 'center', // Center vertically
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
onClick={toggleEditTypeItem} // Move onClick here for the whole div
|
||||
<ArrowDown size={18} />
|
||||
</button>
|
||||
<button
|
||||
className={styles.iconBtn}
|
||||
onClick={toggleEditTypeItem}
|
||||
aria-label="Edit kategori"
|
||||
>
|
||||
<svg
|
||||
fill="#000000"
|
||||
viewBox="0 0 32 32"
|
||||
style={{ fillRule: 'evenodd', clipRule: 'evenodd', strokeLinejoin: 'round', strokeMiterlimit: 2 }}
|
||||
version="1.1"
|
||||
xmlSpace="preserve"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlnsSerif="http://www.serif.com/"
|
||||
xmlnsXlink="http://www.w3.org/1999/xlink"
|
||||
>
|
||||
<g id="SVGRepo_bgCarrier" strokeWidth="0"></g>
|
||||
<g id="SVGRepo_tracerCarrier" strokeLinecap="round" strokeLinejoin="round"></g>
|
||||
<g id="SVGRepo_iconCarrier">
|
||||
<path d="M12.965,5.462c0,-0 -2.584,0.004 -4.979,0.008c-3.034,0.006 -5.49,2.467 -5.49,5.5l0,13.03c0,1.459 0.579,2.858 1.611,3.889c1.031,1.032 2.43,1.611 3.889,1.611l13.003,0c3.038,-0 5.5,-2.462 5.5,-5.5c0,-2.405 0,-5.004 0,-5.004c0,-0.828 -0.672,-1.5 -1.5,-1.5c-0.827,-0 -1.5,0.672 -1.5,1.5l0,5.004c0,1.381 -1.119,2.5 -2.5,2.5l-13.003,0c-0.663,-0 -1.299,-0.263 -1.768,-0.732c-0.469,-0.469 -0.732,-1.105 -0.732,-1.768l0,-13.03c0,-1.379 1.117,-2.497 2.496,-2.5c2.394,-0.004 4.979,-0.008 4.979,-0.008c0.828,-0.002 1.498,-0.675 1.497,-1.503c-0.001,-0.828 -0.675,-1.499 -1.503,-1.497Z"></path>
|
||||
<path d="M20.046,6.411l-6.845,6.846c-0.137,0.137 -0.232,0.311 -0.271,0.501l-1.081,5.152c-0.069,0.329 0.032,0.671 0.268,0.909c0.237,0.239 0.577,0.343 0.907,0.277l5.194,-1.038c0.193,-0.039 0.371,-0.134 0.511,-0.274l6.845,-6.845l-5.528,-5.528Zm1.415,-1.414l5.527,5.528l1.112,-1.111c1.526,-1.527 1.526,-4.001 -0,-5.527c-0.001,-0 -0.001,-0.001 -0.001,-0.001c-1.527,-1.526 -4.001,-1.526 -5.527,-0l-1.111,1.111Z"></path>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
</>
|
||||
<Pencil size={18} />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -85,6 +85,33 @@
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.titleActions {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.iconBtn {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px solid #e6e6e6;
|
||||
background: #ffffff;
|
||||
color: #2d2d2d;
|
||||
border-radius: 8px; /* square rounded */
|
||||
cursor: pointer;
|
||||
}
|
||||
.iconBtn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: default;
|
||||
}
|
||||
.iconBtn:hover:not(:disabled) {
|
||||
background: var(--brand-sage-50, #F0F6F2);
|
||||
border-color: var(--brand-sage, #6B8F71);
|
||||
}
|
||||
|
||||
.title {
|
||||
background-color: transparent;
|
||||
font-family: "Plus Jakarta Sans", sans-serif;
|
||||
@@ -224,4 +251,4 @@
|
||||
font-size: 6vw;
|
||||
color: black;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useRef, useEffect, useState } from "react";
|
||||
import styles from "./ItemType.module.css";
|
||||
import { Coffee, CupSoda, CakeSlice, Utensils, Grid2X2, Plus } from 'lucide-react';
|
||||
|
||||
export default function ItemType({
|
||||
onClick,
|
||||
@@ -57,6 +58,15 @@ export default function ItemType({
|
||||
onCreate(namee, selectedImage);
|
||||
};
|
||||
|
||||
const formatName = (val) => {
|
||||
if (!val || typeof val !== 'string') return val;
|
||||
return val
|
||||
.toLowerCase()
|
||||
.replace(/\b\w/g, (c) => c.toUpperCase());
|
||||
};
|
||||
|
||||
const iconImageUrl = imageUrl === 'uploads/assets/All.png' ? 'icon:all' : imageUrl;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
@@ -72,27 +82,25 @@ export default function ItemType({
|
||||
>
|
||||
<div
|
||||
onClick={
|
||||
rectangular ? (blank ? null : () => onClick(imageUrl)) : onClick
|
||||
rectangular ? (blank ? null : () => onClick(iconImageUrl)) : onClick
|
||||
}
|
||||
className={styles["item-type-rect"]}
|
||||
style={{
|
||||
top: selected ? "-10px" : "initial",
|
||||
// Remove lift-up effect; only color changes when selected
|
||||
backgroundColor: selected ? 'var(--brand-sage, #6B8F71)' : '#ffffff',
|
||||
border: selected ? '1px solid var(--brand-sage, #6B8F71)' : '1px solid #e6e6e6',
|
||||
color: selected ? '#ffffff' : '#4a6b5a'
|
||||
}}
|
||||
>
|
||||
{imageUrl != 'uploads/assets/All.png' ?
|
||||
<img
|
||||
src={previewUrl}
|
||||
alt={namee}
|
||||
className={styles["item-type-image"]}
|
||||
/>
|
||||
:<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
{iconImageUrl === 'uploads/assets/All.png' ? (
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="100%" height="100%" viewBox="0 0 800.000000 800.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
<metadata>
|
||||
Created by potrace 1.16, written by Peter Selinger 2001-2019
|
||||
</metadata>
|
||||
<g transform="translate(0.000000,800.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000" stroke="none">
|
||||
fill="currentColor" stroke="none">
|
||||
<path d="M3708 7165 c-3 -4 -44 -10 -90 -15 -266 -28 -530 -91 -753 -180 -11
|
||||
-4 -42 -16 -70 -26 -27 -9 -129 -57 -225 -106 -186 -94 -188 -95 -262 -145
|
||||
-26 -18 -52 -33 -58 -33 -5 0 -24 -13 -42 -28 -18 -16 -53 -43 -78 -61 -124
|
||||
@@ -138,7 +146,17 @@ c261 0 329 -3 352 -14z m1237 -2 c52 -35 54 -49 54 -379 0 -348 -2 -360 -69
|
||||
58 40 59 387 60 178 0 328 -4 342 -10z"/>
|
||||
</g>
|
||||
</svg>
|
||||
}
|
||||
) : (iconImageUrl && typeof iconImageUrl === 'string' && iconImageUrl.startsWith('icon:')) ? (
|
||||
<div style={{width:'100%',height:'100%',display:'flex',alignItems:'center',justifyContent:'center'}}>
|
||||
<LucideCategoryIcon name={namee} iconKey={(iconImageUrl || '').split(':')[1]} />
|
||||
</div>
|
||||
) : (
|
||||
<img
|
||||
src={previewUrl}
|
||||
alt={namee}
|
||||
className={styles["item-type-image"]}
|
||||
/>
|
||||
)}
|
||||
{blank && rectangular && (
|
||||
<div className={styles["item-type-image-container"]}>
|
||||
<input
|
||||
@@ -155,15 +173,49 @@ c261 0 329 -3 352 -14z m1237 -2 c52 -35 54 -49 54 -379 0 -348 -2 -360 -69
|
||||
<input
|
||||
ref={inputRef}
|
||||
className={`${styles["item-type-name"]} ${styles.noborder}`}
|
||||
value={namee}
|
||||
value={formatName(namee)}
|
||||
onChange={handleNameChange}
|
||||
disabled={true}
|
||||
style={{
|
||||
top: selected ? "-5px" : "initial",
|
||||
borderBottom: selected ? "1px solid #000" : "none",
|
||||
top: 'initial',
|
||||
borderBottom: 'none',
|
||||
color: selected ? '#2d2d2d' : '#333',
|
||||
textTransform: 'capitalize'
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function LucideCategoryIcon({ name, iconKey }) {
|
||||
const key = pickIconKey(name, iconKey);
|
||||
const size = '56%';
|
||||
switch (key) {
|
||||
case 'coffee':
|
||||
return <Coffee color={'currentColor'} size={size} strokeWidth={2} />;
|
||||
case 'drink':
|
||||
return <CupSoda color={'currentColor'} size={size} strokeWidth={2} />;
|
||||
case 'dessert':
|
||||
return <CakeSlice color={'currentColor'} size={size} strokeWidth={2} />;
|
||||
case 'food':
|
||||
return <Utensils color={'currentColor'} size={size} strokeWidth={2} />;
|
||||
case 'all':
|
||||
return <Grid2X2 color={'currentColor'} size={size} strokeWidth={2} />;
|
||||
case 'plus':
|
||||
return <Plus color={'currentColor'} size={size} strokeWidth={2} />;
|
||||
default:
|
||||
return <Utensils color={'currentColor'} size={size} strokeWidth={2} />;
|
||||
}
|
||||
}
|
||||
|
||||
function pickIconKey(name, iconKey) {
|
||||
const n = (name || '').toLowerCase();
|
||||
if (iconKey === 'plus') return 'plus';
|
||||
if (iconKey === 'all') return 'all';
|
||||
if (/(kopi|coffee|espresso|latte|americano|kapal|brew)/.test(n)) return 'coffee';
|
||||
if (/(teh|tea|drink|minum|soda|juice|jus|milk|susu|lemon)/.test(n)) return 'drink';
|
||||
if (/(dessert|cake|kue|manis|ice|es krim|ice-cream)/.test(n)) return 'dessert';
|
||||
if (/(food|makan|snack|cemilan|nasi|mie|noodle|soup|sup|ayam|daging|ikan|roti|sandwich|burger|pizza)/.test(n)) return 'food';
|
||||
return 'food';
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
.item-type {
|
||||
width: calc(25vw - 20px);
|
||||
height: calc(30vw - 20px);
|
||||
margin: 1px 10px 0px;
|
||||
width: auto;
|
||||
height: auto;
|
||||
margin: 0 4px; /* tighter spacing between tiles */
|
||||
overflow: visible;
|
||||
text-align: center;
|
||||
align-items: center;
|
||||
@@ -34,24 +34,33 @@
|
||||
}
|
||||
.item-type-rect {
|
||||
position: relative;
|
||||
height: 13vw;
|
||||
width: 13vw;
|
||||
height: clamp(48px, 9vw, 80px);
|
||||
width: clamp(48px, 9vw, 80px);
|
||||
object-fit: cover;
|
||||
border-radius: 15px;
|
||||
border-radius: 12px;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.item-type-rect:hover {
|
||||
background-color: var(--brand-sage-100, #E9F3ED);
|
||||
border-color: var(--brand-sage, #6B8F71);
|
||||
}
|
||||
|
||||
.item-type-name {
|
||||
font-family: "Plus Jakarta Sans", sans-serif;
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
font-size: 14px;
|
||||
font-size: 12px;
|
||||
color: #333;
|
||||
width: calc(25vw - 30px);
|
||||
width: auto;
|
||||
text-align: center;
|
||||
background-color: transparent;
|
||||
position: relative; /* Needed for positioning the button */
|
||||
position: relative;
|
||||
margin-top: 6px; /* keep label spacing constant; avoid jumping */
|
||||
}
|
||||
|
||||
.item-type-image {
|
||||
|
||||
@@ -1,11 +1,23 @@
|
||||
/* New clean, intuitive category bar */
|
||||
.item-type-lister {
|
||||
width: 100vw;
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
white-space: nowrap;
|
||||
padding: 3px 0px;
|
||||
margin-bottom: -5px;
|
||||
padding: 2px 0px; /* tighter top/bottom padding */
|
||||
}
|
||||
|
||||
.category-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
overflow-x: auto;
|
||||
padding: 8px 12px 4px;
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
}
|
||||
.category-bar::-webkit-scrollbar { display: none; }
|
||||
|
||||
/* Legacy horizontal tile list container (used for tile UI) */
|
||||
.item-type-list {
|
||||
display: inline-flex;
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
@@ -13,11 +25,44 @@
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.item-type {
|
||||
display: inline-block;
|
||||
margin-right: 20px;
|
||||
/* Space between items */
|
||||
.category-chip {
|
||||
flex: 0 0 auto;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
height: 36px;
|
||||
padding: 0 14px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid #e6e6e6;
|
||||
background: #ffffff;
|
||||
color: #2d2d2d;
|
||||
font-family: "Plus Jakarta Sans", sans-serif;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
.category-chip:hover { border-color: #d0d0d0; }
|
||||
.category-chip.selected {
|
||||
background: #73a585;
|
||||
color: #ffffff;
|
||||
border-color: #73a585;
|
||||
}
|
||||
|
||||
.category-chip .chip-icon {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.add-item-chip {
|
||||
background: #f4f7f5;
|
||||
border-color: #dfe7e2;
|
||||
color: #4a6b5a;
|
||||
}
|
||||
.add-item-chip:hover { background: #eaf1ed; }
|
||||
.rect-creator {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
@@ -55,13 +100,4 @@
|
||||
bottom: 0;
|
||||
align-self: center; /* Center the button horizontally */
|
||||
}
|
||||
.item-type-name {
|
||||
font-family: "Plus Jakarta Sans", sans-serif;
|
||||
font-style: normal;
|
||||
height: 20vw;
|
||||
font-size: 1.5rem;
|
||||
font-weight: 500;
|
||||
color: black;
|
||||
text-transform: capitalize;
|
||||
z-index: 301;
|
||||
}
|
||||
/* Legacy styles kept for ItemType grid if needed elsewhere */
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { useState, useRef, useEffect } from "react";
|
||||
import smoothScroll from "smooth-scroll-into-view-if-needed";
|
||||
import "./ItemTypeLister.css";
|
||||
import ItemType from "./ItemType";
|
||||
import { createItem, createItemType } from "../helpers/itemHelper.js";
|
||||
import { createItem } from "../helpers/itemHelper.js";
|
||||
import { getImageUrl } from "../helpers/itemHelper";
|
||||
import ItemLister from "./ItemLister";
|
||||
|
||||
@@ -22,24 +22,6 @@ const ItemTypeLister = ({
|
||||
const newItemDivRef = useRef(null);
|
||||
const [items, setItems] = useState([]);
|
||||
|
||||
const [itemTypeName, setItemTypeName] = useState("");
|
||||
const handleCreateItem = (name, price, selectedImage, previewUrl, description, promoPrice) => {
|
||||
console.log(previewUrl);
|
||||
const newItem = {
|
||||
itemId: items.length + 1,
|
||||
name,
|
||||
price,
|
||||
selectedImage,
|
||||
image: previewUrl,
|
||||
availability: true,
|
||||
description,
|
||||
promoPrice
|
||||
};
|
||||
|
||||
// Update the items state with the new item
|
||||
setItems((prevItems) => [...prevItems, newItem]);
|
||||
};
|
||||
|
||||
// Effect to handle changes to isAddingNewItem
|
||||
useEffect(() => {
|
||||
if (isAddingNewItem && newItemDivRef.current) {
|
||||
@@ -67,90 +49,63 @@ const ItemTypeLister = ({
|
||||
document.body.style.overflow = !isAddingNewItem ? "hidden" : "auto";
|
||||
};
|
||||
|
||||
async function handleCreate(name, selectedImage) {
|
||||
createItemType(shopId, name, selectedImage);
|
||||
}
|
||||
// Removed legacy image upload logic used by the old tile view
|
||||
|
||||
const canManage = user && (user.user_id == shopOwnerId || user.cafeId == shopId);
|
||||
|
||||
const [selectedImage, setSelectedImage] = useState(null);
|
||||
const [previewUrl, setPreviewUrl] = useState("");
|
||||
const [imageUrl, setImaguUrl] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
// if (selectedImage) {
|
||||
// const reader = new FileReader();
|
||||
// reader.onloadend = () => {
|
||||
// setPreviewUrl(reader.result);
|
||||
// };
|
||||
// reader.readAsDataURL(selectedImage);
|
||||
// } else {
|
||||
// setPreviewUrl(getImageUrl(imageUrl));
|
||||
setPreviewUrl(selectedImage);
|
||||
// }
|
||||
}, [selectedImage, imageUrl]);
|
||||
const handleImageChange = (e) => {
|
||||
setSelectedImage(e);
|
||||
const formatName = (name) => {
|
||||
if (!name) return name;
|
||||
return name
|
||||
.toLowerCase()
|
||||
.replace(/\b\w/g, (c) => c.toUpperCase());
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className="item-type-lister"
|
||||
style={{ overflowX: isAddingNewItem ? "hidden" : "" }}
|
||||
>
|
||||
<div
|
||||
ref={newItemDivRef}
|
||||
className="item-type-list"
|
||||
style={{ display: isAddingNewItem ? "inline-flex" : "inline-flex" }}
|
||||
>
|
||||
{isEditMode &&
|
||||
!isAddingNewItem &&
|
||||
user && (
|
||||
user.user_id == shopOwnerId || user.cafeId == shopId) && (
|
||||
<ItemType
|
||||
onClick={toggleAddNewItem}
|
||||
name={"buat baru"}
|
||||
imageUrl={getImageUrl("uploads/assets/addnew.png")}
|
||||
/>
|
||||
)}
|
||||
{user &&(
|
||||
user.user_id == shopOwnerId || user.cafeId == shopId) &&
|
||||
isAddingNewItem && (
|
||||
<>
|
||||
<ItemLister
|
||||
shopId={shopId}
|
||||
shopOwnerId={shopOwnerId}
|
||||
user={user}
|
||||
typeName={""}
|
||||
setShopItems={setShopItems}
|
||||
itemList={items}
|
||||
isEditMode={true}
|
||||
handleCreateItem={(itemTypeId, name, price, selectedImage, description, promoPrice) => createItem(shopId, name, price, selectedImage, itemTypeId, description, promoPrice)}
|
||||
beingEditedType={beingEditedType}
|
||||
setBeingEditedType={setBeingEditedType}
|
||||
alwaysEdit={true}
|
||||
handleUnEdit={toggleAddNewItem}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<div className="item-type-lister" style={{ overflowX: isAddingNewItem ? 'hidden' : 'auto' }}>
|
||||
<div ref={newItemDivRef} className="item-type-list" style={{ display: 'inline-flex' }}>
|
||||
{isEditMode && !isAddingNewItem && canManage && (
|
||||
<ItemType
|
||||
onClick={toggleAddNewItem}
|
||||
name={"buat baru"}
|
||||
imageUrl={"icon:plus"}
|
||||
/>
|
||||
)}
|
||||
|
||||
{canManage && isAddingNewItem && (
|
||||
<ItemLister
|
||||
shopId={shopId}
|
||||
shopOwnerId={shopOwnerId}
|
||||
user={user}
|
||||
typeName={""}
|
||||
setShopItems={setShopItems}
|
||||
itemList={items}
|
||||
isEditMode={true}
|
||||
handleCreateItem={(itemTypeId, name, price, selectedImage, description, promoPrice) => createItem(shopId, name, price, selectedImage, itemTypeId, description, promoPrice)}
|
||||
beingEditedType={beingEditedType}
|
||||
setBeingEditedType={setBeingEditedType}
|
||||
alwaysEdit={true}
|
||||
handleUnEdit={toggleAddNewItem}
|
||||
/>
|
||||
)}
|
||||
|
||||
{itemTypes && itemTypes.length > 0 && (
|
||||
<ItemType
|
||||
name={"semua"}
|
||||
onClick={() => onFilterChange(0)}
|
||||
imageUrl={"uploads/assets/All.png"}
|
||||
imageUrl={"icon:all"}
|
||||
selected={filterId === 0}
|
||||
/>
|
||||
)}
|
||||
{itemTypes &&
|
||||
itemTypes.map(
|
||||
(itemType) =>
|
||||
(
|
||||
itemType.itemList.length > 0 || (user && (user.user_id == shopOwnerId || user.cafeId == shopId))) && (
|
||||
<ItemType
|
||||
key={itemType.itemTypeId}
|
||||
name={itemType.name}
|
||||
imageUrl={getImageUrl(itemType.image)}
|
||||
onClick={() => onFilterChange(itemType.itemTypeId)}
|
||||
selected={filterId === itemType.itemTypeId}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
|
||||
{itemTypes && itemTypes.map((itemType) => (
|
||||
<ItemType
|
||||
key={itemType.itemTypeId}
|
||||
name={itemType.name}
|
||||
imageUrl={"icon:category"}
|
||||
onClick={() => onFilterChange(itemType.itemTypeId)}
|
||||
selected={filterId === itemType.itemTypeId}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -55,6 +55,7 @@ function CafePage({
|
||||
const [searchParams] = useSearchParams();
|
||||
const token = searchParams.get("token");
|
||||
const { shopIdentifier, tableCode } = useParams();
|
||||
// Send params to parent immediately (original behavior)
|
||||
sendParam({ shopIdentifier, tableCode });
|
||||
|
||||
const {
|
||||
@@ -319,7 +320,7 @@ function CafePage({
|
||||
/>
|
||||
))}
|
||||
{!isEditMode && (user.username || cartItemsLength > 0) &&
|
||||
<div style={{ marginTop: '10px', height: '40px', position: 'sticky', bottom: '40px', display: 'flex', justifyContent: 'center', alignItems: 'center', textAlign: 'center' }}>
|
||||
<div className="StickyCartBar" style={{ marginTop: '10px', height: '40px', position: 'sticky', bottom: '40px', display: 'flex', justifyContent: 'center', alignItems: 'center', textAlign: 'center' }}>
|
||||
{(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 style={{ display: 'flex', flexWrap: 'wrap', alignContent: 'center' }}>{lastTransaction != null && '+'}{cartItemsLength} item</div>
|
||||
@@ -338,7 +339,7 @@ function CafePage({
|
||||
</div>
|
||||
}
|
||||
{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 onClick={goToTransactions} style={{ backgroundColor: '#73a585', width: '15vw', height: '40px', borderRadius: '30px', display: 'flex', justifyContent: 'center', marginLeft: (lastTransaction != null || cartItemsLength > 0) ? '6px' : '0px' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', width: '38px', marginRight: '5px' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', marginLeft: '5px', width: '20px' }}>
|
||||
<svg viewBox="0 0 512 512">
|
||||
|
||||
Reference in New Issue
Block a user