working on material and table

This commit is contained in:
zadit frontend
2024-08-03 15:05:10 +00:00
parent 5b4de33afb
commit 6461279b06
8 changed files with 631 additions and 210 deletions

View File

@@ -11,6 +11,7 @@ import {
import socket from "./services/socketService";
import Dashboard from "./pages/Dashboard";
import ScanMeja from "./pages/ScanMeja";
import LoginPage from "./pages/LoginPage";
import CafePage from "./pages/CafePage";
import SearchResult from "./pages/SearchResult";
@@ -23,8 +24,6 @@ import GuestSideLogin from "./pages/GuestSideLogin";
import GuestSide from "./pages/GuestSide";
import { getItemTypesWithItems } from "./helpers/itemHelper.js";
import MaterialList from "./pages/MaterialList";
import {
getConnectedGuestSides,
getClerks,
@@ -227,6 +226,26 @@ function App() {
}
/>
<Route path="/login" element={<LoginPage />} />
<Route
path="/scan"
element={
<>
<ScanMeja
sendParam={handleSetParam}
shopName={shop.name}
shopOwnerId={shop.ownerId}
shopItems={shopItems}
shopClerks={shopClerks}
socket={socket}
user={user}
guestSides={guestSides}
guestSideOfClerk={guestSideOfClerk}
removeConnectedGuestSides={rmConnectedGuestSides}
setModal={setModal} // Pass the function to open modal
/>
</>
}
/>
<Route
path="/:shopId/:tableId?"
element={
@@ -257,7 +276,7 @@ function App() {
path="/:shopId/:tableId?/search"
element={
<>
<MaterialList
<SearchResult
cafeId={shopId}
sendParam={handleSetParam}
user={user}

View File

@@ -1,4 +1,4 @@
import React from "react";
import React, { useState, useEffect, useRef } from "react";
import styles from "./Footer.module.css"; // assuming you have a CSS module for Footer
import { useNavigationHelpers } from "../helpers/navigationHelpers";
@@ -8,58 +8,115 @@ export default function Footer({
cartItemsLength,
selectedPage,
}) {
const { goToShop, goToSearch, goToCart, goToTransactions } =
useNavigationHelpers(shopId, tableId);
const {
goToShop,
goToSearch,
goToCart,
goToTransactions,
goToScan,
goToNonTable,
} = useNavigationHelpers(shopId, tableId);
const [isStretched, setIsStretched] = useState(false);
const scanMejaRef = useRef(null);
const handleScanMejaClick = () => {
if (tableId) {
setIsStretched(true);
} else {
goToTransactions();
}
};
const handleClickOutside = (event) => {
if (scanMejaRef.current && !scanMejaRef.current.contains(event.target)) {
setIsStretched(false);
}
};
useEffect(() => {
document.addEventListener("mousedown", handleClickOutside);
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, []);
return (
<div className={styles.item}>
<div className={styles["footer-rect"]}>
{/* Shop Icon */}
<div onClick={goToShop} className={styles["footer-icon"]}>
<svg
viewBox="0 0 34 34"
style={{ fill: selectedPage === 0 ? "black" : "#8F8787" }}
>
<path d="M14.0834 29.1667V18.9167H20.9167V29.1667H29.4584V15.5H34.5834L17.5001 0.125L0.416748 15.5H5.54175V29.1667H14.0834Z" />
</svg>
</div>
<div className={styles.footerContainer}>
<div className={styles.item}>
<div className={styles["footer-rect"]}>
{/* Shop Icon */}
<div onClick={goToShop} className={styles["footer-icon"]}>
<svg
viewBox="0 0 34 34"
style={{ fill: selectedPage === 0 ? "black" : "#8F8787" }}
>
<path d="M14.0834 29.1667V18.9167H20.9167V29.1667H29.4584V15.5H34.5834L17.5001 0.125L0.416748 15.5H5.54175V29.1667H14.0834Z" />
</svg>
</div>
{/* Search Icon */}
<div onClick={goToSearch} className={styles["footer-icon"]}>
<svg
viewBox="0 0 34 34"
style={{ fill: selectedPage === 1 ? "black" : "#8F8787" }}
>
<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" />
</svg>
</div>
{/* Search Icon */}
<div onClick={goToSearch} className={styles["footer-icon"]}>
<svg
viewBox="0 0 34 34"
style={{ fill: selectedPage === 1 ? "black" : "#8F8787" }}
>
<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" />
</svg>
</div>
{/* Cart Icon */}
<div onClick={goToCart} className={styles["footer-icon"]}>
{cartItemsLength != "0" && (
<div className={styles.circle}>{cartItemsLength}</div>
)}
<svg
viewBox="0 0 34 34"
style={{ fill: selectedPage === 2 ? "black" : "#8F8787" }}
>
<path d="M9.79175 24.75C8.09591 24.75 6.72383 26.1375 6.72383 27.8333C6.72383 29.5292 8.09591 30.9167 9.79175 30.9167C11.4876 30.9167 12.8751 29.5292 12.8751 27.8333C12.8751 26.1375 11.4876 24.75 9.79175 24.75ZM0.541748 0.0833435V3.16668H3.62508L9.17508 14.8679L7.09383 18.645C6.84717 19.0767 6.70842 19.5854 6.70842 20.125C6.70842 21.8208 8.09591 23.2083 9.79175 23.2083H28.2917V20.125H10.4392C10.2234 20.125 10.0538 19.9554 10.0538 19.7396L10.1001 19.5546L11.4876 17.0417H22.973C24.1292 17.0417 25.1467 16.4096 25.6709 15.4538L31.1901 5.44834C31.3134 5.23251 31.3751 4.97043 31.3751 4.70834C31.3751 3.86043 30.6813 3.16668 29.8334 3.16668H7.03217L5.583 0.0833435H0.541748ZM25.2084 24.75C23.5126 24.75 22.1405 26.1375 22.1405 27.8333C22.1405 29.5292 23.5126 30.9167 25.2084 30.9167C26.9042 30.9167 28.2917 29.5292 28.2917 27.8333C28.2917 26.1375 26.9042 24.75 25.2084 24.75Z" />
</svg>
</div>
{/* Cart Icon */}
<div onClick={goToCart} className={styles["footer-icon"]}>
{cartItemsLength !== 0 && (
<div className={styles.circle}>{cartItemsLength}</div>
)}
<svg
viewBox="0 0 34 34"
style={{ fill: selectedPage === 2 ? "black" : "#8F8787" }}
>
<path d="M9.79175 24.75C8.09591 24.75 6.72383 26.1375 6.72383 27.8333C6.72383 29.5292 8.09591 30.9167 9.79175 30.9167C11.4876 30.9167 12.8751 29.5292 12.8751 27.8333C12.8751 26.1375 11.4876 24.75 9.79175 24.75ZM0.541748 0.0833435V3.16668H3.62508L9.17508 14.8679L7.09383 18.645C6.84717 19.0767 6.70842 19.5854 6.70842 20.125C6.70842 21.8208 8.09591 23.2083 9.79175 23.2083H28.2917V20.125H10.4392C10.2234 20.125 10.0538 19.9554 10.0538 19.7396L10.1001 19.5546L11.4876 17.0417H22.973C24.1292 17.0417 25.1467 16.4096 25.6709 15.4538L31.1901 5.44834C31.3134 5.23251 31.3751 4.97043 31.3751 4.70834C31.3751 3.86043 30.6813 3.16668 29.8334 3.16668H7.03217L5.583 0.0833435H0.541748ZM25.2084 24.75C23.5126 24.75 22.1405 26.1375 22.1405 27.8333C22.1405 29.5292 23.5126 30.9167 25.2084 30.9167C26.9042 30.9167 28.2917 29.5292 28.2917 27.8333C28.2917 26.1375 26.9042 24.75 25.2084 24.75Z" />
</svg>
</div>
{/* Profile Icon */}
<div onClick={goToTransactions} className={styles["footer-icon"]}>
<svg
viewBox="0 0 34 34"
style={{ fill: selectedPage === 3 ? "black" : "#8F8787" }}
>
<path d="M15.9842 0.166656C7.24421 0.166656 0.150879 7.25999 0.150879 16C0.150879 24.74 7.24421 31.8333 15.9842 31.8333C24.7242 31.8333 31.8175 24.74 31.8175 16C31.8175 7.25999 24.7242 0.166656 15.9842 0.166656ZM21.7 10.205C23.3942 10.205 24.7559 11.5667 24.7559 13.2608C24.7559 14.955 23.3942 16.3167 21.7 16.3167C20.0059 16.3167 18.6442 14.955 18.6442 13.2608C18.6284 11.5667 20.0059 10.205 21.7 10.205ZM12.2 7.70332C14.2584 7.70332 15.9367 9.38166 15.9367 11.44C15.9367 13.4983 14.2584 15.1767 12.2 15.1767C10.1417 15.1767 8.46338 13.4983 8.46338 11.44C8.46338 9.36582 10.1259 7.70332 12.2 7.70332ZM12.2 22.1592V28.0967C8.40005 26.9092 5.39171 23.98 4.06171 20.2433C5.72421 18.47 9.87255 17.5675 12.2 17.5675C13.0392 17.5675 14.1 17.6942 15.2084 17.9158C12.6117 19.2933 12.2 21.1142 12.2 22.1592ZM15.9842 28.6667C15.5567 28.6667 15.145 28.6508 14.7334 28.6033V22.1592C14.7334 19.9108 19.3884 18.7867 21.7 18.7867C23.3942 18.7867 26.3234 19.4042 27.78 20.6075C25.9275 25.31 21.3517 28.6667 15.9842 28.6667Z" />
</svg>
{/* Profile Icon */}
<div onClick={goToTransactions} className={styles["footer-icon"]}>
<svg
viewBox="0 0 34 34"
style={{ fill: selectedPage === 3 ? "black" : "#8F8787" }}
>
<path d="M15.9842 0.166656C7.24421 0.166656 0.150879 7.25999 0.150879 16C0.150879 24.74 7.24421 31.8333 15.9842 31.8333C24.7242 31.8333 31.8175 24.74 31.8175 16C31.8175 7.25999 24.7242 0.166656 15.9842 0.166656ZM21.7 10.205C23.3942 10.205 24.7559 11.5667 24.7559 13.2608C24.7559 14.955 23.3942 16.3167 21.7 16.3167C20.0059 16.3167 18.6442 14.955 18.6442 13.2608C18.6284 11.5667 20.0059 10.205 21.7 10.205ZM12.2 7.70332C14.2584 7.70332 15.9367 9.38166 15.9367 11.44C15.9367 13.4983 14.2584 15.1767 12.2 15.1767C10.1417 15.1767 8.46338 13.4983 8.46338 11.44C8.46338 9.36582 10.1259 7.70332 12.2 7.70332ZM12.2 22.1592V28.0967C8.40005 26.9092 5.39171 23.98 4.06171 20.2433C5.72421 18.47 9.87255 17.5675 12.2 17.5675C13.0392 17.5675 14.1 17.6942 15.2084 17.9158C12.6117 19.2933 12.2 21.1142 12.2 22.1592ZM15.9842 28.6667C15.5567 28.6667 15.145 28.6508 14.7334 28.6033V22.1592C14.7334 19.9108 19.3884 18.7867 21.7 18.7867C23.3942 18.7867 26.3234 19.4042 27.78 20.6075C25.9275 25.31 21.3517 28.6667 15.9842 28.6667Z" />
</svg>
</div>
</div>
{/* Add more SVG elements as needed */}
<div className={styles["footer-bottom"]}></div>
</div>
<div className={styles["footer-bottom"]}></div>
{/* Rounded Rectangle with "Scan Meja" and QR Icon */}
{shopId && (
<div
ref={scanMejaRef}
onClick={!tableId ? goToScan : handleScanMejaClick}
className={`${styles.scanMeja} ${
isStretched ? styles.stretched : ""
}`}
>
<span>
{tableId ? `Diantar ke meja ${tableId}` : `Scan Meja\u00A0`}
</span>
{!tableId && (
<img
src="https://static-00.iconduck.com/assets.00/qr-scan-icon-2048x2048-aeh36n7y.png"
alt="QR Code"
className={styles.qrIcon}
/>
)}
{tableId && isStretched && (
<button onClick={goToNonTable} className={styles.hapusMejaBtn}>
Hapus Meja
</button>
)}
</div>
)}
</div>
);
}

View File

@@ -45,10 +45,52 @@
/* Just making it pretty */
background: #38a9e4;
color: white;
font-family:
Helvetica,
Arial Black,
sans;
font-family: Helvetica, Arial Black, sans;
font-size: 20px;
text-align: center;
}
.scanMeja {
position: fixed;
bottom: 90px;
left: 50%;
transform: translateX(-50%);
background-color: rgb(143, 135, 135);
color: #fff;
width: 147px;
height: 40px;
border-radius: 25px;
padding: 0px 10px;
font-size: 16px;
text-align: center;
font-weight: 600;
color: black;
display: flex;
align-items: center;
justify-content: center;
transition: height 0.3s ease;
}
.scanMeja.stretched {
height: 70px;
flex-direction: column;
padding: 10px;
}
.qrIcon {
top: 2px;
position: relative;
width: 24px;
height: 24px;
background-color: rgb(143, 135, 135);
}
.hapusMejaBtn {
margin-top: 10px;
background-color: #d9534f;
color: white;
border: none;
border-radius: 5px;
padding: 8px 16px;
font-size: 14px;
cursor: pointer;
}

View File

@@ -63,3 +63,19 @@ export const getMaterialMutationById = async (mutationId) => {
throw error;
}
};
// Get all material mutations by materialId
export const getMaterialMutationsByMaterialId = async (materialId) => {
try {
const response = await fetch(
`${API_BASE_URL}/mutation/get-material-mutations-by-material/${materialId}`,
getHeaders()
);
if (!response.ok)
throw new Error("Failed to retrieve material mutations by material ID.");
return await response.json();
} catch (error) {
console.error(error);
throw error;
}
};

View File

@@ -25,7 +25,21 @@ export const useNavigationHelpers = (shopId, tableId) => {
// Perform the navigation
navigate(url);
};
const goToScan = () => {
// Construct the base URL for the shop
let url = `/scan`;
// Perform the navigation
navigate(url);
};
const goToNonTable = () => {
// Construct the base URL for the shop
let url = `/${shopId}`;
// Perform the navigation
navigate(url);
};
const goToShop = () => {
// Construct the base URL for the shop
let url = `/${shopId}`;
@@ -103,5 +117,7 @@ export const useNavigationHelpers = (shopId, tableId) => {
goToTransactions,
goToGuestSideLogin,
goToAdminCafes,
goToScan,
goToNonTable,
};
};

View File

@@ -4,7 +4,7 @@ import {
getMaterials,
createMaterial,
deleteMaterial,
} from "../helpers/materialHelpers"; // Update import
} from "../helpers/materialHelpers";
const MaterialList = ({ cafeId }) => {
const [materials, setMaterials] = useState([]);
@@ -14,7 +14,7 @@ const MaterialList = ({ cafeId }) => {
const [deleting, setDeleting] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [showForm, setShowForm] = useState(false); // For form visibility
const [showForm, setShowForm] = useState(false);
useEffect(() => {
const fetchMaterials = async () => {
@@ -22,7 +22,7 @@ const MaterialList = ({ cafeId }) => {
try {
const data = await getMaterials(cafeId);
setMaterials(data);
setError(null); // Clear any previous error
setError(null);
} catch (error) {
console.error("Error fetching materials:", error);
setError("Failed to fetch materials.");
@@ -41,6 +41,7 @@ const MaterialList = ({ cafeId }) => {
const formData = new FormData();
formData.append("name", newMaterialName);
formData.append("unit", newMaterialUnit);
console.log(newMaterialImage);
if (newMaterialImage) {
formData.append("image", newMaterialImage);
}
@@ -50,10 +51,10 @@ const MaterialList = ({ cafeId }) => {
setNewMaterialName("");
setNewMaterialUnit("kilogram");
setNewMaterialImage(null);
setShowForm(false); // Hide the form after successful creation
setShowForm(false);
const data = await getMaterials(cafeId);
setMaterials(data);
setError(null); // Clear any previous error
setError(null);
} catch (error) {
console.error("Error creating material:", error);
setError("Failed to create material.");
@@ -69,7 +70,7 @@ const MaterialList = ({ cafeId }) => {
setMaterials(
materials.filter((material) => material.materialId !== materialId)
);
setError(null); // Clear any previous error
setError(null);
} catch (error) {
console.error("Error deleting material:", error);
setError("Failed to delete material.");
@@ -80,7 +81,7 @@ const MaterialList = ({ cafeId }) => {
return (
<div style={styles.container}>
<h2>Materials List</h2>
<h1 style={styles.heading}>Materials List</h1>
{/* Display error message if any */}
{error && <p style={styles.error}>{error}</p>}
@@ -126,9 +127,13 @@ const MaterialList = ({ cafeId }) => {
onChange={(e) => setNewMaterialUnit(e.target.value)}
style={styles.input}
>
<option value="gram">Gram</option>
<option value="ons">Ons</option>
<option value="kilogram">Kilogram</option>
<option value="kuintal">Kuintal</option>
<option value="liter">Liter</option>
<option value="piece">Piece</option>
<option value="meter">Meter</option>
</select>
</div>
<div style={styles.formGroup}>
@@ -142,7 +147,7 @@ const MaterialList = ({ cafeId }) => {
style={styles.input}
/>
</div>
<button type="submit" style={styles.button} disabled={loading}>
<button type="submit" style={styles.submitButton} disabled={loading}>
{loading ? "Creating..." : "Create Material"}
</button>
</form>
@@ -152,17 +157,20 @@ const MaterialList = ({ cafeId }) => {
{loading ? (
<p>Loading materials...</p>
) : (
<ul style={styles.list}>
<div style={styles.materialList}>
{materials.map((material) => (
<li key={material.materialId} style={styles.listItem}>
{material.name} - {material.unit}
<div key={material.materialId} style={styles.materialCard}>
{material.image && (
<img
src={`${API_BASE_URL}/uploads/${material.image}`}
src={`${API_BASE_URL}/${material.image}`}
alt={material.name}
style={styles.image}
/>
)}
<div style={styles.cardContent}>
<h3 style={styles.cardTitle}>{material.name}</h3>
<p>{material.unit}</p>
</div>
<button
onClick={() => handleDeleteMaterial(material.materialId)}
disabled={deleting === material.materialId || loading}
@@ -170,9 +178,9 @@ const MaterialList = ({ cafeId }) => {
>
{deleting === material.materialId ? "Deleting..." : "Delete"}
</button>
</li>
</div>
))}
</ul>
</div>
)}
</div>
);
@@ -182,22 +190,24 @@ const MaterialList = ({ cafeId }) => {
const styles = {
container: {
padding: "20px",
maxWidth: "600px",
maxWidth: "800px",
margin: "0 auto",
backgroundColor: "#f9f9f9",
borderRadius: "8px",
boxShadow: "0 2px 4px rgba(0,0,0,0.1)",
},
heading: {
textAlign: "center",
},
toggleButton: {
marginBottom: "20px",
padding: "10px 15px",
display: "block",
width: "100%",
padding: "10px",
borderRadius: "5px",
border: "none",
borderRadius: "4px",
backgroundColor: "#007bff",
color: "#fff",
cursor: "pointer",
color: "white",
fontSize: "16px",
transition: "background-color 0.3s",
cursor: "pointer",
marginBottom: "20px",
transition: "background-color 0.3s ease",
},
formContainer: {
transition: "height 0.5s ease-in-out",
@@ -207,56 +217,77 @@ const styles = {
marginBottom: "20px",
},
formGroup: {
marginBottom: "10px",
marginBottom: "15px",
},
label: {
display: "block",
marginBottom: "5px",
fontWeight: "bold",
fontWeight: "600",
},
input: {
width: "100%",
padding: "8px",
padding: "10px",
border: "1px solid #ddd",
borderRadius: "4px",
borderRadius: "8px",
boxSizing: "border-box",
},
button: {
padding: "10px 15px",
submitButton: {
padding: "12px 20px",
border: "none",
borderRadius: "4px",
borderRadius: "8px",
backgroundColor: "#28a745",
color: "#fff",
cursor: "pointer",
fontSize: "16px",
transition: "background-color 0.3s, transform 0.3s",
boxShadow: "0 4px 8px rgba(0, 0, 0, 0.1)",
},
deleteButton: {
marginLeft: "10px",
padding: "5px 10px",
padding: "8px 15px",
border: "none",
borderRadius: "4px",
borderRadius: "8px",
backgroundColor: "#dc3545",
color: "#fff",
cursor: "pointer",
fontSize: "14px",
transition: "background-color 0.3s, transform 0.3s",
boxShadow: "0 4px 8px rgba(0, 0, 0, 0.1)",
},
list: {
listStyleType: "none",
padding: "0",
margin: "0",
},
listItem: {
padding: "10px",
borderBottom: "1px solid #ddd",
materialList: {
display: "flex",
flexWrap: "wrap",
gap: "15px",
justifyContent: "center",
maxHeight: "500px", // Adjust the height as needed
overflowY: "auto", // Makes the container scrollable vertically
},
materialCard: {
flex: "1 1 200px",
padding: "15px",
borderRadius: "8px",
border: "1px solid #ddd",
backgroundColor: "#fff",
boxShadow: "0 4px 8px rgba(0, 0, 0, 0.1)",
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "space-between",
textAlign: "center",
},
cardContent: {
marginBottom: "10px",
},
cardTitle: {
fontSize: "18px",
fontWeight: "600",
marginBottom: "5px",
},
image: {
marginLeft: "10px",
height: "50px",
width: "50px",
width: "80px",
height: "80px",
objectFit: "cover",
borderRadius: "8px",
marginBottom: "10px",
},
error: {
color: "#dc3545",

View File

@@ -1,4 +1,3 @@
// src/pages/MaterialMutationPage.js
import React, { useEffect, useState } from "react";
import { getMaterials } from "../helpers/materialHelpers";
import {
@@ -6,9 +5,145 @@ import {
getMaterialMutations,
} from "../helpers/materialMutationHelpers";
// Keyframes for grow animation
const growKeyframes = `
@keyframes grow {
0% {
width: 60px;
height: 60px;
border-top-left-radius: 50%;
border-bottom-left-radius: 50%;
}
100% {
width: 100%;
height: auto;
border-top-left-radius: 20px;
border-bottom-left-radius: 20px;
}
}
`;
// Styles
const styles = {
container: {
padding: "20px",
maxWidth: "800px",
margin: "0 auto",
},
heading: {
textAlign: "center",
},
button: {
display: "block",
width: "100%",
padding: "10px",
borderRadius: "5px",
border: "none",
backgroundColor: "#007bff",
color: "white",
fontSize: "16px",
cursor: "pointer",
marginBottom: "20px",
transition: "background-color 0.3s ease",
},
buttonHover: {
backgroundColor: "#0056b3",
},
expandingContainer: (expanded) => ({
overflow: "hidden",
transition: "all 0.3s ease",
animation: expanded ? "grow 0.5s forwards" : "none",
marginBottom: "20px",
}),
materialSelection: {
padding: "10px",
},
materialList: {
display: "flex",
flexDirection: "row",
overflowX: "auto", // Enable horizontal scrolling
overflowY: "hidden", // Prevent vertical scrolling
whiteSpace: "nowrap", // Prevent wrapping of items
gap: "10px", // Space between items
padding: "10px 0", // Padding for better appearance
},
materialCard: (selected) => ({
flex: "0 0 auto", // Prevent growing or shrinking
minWidth: "100px", // Minimum width of the card
maxWidth: "150px", // Maximum width of the card
padding: "15px",
borderRadius: "10px",
border: "1px solid #ddd",
backgroundColor: selected ? "#007bff" : "#f9f9f9",
color: selected ? "white" : "black",
boxShadow: "0 2px 4px rgba(0, 0, 0, 0.1)",
cursor: "pointer",
transition: "background-color 0.3s ease",
textAlign: "center", // Center text horizontally
display: "flex", // Flexbox for vertical centering
alignItems: "center", // Center text vertically
justifyContent: "center", // Center text horizontally
}),
mutationDetails: {
padding: "10px",
},
detailInput: {
marginBottom: "15px",
},
detailLabel: {
display: "block",
marginBottom: "5px",
},
detailInputField: {
width: "100%",
padding: "10px",
borderRadius: "5px",
border: "1px solid #ddd",
},
btnSubmit: {
width: "100%",
padding: "10px",
borderRadius: "5px",
border: "none",
backgroundColor: "#28a745",
color: "white",
fontSize: "16px",
cursor: "pointer",
transition: "background-color 0.3s ease",
},
btnSubmitHover: {
backgroundColor: "#218838",
},
message: {
textAlign: "center",
fontSize: "16px",
},
successMessage: {
color: "#28a745",
},
errorMessage: {
color: "#dc3545",
},
mutationList: {
display: "flex",
flexDirection: "column",
gap: "10px",
maxHeight: "400px", // Set the desired height
overflowY: "auto", // Enable vertical scrolling
},
mutationCard: {
padding: "15px",
borderRadius: "10px",
border: "1px solid #ddd",
backgroundColor: "#f9f9f9",
boxShadow: "0 2px 4px rgba(0, 0, 0, 0.1)",
},
};
const MaterialMutationPage = ({ cafeId }) => {
const [materials, setMaterials] = useState([]);
const [materialMutations, setMaterialMutations] = useState([]);
const [mutations, setMutations] = useState([]);
const [selectedMaterialId, setSelectedMaterialId] = useState("");
const [oldStock, setOldStock] = useState("");
const [newStock, setNewStock] = useState("");
@@ -16,8 +151,8 @@ const MaterialMutationPage = ({ cafeId }) => {
const [reason, setReason] = useState("");
const [successMessage, setSuccessMessage] = useState("");
const [error, setError] = useState("");
const [expanded, setExpanded] = useState(false);
// Fetch materials when the component mounts
useEffect(() => {
const fetchMaterials = async () => {
try {
@@ -28,31 +163,30 @@ const MaterialMutationPage = ({ cafeId }) => {
}
};
fetchMaterials();
}, [cafeId]);
// Fetch material mutations when the component mounts
useEffect(() => {
const fetchMaterialMutations = async () => {
const fetchMutations = async () => {
try {
const data = await getMaterialMutations(cafeId);
setMaterialMutations(data);
setMutations(data);
} catch (err) {
setError(err.message);
}
};
fetchMaterialMutations();
fetchMaterials();
fetchMutations();
}, [cafeId]);
// Handle form submission
const handleSubmit = async (e) => {
e.preventDefault();
if (!selectedMaterialId) {
setError("Please select a material.");
return;
}
const handleToggle = () => setExpanded(!expanded);
const handleCancel = () => {
setSelectedMaterialId(null);
handleToggle();
};
const handleMaterialSelect = (materialId) => {
setSelectedMaterialId(materialId);
};
const handleSubmit = async () => {
try {
const data = { oldStock, newStock, changeDate, reason };
await createMaterialMutation(selectedMaterialId, data);
@@ -62,118 +196,140 @@ const MaterialMutationPage = ({ cafeId }) => {
setChangeDate("");
setReason("");
setSelectedMaterialId("");
// Refresh material mutations list after creation
// Refresh the mutations list
const updatedMutations = await getMaterialMutations(cafeId);
setMaterialMutations(updatedMutations);
setMutations(updatedMutations);
} catch (err) {
setError(err.message);
}
};
// Filtered mutations based on selected material
const filteredMutations = selectedMaterialId
? mutations.filter((mutation) => mutation.materialId === selectedMaterialId)
: mutations;
return (
<div>
<h1>Material Mutations</h1>
<div style={styles.container}>
<h1 style={styles.heading}>Material Mutations</h1>
<h2>Create Material Mutation</h2>
<form onSubmit={handleSubmit}>
<div>
<label>
Select Material:
<select
value={selectedMaterialId}
onChange={(e) => setSelectedMaterialId(e.target.value)}
required
>
<option value="">Select a material</option>
<button style={styles.button} onClick={handleCancel}>
{expanded ? "Cancel" : "Create Material Mutation"}
</button>
<div style={styles.expandingContainer(expanded)}>
{!expanded && (
<div style={styles.materialSelection}>
<div style={styles.materialList}>
<div
style={styles.materialCard(selectedMaterialId === "")}
onClick={() => handleMaterialSelect("")}
>
All
</div>
{materials.map((material) => (
<option key={material.materialId} value={material.materialId}>
<div
key={material.materialId}
style={styles.materialCard(
selectedMaterialId === material.materialId
)}
onClick={() => handleMaterialSelect(material.materialId)}
>
{material.name}
</option>
</div>
))}
</select>
</label>
</div>
</div>
</div>
)}
{expanded && !selectedMaterialId && (
<div style={styles.materialSelection}>
<div style={styles.materialList}>
{materials.map((material) => (
<div
key={material.materialId}
style={styles.materialCard(
selectedMaterialId === material.materialId
)}
onClick={() => handleMaterialSelect(material.materialId)}
>
{material.name}
</div>
))}
</div>
</div>
)}
<div>
<label>
Old Stock:
<input
type="number"
value={oldStock}
onChange={(e) => setOldStock(e.target.value)}
required
/>
</label>
</div>
{expanded && selectedMaterialId && (
<div style={styles.mutationDetails}>
<div style={styles.detailInput}>
<label style={styles.detailLabel}>Old Stock:</label>
<input
type="number"
style={styles.detailInputField}
value={oldStock}
onChange={(e) => setOldStock(e.target.value)}
/>
</div>
<div>
<label>
New Stock:
<input
type="number"
value={newStock}
onChange={(e) => setNewStock(e.target.value)}
required
/>
</label>
</div>
<div style={styles.detailInput}>
<label style={styles.detailLabel}>New Stock:</label>
<input
type="number"
style={styles.detailInputField}
value={newStock}
onChange={(e) => setNewStock(e.target.value)}
/>
</div>
<div>
<label>
Change Date:
<input
type="datetime-local"
value={changeDate}
onChange={(e) => setChangeDate(e.target.value)}
required
/>
</label>
</div>
<div style={styles.detailInput}>
<label style={styles.detailLabel}>Change Date:</label>
<input
type="datetime-local"
style={styles.detailInputField}
value={changeDate}
onChange={(e) => setChangeDate(e.target.value)}
/>
</div>
<div>
<label>
Reason:
<textarea
value={reason}
onChange={(e) => setReason(e.target.value)}
/>
</label>
</div>
<div style={styles.detailInput}>
<label style={styles.detailLabel}>Reason:</label>
<textarea
style={styles.detailInputField}
value={reason}
onChange={(e) => setReason(e.target.value)}
/>
</div>
<button type="submit">Create Mutation</button>
</form>
<button style={styles.btnSubmit} onClick={handleSubmit}>
Create Mutation
</button>
</div>
)}
</div>
{successMessage && <p>{successMessage}</p>}
{error && <p>Error: {error}</p>}
<h2>Existing Material Mutations</h2>
{materialMutations.length > 0 ? (
<ul>
{materialMutations.map((mutation) => (
<li key={mutation.mutationId}>
<p>
<strong>Material ID:</strong> {mutation.materialId}
</p>
<p>
<strong>Old Stock:</strong> {mutation.oldStock}
</p>
<p>
<strong>New Stock:</strong> {mutation.newStock}
</p>
<p>
<strong>Change Date:</strong>{" "}
{new Date(mutation.changeDate).toLocaleString()}
</p>
<p>
<strong>Reason:</strong> {mutation.reason}
</p>
</li>
))}
</ul>
) : (
<p>No material mutations found.</p>
{successMessage && (
<p style={{ ...styles.message, ...styles.successMessage }}>
{successMessage}
</p>
)}
{error && (
<p style={{ ...styles.message, ...styles.errorMessage }}>
Error: {error}
</p>
)}
<div style={styles.mutationList}>
{filteredMutations.map((mutation) => (
<div key={mutation.mutationId} style={styles.mutationCard}>
<h3>
{mutation.Material.name}-{mutation.reason}
</h3>
<p>Old Stock: {mutation.oldStock}</p>
<p>New Stock: {mutation.newStock}</p>
<p>Change Date: {new Date(mutation.changeDate).toLocaleString()}</p>
</div>
))}
</div>
</div>
);
};

84
src/pages/ScanMeja.js Normal file
View File

@@ -0,0 +1,84 @@
import React, { useState, useEffect } from "react";
import { useNavigate } from "react-router-dom";
import { QrReader } from "react-qr-reader"; // Import QrReader as named import
import styles from "./GuestSideLogin.module.css"; // Import module CSS file for styles
import { getLocalStorage } from "../helpers/localStorageHelpers";
const GuestSideLogin = ({ socket }) => {
const navigate = useNavigate();
const [qrCode, setQRCode] = useState(""); // State to store QR code
socket.on("qrCode_readSuccess", (response) => {
const { shopId } = response;
console.log("qr has been read");
navigate("/" + shopId);
});
const setLoginGuestSide = () => {
const token = getLocalStorage("auth");
socket.emit("read_qrCode", { qrCode, token });
};
// Function to handle QR code scan
const handleScan = (data) => {
if (data) {
setQRCode(data.text); // Set scanned QR code to state
setLoginGuestSide(); // Send QR code to backend
}
};
// Function to handle QR scan error
const handleError = (err) => {
console.error(err);
};
// Function to handle manual input
const handleManualInput = (e) => {
setQRCode(e.target.value);
};
useEffect(() => {
if (qrCode.length === 11) {
const timer = setTimeout(() => {
setLoginGuestSide();
}, 1000); // Delay of 1 second (1000 milliseconds)
return () => clearTimeout(timer); // Cleanup the timer if qrCode changes before the delay completes
}
}, [qrCode]);
return (
<div className={styles.qrisReaderContainer}>
<div className={styles.qrScannerContainer}>
<QrReader
constraints={{ facingMode: "environment" }}
delay={500}
onResult={handleScan}
onError={handleError}
videoId="video"
className={styles.qrReader} // Apply the class
videoContainerStyle={{
width: "100vw",
height: "100vh",
paddingTop: "0px",
}}
videoStyle={{ width: "100%", height: "100%" }}
/>
<div className={styles.focusSquare}></div>
</div>
<div className={styles.inputContainer}>
{/* Manual input form */}
<input
type="text"
value={qrCode}
onChange={handleManualInput}
placeholder="Enter QRIS Code"
/>
</div>
</div>
);
};
export default GuestSideLogin;