ok
This commit is contained in:
12
public/sw.js
Normal file
12
public/sw.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
if ("serviceWorker" in navigator) {
|
||||||
|
window.addEventListener("load", () => {
|
||||||
|
navigator.serviceWorker
|
||||||
|
.register("/service-worker.js")
|
||||||
|
.then((registration) => {
|
||||||
|
console.log(
|
||||||
|
"Service Worker registered with scope:",
|
||||||
|
registration.scope
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
60
src/App.js
60
src/App.js
@@ -9,6 +9,8 @@ import {
|
|||||||
useLocation,
|
useLocation,
|
||||||
} from "react-router-dom";
|
} from "react-router-dom";
|
||||||
import socket from "./services/socketService";
|
import socket from "./services/socketService";
|
||||||
|
import { SubscriptionService } from "./services/subscriptionService";
|
||||||
|
import { NotificationService } from "./services/notificationService";
|
||||||
|
|
||||||
import Dashboard from "./pages/Dashboard";
|
import Dashboard from "./pages/Dashboard";
|
||||||
import ScanMeja from "./pages/ScanMeja";
|
import ScanMeja from "./pages/ScanMeja";
|
||||||
@@ -77,19 +79,48 @@ function App() {
|
|||||||
|
|
||||||
if (tableCode)
|
if (tableCode)
|
||||||
if (table.length == 0) {
|
if (table.length == 0) {
|
||||||
const gettable = await getTableByCode(tableCode);
|
const gettable = await getTableByCode(shopId, tableCode);
|
||||||
if (gettable) setTable(gettable);
|
if (gettable) setTable(gettable);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function fetchData() {
|
const fetchData = async () => {
|
||||||
console.log("gettingItems");
|
console.log("gettingItems");
|
||||||
try {
|
try {
|
||||||
const { response, cafe, data } = await getItemTypesWithItems(shopId);
|
const { response, cafe, data } = await getItemTypesWithItems(shopId);
|
||||||
if (response.status === 200) {
|
if (response.status === 200) {
|
||||||
setShop(cafe);
|
setShop(cafe);
|
||||||
setShopItems(data);
|
setShopItems(data);
|
||||||
|
|
||||||
|
// Filter out unavailable items
|
||||||
|
const filteredData = data
|
||||||
|
.map((itemType) => ({
|
||||||
|
...itemType,
|
||||||
|
itemList: itemType.itemList.filter((item) => item.availability),
|
||||||
|
}))
|
||||||
|
.filter((itemType) => itemType.itemList.length > 0); // Remove empty itemTypes
|
||||||
|
|
||||||
|
// Update local storage by removing unavailable items
|
||||||
|
const updatedLocalStorage =
|
||||||
|
JSON.parse(localStorage.getItem("cart")) || [];
|
||||||
|
const newLocalStorage = updatedLocalStorage.map((cafe) => {
|
||||||
|
if (cafe.cafeId === shopId) {
|
||||||
|
return {
|
||||||
|
...cafe,
|
||||||
|
items: cafe.items.filter((item) =>
|
||||||
|
filteredData.some((filtered) =>
|
||||||
|
filtered.itemList.some(
|
||||||
|
(i) => i.itemId === item.itemId && i.availability
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return cafe;
|
||||||
|
});
|
||||||
|
localStorage.setItem("cart", JSON.stringify(newLocalStorage));
|
||||||
|
|
||||||
socket.on("transaction_created", () => {
|
socket.on("transaction_created", () => {
|
||||||
console.log("transaction created");
|
console.log("transaction created");
|
||||||
});
|
});
|
||||||
@@ -97,7 +128,7 @@ function App() {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching shop items:", error);
|
console.error("Error fetching shop items:", error);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
if (shopId !== "") fetchData();
|
if (shopId !== "") fetchData();
|
||||||
}, [shopId]);
|
}, [shopId]);
|
||||||
@@ -107,6 +138,20 @@ function App() {
|
|||||||
setGuestSides(sessionLeft.guestSideList);
|
setGuestSides(sessionLeft.guestSideList);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const checkNotifications = async (userId) => {
|
||||||
|
try {
|
||||||
|
const permissionGranted =
|
||||||
|
await NotificationService.requestNotificationPermission(setModal);
|
||||||
|
if (permissionGranted) {
|
||||||
|
await SubscriptionService.subscribeUserToNotifications(userId);
|
||||||
|
} else {
|
||||||
|
setModal("blocked_notification");
|
||||||
|
console.log("req notif");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error handling notifications:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (socket == null) return;
|
if (socket == null) return;
|
||||||
|
|
||||||
@@ -129,7 +174,7 @@ function App() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
socket.on("transaction_confirmed", async (data) => {
|
socket.on("transaction_confirmed", async (data) => {
|
||||||
console.log("transaction notification");
|
console.log("transaction notification" + data);
|
||||||
setModal("transaction_confirmed", data);
|
setModal("transaction_confirmed", data);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -153,6 +198,11 @@ function App() {
|
|||||||
setModal("transaction_failed", data);
|
setModal("transaction_failed", data);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
socket.on("transaction_canceled", async (data) => {
|
||||||
|
console.log("transaction notification");
|
||||||
|
setModal("transaction_canceled", data);
|
||||||
|
});
|
||||||
|
|
||||||
//for clerk
|
//for clerk
|
||||||
socket.on("transaction_created", async (data) => {
|
socket.on("transaction_created", async (data) => {
|
||||||
console.log("transaction notification");
|
console.log("transaction notification");
|
||||||
@@ -175,6 +225,8 @@ function App() {
|
|||||||
setGuestSides(connectedGuestSides.sessionDatas);
|
setGuestSides(connectedGuestSides.sessionDatas);
|
||||||
console.log("getting guest side");
|
console.log("getting guest side");
|
||||||
setDeviceType("clerk");
|
setDeviceType("clerk");
|
||||||
|
|
||||||
|
checkNotifications(data.data.user.userId);
|
||||||
} else {
|
} else {
|
||||||
setDeviceType("guestDevice");
|
setDeviceType("guestDevice");
|
||||||
}
|
}
|
||||||
|
|||||||
49
src/components/ButtonWithReplica.css
Normal file
49
src/components/ButtonWithReplica.css
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
.container {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
position: relative;
|
||||||
|
z-index: 99; /* Make sure the button is above the replica */
|
||||||
|
|
||||||
|
font-family: "Poppins", sans-serif;
|
||||||
|
font-weight: 500;
|
||||||
|
font-style: normal;
|
||||||
|
font-size: 70%; /* Adjusted for better readability */
|
||||||
|
padding: 12px 24px; /* Added padding for a better look */
|
||||||
|
border-radius: 50px;
|
||||||
|
background-color: rgba(88, 55, 50, 1);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
margin: 0 auto;
|
||||||
|
cursor: pointer;
|
||||||
|
display: block; /* Centering the button */
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.replica {
|
||||||
|
height: 40px;
|
||||||
|
width: 140px;
|
||||||
|
border-radius: 30px;
|
||||||
|
position: absolute;
|
||||||
|
background-color: rgb(146, 146, 146); /* Semi-transparent blue */
|
||||||
|
border: none;
|
||||||
|
transition: all 0.5s ease-in-out;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
z-index: 98; /* Behind the button */
|
||||||
|
}
|
||||||
|
|
||||||
|
.replica.active {
|
||||||
|
width: 200vw; /* Full screen */
|
||||||
|
height: 200vh; /* Full screen */
|
||||||
|
position: absolute;
|
||||||
|
overflow-y: hidden;
|
||||||
|
top: 30%;
|
||||||
|
z-index: 200;
|
||||||
|
border-radius: 0px;
|
||||||
|
}
|
||||||
24
src/components/ButtonWithReplica.js
Normal file
24
src/components/ButtonWithReplica.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import "./ButtonWithReplica.css";
|
||||||
|
|
||||||
|
const ButtonWithReplica = ({ children }) => {
|
||||||
|
const [isActive, setIsActive] = useState(false);
|
||||||
|
|
||||||
|
const handleClick = () => {
|
||||||
|
setIsActive(true);
|
||||||
|
setTimeout(() => {
|
||||||
|
setIsActive(false);
|
||||||
|
}, 1000); // Duration of the animation
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="container">
|
||||||
|
<button className="button" onClick={handleClick}>
|
||||||
|
{children}
|
||||||
|
</button>
|
||||||
|
<div className={`replica ${isActive ? "active" : ""}`}></div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ButtonWithReplica;
|
||||||
@@ -89,7 +89,7 @@ export default function Footer({
|
|||||||
<div onClick={goToTransactions} className={styles["footer-icon"]}>
|
<div onClick={goToTransactions} className={styles["footer-icon"]}>
|
||||||
<svg viewBox="0 0 512 512">
|
<svg viewBox="0 0 512 512">
|
||||||
<g
|
<g
|
||||||
transform="translate(0 512) scale(0.1 -0.1)"
|
transform="translate(0 460) scale(0.09 -0.09)"
|
||||||
style={{ fill: selectedPage === 3 ? "black" : "#8F8787" }}
|
style={{ fill: selectedPage === 3 ? "black" : "#8F8787" }}
|
||||||
stroke="none"
|
stroke="none"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -68,6 +68,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
transition: height 0.3s ease;
|
transition: height 0.3s ease;
|
||||||
|
z-index: 200;
|
||||||
}
|
}
|
||||||
|
|
||||||
.scanMeja.stretched {
|
.scanMeja.stretched {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React, { useState, useRef, useEffect } from "react";
|
|||||||
import styled, { keyframes } from "styled-components";
|
import styled, { keyframes } from "styled-components";
|
||||||
import { useLocation } from "react-router-dom";
|
import { useLocation } from "react-router-dom";
|
||||||
import { useNavigationHelpers } from "../helpers/navigationHelpers";
|
import { useNavigationHelpers } from "../helpers/navigationHelpers";
|
||||||
|
import Switch from "react-switch";
|
||||||
|
|
||||||
const HeaderBar = styled.div`
|
const HeaderBar = styled.div`
|
||||||
margin-top: 25px;
|
margin-top: 25px;
|
||||||
@@ -11,6 +12,7 @@ const HeaderBar = styled.div`
|
|||||||
padding: 20px 15px;
|
padding: 20px 15px;
|
||||||
color: black;
|
color: black;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
|
z-index: 200;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Title = styled.h2`
|
const Title = styled.h2`
|
||||||
@@ -170,6 +172,7 @@ const Rectangle = styled.div`
|
|||||||
overflow-y: auto; /* Enable vertical scrolling */
|
overflow-y: auto; /* Enable vertical scrolling */
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
overflow-x: hidden;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const ChildContainer = styled.div`
|
const ChildContainer = styled.div`
|
||||||
@@ -223,6 +226,7 @@ const Header = ({
|
|||||||
guestSideOfClerk,
|
guestSideOfClerk,
|
||||||
removeConnectedGuestSides,
|
removeConnectedGuestSides,
|
||||||
setIsEditMode,
|
setIsEditMode,
|
||||||
|
isEditMode,
|
||||||
}) => {
|
}) => {
|
||||||
const { goToLogin, goToGuestSideLogin, goToAdminCafes } =
|
const { goToLogin, goToGuestSideLogin, goToAdminCafes } =
|
||||||
useNavigationHelpers(shopId, tableCode);
|
useNavigationHelpers(shopId, tableCode);
|
||||||
@@ -280,9 +284,27 @@ const Header = ({
|
|||||||
console.log(guestSideOfClerk);
|
console.log(guestSideOfClerk);
|
||||||
}, [guestSideOfClerk]);
|
}, [guestSideOfClerk]);
|
||||||
|
|
||||||
|
const generateMenuHeader = (cafeName) => {
|
||||||
|
// Check if the name already ends with "'s"
|
||||||
|
if (cafeName.endsWith("'s")) {
|
||||||
|
return `${cafeName} menu`; // Return as-is for already possessive names
|
||||||
|
}
|
||||||
|
if (cafeName.endsWith("s")) {
|
||||||
|
return `${cafeName} menu`; // Return as-is for already possessive names
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, use the possessive function
|
||||||
|
return `${cafeName}'s menu`;
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<HeaderBar>
|
<HeaderBar>
|
||||||
<Title>{HeaderText}</Title>
|
<Title>
|
||||||
|
{shopName == null
|
||||||
|
? HeaderText == null
|
||||||
|
? "Groovebrew"
|
||||||
|
: HeaderText
|
||||||
|
: generateMenuHeader(shopName)}
|
||||||
|
</Title>
|
||||||
<div style={{ visibility: showProfile ? "visible" : "hidden" }}>
|
<div style={{ visibility: showProfile ? "visible" : "hidden" }}>
|
||||||
<ProfileImage
|
<ProfileImage
|
||||||
src="https://i.ibb.co.com/fpg1v8J/profile-major-icon-1024x1024-9rtgyx30.png"
|
src="https://i.ibb.co.com/fpg1v8J/profile-major-icon-1024x1024-9rtgyx30.png"
|
||||||
@@ -321,16 +343,15 @@ const Header = ({
|
|||||||
</Child> */}
|
</Child> */}
|
||||||
<Child hasChildren>
|
<Child hasChildren>
|
||||||
{shopName}
|
{shopName}
|
||||||
|
|
||||||
<div class="toggle-switch">
|
<div class="toggle-switch">
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
className="toggle-switch-checkbox"
|
|
||||||
// checked={isChecked}
|
|
||||||
onChange={(e) => setIsEditMode(e.target.checked)}
|
|
||||||
/>
|
|
||||||
<label class="toggle-switch-label" for="toggleSwitch">
|
<label class="toggle-switch-label" for="toggleSwitch">
|
||||||
Edit Mode
|
Edit Mode
|
||||||
</label>
|
</label>
|
||||||
|
<Switch
|
||||||
|
checked={isEditMode}
|
||||||
|
onChange={() => setIsEditMode(!isEditMode)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Child onClick={() => setModal("add_material")}>
|
<Child onClick={() => setModal("add_material")}>
|
||||||
stock
|
stock
|
||||||
@@ -372,16 +393,15 @@ const Header = ({
|
|||||||
user.roleId === 2 && (
|
user.roleId === 2 && (
|
||||||
<Child hasChildren>
|
<Child hasChildren>
|
||||||
{shopName}
|
{shopName}
|
||||||
|
|
||||||
<div class="toggle-switch">
|
<div class="toggle-switch">
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
className="toggle-switch-checkbox"
|
|
||||||
// checked={isChecked}
|
|
||||||
onChange={(e) => setIsEditMode(e.target.checked)}
|
|
||||||
/>
|
|
||||||
<label class="toggle-switch-label" for="toggleSwitch">
|
<label class="toggle-switch-label" for="toggleSwitch">
|
||||||
Edit Mode
|
Edit Mode
|
||||||
</label>
|
</label>
|
||||||
|
<Switch
|
||||||
|
checked={isEditMode}
|
||||||
|
onChange={() => setIsEditMode(!isEditMode)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Child onClick={() => setModal("add_material")}>
|
<Child onClick={() => setModal("add_material")}>
|
||||||
stock
|
stock
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ const Item = ({
|
|||||||
onNegativeClick,
|
onNegativeClick,
|
||||||
handleCreateItem,
|
handleCreateItem,
|
||||||
onRemoveClick,
|
onRemoveClick,
|
||||||
|
isAvailable,
|
||||||
}) => {
|
}) => {
|
||||||
const [selectedImage, setSelectedImage] = useState(null);
|
const [selectedImage, setSelectedImage] = useState(null);
|
||||||
const [previewUrl, setPreviewUrl] = useState(imageUrl);
|
const [previewUrl, setPreviewUrl] = useState(imageUrl);
|
||||||
@@ -92,6 +93,9 @@ const Item = ({
|
|||||||
"https://png.pngtree.com/png-vector/20221125/ourmid/pngtree-no-image-available-icon-flatvector-illustration-pic-design-profile-vector-png-image_40966566.jpg";
|
"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}
|
alt={itemName}
|
||||||
|
style={{
|
||||||
|
filter: !isAvailable ? "grayscale(100%)" : "none",
|
||||||
|
}}
|
||||||
className={styles.itemImage}
|
className={styles.itemImage}
|
||||||
/>
|
/>
|
||||||
{blank && (
|
{blank && (
|
||||||
@@ -113,8 +117,11 @@ const Item = ({
|
|||||||
<input
|
<input
|
||||||
className={`${
|
className={`${
|
||||||
forInvoice ? styles.itemInvoiceName : styles.itemName
|
forInvoice ? styles.itemInvoiceName : styles.itemName
|
||||||
} ${blank ? styles.blank : styles.notblank}`}
|
} ${blank ? styles.blank : styles.notblank} ${
|
||||||
|
!isAvailable ? styles.disabled : ""
|
||||||
|
}`}
|
||||||
value={itemName}
|
value={itemName}
|
||||||
|
placeholder="name"
|
||||||
onChange={handleNameChange}
|
onChange={handleNameChange}
|
||||||
disabled={!blank}
|
disabled={!blank}
|
||||||
/>
|
/>
|
||||||
@@ -129,56 +136,83 @@ const Item = ({
|
|||||||
<input
|
<input
|
||||||
className={`${styles.itemPrice} ${
|
className={`${styles.itemPrice} ${
|
||||||
blank ? styles.blank : styles.notblank
|
blank ? styles.blank : styles.notblank
|
||||||
}`}
|
} ${!isAvailable ? styles.disabled : ""}`}
|
||||||
value={itemPrice}
|
value={itemPrice}
|
||||||
|
placeholder="price"
|
||||||
onChange={handlePriceChange}
|
onChange={handlePriceChange}
|
||||||
disabled={!blank}
|
disabled={!blank}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!forInvoice && (
|
{!forInvoice &&
|
||||||
<div className={styles.itemQty}>
|
(itemQty != 0 ? (
|
||||||
<svg
|
<div className={styles.itemQty}>
|
||||||
className={styles.plusNegative}
|
<svg
|
||||||
onClick={handleNegativeClick}
|
className={styles.plusNegative}
|
||||||
clipRule="evenodd"
|
onClick={handleNegativeClick}
|
||||||
fillRule="evenodd"
|
clipRule="evenodd"
|
||||||
strokeLinejoin="round"
|
fillRule="evenodd"
|
||||||
strokeMiterlimit="2"
|
strokeLinejoin="round"
|
||||||
viewBox="0 0 24 24"
|
strokeMiterlimit="2"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
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"
|
<path
|
||||||
fillRule="nonzero"
|
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 && <p className={styles.itemQtyValue}>{itemQty}</p>}
|
</svg>
|
||||||
{blank && (
|
{!blank ? (
|
||||||
<input
|
<p className={styles.itemQtyValue}>{itemQty}</p>
|
||||||
className={styles.itemQtyInput}
|
) : (
|
||||||
value={itemQty}
|
<input
|
||||||
onChange={handleQtyChange}
|
className={styles.itemQtyInput}
|
||||||
disabled={!blank}
|
value={itemQty}
|
||||||
/>
|
onChange={handleQtyChange}
|
||||||
)}
|
disabled={!blank}
|
||||||
<svg
|
/>
|
||||||
className={styles.plusNegative}
|
)}
|
||||||
onClick={handlePlusClick}
|
<svg
|
||||||
clipRule="evenodd"
|
className={styles.plusNegative}
|
||||||
fillRule="evenodd"
|
onClick={handlePlusClick}
|
||||||
strokeLinejoin="round"
|
clipRule="evenodd"
|
||||||
strokeMiterlimit="2"
|
fillRule="evenodd"
|
||||||
viewBox="0 0 24 24"
|
strokeLinejoin="round"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
strokeMiterlimit="2"
|
||||||
>
|
viewBox="0 0 24 24"
|
||||||
<path
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
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"
|
<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"
|
||||||
</svg>
|
fillRule="nonzero"
|
||||||
</div>
|
/>
|
||||||
)}
|
</svg>
|
||||||
|
</div>
|
||||||
|
) : !blank ? (
|
||||||
|
<div className={styles.itemQty}>
|
||||||
|
<button
|
||||||
|
className={styles.addButton}
|
||||||
|
style={{ backgroundColor: !isAvailable ? "gray" : "#4da94d" }}
|
||||||
|
onClick={handlePlusClick}
|
||||||
|
disabled={!isAvailable} // Optionally disable the button if not available
|
||||||
|
>
|
||||||
|
Tambah
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className={styles.itemQty}>
|
||||||
|
<button
|
||||||
|
className={styles.addButton}
|
||||||
|
style={{
|
||||||
|
backgroundColor: "#4da94d",
|
||||||
|
width: "150px",
|
||||||
|
}}
|
||||||
|
onClick={handleCreate}
|
||||||
|
>
|
||||||
|
+
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
|
||||||
{forInvoice && (
|
{forInvoice && (
|
||||||
<p className={styles.itemPriceInvoice}>Rp {itemQty * itemPrice}</p>
|
<p className={styles.itemPriceInvoice}>Rp {itemQty * itemPrice}</p>
|
||||||
@@ -189,11 +223,11 @@ const Item = ({
|
|||||||
ⓧ
|
ⓧ
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{blank && (
|
{/* {blank && (
|
||||||
<button className={styles.createItem} onClick={handleCreate}>
|
<button className={styles.createItem} onClick={handleCreate}>
|
||||||
Create Item
|
Create Item
|
||||||
</button>
|
</button>
|
||||||
)}
|
)} */}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -166,6 +166,26 @@
|
|||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.addButton {
|
||||||
|
background-color: #04aa6d;
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
width: 95px;
|
||||||
|
height: 35px;
|
||||||
|
margin-left: 5px;
|
||||||
|
margin-top: 5px;
|
||||||
|
border-radius: 20px;
|
||||||
|
}
|
||||||
|
.grayscale {
|
||||||
|
filter: grayscale(100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.disabled {
|
||||||
|
color: gray;
|
||||||
|
}
|
||||||
.plusNegative {
|
.plusNegative {
|
||||||
width: 35px;
|
width: 35px;
|
||||||
height: 35px;
|
height: 35px;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useRef } from "react";
|
import React, { useEffect, useState, useRef } from "react";
|
||||||
import styles from "./ItemLister.module.css";
|
import styles from "./ItemLister.module.css";
|
||||||
import Item from "./Item";
|
import Item from "./Item";
|
||||||
import Switch from "react-switch";
|
import Switch from "react-switch";
|
||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
getImageUrl,
|
getImageUrl,
|
||||||
createItem,
|
createItem,
|
||||||
|
updateItemAvalilability,
|
||||||
updateItemType,
|
updateItemType,
|
||||||
deleteItemType,
|
deleteItemType,
|
||||||
} from "../helpers/itemHelper.js";
|
} from "../helpers/itemHelper.js";
|
||||||
@@ -25,6 +26,7 @@ const ItemLister = ({
|
|||||||
forCart,
|
forCart,
|
||||||
forInvoice,
|
forInvoice,
|
||||||
isEditMode,
|
isEditMode,
|
||||||
|
raw,
|
||||||
}) => {
|
}) => {
|
||||||
const [items, setItems] = useState(
|
const [items, setItems] = useState(
|
||||||
itemList.map((item) => ({
|
itemList.map((item) => ({
|
||||||
@@ -32,6 +34,16 @@ const ItemLister = ({
|
|||||||
qty: getItemQtyFromCart(shopId, item.itemId),
|
qty: getItemQtyFromCart(shopId, item.itemId),
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setItems(
|
||||||
|
itemList.map((item) => ({
|
||||||
|
...item,
|
||||||
|
qty: getItemQtyFromCart(shopId, item.itemId),
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}, [itemList]);
|
||||||
|
|
||||||
const [isEdit, setIsEditing] = useState(false);
|
const [isEdit, setIsEditing] = useState(false);
|
||||||
const [isAddingNewItem, setIsAddingNewItem] = useState(false);
|
const [isAddingNewItem, setIsAddingNewItem] = useState(false);
|
||||||
const [editedTypeName, setEditedTypeName] = useState(typeName);
|
const [editedTypeName, setEditedTypeName] = useState(typeName);
|
||||||
@@ -111,69 +123,135 @@ const ItemLister = ({
|
|||||||
const toggleAddNewItem = () => {
|
const toggleAddNewItem = () => {
|
||||||
setIsAddingNewItem((prev) => !prev);
|
setIsAddingNewItem((prev) => !prev);
|
||||||
};
|
};
|
||||||
|
const handleChange = async (itemId) => {
|
||||||
|
// Find the item in the current items array
|
||||||
|
console.log(itemId);
|
||||||
|
const itemIndex = items.findIndex((item) => item.itemId === itemId);
|
||||||
|
if (itemIndex === -1) return; // Item not found
|
||||||
|
|
||||||
|
// Create a copy of the current items array
|
||||||
|
const updatedItems = [...items];
|
||||||
|
const item = updatedItems[itemIndex];
|
||||||
|
|
||||||
|
// Toggle the availability locally
|
||||||
|
const newAvailability = !item.availability;
|
||||||
|
updatedItems[itemIndex] = {
|
||||||
|
...item,
|
||||||
|
availability: newAvailability,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Update the state with the local change
|
||||||
|
setItems(updatedItems);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Wait for the updateItemAvailability response
|
||||||
|
const response = await updateItemAvalilability(itemId, newAvailability);
|
||||||
|
|
||||||
|
// Assuming response contains the updated item data
|
||||||
|
const updatedItem = response;
|
||||||
|
console.log(updatedItem);
|
||||||
|
// Update only the specified item in the state
|
||||||
|
setItems((prevItems) =>
|
||||||
|
prevItems.map((prevItem) =>
|
||||||
|
prevItem.itemId === itemId ? updatedItem : prevItem
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
// Handle error (e.g., revert the change or show an error message)
|
||||||
|
console.error("Error updating item availability:", error);
|
||||||
|
|
||||||
|
// Optionally revert to the previous availability if needed
|
||||||
|
updatedItems[itemIndex].availability = item.availability; // revert back
|
||||||
|
setItems(updatedItems);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{(items.length > 0 ||
|
{(items.length > 0 ||
|
||||||
(user && user.roleId == 1 && user.userId == shopOwnerId)) && (
|
(user && user.roleId == 1 && user.userId == shopOwnerId)) && (
|
||||||
<div className={styles["item-lister"]}>
|
<div className={styles["item-lister"]}>
|
||||||
<div className={styles["title-container"]}>
|
{!raw && (
|
||||||
<input
|
<div className={styles["title-container"]}>
|
||||||
ref={typeNameInputRef}
|
<input
|
||||||
className={`${styles.title} ${
|
ref={typeNameInputRef}
|
||||||
user && user.roleId == 1 && user.userId == shopOwnerId && isEdit
|
className={`${styles.title} ${
|
||||||
? styles.border
|
user &&
|
||||||
: styles.noborder
|
user.roleId == 1 &&
|
||||||
}`}
|
user.userId == shopOwnerId &&
|
||||||
value={editedTypeName}
|
isEdit
|
||||||
onChange={(e) => setEditedTypeName(e.target.value)}
|
? styles.border
|
||||||
disabled={!isEdit}
|
: styles.noborder
|
||||||
/>
|
}`}
|
||||||
{user && user.roleId == 1 && user.userId == shopOwnerId && (
|
value={editedTypeName}
|
||||||
<>
|
onChange={(e) => setEditedTypeName(e.target.value)}
|
||||||
<button
|
disabled={!isEdit}
|
||||||
className={styles["edit-typeItem-button"]}
|
/>
|
||||||
onClick={toggleEditTypeItem}
|
{isEditMode &&
|
||||||
>
|
user &&
|
||||||
{isEdit ? "Cancel" : "Edit"}
|
user.roleId == 1 &&
|
||||||
</button>
|
user.userId == shopOwnerId && (
|
||||||
{isEdit && (
|
<>
|
||||||
<button
|
<button
|
||||||
className={styles["edit-typeItem-button"]}
|
className={styles["edit-typeItem-button"]}
|
||||||
onClick={handleSaveType}
|
onClick={toggleEditTypeItem}
|
||||||
>
|
>
|
||||||
Save
|
{isEdit ? "Cancel" : "Edit"}
|
||||||
</button>
|
</button>
|
||||||
|
{isEdit && (
|
||||||
|
<button
|
||||||
|
className={styles["edit-typeItem-button"]}
|
||||||
|
onClick={handleSaveType}
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
|
||||||
<div className={styles["item-list"]}>
|
<div className={styles["item-list"]}>
|
||||||
{user &&
|
{user &&
|
||||||
user.roleId == 1 &&
|
user.roleId == 1 &&
|
||||||
user.userId == shopOwnerId &&
|
user.userId == shopOwnerId &&
|
||||||
isEdit && (
|
isEditMode && (
|
||||||
<>
|
<>
|
||||||
<button
|
{!isAddingNewItem && (
|
||||||
className={styles["add-item-button"]}
|
<button
|
||||||
onClick={toggleAddNewItem}
|
className={styles["add-item-button"]}
|
||||||
>
|
onClick={toggleAddNewItem}
|
||||||
{isAddingNewItem ? "Cancel" : "Add new Item"}
|
style={{
|
||||||
</button>
|
display: "inline-block",
|
||||||
|
height: "159px",
|
||||||
|
fontSize: "50px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
+
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
{isAddingNewItem && (
|
{isAddingNewItem && (
|
||||||
<Item
|
<>
|
||||||
blank={true}
|
<button
|
||||||
handleCreateItem={(name, price, qty, selectedImage) =>
|
className={styles["add-item-button"]}
|
||||||
createItem(
|
onClick={toggleAddNewItem}
|
||||||
shopId,
|
style={{ display: "inline-block" }}
|
||||||
name,
|
>
|
||||||
price,
|
↩
|
||||||
qty,
|
</button>
|
||||||
selectedImage,
|
<Item
|
||||||
itemTypeId
|
blank={true}
|
||||||
)
|
handleCreateItem={(name, price, qty, selectedImage) =>
|
||||||
}
|
createItem(
|
||||||
/>
|
shopId,
|
||||||
|
name,
|
||||||
|
price,
|
||||||
|
qty,
|
||||||
|
selectedImage,
|
||||||
|
itemTypeId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@@ -182,8 +260,13 @@ const ItemLister = ({
|
|||||||
<div className={styles["itemWrapper"]}>
|
<div className={styles["itemWrapper"]}>
|
||||||
{isEditMode && (
|
{isEditMode && (
|
||||||
<div className={styles["editModeLayout"]}>
|
<div className={styles["editModeLayout"]}>
|
||||||
<Switch checked={true} />
|
{isEditMode && (
|
||||||
<h3>available</h3>
|
<Switch
|
||||||
|
onChange={() => handleChange(item.itemId)}
|
||||||
|
checked={item.availability}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<h3>{item.availability ? "available" : "unavailable"}</h3>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<Item
|
<Item
|
||||||
@@ -198,6 +281,7 @@ const ItemLister = ({
|
|||||||
onNegativeClick={() => handleNegativeClick(item.itemId)}
|
onNegativeClick={() => handleNegativeClick(item.itemId)}
|
||||||
onRemoveClick={() => handleRemoveClick(item.itemId)}
|
onRemoveClick={() => handleRemoveClick(item.itemId)}
|
||||||
isEditMode={isEditMode}
|
isEditMode={isEditMode}
|
||||||
|
isAvailable={item.availability}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : null;
|
) : null;
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
padding: 8px 16px; /* Adjust padding as needed */
|
padding: 8px 16px; /* Adjust padding as needed */
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
background-color: #007bff;
|
background-color: #359d42d1;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
@@ -66,6 +66,7 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
.editModeLayout {
|
.editModeLayout {
|
||||||
|
border-radius: 4px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
background-color: #0000008c;
|
background-color: #0000008c;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ export default function ItemType({
|
|||||||
blank,
|
blank,
|
||||||
name: initialName = "",
|
name: initialName = "",
|
||||||
imageUrl,
|
imageUrl,
|
||||||
|
selected,
|
||||||
}) {
|
}) {
|
||||||
const inputRef = useRef(null);
|
const inputRef = useRef(null);
|
||||||
const [name, setName] = useState(initialName);
|
const [name, setName] = useState(initialName);
|
||||||
@@ -51,7 +52,11 @@ export default function ItemType({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles["item-type"]}>
|
<div className={styles["item-type"]}>
|
||||||
<div onClick={onClick} className={styles["item-type-rect"]}>
|
<div
|
||||||
|
onClick={onClick}
|
||||||
|
className={styles["item-type-rect"]}
|
||||||
|
style={{ top: selected ? "-10px" : "initial" }}
|
||||||
|
>
|
||||||
<img
|
<img
|
||||||
src={previewUrl}
|
src={previewUrl}
|
||||||
alt={name}
|
alt={name}
|
||||||
@@ -84,6 +89,7 @@ export default function ItemType({
|
|||||||
value={name}
|
value={name}
|
||||||
onChange={handleNameChange}
|
onChange={handleNameChange}
|
||||||
disabled={!blank}
|
disabled={!blank}
|
||||||
|
style={{ top: selected ? "-5px" : "initial" }}
|
||||||
/>
|
/>
|
||||||
{blank && (
|
{blank && (
|
||||||
<button className={styles["item-type-create"]} onClick={handleCreate}>
|
<button className={styles["item-type-create"]} onClick={handleCreate}>
|
||||||
|
|||||||
@@ -3,7 +3,15 @@ import "./ItemTypeLister.css";
|
|||||||
import ItemType from "./ItemType";
|
import ItemType from "./ItemType";
|
||||||
import { createItemType } from "../helpers/itemHelper.js";
|
import { createItemType } from "../helpers/itemHelper.js";
|
||||||
|
|
||||||
const ItemTypeLister = ({ shopId, shopOwnerId, user, itemTypes }) => {
|
const ItemTypeLister = ({
|
||||||
|
shopId,
|
||||||
|
shopOwnerId,
|
||||||
|
user,
|
||||||
|
itemTypes,
|
||||||
|
onFilterChange,
|
||||||
|
filterId,
|
||||||
|
isEditMode,
|
||||||
|
}) => {
|
||||||
const [isAddingNewItem, setIsAddingNewItem] = useState(false);
|
const [isAddingNewItem, setIsAddingNewItem] = useState(false);
|
||||||
|
|
||||||
const toggleAddNewItem = () => {
|
const toggleAddNewItem = () => {
|
||||||
@@ -19,7 +27,11 @@ const ItemTypeLister = ({ shopId, shopOwnerId, user, itemTypes }) => {
|
|||||||
<div className="item-type-lister">
|
<div className="item-type-lister">
|
||||||
<div className="item-type-list">
|
<div className="item-type-list">
|
||||||
{itemTypes && itemTypes.length > 1 && (
|
{itemTypes && itemTypes.length > 1 && (
|
||||||
<ItemType name={"All"} imageUrl={"uploads/1718732420960.png"} />
|
<ItemType
|
||||||
|
name={"All"}
|
||||||
|
onClick={() => onFilterChange(0)}
|
||||||
|
imageUrl={"uploads/1718732420960.png"}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
{itemTypes &&
|
{itemTypes &&
|
||||||
itemTypes.map(
|
itemTypes.map(
|
||||||
@@ -30,6 +42,8 @@ const ItemTypeLister = ({ shopId, shopOwnerId, user, itemTypes }) => {
|
|||||||
key={itemType.itemTypeId}
|
key={itemType.itemTypeId}
|
||||||
name={itemType.name}
|
name={itemType.name}
|
||||||
imageUrl={itemType.image}
|
imageUrl={itemType.image}
|
||||||
|
onClick={() => onFilterChange(itemType.itemTypeId)}
|
||||||
|
selected={filterId == itemType.itemTypeId}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
@@ -39,16 +53,15 @@ const ItemTypeLister = ({ shopId, shopOwnerId, user, itemTypes }) => {
|
|||||||
isAddingNewItem && (
|
isAddingNewItem && (
|
||||||
<ItemType blank={true} name={"blank"} onCreate={handleCreate} />
|
<ItemType blank={true} name={"blank"} onCreate={handleCreate} />
|
||||||
)}
|
)}
|
||||||
{!isAddingNewItem &&
|
{isEditMode &&
|
||||||
|
!isAddingNewItem &&
|
||||||
user &&
|
user &&
|
||||||
user.roleId == 1 &&
|
user.roleId == 1 &&
|
||||||
user.userId == shopOwnerId && (
|
user.userId == shopOwnerId && (
|
||||||
<ItemType
|
<ItemType
|
||||||
onClick={toggleAddNewItem}
|
onClick={toggleAddNewItem}
|
||||||
name={"create"}
|
name={"create"}
|
||||||
imageUrl={
|
imageUrl={"uploads/addnew.png"}
|
||||||
"https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQnd07OYAm1f7T6JzziFU7U8X1_IL3bADiVrg&usqp=CAU"
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import Payment_claimed from "../pages/Payment_claimed";
|
|||||||
import MaterialList from "../pages/MaterialList.js";
|
import MaterialList from "../pages/MaterialList.js";
|
||||||
import MaterialMutationsPage from "../pages/MaterialMutationsPage.js";
|
import MaterialMutationsPage from "../pages/MaterialMutationsPage.js";
|
||||||
import Reports from "../pages/Reports.js";
|
import Reports from "../pages/Reports.js";
|
||||||
|
import NotificationBlocked from "../pages/NotificationBlocked.js";
|
||||||
|
|
||||||
const Modal = ({ shop, isOpen, onClose, modalContent }) => {
|
const Modal = ({ shop, isOpen, onClose, modalContent }) => {
|
||||||
if (!isOpen) return null;
|
if (!isOpen) return null;
|
||||||
@@ -35,6 +36,7 @@ const Modal = ({ shop, isOpen, onClose, modalContent }) => {
|
|||||||
<button onClick={onClose} className={styles.closeButton}>
|
<button onClick={onClose} className={styles.closeButton}>
|
||||||
×
|
×
|
||||||
</button>
|
</button>
|
||||||
|
{modalContent === "req_notification" && <NotificationBlocked />}
|
||||||
{modalContent === "edit_tables" && <TablesPage shop={shop} />}
|
{modalContent === "edit_tables" && <TablesPage shop={shop} />}
|
||||||
{modalContent === "new_transaction" && (
|
{modalContent === "new_transaction" && (
|
||||||
<Transaction propsShopId={shop.cafeId} />
|
<Transaction propsShopId={shop.cafeId} />
|
||||||
@@ -53,7 +55,7 @@ const Modal = ({ shop, isOpen, onClose, modalContent }) => {
|
|||||||
{modalContent === "transaction_end" && <Transaction_end />}
|
{modalContent === "transaction_end" && <Transaction_end />}
|
||||||
{modalContent === "transaction_failed" && <Transaction_failed />}
|
{modalContent === "transaction_failed" && <Transaction_failed />}
|
||||||
{modalContent === "payment_option" && (
|
{modalContent === "payment_option" && (
|
||||||
<PaymentOptions paymentUrl={shop.qrPayment} shopId={shop.cafeId} />
|
<PaymentOptions shopId={shop.cafeId} />
|
||||||
)}
|
)}
|
||||||
{modalContent === "add_material" && (
|
{modalContent === "add_material" && (
|
||||||
<MaterialList cafeId={shop.cafeId} />
|
<MaterialList cafeId={shop.cafeId} />
|
||||||
|
|||||||
@@ -12,13 +12,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.modalContent {
|
.modalContent {
|
||||||
background: white;
|
|
||||||
border-radius: 5px;
|
|
||||||
width: 90%;
|
width: 90%;
|
||||||
height: 80%;
|
height: 80%;
|
||||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden; /* Add this line to enable scrolling */
|
overflow: visible; /* Add this line to enable scrolling */
|
||||||
}
|
}
|
||||||
|
|
||||||
.closeButton {
|
.closeButton {
|
||||||
|
|||||||
@@ -1,27 +1,43 @@
|
|||||||
import React, { useState, useRef, useEffect } from "react";
|
import React, { useState, useRef, useEffect } from "react";
|
||||||
import jsQR from "jsqr"; // Import jsQR library
|
import jsQR from "jsqr"; // Import jsQR library
|
||||||
import { getImageUrl } from "../helpers/itemHelper";
|
import { getImageUrl } from "../helpers/itemHelper";
|
||||||
import { saveCafeDetails } from "../helpers/cafeHelpers"; // Import the helper function
|
import {
|
||||||
|
getCafe,
|
||||||
|
saveCafeDetails,
|
||||||
|
setConfirmationStatus,
|
||||||
|
} from "../helpers/cafeHelpers"; // Import the helper function
|
||||||
|
import Switch from "react-switch";
|
||||||
|
|
||||||
const SetPaymentQr = ({
|
const SetPaymentQr = ({ shopId }) => {
|
||||||
isConfigure,
|
const [qrPosition, setQrPosition] = useState([50, 50]);
|
||||||
tableNo,
|
const [qrSize, setQrSize] = useState(50);
|
||||||
qrCodeUrl,
|
const [qrPayment, setQrPayment] = useState();
|
||||||
paymentUrl,
|
|
||||||
initialQrPosition,
|
|
||||||
initialQrSize,
|
|
||||||
handleQrSave,
|
|
||||||
shopId, // Pass cafeId as a prop to identify which cafe to update
|
|
||||||
}) => {
|
|
||||||
const [qrPosition, setQrPosition] = useState(initialQrPosition);
|
|
||||||
const [qrSize, setQrSize] = useState(initialQrSize);
|
|
||||||
const [qrPayment, setQrPayment] = useState(getImageUrl(paymentUrl));
|
|
||||||
const [qrCodeDetected, setQrCodeDetected] = useState(false);
|
const [qrCodeDetected, setQrCodeDetected] = useState(false);
|
||||||
const qrPaymentInputRef = useRef(null);
|
const qrPaymentInputRef = useRef(null);
|
||||||
const overlayTextRef = useRef(null);
|
const overlayTextRef = useRef(null);
|
||||||
const qrCodeContainerRef = useRef(null);
|
const qrCodeContainerRef = useRef(null);
|
||||||
|
const [isNeedConfirmation, setIsNeedConfirmation] = useState(false);
|
||||||
|
const [cafe, setCafe] = useState([]);
|
||||||
|
|
||||||
// Use useEffect to detect QR code after qrPayment updates
|
// Use useEffect to detect QR code after qrPayment updates
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchCafe = async () => {
|
||||||
|
try {
|
||||||
|
const response = await getCafe(shopId);
|
||||||
|
setCafe(response);
|
||||||
|
setQrPayment(getImageUrl(response.qrPayment));
|
||||||
|
setIsNeedConfirmation(response.needsConfirmation);
|
||||||
|
setQrPosition([response.xposition, response.yposition]);
|
||||||
|
setQrSize(response.scale);
|
||||||
|
console.log(response);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching cafe:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchCafe();
|
||||||
|
}, [shopId]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (qrPayment) {
|
if (qrPayment) {
|
||||||
detectQRCodeFromContainer();
|
detectQRCodeFromContainer();
|
||||||
@@ -77,7 +93,7 @@ const SetPaymentQr = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Call saveCafeDetails function with the updated details object
|
// Call saveCafeDetails function with the updated details object
|
||||||
saveCafeDetails(shopId, details)
|
saveCafeDetails(cafe.cafeId, details)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
console.log("Cafe details saved:", response);
|
console.log("Cafe details saved:", response);
|
||||||
// handleQrSave(qrPosition, qrSize, qrPayment);
|
// handleQrSave(qrPosition, qrSize, qrPayment);
|
||||||
@@ -87,6 +103,24 @@ const SetPaymentQr = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleChange = async () => {
|
||||||
|
console.log(isNeedConfirmation);
|
||||||
|
setIsNeedConfirmation(!isNeedConfirmation);
|
||||||
|
console.log(!isNeedConfirmation);
|
||||||
|
try {
|
||||||
|
// Wait for the updateItemAvailability response
|
||||||
|
const response = await setConfirmationStatus(
|
||||||
|
cafe.cafeId,
|
||||||
|
!isNeedConfirmation
|
||||||
|
);
|
||||||
|
|
||||||
|
setIsNeedConfirmation(response.needsConfirmation);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
setIsNeedConfirmation(cafe.needsConfirmation);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
@@ -143,6 +177,8 @@ const SetPaymentQr = ({
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Switch onChange={() => handleChange()} checked={isNeedConfirmation} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -163,7 +163,7 @@ const QRCodeWithBackground = ({
|
|||||||
style={styles.overlayText}
|
style={styles.overlayText}
|
||||||
onClick={() => qrBackgroundInputRef.current.click()}
|
onClick={() => qrBackgroundInputRef.current.click()}
|
||||||
>
|
>
|
||||||
Click To Change Image
|
Click To Change Background
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{/* Hidden file input */}
|
{/* Hidden file input */}
|
||||||
@@ -191,7 +191,6 @@ const QRCodeWithBackground = ({
|
|||||||
onChange={handleSizeChange}
|
onChange={handleSizeChange}
|
||||||
style={styles.input}
|
style={styles.input}
|
||||||
/>
|
/>
|
||||||
<span style={styles.labelEnd}>100%</span>
|
|
||||||
<span style={styles.value}>{qrSize}%</span>
|
<span style={styles.value}>{qrSize}%</span>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
@@ -211,7 +210,6 @@ const QRCodeWithBackground = ({
|
|||||||
onChange={handlePositionChange}
|
onChange={handlePositionChange}
|
||||||
style={styles.input}
|
style={styles.input}
|
||||||
/>
|
/>
|
||||||
<span style={styles.labelEnd}>100%</span>
|
|
||||||
<span style={styles.value}>{qrPosition.left}%</span>
|
<span style={styles.value}>{qrPosition.left}%</span>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
@@ -231,7 +229,6 @@ const QRCodeWithBackground = ({
|
|||||||
onChange={handlePositionChange}
|
onChange={handlePositionChange}
|
||||||
style={styles.input}
|
style={styles.input}
|
||||||
/>
|
/>
|
||||||
<span style={styles.labelEnd}>100%</span>
|
|
||||||
<span style={styles.value}>{qrPosition.top}%</span>
|
<span style={styles.value}>{qrPosition.top}%</span>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
|
|||||||
@@ -74,15 +74,17 @@ export default function SearchInput({
|
|||||||
let url = "";
|
let url = "";
|
||||||
if (autofocus || songName != "") {
|
if (autofocus || songName != "") {
|
||||||
url = tableCode
|
url = tableCode
|
||||||
? `/${shopId}/${tableCode}/search?query=${encodeURIComponent(songName)}`
|
? `/${shopId}/${tableCode}/search?query=${encodeURIComponent(
|
||||||
|
songName
|
||||||
|
)}`
|
||||||
: `/${shopId}/search?query=${encodeURIComponent(songName)}`;
|
: `/${shopId}/search?query=${encodeURIComponent(songName)}`;
|
||||||
navigate(url);
|
navigate(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (autofocus) {
|
if (autofocus) {
|
||||||
if (songName == "") {
|
if (songName == "") {
|
||||||
if (tableCode) navigate(`/${shopId}/${tableCode}`);
|
if (tableCode) navigate(`/${shopId}/${tableCode}?find=true`);
|
||||||
else navigate(`/${shopId}`);
|
else navigate(`/${shopId}?find=true`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (onSearchChange) onSearchChange(songName);
|
if (onSearchChange) onSearchChange(songName);
|
||||||
@@ -100,9 +102,18 @@ export default function SearchInput({
|
|||||||
|
|
||||||
// Focus input when component mounts
|
// Focus input when component mounts
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (autofocus) if (inputRef.current) inputRef.current.focus();
|
const isFinding = searchParams.get("find") || false;
|
||||||
|
if (autofocus || isFinding) if (inputRef.current) inputRef.current.focus();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const handleBlur = () => {
|
||||||
|
const isFinding = searchParams.get("find") || false;
|
||||||
|
if (isFinding) {
|
||||||
|
if (tableCode) navigate(`/${shopId}/${tableCode}`);
|
||||||
|
else navigate(`/${shopId}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SearchBox>
|
<SearchBox>
|
||||||
<SearchContainer>
|
<SearchContainer>
|
||||||
@@ -112,6 +123,7 @@ export default function SearchInput({
|
|||||||
placeholder="Search..."
|
placeholder="Search..."
|
||||||
value={songName}
|
value={songName}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
/>
|
/>
|
||||||
<SearchIcon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
<SearchIcon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
<path d="M20.8333 18.3333H19.5167L19.05 17.8833C20.6833 15.9833 21.6667 13.5167 21.6667 10.8333C21.6667 4.85 16.8167 0 10.8333 0C4.85 0 0 4.85 0 10.8333C0 16.8167 4.85 21.6667 10.8333 21.6667C13.5167 21.6667 15.9833 20.6833 17.8833 19.05L18.3333 19.5167V20.8333L26.6667 29.15L29.15 26.6667L20.8333 18.3333ZM10.8333 18.3333C6.68333 18.3333 3.33333 14.9833 3.33333 10.8333C3.33333 6.68333 6.68333 3.33333 10.8333 3.33333C14.9833 3.33333 18.3333 6.68333 18.3333 10.8333C18.3333 14.9833 14.9833 18.3333 10.8333 18.3333Z" />
|
<path d="M20.8333 18.3333H19.5167L19.05 17.8833C20.6833 15.9833 21.6667 13.5167 21.6667 10.8333C21.6667 4.85 16.8167 0 10.8333 0C4.85 0 0 4.85 0 10.8333C0 16.8167 4.85 21.6667 10.8333 21.6667C13.5167 21.6667 15.9833 20.6833 17.8833 19.05L18.3333 19.5167V20.8333L26.6667 29.15L29.15 26.6667L20.8333 18.3333ZM10.8333 18.3333C6.68333 18.3333 3.33333 14.9833 3.33333 10.8333C3.33333 6.68333 6.68333 3.33333 10.8333 3.33333C14.9833 3.33333 18.3333 6.68333 18.3333 10.8333C18.3333 14.9833 14.9833 18.3333 10.8333 18.3333Z" />
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// src/config.js
|
// src/config.js
|
||||||
|
|
||||||
const API_BASE_URL = "https://5n2rcx-5000.csb.app"; // Replace with your actual backend URL
|
const API_BASE_URL = "https://p8hlyz-5000.csb.app"; // Replace with your actual backend URL
|
||||||
|
|
||||||
export default API_BASE_URL;
|
export default API_BASE_URL;
|
||||||
|
|||||||
@@ -4,6 +4,26 @@ function getAuthToken() {
|
|||||||
return localStorage.getItem("auth");
|
return localStorage.getItem("auth");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getCafe(cafeId) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/cafe/get-cafe/` + cafeId, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Failed to fetch cafes");
|
||||||
|
}
|
||||||
|
|
||||||
|
const cafe = await response.json();
|
||||||
|
return cafe;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function getOwnedCafes(userId) {
|
export async function getOwnedCafes(userId) {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
@@ -36,8 +56,9 @@ export async function createCafe(cafeName) {
|
|||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
Authorization: `Bearer ${getAuthToken()}`,
|
Authorization: `Bearer ${getAuthToken()}`,
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
name: cafeName }),
|
name: cafeName,
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
@@ -73,6 +94,33 @@ export async function updateCafe(cafeId, cafeDetails) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function setConfirmationStatus(cafeId, isNeedConfirmation) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
`${API_BASE_URL}/cafe/confirmation-status/` + cafeId,
|
||||||
|
{
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Authorization: `Bearer ${getAuthToken()}`,
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ isNeedConfirmation: isNeedConfirmation }),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
// throw new Error(`Error: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
console.log(data);
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to update item type:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// helpers/cafeHelpers.js
|
// helpers/cafeHelpers.js
|
||||||
export async function saveCafeDetails(cafeId, details) {
|
export async function saveCafeDetails(cafeId, details) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { getItemsByCafeId } from "./cartHelpers.js";
|
|||||||
export async function getItemTypesWithItems(shopId) {
|
export async function getItemTypesWithItems(shopId) {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`${API_BASE_URL}/item/get-cafe-items/` + shopId,
|
`${API_BASE_URL}/item/get-cafe-items/` + shopId
|
||||||
);
|
);
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
@@ -37,7 +37,7 @@ export async function getCartDetails(shopId) {
|
|||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify(getItemsByCafeId(shopId)),
|
body: JSON.stringify(getItemsByCafeId(shopId)),
|
||||||
},
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
@@ -66,7 +66,7 @@ export async function createItem(
|
|||||||
price,
|
price,
|
||||||
qty,
|
qty,
|
||||||
selectedImage,
|
selectedImage,
|
||||||
itemTypeId,
|
itemTypeId
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
console.log(selectedImage);
|
console.log(selectedImage);
|
||||||
@@ -98,6 +98,33 @@ export async function createItem(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function updateItemAvalilability(itemId, isAvailable) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
`${API_BASE_URL}/item/set-availability/` + itemId,
|
||||||
|
{
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Authorization: `Bearer ${getAuthToken()}`,
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ isAvailable: isAvailable }),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
// throw new Error(`Error: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
console.log(data);
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to update item type:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function createItemType(shopId, name, selectedImage) {
|
export async function createItemType(shopId, name, selectedImage) {
|
||||||
try {
|
try {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
@@ -136,7 +163,7 @@ export async function updateItemType(shopId, itemTypeId, newName) {
|
|||||||
Authorization: `Bearer ${getAuthToken()}`,
|
Authorization: `Bearer ${getAuthToken()}`,
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ newName }),
|
body: JSON.stringify({ newName }),
|
||||||
},
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
@@ -160,7 +187,7 @@ export async function deleteItemType(shopId, itemTypeId) {
|
|||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${getAuthToken()}`,
|
Authorization: `Bearer ${getAuthToken()}`,
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|||||||
@@ -103,10 +103,10 @@ export async function getTable(shopId, tableNo) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getTableByCode(tableCode) {
|
export async function getTableByCode(shopId, tableCode) {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`${API_BASE_URL}/table/get-table-by-code/${tableCode}`,
|
`${API_BASE_URL}/table/get-table-by-code/${shopId}/${tableCode}`,
|
||||||
{
|
{
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
|
|||||||
@@ -52,7 +52,6 @@ export async function declineTransaction(transactionId) {
|
|||||||
|
|
||||||
export async function cancelTransaction(transactionId) {
|
export async function cancelTransaction(transactionId) {
|
||||||
try {
|
try {
|
||||||
console.log(transactionId);
|
|
||||||
const token = getLocalStorage("auth");
|
const token = getLocalStorage("auth");
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`${API_BASE_URL}/transaction/cancel-transaction/${transactionId}`,
|
`${API_BASE_URL}/transaction/cancel-transaction/${transactionId}`,
|
||||||
@@ -65,6 +64,7 @@ export async function cancelTransaction(transactionId) {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
console.log(response);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ function CafePage({
|
|||||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
|
|
||||||
const [isEditMode, setIsEditMode] = useState(false);
|
const [isEditMode, setIsEditMode] = useState(false);
|
||||||
|
const [filterId, setFilterId] = useState(0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (user.cafeId != null && user.cafeId != shopId) {
|
if (user.cafeId != null && user.cafeId != shopId) {
|
||||||
@@ -112,6 +113,7 @@ function CafePage({
|
|||||||
guestSideOfClerk={guestSideOfClerk}
|
guestSideOfClerk={guestSideOfClerk}
|
||||||
removeConnectedGuestSides={removeConnectedGuestSides}
|
removeConnectedGuestSides={removeConnectedGuestSides}
|
||||||
setIsEditMode={(e) => setIsEditMode(e)}
|
setIsEditMode={(e) => setIsEditMode(e)}
|
||||||
|
isEditMode={isEditMode}
|
||||||
/>
|
/>
|
||||||
<div style={{ marginTop: "5px" }}></div>
|
<div style={{ marginTop: "5px" }}></div>
|
||||||
<SearchInput shopId={shopId} tableCode={table.tableCode} />
|
<SearchInput shopId={shopId} tableCode={table.tableCode} />
|
||||||
@@ -121,28 +123,43 @@ function CafePage({
|
|||||||
shopOwnerId={shopOwnerId}
|
shopOwnerId={shopOwnerId}
|
||||||
shopId={shopId}
|
shopId={shopId}
|
||||||
itemTypes={shopItems}
|
itemTypes={shopItems}
|
||||||
|
isEditMode={isEditMode}
|
||||||
|
onFilterChange={(e) => setFilterId(e)}
|
||||||
|
filterId={filterId}
|
||||||
/>
|
/>
|
||||||
<div style={{ marginTop: "-13px" }}></div>
|
<div style={{ marginTop: "-13px" }}></div>
|
||||||
<h2 className="title">Music Req.</h2>
|
{filterId === 0 ? (
|
||||||
<MusicPlayer
|
<>
|
||||||
socket={socket}
|
<h2 className="title">Music Req.</h2>
|
||||||
shopId={shopId}
|
<MusicPlayer
|
||||||
user={user}
|
socket={socket}
|
||||||
isSpotifyNeedLogin={isSpotifyNeedLogin}
|
shopId={shopId}
|
||||||
/>
|
user={user}
|
||||||
|
isSpotifyNeedLogin={isSpotifyNeedLogin}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<div style={{ marginTop: "35px" }}></div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div style={{ marginTop: "-15px" }}></div>
|
<div style={{ marginTop: "-15px" }}></div>
|
||||||
{shopItems.map((itemType) => (
|
{shopItems
|
||||||
<ItemLister
|
.filter(
|
||||||
shopId={shopId}
|
(itemType) => filterId == 0 || itemType.itemTypeId === filterId
|
||||||
shopOwnerId={shopOwnerId}
|
)
|
||||||
user={user}
|
.map((itemType) => (
|
||||||
key={itemType.itemTypeId}
|
<ItemLister
|
||||||
itemTypeId={itemType.itemTypeId}
|
shopId={shopId}
|
||||||
typeName={itemType.name}
|
shopOwnerId={shopOwnerId}
|
||||||
itemList={itemType.itemList}
|
user={user}
|
||||||
isEditMode={isEditMode}
|
key={itemType.itemTypeId}
|
||||||
/>
|
itemTypeId={itemType.itemTypeId}
|
||||||
))}
|
typeName={itemType.name}
|
||||||
|
itemList={itemType.itemList}
|
||||||
|
isEditMode={isEditMode}
|
||||||
|
raw={isEditMode || filterId == 0 ? false : true}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
</body>
|
</body>
|
||||||
{user.username && (
|
{user.username && (
|
||||||
<AccountUpdateModal
|
<AccountUpdateModal
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { useRef, useEffect, useState } from "react";
|
import React, { useRef, useEffect, useState } from "react";
|
||||||
import styles from "./Invoice.module.css";
|
import styles from "./Invoice.module.css";
|
||||||
import { useParams, useLocation } from "react-router-dom"; // Changed from useSearchParams to useLocation
|
import { useParams } from "react-router-dom"; // Changed from useSearchParams to useLocation
|
||||||
import { ThreeDots, ColorRing } from "react-loader-spinner";
|
import { ThreeDots, ColorRing } from "react-loader-spinner";
|
||||||
|
|
||||||
import ItemLister from "../components/ItemLister";
|
import ItemLister from "../components/ItemLister";
|
||||||
@@ -15,19 +15,12 @@ export default function Invoice({ table, sendParam, deviceType, socket }) {
|
|||||||
const { shopId, tableCode } = useParams();
|
const { shopId, tableCode } = useParams();
|
||||||
sendParam({ shopId, tableCode });
|
sendParam({ shopId, tableCode });
|
||||||
|
|
||||||
const location = useLocation(); // Use useLocation hook instead of useSearchParams
|
|
||||||
const searchParams = new URLSearchParams(location.search); // Pass location.search directly
|
|
||||||
|
|
||||||
// const email = searchParams.get("email");
|
|
||||||
// const orderType = searchParams.get("orderType");
|
|
||||||
// const tableNumber = searchParams.get("tableNumber");
|
|
||||||
|
|
||||||
const [cartItems, setCartItems] = useState([]);
|
const [cartItems, setCartItems] = useState([]);
|
||||||
const [totalPrice, setTotalPrice] = useState(0);
|
const [totalPrice, setTotalPrice] = useState(0);
|
||||||
const [isPaymentLoading, setIsPaymentLoading] = useState(false); // State for payment button loading animation
|
const [isPaymentLoading, setIsPaymentLoading] = useState(false); // State for payment button loading animation
|
||||||
|
|
||||||
const textareaRef = useRef(null);
|
const textareaRef = useRef(null);
|
||||||
const [orderType, setOrderType] = useState("serve");
|
const [orderType, setOrderType] = useState("pickup");
|
||||||
const [tableNumber, setTableNumber] = useState("");
|
const [tableNumber, setTableNumber] = useState("");
|
||||||
const [email, setEmail] = useState("");
|
const [email, setEmail] = useState("");
|
||||||
|
|
||||||
@@ -35,10 +28,41 @@ export default function Invoice({ table, sendParam, deviceType, socket }) {
|
|||||||
const fetchCartItems = async () => {
|
const fetchCartItems = async () => {
|
||||||
try {
|
try {
|
||||||
const items = await getCartDetails(shopId);
|
const items = await getCartDetails(shopId);
|
||||||
setCartItems(items);
|
console.log(items);
|
||||||
|
|
||||||
// Calculate total price based on fetched cart items
|
// Filter out unavailable items
|
||||||
const totalPrice = items.reduce((total, itemType) => {
|
const filteredItems = items
|
||||||
|
.map((itemType) => ({
|
||||||
|
...itemType,
|
||||||
|
itemList: itemType.itemList.filter((item) => item.availability),
|
||||||
|
}))
|
||||||
|
.filter((itemType) => itemType.itemList.length > 0); // Remove empty itemTypes
|
||||||
|
|
||||||
|
setCartItems(filteredItems);
|
||||||
|
|
||||||
|
// Update local storage by removing unavailable items
|
||||||
|
const updatedLocalStorage =
|
||||||
|
JSON.parse(localStorage.getItem("cart")) || [];
|
||||||
|
const newLocalStorage = updatedLocalStorage.map((cafe) => {
|
||||||
|
if (cafe.cafeId === shopId) {
|
||||||
|
return {
|
||||||
|
...cafe,
|
||||||
|
items: cafe.items.filter((item) =>
|
||||||
|
filteredItems.some((filtered) =>
|
||||||
|
filtered.itemList.some(
|
||||||
|
(i) => i.itemId === item.itemId && i.availability
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return cafe;
|
||||||
|
});
|
||||||
|
localStorage.setItem("cart", JSON.stringify(newLocalStorage));
|
||||||
|
|
||||||
|
window.dispatchEvent(new Event("localStorageUpdated"));
|
||||||
|
// Calculate total price based on filtered cart items
|
||||||
|
const totalPrice = filteredItems.reduce((total, itemType) => {
|
||||||
return (
|
return (
|
||||||
total +
|
total +
|
||||||
itemType.itemList.reduce((subtotal, item) => {
|
itemType.itemList.reduce((subtotal, item) => {
|
||||||
@@ -107,6 +131,11 @@ export default function Invoice({ table, sendParam, deviceType, socket }) {
|
|||||||
}
|
}
|
||||||
}, [textareaRef.current]);
|
}, [textareaRef.current]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (table?.tableId != undefined) setOrderType("serve");
|
||||||
|
console.log(table);
|
||||||
|
}, [table]);
|
||||||
|
|
||||||
const handleOrderTypeChange = (event) => {
|
const handleOrderTypeChange = (event) => {
|
||||||
setOrderType(event.target.value);
|
setOrderType(event.target.value);
|
||||||
};
|
};
|
||||||
|
|||||||
66
src/pages/NotificationBlocked.js
Normal file
66
src/pages/NotificationBlocked.js
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
// NotificationBlocked.js
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const NotificationBlocked = () => {
|
||||||
|
return (
|
||||||
|
<div style={styles.container}>
|
||||||
|
<h2 style={styles.header}>Notifications Blocked</h2>
|
||||||
|
<p style={styles.message}>
|
||||||
|
It looks like notifications are currently blocked in your browser.
|
||||||
|
Enabling notifications will help you receive important updates, such as
|
||||||
|
new orders or alerts, directly on your device.
|
||||||
|
</p>
|
||||||
|
<h3 style={styles.instructionsHeader}>To enable notifications:</h3>
|
||||||
|
<ol style={styles.instructions}>
|
||||||
|
<li>Open Chrome and go to our café's website.</li>
|
||||||
|
<li>Tap the menu (three dots) in the top-right corner.</li>
|
||||||
|
<li>
|
||||||
|
Go to <strong>Settings</strong> > <strong>Site settings</strong>{" "}
|
||||||
|
> <strong>Notifications</strong>.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Find our café's site in the list and change the setting to{" "}
|
||||||
|
<strong>Allow</strong>.
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
<p style={styles.footer}>
|
||||||
|
Once you enable notifications, you'll start receiving updates right
|
||||||
|
away! If you need help, feel free to ask!
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const styles = {
|
||||||
|
container: {
|
||||||
|
padding: "20px",
|
||||||
|
border: "1px solid #ddd",
|
||||||
|
borderRadius: "5px",
|
||||||
|
backgroundColor: "#f9f9f9",
|
||||||
|
boxShadow: "0 2px 5px rgba(0,0,0,0.1)",
|
||||||
|
maxWidth: "400px",
|
||||||
|
margin: "20px auto",
|
||||||
|
textAlign: "center",
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
color: "#e74c3c",
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
marginBottom: "20px",
|
||||||
|
},
|
||||||
|
instructionsHeader: {
|
||||||
|
marginTop: "20px",
|
||||||
|
fontWeight: "bold",
|
||||||
|
},
|
||||||
|
instructions: {
|
||||||
|
listStyleType: "decimal",
|
||||||
|
paddingLeft: "20px",
|
||||||
|
textAlign: "left",
|
||||||
|
},
|
||||||
|
footer: {
|
||||||
|
marginTop: "20px",
|
||||||
|
fontStyle: "italic",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NotificationBlocked;
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
// src/CafePage.js
|
// src/CafePage.js
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useParams, useSearchParams, useNavigate } from "react-router-dom";
|
import { useParams, useSearchParams, useNavigate } from "react-router-dom";
|
||||||
@@ -17,7 +16,7 @@ function SearchResult({ user, shopItems, sendParam }) {
|
|||||||
sendParam({ shopId, tableCode });
|
sendParam({ shopId, tableCode });
|
||||||
|
|
||||||
const [searchValue, setSearchValue] = useState(
|
const [searchValue, setSearchValue] = useState(
|
||||||
"dwadawa vvwqd21qb13 4kfawfdwa dhawldhawr dliawbdjawndlks"
|
"dwakbdawkjhbdaw wadhbakdbaw wadh abkd wba aww wadhbd kablawdloq w"
|
||||||
);
|
);
|
||||||
|
|
||||||
// Function to handle search input change
|
// Function to handle search input change
|
||||||
@@ -69,4 +68,4 @@ function SearchResult({ user, shopItems, sendParam }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SearchResult;
|
export default SearchResult;
|
||||||
|
|||||||
@@ -188,7 +188,7 @@ export default function Transactions({ propsShopId, sendParam, deviceType }) {
|
|||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{transaction.confirmed == 0 && (
|
{transaction.confirmed < 2 && (
|
||||||
<h5
|
<h5
|
||||||
className={styles.DeclineButton}
|
className={styles.DeclineButton}
|
||||||
onClick={() => handleDecline(transaction.transactionId)}
|
onClick={() => handleDecline(transaction.transactionId)}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React, { useRef, useEffect, useState } from "react";
|
|||||||
import styles from "./Transactions.module.css";
|
import styles from "./Transactions.module.css";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import { ColorRing } from "react-loader-spinner";
|
import { ColorRing } from "react-loader-spinner";
|
||||||
|
import ButtonWithReplica from "../components/ButtonWithReplica";
|
||||||
import {
|
import {
|
||||||
getTransaction,
|
getTransaction,
|
||||||
confirmTransaction,
|
confirmTransaction,
|
||||||
@@ -156,11 +157,10 @@ export default function Transactions({ propsShopId, sendParam, deviceType }) {
|
|||||||
Rp {calculateTotalPrice(transaction.DetailedTransactions)}
|
Rp {calculateTotalPrice(transaction.DetailedTransactions)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.TotalContainer}>
|
<div className={styles.PaymentContainer}>
|
||||||
<button
|
<ButtonWithReplica
|
||||||
className={styles.PayButton}
|
disabled={isPaymentLoading}
|
||||||
onClick={() => handleConfirm(transaction.transactionId)}
|
onClick={() => handleConfirm(transaction.transactionId)}
|
||||||
disabled={isPaymentLoading} // Disable button if confirmed (1) or declined (-1) or loading
|
|
||||||
>
|
>
|
||||||
{isPaymentLoading ? (
|
{isPaymentLoading ? (
|
||||||
<ColorRing height="50" width="50" color="white" />
|
<ColorRing height="50" width="50" color="white" />
|
||||||
@@ -175,7 +175,7 @@ export default function Transactions({ propsShopId, sendParam, deviceType }) {
|
|||||||
) : (
|
) : (
|
||||||
"Confirm availability" // Display "Confirm availability" if the transaction is not confirmed (0)
|
"Confirm availability" // Display "Confirm availability" if the transaction is not confirmed (0)
|
||||||
)}
|
)}
|
||||||
</button>
|
</ButtonWithReplica>
|
||||||
</div>
|
</div>
|
||||||
<h5
|
<h5
|
||||||
className={styles.DeclineButton}
|
className={styles.DeclineButton}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
} from "../helpers/transactionHelpers";
|
} from "../helpers/transactionHelpers";
|
||||||
import { getTables } from "../helpers/tableHelper";
|
import { getTables } from "../helpers/tableHelper";
|
||||||
import TableCanvas from "../components/TableCanvas";
|
import TableCanvas from "../components/TableCanvas";
|
||||||
|
import ButtonWithReplica from "../components/ButtonWithReplica";
|
||||||
|
|
||||||
export default function Transactions({ propsShopId, sendParam, deviceType }) {
|
export default function Transactions({ propsShopId, sendParam, deviceType }) {
|
||||||
const { shopId, tableId } = useParams();
|
const { shopId, tableId } = useParams();
|
||||||
@@ -143,6 +144,7 @@ export default function Transactions({ propsShopId, sendParam, deviceType }) {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.TotalContainer}>
|
<div className={styles.TotalContainer}>
|
||||||
|
<ButtonWithReplica />
|
||||||
<button
|
<button
|
||||||
className={styles.PayButton}
|
className={styles.PayButton}
|
||||||
onClick={() => handleConfirm(transaction.transactionId)}
|
onClick={() => handleConfirm(transaction.transactionId)}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
font-size: calc(10px + 2vmin);
|
font-size: calc(10px + 2vmin);
|
||||||
color: rgba(88, 55, 50, 1);
|
color: rgba(88, 55, 50, 1);
|
||||||
background-color: #e9e9e9;
|
background-color: #e9e9e9;
|
||||||
|
border-radius: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Transactions-title {
|
.Transactions-title {
|
||||||
@@ -34,6 +35,8 @@
|
|||||||
|
|
||||||
.TransactionListContainer {
|
.TransactionListContainer {
|
||||||
overflow-y: auto; /* Enables vertical scrolling */
|
overflow-y: auto; /* Enables vertical scrolling */
|
||||||
|
background-color: #dbdbdb;
|
||||||
|
overflow: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
.TotalContainer {
|
.TotalContainer {
|
||||||
@@ -50,6 +53,21 @@
|
|||||||
margin-bottom: 17px;
|
margin-bottom: 17px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.PaymentContainer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%; /* Ensures it takes up full width */
|
||||||
|
margin: 0 auto;
|
||||||
|
font-family: "Poppins", sans-serif;
|
||||||
|
font-weight: 600;
|
||||||
|
font-style: normal;
|
||||||
|
font-size: 1.5em;
|
||||||
|
padding: 10px;
|
||||||
|
box-sizing: border-box; /* Includes padding in width */
|
||||||
|
margin-bottom: 17px;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
.PayButton {
|
.PayButton {
|
||||||
font-family: "Poppins", sans-serif;
|
font-family: "Poppins", sans-serif;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
|||||||
26
src/services/notificationService.js
Normal file
26
src/services/notificationService.js
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import API_BASE_URL from "../config.js";
|
||||||
|
export const NotificationService = {
|
||||||
|
async fetchVapidPublicKey() {
|
||||||
|
const response = await fetch(API_BASE_URL + "/vapid-public-key"); // Adjust URL if necessary
|
||||||
|
const data = await response.json();
|
||||||
|
return data.publicKey;
|
||||||
|
},
|
||||||
|
|
||||||
|
async requestNotificationPermission(setModal) {
|
||||||
|
if (!("Notification" in window)) {
|
||||||
|
throw new Error("This browser does not support desktop notification");
|
||||||
|
}
|
||||||
|
setModal("req_notification");
|
||||||
|
const permission = await Notification.requestPermission();
|
||||||
|
return permission === "granted";
|
||||||
|
},
|
||||||
|
|
||||||
|
urlB64ToUint8Array(base64String) {
|
||||||
|
const padding = "=".repeat((4 - (base64String.length % 4)) % 4);
|
||||||
|
const base64 = (base64String + padding)
|
||||||
|
.replace(/-/g, "+")
|
||||||
|
.replace(/_/g, "/");
|
||||||
|
const rawData = window.atob(base64);
|
||||||
|
return Uint8Array.from(rawData, (char) => char.charCodeAt(0));
|
||||||
|
},
|
||||||
|
};
|
||||||
27
src/services/subscriptionService.js
Normal file
27
src/services/subscriptionService.js
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import API_BASE_URL from "../config.js";
|
||||||
|
import { NotificationService } from "./notificationService";
|
||||||
|
|
||||||
|
export const SubscriptionService = {
|
||||||
|
async subscribeUserToNotifications(userId) {
|
||||||
|
const registration = await navigator.serviceWorker.ready;
|
||||||
|
|
||||||
|
const publicKey = await NotificationService.fetchVapidPublicKey(); // Fetch the public key
|
||||||
|
const subscription = await registration.pushManager.subscribe({
|
||||||
|
userVisibleOnly: true,
|
||||||
|
applicationServerKey: NotificationService.urlB64ToUint8Array(publicKey),
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.saveSubscription(userId, subscription);
|
||||||
|
console.log("User is subscribed:", subscription);
|
||||||
|
},
|
||||||
|
|
||||||
|
async saveSubscription(userId, subscription) {
|
||||||
|
await fetch(API_BASE_URL + "/subscribe", {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({ userId, subscription }),
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user