From 69b3fe434754d6fb8a56911a49d49fdf56fd1f14 Mon Sep 17 00:00:00 2001 From: client perkafean Date: Fri, 27 Sep 2024 09:07:57 +0000 Subject: [PATCH] ok --- public/sw.js | 12 ++ src/App.js | 60 ++++++++- src/components/ButtonWithReplica.css | 49 +++++++ src/components/ButtonWithReplica.js | 24 ++++ src/components/Footer.js | 2 +- src/components/Footer.module.css | 1 + src/components/Header.js | 46 +++++-- src/components/Item.js | 128 +++++++++++------- src/components/Item.module.css | 20 +++ src/components/ItemLister.js | 190 +++++++++++++++++++-------- src/components/ItemLister.module.css | 3 +- src/components/ItemType.js | 8 +- src/components/ItemTypeLister.js | 25 +++- src/components/Modal.js | 4 +- src/components/Modal.module.css | 4 +- src/components/PaymentOptions.js | 66 +++++++--- src/components/QR.js | 5 +- src/components/SearchInput.js | 20 ++- src/config.js | 2 +- src/helpers/cafeHelpers.js | 52 +++++++- src/helpers/itemHelper.js | 37 +++++- src/helpers/tableHelper.js | 4 +- src/helpers/transactionHelpers.js | 2 +- src/pages/CafePage.js | 55 +++++--- src/pages/Cart.js | 53 ++++++-- src/pages/NotificationBlocked.js | 66 ++++++++++ src/pages/SearchResult.js | 5 +- src/pages/Transaction.js | 2 +- src/pages/Transaction_confirmed.js | 10 +- src/pages/Transactions.js | 2 + src/pages/Transactions.module.css | 18 +++ src/services/notificationService.js | 26 ++++ src/services/subscriptionService.js | 27 ++++ 33 files changed, 824 insertions(+), 204 deletions(-) create mode 100644 public/sw.js create mode 100644 src/components/ButtonWithReplica.css create mode 100644 src/components/ButtonWithReplica.js create mode 100644 src/pages/NotificationBlocked.js create mode 100644 src/services/notificationService.js create mode 100644 src/services/subscriptionService.js diff --git a/public/sw.js b/public/sw.js new file mode 100644 index 0000000..32f36cb --- /dev/null +++ b/public/sw.js @@ -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 + ); + }); + }); +} diff --git a/src/App.js b/src/App.js index ceb1dc5..02c8425 100644 --- a/src/App.js +++ b/src/App.js @@ -9,6 +9,8 @@ import { useLocation, } from "react-router-dom"; import socket from "./services/socketService"; +import { SubscriptionService } from "./services/subscriptionService"; +import { NotificationService } from "./services/notificationService"; import Dashboard from "./pages/Dashboard"; import ScanMeja from "./pages/ScanMeja"; @@ -77,19 +79,48 @@ function App() { if (tableCode) if (table.length == 0) { - const gettable = await getTableByCode(tableCode); + const gettable = await getTableByCode(shopId, tableCode); if (gettable) setTable(gettable); } }; useEffect(() => { - async function fetchData() { + const fetchData = async () => { console.log("gettingItems"); try { const { response, cafe, data } = await getItemTypesWithItems(shopId); if (response.status === 200) { setShop(cafe); 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", () => { console.log("transaction created"); }); @@ -97,7 +128,7 @@ function App() { } catch (error) { console.error("Error fetching shop items:", error); } - } + }; if (shopId !== "") fetchData(); }, [shopId]); @@ -107,6 +138,20 @@ function App() { 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(() => { if (socket == null) return; @@ -129,7 +174,7 @@ function App() { }); socket.on("transaction_confirmed", async (data) => { - console.log("transaction notification"); + console.log("transaction notification" + data); setModal("transaction_confirmed", data); }); @@ -153,6 +198,11 @@ function App() { setModal("transaction_failed", data); }); + socket.on("transaction_canceled", async (data) => { + console.log("transaction notification"); + setModal("transaction_canceled", data); + }); + //for clerk socket.on("transaction_created", async (data) => { console.log("transaction notification"); @@ -175,6 +225,8 @@ function App() { setGuestSides(connectedGuestSides.sessionDatas); console.log("getting guest side"); setDeviceType("clerk"); + + checkNotifications(data.data.user.userId); } else { setDeviceType("guestDevice"); } diff --git a/src/components/ButtonWithReplica.css b/src/components/ButtonWithReplica.css new file mode 100644 index 0000000..ce6980f --- /dev/null +++ b/src/components/ButtonWithReplica.css @@ -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; +} diff --git a/src/components/ButtonWithReplica.js b/src/components/ButtonWithReplica.js new file mode 100644 index 0000000..dbaa995 --- /dev/null +++ b/src/components/ButtonWithReplica.js @@ -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 ( +
+ +
+
+ ); +}; + +export default ButtonWithReplica; diff --git a/src/components/Footer.js b/src/components/Footer.js index 639734d..c070023 100644 --- a/src/components/Footer.js +++ b/src/components/Footer.js @@ -89,7 +89,7 @@ export default function Footer({
diff --git a/src/components/Footer.module.css b/src/components/Footer.module.css index 8b8a904..f8652ce 100644 --- a/src/components/Footer.module.css +++ b/src/components/Footer.module.css @@ -68,6 +68,7 @@ align-items: center; justify-content: center; transition: height 0.3s ease; + z-index: 200; } .scanMeja.stretched { diff --git a/src/components/Header.js b/src/components/Header.js index 717e8da..1a5cf6a 100644 --- a/src/components/Header.js +++ b/src/components/Header.js @@ -2,6 +2,7 @@ import React, { useState, useRef, useEffect } from "react"; import styled, { keyframes } from "styled-components"; import { useLocation } from "react-router-dom"; import { useNavigationHelpers } from "../helpers/navigationHelpers"; +import Switch from "react-switch"; const HeaderBar = styled.div` margin-top: 25px; @@ -11,6 +12,7 @@ const HeaderBar = styled.div` padding: 20px 15px; color: black; background-color: white; + z-index: 200; `; const Title = styled.h2` @@ -170,6 +172,7 @@ const Rectangle = styled.div` overflow-y: auto; /* Enable vertical scrolling */ padding: 10px; box-sizing: border-box; + overflow-x: hidden; `; const ChildContainer = styled.div` @@ -223,6 +226,7 @@ const Header = ({ guestSideOfClerk, removeConnectedGuestSides, setIsEditMode, + isEditMode, }) => { const { goToLogin, goToGuestSideLogin, goToAdminCafes } = useNavigationHelpers(shopId, tableCode); @@ -280,9 +284,27 @@ const Header = ({ console.log(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 ( - {HeaderText} + + {shopName == null + ? HeaderText == null + ? "Groovebrew" + : HeaderText + : generateMenuHeader(shopName)} +
*/} {shopName} +
- setIsEditMode(e.target.checked)} - /> + setIsEditMode(!isEditMode)} + />
setModal("add_material")}> stock @@ -372,16 +393,15 @@ const Header = ({ user.roleId === 2 && ( {shopName} +
- setIsEditMode(e.target.checked)} - /> + setIsEditMode(!isEditMode)} + />
setModal("add_material")}> stock diff --git a/src/components/Item.js b/src/components/Item.js index eae27b6..dbbb4c0 100644 --- a/src/components/Item.js +++ b/src/components/Item.js @@ -14,6 +14,7 @@ const Item = ({ onNegativeClick, handleCreateItem, onRemoveClick, + isAvailable, }) => { const [selectedImage, setSelectedImage] = useState(null); 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"; }} alt={itemName} + style={{ + filter: !isAvailable ? "grayscale(100%)" : "none", + }} className={styles.itemImage} /> {blank && ( @@ -113,8 +117,11 @@ const Item = ({ @@ -129,56 +136,83 @@ const Item = ({ )} - {!forInvoice && ( -
- - - - {!blank &&

{itemQty}

} - {blank && ( - - )} - - - -
- )} + {!forInvoice && + (itemQty != 0 ? ( +
+ + + + {!blank ? ( +

{itemQty}

+ ) : ( + + )} + + + +
+ ) : !blank ? ( +
+ +
+ ) : ( +
+ +
+ ))} {forInvoice && (

Rp {itemQty * itemPrice}

@@ -189,11 +223,11 @@ const Item = ({ ⓧ
)} - {blank && ( + {/* {blank && ( - )} + )} */}
); }; diff --git a/src/components/Item.module.css b/src/components/Item.module.css index c2c8f13..a3f2f72 100644 --- a/src/components/Item.module.css +++ b/src/components/Item.module.css @@ -166,6 +166,26 @@ 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 { width: 35px; height: 35px; diff --git a/src/components/ItemLister.js b/src/components/ItemLister.js index 7eab997..a606fcf 100644 --- a/src/components/ItemLister.js +++ b/src/components/ItemLister.js @@ -1,4 +1,4 @@ -import React, { useState, useRef } from "react"; +import React, { useEffect, useState, useRef } from "react"; import styles from "./ItemLister.module.css"; import Item from "./Item"; import Switch from "react-switch"; @@ -10,6 +10,7 @@ import { import { getImageUrl, createItem, + updateItemAvalilability, updateItemType, deleteItemType, } from "../helpers/itemHelper.js"; @@ -25,6 +26,7 @@ const ItemLister = ({ forCart, forInvoice, isEditMode, + raw, }) => { const [items, setItems] = useState( itemList.map((item) => ({ @@ -32,6 +34,16 @@ const ItemLister = ({ qty: getItemQtyFromCart(shopId, item.itemId), })) ); + + useEffect(() => { + setItems( + itemList.map((item) => ({ + ...item, + qty: getItemQtyFromCart(shopId, item.itemId), + })) + ); + }, [itemList]); + const [isEdit, setIsEditing] = useState(false); const [isAddingNewItem, setIsAddingNewItem] = useState(false); const [editedTypeName, setEditedTypeName] = useState(typeName); @@ -111,69 +123,135 @@ const ItemLister = ({ const toggleAddNewItem = () => { 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 ( <> {(items.length > 0 || (user && user.roleId == 1 && user.userId == shopOwnerId)) && (
-
- setEditedTypeName(e.target.value)} - disabled={!isEdit} - /> - {user && user.roleId == 1 && user.userId == shopOwnerId && ( - <> - - {isEdit && ( - + {!raw && ( +
+ setEditedTypeName(e.target.value)} + disabled={!isEdit} + /> + {isEditMode && + user && + user.roleId == 1 && + user.userId == shopOwnerId && ( + <> + + {isEdit && ( + + )} + )} - - )} -
+
+ )}
{user && user.roleId == 1 && user.userId == shopOwnerId && - isEdit && ( + isEditMode && ( <> - + {!isAddingNewItem && ( + + )} {isAddingNewItem && ( - - createItem( - shopId, - name, - price, - qty, - selectedImage, - itemTypeId - ) - } - /> + <> + + + createItem( + shopId, + name, + price, + qty, + selectedImage, + itemTypeId + ) + } + /> + )} )} @@ -182,8 +260,13 @@ const ItemLister = ({
{isEditMode && (
- -

available

+ {isEditMode && ( + handleChange(item.itemId)} + checked={item.availability} + /> + )} +

{item.availability ? "available" : "unavailable"}

)} handleNegativeClick(item.itemId)} onRemoveClick={() => handleRemoveClick(item.itemId)} isEditMode={isEditMode} + isAvailable={item.availability} />
) : null; diff --git a/src/components/ItemLister.module.css b/src/components/ItemLister.module.css index 227cf1b..f455925 100644 --- a/src/components/ItemLister.module.css +++ b/src/components/ItemLister.module.css @@ -42,7 +42,7 @@ margin-top: 10px; padding: 8px 16px; /* Adjust padding as needed */ font-size: 14px; - background-color: #007bff; + background-color: #359d42d1; color: #fff; border: none; border-radius: 4px; @@ -66,6 +66,7 @@ position: relative; } .editModeLayout { + border-radius: 4px; position: absolute; z-index: 100; background-color: #0000008c; diff --git a/src/components/ItemType.js b/src/components/ItemType.js index 50067fa..8cc748c 100644 --- a/src/components/ItemType.js +++ b/src/components/ItemType.js @@ -8,6 +8,7 @@ export default function ItemType({ blank, name: initialName = "", imageUrl, + selected, }) { const inputRef = useRef(null); const [name, setName] = useState(initialName); @@ -51,7 +52,11 @@ export default function ItemType({ return (
-
+
{name} {blank && ( + {modalContent === "req_notification" && } {modalContent === "edit_tables" && } {modalContent === "new_transaction" && ( @@ -53,7 +55,7 @@ const Modal = ({ shop, isOpen, onClose, modalContent }) => { {modalContent === "transaction_end" && } {modalContent === "transaction_failed" && } {modalContent === "payment_option" && ( - + )} {modalContent === "add_material" && ( diff --git a/src/components/Modal.module.css b/src/components/Modal.module.css index ae19b22..b4b6e91 100644 --- a/src/components/Modal.module.css +++ b/src/components/Modal.module.css @@ -12,13 +12,11 @@ } .modalContent { - background: white; - border-radius: 5px; width: 90%; height: 80%; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); position: relative; - overflow: hidden; /* Add this line to enable scrolling */ + overflow: visible; /* Add this line to enable scrolling */ } .closeButton { diff --git a/src/components/PaymentOptions.js b/src/components/PaymentOptions.js index 64c6cd5..da344b2 100644 --- a/src/components/PaymentOptions.js +++ b/src/components/PaymentOptions.js @@ -1,27 +1,43 @@ import React, { useState, useRef, useEffect } from "react"; import jsQR from "jsqr"; // Import jsQR library 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 = ({ - isConfigure, - tableNo, - qrCodeUrl, - 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 SetPaymentQr = ({ shopId }) => { + const [qrPosition, setQrPosition] = useState([50, 50]); + const [qrSize, setQrSize] = useState(50); + const [qrPayment, setQrPayment] = useState(); const [qrCodeDetected, setQrCodeDetected] = useState(false); const qrPaymentInputRef = useRef(null); const overlayTextRef = useRef(null); const qrCodeContainerRef = useRef(null); + const [isNeedConfirmation, setIsNeedConfirmation] = useState(false); + const [cafe, setCafe] = useState([]); // 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(() => { if (qrPayment) { detectQRCodeFromContainer(); @@ -77,7 +93,7 @@ const SetPaymentQr = ({ }; // Call saveCafeDetails function with the updated details object - saveCafeDetails(shopId, details) + saveCafeDetails(cafe.cafeId, details) .then((response) => { console.log("Cafe details saved:", response); // 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 (
+ + handleChange()} checked={isNeedConfirmation} />
); }; diff --git a/src/components/QR.js b/src/components/QR.js index faa75f4..2ffc1d9 100644 --- a/src/components/QR.js +++ b/src/components/QR.js @@ -163,7 +163,7 @@ const QRCodeWithBackground = ({ style={styles.overlayText} onClick={() => qrBackgroundInputRef.current.click()} > - Click To Change Image + Click To Change Background
)} {/* Hidden file input */} @@ -191,7 +191,6 @@ const QRCodeWithBackground = ({ onChange={handleSizeChange} style={styles.input} /> - 100% {qrSize}%
@@ -211,7 +210,6 @@ const QRCodeWithBackground = ({ onChange={handlePositionChange} style={styles.input} /> - 100% {qrPosition.left}%
@@ -231,7 +229,6 @@ const QRCodeWithBackground = ({ onChange={handlePositionChange} style={styles.input} /> - 100% {qrPosition.top}%
diff --git a/src/components/SearchInput.js b/src/components/SearchInput.js index 0e87830..d4bee5a 100644 --- a/src/components/SearchInput.js +++ b/src/components/SearchInput.js @@ -74,15 +74,17 @@ export default function SearchInput({ let url = ""; if (autofocus || songName != "") { url = tableCode - ? `/${shopId}/${tableCode}/search?query=${encodeURIComponent(songName)}` + ? `/${shopId}/${tableCode}/search?query=${encodeURIComponent( + songName + )}` : `/${shopId}/search?query=${encodeURIComponent(songName)}`; navigate(url); } if (autofocus) { if (songName == "") { - if (tableCode) navigate(`/${shopId}/${tableCode}`); - else navigate(`/${shopId}`); + if (tableCode) navigate(`/${shopId}/${tableCode}?find=true`); + else navigate(`/${shopId}?find=true`); } } if (onSearchChange) onSearchChange(songName); @@ -100,9 +102,18 @@ export default function SearchInput({ // Focus input when component mounts 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 ( @@ -112,6 +123,7 @@ export default function SearchInput({ placeholder="Search..." value={songName} onChange={handleChange} + onBlur={handleBlur} /> diff --git a/src/config.js b/src/config.js index 6a8f339..542c831 100644 --- a/src/config.js +++ b/src/config.js @@ -1,5 +1,5 @@ // 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; diff --git a/src/helpers/cafeHelpers.js b/src/helpers/cafeHelpers.js index 4c9846d..e23e08c 100644 --- a/src/helpers/cafeHelpers.js +++ b/src/helpers/cafeHelpers.js @@ -4,6 +4,26 @@ function getAuthToken() { 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) { try { const response = await fetch( @@ -36,8 +56,9 @@ export async function createCafe(cafeName) { "Content-Type": "application/json", Authorization: `Bearer ${getAuthToken()}`, }, - body: JSON.stringify({ - name: cafeName }), + body: JSON.stringify({ + name: cafeName, + }), }); 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 export async function saveCafeDetails(cafeId, details) { try { diff --git a/src/helpers/itemHelper.js b/src/helpers/itemHelper.js index 6c4f8a1..ae39d6e 100644 --- a/src/helpers/itemHelper.js +++ b/src/helpers/itemHelper.js @@ -4,7 +4,7 @@ import { getItemsByCafeId } from "./cartHelpers.js"; export async function getItemTypesWithItems(shopId) { try { 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(); @@ -37,7 +37,7 @@ export async function getCartDetails(shopId) { "Content-Type": "application/json", }, body: JSON.stringify(getItemsByCafeId(shopId)), - }, + } ); if (!response.ok) { @@ -66,7 +66,7 @@ export async function createItem( price, qty, selectedImage, - itemTypeId, + itemTypeId ) { try { 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) { try { const formData = new FormData(); @@ -136,7 +163,7 @@ export async function updateItemType(shopId, itemTypeId, newName) { Authorization: `Bearer ${getAuthToken()}`, }, body: JSON.stringify({ newName }), - }, + } ); if (!response.ok) { @@ -160,7 +187,7 @@ export async function deleteItemType(shopId, itemTypeId) { headers: { Authorization: `Bearer ${getAuthToken()}`, }, - }, + } ); if (!response.ok) { diff --git a/src/helpers/tableHelper.js b/src/helpers/tableHelper.js index cef4247..260922e 100644 --- a/src/helpers/tableHelper.js +++ b/src/helpers/tableHelper.js @@ -103,10 +103,10 @@ export async function getTable(shopId, tableNo) { } } -export async function getTableByCode(tableCode) { +export async function getTableByCode(shopId, tableCode) { try { 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", headers: { diff --git a/src/helpers/transactionHelpers.js b/src/helpers/transactionHelpers.js index 97a2f51..b103815 100644 --- a/src/helpers/transactionHelpers.js +++ b/src/helpers/transactionHelpers.js @@ -52,7 +52,6 @@ export async function declineTransaction(transactionId) { export async function cancelTransaction(transactionId) { try { - console.log(transactionId); const token = getLocalStorage("auth"); const response = await fetch( `${API_BASE_URL}/transaction/cancel-transaction/${transactionId}`, @@ -65,6 +64,7 @@ export async function cancelTransaction(transactionId) { } ); + console.log(response); if (!response.ok) { return false; } diff --git a/src/pages/CafePage.js b/src/pages/CafePage.js index dc958c1..f7e644d 100644 --- a/src/pages/CafePage.js +++ b/src/pages/CafePage.js @@ -47,6 +47,7 @@ function CafePage({ const [isModalOpen, setIsModalOpen] = useState(false); const [isEditMode, setIsEditMode] = useState(false); + const [filterId, setFilterId] = useState(0); useEffect(() => { if (user.cafeId != null && user.cafeId != shopId) { @@ -112,6 +113,7 @@ function CafePage({ guestSideOfClerk={guestSideOfClerk} removeConnectedGuestSides={removeConnectedGuestSides} setIsEditMode={(e) => setIsEditMode(e)} + isEditMode={isEditMode} />
@@ -121,28 +123,43 @@ function CafePage({ shopOwnerId={shopOwnerId} shopId={shopId} itemTypes={shopItems} + isEditMode={isEditMode} + onFilterChange={(e) => setFilterId(e)} + filterId={filterId} />
-

Music Req.

- + {filterId === 0 ? ( + <> +

Music Req.

+ + + ) : ( +
+ )} +
- {shopItems.map((itemType) => ( - - ))} + {shopItems + .filter( + (itemType) => filterId == 0 || itemType.itemTypeId === filterId + ) + .map((itemType) => ( + + ))} {user.username && ( { try { const items = await getCartDetails(shopId); - setCartItems(items); + console.log(items); - // Calculate total price based on fetched cart items - const totalPrice = items.reduce((total, itemType) => { + // Filter out unavailable items + 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 ( total + itemType.itemList.reduce((subtotal, item) => { @@ -107,6 +131,11 @@ export default function Invoice({ table, sendParam, deviceType, socket }) { } }, [textareaRef.current]); + useEffect(() => { + if (table?.tableId != undefined) setOrderType("serve"); + console.log(table); + }, [table]); + const handleOrderTypeChange = (event) => { setOrderType(event.target.value); }; diff --git a/src/pages/NotificationBlocked.js b/src/pages/NotificationBlocked.js new file mode 100644 index 0000000..46b1108 --- /dev/null +++ b/src/pages/NotificationBlocked.js @@ -0,0 +1,66 @@ +// NotificationBlocked.js +import React from "react"; + +const NotificationBlocked = () => { + return ( +
+

Notifications Blocked

+

+ 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. +

+

To enable notifications:

+
    +
  1. Open Chrome and go to our café's website.
  2. +
  3. Tap the menu (three dots) in the top-right corner.
  4. +
  5. + Go to Settings > Site settings{" "} + > Notifications. +
  6. +
  7. + Find our café's site in the list and change the setting to{" "} + Allow. +
  8. +
+

+ Once you enable notifications, you'll start receiving updates right + away! If you need help, feel free to ask! +

+
+ ); +}; + +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; diff --git a/src/pages/SearchResult.js b/src/pages/SearchResult.js index eede856..3653cbe 100644 --- a/src/pages/SearchResult.js +++ b/src/pages/SearchResult.js @@ -1,4 +1,3 @@ - // src/CafePage.js import React, { useState } from "react"; import { useParams, useSearchParams, useNavigate } from "react-router-dom"; @@ -17,7 +16,7 @@ function SearchResult({ user, shopItems, sendParam }) { sendParam({ shopId, tableCode }); const [searchValue, setSearchValue] = useState( - "dwadawa vvwqd21qb13 4kfawfdwa dhawldhawr dliawbdjawndlks" + "dwakbdawkjhbdaw wadhbakdbaw wadh abkd wba aww wadhbd kablawdloq w" ); // Function to handle search input change @@ -69,4 +68,4 @@ function SearchResult({ user, shopItems, sendParam }) { ); } -export default SearchResult; \ No newline at end of file +export default SearchResult; diff --git a/src/pages/Transaction.js b/src/pages/Transaction.js index 4e11766..d96575c 100644 --- a/src/pages/Transaction.js +++ b/src/pages/Transaction.js @@ -188,7 +188,7 @@ export default function Transactions({ propsShopId, sendParam, deviceType }) { )} - {transaction.confirmed == 0 && ( + {transaction.confirmed < 2 && (
handleDecline(transaction.transactionId)} diff --git a/src/pages/Transaction_confirmed.js b/src/pages/Transaction_confirmed.js index bc5a378..e7925b7 100644 --- a/src/pages/Transaction_confirmed.js +++ b/src/pages/Transaction_confirmed.js @@ -2,6 +2,7 @@ import React, { useRef, useEffect, useState } from "react"; import styles from "./Transactions.module.css"; import { useParams } from "react-router-dom"; import { ColorRing } from "react-loader-spinner"; +import ButtonWithReplica from "../components/ButtonWithReplica"; import { getTransaction, confirmTransaction, @@ -156,11 +157,10 @@ export default function Transactions({ propsShopId, sendParam, deviceType }) { Rp {calculateTotalPrice(transaction.DetailedTransactions)} -
- +
+