ok
This commit is contained in:
@@ -386,7 +386,7 @@ const Header = ({
|
||||
Desain kafe
|
||||
</Child>
|
||||
<Child onClick={() => setModal("edit_tables")}>
|
||||
Daftar meja
|
||||
QR kedai dan meja
|
||||
</Child>
|
||||
<Child onClick={() => setModal("payment_option")}>
|
||||
Metode pembayaran
|
||||
|
||||
@@ -67,6 +67,8 @@ const ItemLister = ({
|
||||
const [itemsToCreate, setItemsToCreate] = useState([]);
|
||||
const [itemsToUpdate, setItemsToUpdate] = useState([]);
|
||||
|
||||
const [isFirstStep, setIsFirstStep] = useState(true);
|
||||
|
||||
const handlePlusClick = (itemId) => {
|
||||
const updatedItems = items.map((item) => {
|
||||
if (item.itemId === itemId) {
|
||||
@@ -483,6 +485,7 @@ const ItemLister = ({
|
||||
setItemsToUpdate([]);
|
||||
setIsEditing(false);
|
||||
if (handleUnEdit) handleUnEdit();
|
||||
setIsFirstStep(true);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -495,6 +498,8 @@ const ItemLister = ({
|
||||
}`}
|
||||
style={{ paddingBottom: isEdit ? "28vh" : "" }}
|
||||
>
|
||||
|
||||
{ (isEdit && isFirstStep || !isEdit) &&
|
||||
<div className={styles["title-container"]}>
|
||||
{isEdit && <ItemType blank={true} imageUrl={previewUrl} />}
|
||||
<input
|
||||
@@ -590,7 +595,9 @@ const ItemLister = ({
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{isEdit && (
|
||||
}
|
||||
{isEdit && isFirstStep && (
|
||||
<>
|
||||
<div className={styles["grid-container"]}>
|
||||
<ItemType
|
||||
rectangular={true}
|
||||
@@ -744,8 +751,13 @@ const ItemLister = ({
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<button onClick={()=>setIsFirstStep(false)}style={{width: '100%', height: '40px', borderRadius: '20px'}}>selanjutnya</button>
|
||||
</>
|
||||
)}
|
||||
|
||||
{ (isEdit && !isFirstStep || !isEdit) &&
|
||||
<>
|
||||
{isEdit && <div style={{display: 'flex', justifyContent: 'space-between'}}><div onClick={()=>setIsFirstStep(true)}style={{color: 'black', fontSize: '50px', width: '30px'}}>←</div>
|
||||
<h3 style={{color: 'black'}}>Daftar item</h3><button style={{visibility: 'hidden', width: '30px'}}></button></div>}
|
||||
<div className={styles["item-list"]}>
|
||||
{user && (
|
||||
user.userId == shopOwnerId || user.cafeId == shopId) &&
|
||||
@@ -943,15 +955,18 @@ const ItemLister = ({
|
||||
user.userId == shopOwnerId &&
|
||||
isEdit && (
|
||||
<>
|
||||
<button
|
||||
{/* <button
|
||||
className={styles["add-item-button"]}
|
||||
onClick={handleRemoveType}
|
||||
>
|
||||
Remove
|
||||
</button>
|
||||
</button> */}
|
||||
</>
|
||||
)}
|
||||
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
{isEdit && (
|
||||
<div className={styles.PaymentOption}>
|
||||
<div className={styles.TotalContainer}>
|
||||
|
||||
@@ -22,11 +22,63 @@
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 10px;
|
||||
/* padding: 10px; */
|
||||
max-height: calc(3 * (25vw - 20px) + 20px);
|
||||
/* max-height: calc(3 * (25vw - 20px) + 20px); */
|
||||
overflow-y: auto;
|
||||
height: calc(49vw - 20px);
|
||||
}
|
||||
|
||||
@media (min-height: 0px) {
|
||||
.grid-container {
|
||||
height: 27vh;
|
||||
}
|
||||
}
|
||||
@media (min-height: 630px) {
|
||||
.grid-container {
|
||||
height: 27vh;
|
||||
}
|
||||
}
|
||||
@media (min-height: 636px) {
|
||||
.grid-container {
|
||||
height: 29vh;
|
||||
}
|
||||
}
|
||||
@media (min-height: 650px) {
|
||||
.grid-container {
|
||||
height: 34vh;
|
||||
}
|
||||
}
|
||||
@media (min-height: 705px) {
|
||||
.grid-container {
|
||||
height: 37vh;
|
||||
}
|
||||
}
|
||||
@media (min-height: 735px) {
|
||||
.grid-container {
|
||||
height: 38vh;
|
||||
}
|
||||
}
|
||||
@media (min-height: 759px) {
|
||||
.grid-container {
|
||||
height: 40vh;
|
||||
}
|
||||
}
|
||||
@media (min-height: 819px) {
|
||||
.grid-container {
|
||||
height: 44vh;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-height: 830px) {
|
||||
.grid-container {
|
||||
height: 47vh;
|
||||
}
|
||||
}
|
||||
@media (min-height: 892px) {
|
||||
.grid-container {
|
||||
height: 49vh;
|
||||
}
|
||||
}
|
||||
|
||||
.title-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -124,7 +176,6 @@
|
||||
font-style: normal;
|
||||
font-size: 1.5em;
|
||||
padding: 10px 0;
|
||||
margin-bottom: 17px;
|
||||
}
|
||||
.OptionContainer {
|
||||
display: flex;
|
||||
@@ -136,7 +187,6 @@
|
||||
font-style: normal;
|
||||
font-size: 0.9em;
|
||||
padding: 10px 0;
|
||||
margin-bottom: 17px;
|
||||
}
|
||||
.PayButton {
|
||||
font-family: "Plus Jakarta Sans", sans-serif;
|
||||
|
||||
@@ -85,7 +85,7 @@ const Modal = ({ user, shop, isOpen, onClose, modalContent, setModal, handleMove
|
||||
{modalContent === "transaction_end" && <Transaction_end />}
|
||||
{modalContent === "transaction_failed" && <Transaction_failed />}
|
||||
{modalContent === "payment_option" && (
|
||||
<PaymentOptions shopId={shop.cafeId} />
|
||||
<PaymentOptions shop={shop} shopId={shop.cafeId} />
|
||||
)}
|
||||
{modalContent === "add_material" && (
|
||||
<MaterialList handleClose={handleOverlayClick} cafeId={shop.cafeId} />
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useState, useRef, useEffect } from "react";
|
||||
import jsQR from "jsqr";
|
||||
import { getImageUrl } from "../helpers/itemHelper";
|
||||
import { getTables, updateTable, createTable } from "../helpers/tableHelper";
|
||||
import {
|
||||
getCafe,
|
||||
saveCafeDetails,
|
||||
@@ -8,158 +9,195 @@ import {
|
||||
} from "../helpers/cafeHelpers";
|
||||
import Switch from "react-switch"; // Import the Switch component
|
||||
|
||||
const SetPaymentQr = ({ shopId }) => {
|
||||
const [qrPosition, setQrPosition] = useState([50, 50]);
|
||||
const [qrSize, setQrSize] = useState(50);
|
||||
const [qrPayment, setQrPayment] = useState();
|
||||
const [qrCodeDetected, setQrCodeDetected] = useState(false);
|
||||
const [isNeedConfirmationState, setIsNeedConfirmationState] = useState(0);
|
||||
const qrPaymentInputRef = useRef(null);
|
||||
const qrCodeContainerRef = useRef(null);
|
||||
const [cafe, setCafe] = useState({});
|
||||
const SetPaymentQr = ({ shop }) => {
|
||||
const [initialPos, setInitialPos] = useState({
|
||||
left: shop.xposition,
|
||||
top: shop.yposition,
|
||||
});
|
||||
const [initialSize, setInitialSize] = useState(shop.scale);
|
||||
const [bgImageUrl, setBgImageUrl] = useState(getImageUrl(shop.qrBackground));
|
||||
const qrBackgroundInputRef = useRef(null);
|
||||
const shopUrl = window.location.hostname + "/" + shop.cafeId;
|
||||
|
||||
const generateQRCodeUrl = (tableCode) => {
|
||||
if (tableCode != null) {
|
||||
return `https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${encodeURIComponent(
|
||||
shopUrl + "/" + tableCode
|
||||
)}`;
|
||||
} else {
|
||||
return `https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${encodeURIComponent(
|
||||
shopUrl
|
||||
)}`;
|
||||
}
|
||||
};
|
||||
|
||||
const [isConfig, setIsConfig] = useState(false);
|
||||
const [isViewTables, setIsViewTables] = useState(false);
|
||||
|
||||
const [tables, setTables] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchCafe = async () => {
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
const response = await getCafe(shopId);
|
||||
setCafe(response);
|
||||
setQrPayment(getImageUrl(response.qrPayment));
|
||||
console.log(response.needsConfirmation);
|
||||
|
||||
setIsNeedConfirmationState(response.needsConfirmation === true ? 1 : 0); // Set state based on fetched data
|
||||
setQrPosition([response.xposition, response.yposition]);
|
||||
setQrSize(response.scale);
|
||||
console.log(shop);
|
||||
const fetchedTables = await getTables(shop.cafeId);
|
||||
setTables(fetchedTables);
|
||||
} catch (error) {
|
||||
console.error("Error fetching cafe:", error);
|
||||
console.error("Error fetching tables:", error);
|
||||
}
|
||||
};
|
||||
fetchCafe();
|
||||
}, [shopId]);
|
||||
|
||||
// Detect QR code when qrPayment updates
|
||||
useEffect(() => {
|
||||
if (qrPayment) {
|
||||
detectQRCodeFromContainer();
|
||||
}
|
||||
}, [qrPayment]);
|
||||
fetchData();
|
||||
}, [shop.cafeId]);
|
||||
|
||||
const handleSave = () => {
|
||||
const qrBackgroundFile = qrBackgroundInputRef.current.files[0]; // Get the selected file for qrBackground
|
||||
|
||||
// Prepare the details object
|
||||
const details = {
|
||||
qrSize: initialSize,
|
||||
qrPosition: initialPos,
|
||||
qrBackgroundFile, // Include qrBackground file
|
||||
};
|
||||
|
||||
// Call saveCafeDetails function with the updated details object
|
||||
saveCafeDetails(shop.cafeId, details)
|
||||
.then((response) => {
|
||||
console.log("Cafe details saved:", response);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error saving cafe details:", error);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
const handlePositionChange = (e) => {
|
||||
const { name, value } = e.target;
|
||||
setInitialPos((prevPosition) => ({
|
||||
...prevPosition,
|
||||
[name]: parseFloat(value).toFixed(2),
|
||||
}));
|
||||
};
|
||||
|
||||
const handleSizeChange = (e) => {
|
||||
setInitialSize(parseFloat(e.target.value).toFixed(2));
|
||||
};
|
||||
|
||||
|
||||
// Handle file input change
|
||||
const handleFileChange = (e) => {
|
||||
const file = e.target.files[0];
|
||||
if (file) {
|
||||
const newqrPayment = URL.createObjectURL(file);
|
||||
setQrPayment(newqrPayment);
|
||||
const newBgImage = URL.createObjectURL(file); // Create a temporary URL for display
|
||||
setBgImageUrl(newBgImage);
|
||||
}
|
||||
};
|
||||
|
||||
// Detect QR code from the container
|
||||
const detectQRCodeFromContainer = () => {
|
||||
const container = qrCodeContainerRef.current;
|
||||
const canvas = document.createElement("canvas");
|
||||
const context = canvas.getContext("2d");
|
||||
const img = new Image();
|
||||
img.crossOrigin = "Anonymous";
|
||||
img.onload = () => {
|
||||
canvas.width = container.offsetWidth;
|
||||
canvas.height = container.offsetHeight;
|
||||
context.drawImage(img, 0, 0, canvas.width, canvas.height);
|
||||
const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
|
||||
const qrCode = jsQR(imageData.data, canvas.width, canvas.height);
|
||||
setQrCodeDetected(!!qrCode);
|
||||
if (qrCode) {
|
||||
console.log("QR Code detected:", qrCode.data);
|
||||
}
|
||||
};
|
||||
img.src = qrPayment;
|
||||
};
|
||||
|
||||
// Save cafe details
|
||||
const handleSave = async () => {
|
||||
const qrPaymentFile = qrPaymentInputRef.current.files[0];
|
||||
const details = {
|
||||
qrPosition,
|
||||
qrSize,
|
||||
qrPaymentFile,
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await saveCafeDetails(cafe.cafeId, details);
|
||||
console.log("Cafe details saved:", response);
|
||||
} catch (error) {
|
||||
console.error("Error saving cafe details:", error);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await setConfirmationStatus(cafe.cafeId, isNeedConfirmationState === 1);
|
||||
setIsNeedConfirmationState(response.needsConfirmation ? 1 : 0); // Update state after saving
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
setIsNeedConfirmationState(cafe.needsConfirmation ? 1 : 0); // Fallback to initial value
|
||||
}
|
||||
};
|
||||
|
||||
// Handle Switch toggle
|
||||
const handleChange = (checked) => {
|
||||
setIsNeedConfirmationState(checked ? 1 : 0); // Toggle state based on the switch
|
||||
};
|
||||
|
||||
const handleError = () => {
|
||||
setQrPayment('https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSPsNr0TPq8dHT3nBwDQ6OHQQTrqzVFoeBOmuWfgyErrLbJi6f6CnnYhpNHEvkJ_2X-kyI&usqp=CAU'); // Set your fallback image here
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={styles.container}>
|
||||
<h3 style={styles.title}>Konfigurasi pembayaran</h3>
|
||||
|
||||
<div
|
||||
id="qr-code-container"
|
||||
ref={qrCodeContainerRef}
|
||||
onClick={() => qrPaymentInputRef.current.click()}
|
||||
style={{
|
||||
background: `center center / contain no-repeat url(${bgImageUrl})`,
|
||||
backgroundSize: "contain",
|
||||
border: "1px solid #ddd",
|
||||
|
||||
...styles.qrCodeContainer,
|
||||
backgroundImage: `url(${qrPayment})`,
|
||||
backgroundImage: `url(${bgImageUrl})`,
|
||||
backgroundPosition: "center",
|
||||
backgroundRepeat: "no-repeat",
|
||||
backgroundSize: "contain",
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={qrPayment}
|
||||
onError={handleError}
|
||||
alt="QR Payment"
|
||||
style={{ maxWidth: '100%',visibility: 'hidden' }} // hide the img element
|
||||
/>
|
||||
src={generateQRCodeUrl(shopUrl)}
|
||||
alt="QR Code"
|
||||
style={{
|
||||
position: "absolute",
|
||||
width: `${initialSize}%`,
|
||||
left: `${initialPos.left}%`,
|
||||
top: `${initialPos.top}%`,
|
||||
transform: "translate(-50%, -50%)",
|
||||
}} />
|
||||
<input
|
||||
type="file"
|
||||
accept="image/*"
|
||||
ref={qrPaymentInputRef}
|
||||
ref={qrBackgroundInputRef}
|
||||
style={{ display: "none" }}
|
||||
onChange={handleFileChange}
|
||||
/>
|
||||
</div>
|
||||
<div style={styles.uploadMessage}>
|
||||
<p>Klik untuk unggah QRIS</p>
|
||||
<p>Klik untuk ganti background</p>
|
||||
</div>
|
||||
<div style={styles.resultMessage}>
|
||||
{qrCodeDetected && qrPayment !== 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSPsNr0TPq8dHT3nBwDQ6OHQQTrqzVFoeBOmuWfgyErrLbJi6f6CnnYhpNHEvkJ_2X-kyI&usqp=CAU' ? <p>QR terdeteksi</p> : <p>Tidak ada qr terdeteksi</p>}
|
||||
{qrCodeDetected && qrPayment !== 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSPsNr0TPq8dHT3nBwDQ6OHQQTrqzVFoeBOmuWfgyErrLbJi6f6CnnYhpNHEvkJ_2X-kyI&usqp=CAU' ? <div
|
||||
onClick={() => qrPaymentInputRef.current.click()} style={styles.uploadButton}>Ganti</div> : <div
|
||||
onClick={() => qrPaymentInputRef.current.click()} style={styles.uploadButton}>Unggah</div>}
|
||||
<p onClick={() => setIsConfig(!isConfig)}> {isConfig ? '˅' : '˃'} Konfigurasi QR</p>
|
||||
<div
|
||||
onClick={() => qrBackgroundInputRef.current.click()} style={styles.uploadButton}>Ganti</div>
|
||||
</div>
|
||||
<div style={styles.switchContainer}>
|
||||
<h3>Pengecekan ketersediaan ganda</h3>
|
||||
<p style={styles.description}>
|
||||
Nyalakan agar kasir memeriksa kembali ketersediaan produk sebelum pelanggan membayar.
|
||||
{isConfig && <>
|
||||
<div style={styles.sliderContainer}>
|
||||
<label style={styles.label}>
|
||||
QR Code Size:
|
||||
<div style={styles.sliderWrapper}>
|
||||
<span style={styles.labelStart}>10%</span>
|
||||
<input
|
||||
type="range"
|
||||
step="0.25"
|
||||
min="10"
|
||||
max="100"
|
||||
value={initialSize}
|
||||
onChange={handleSizeChange}
|
||||
style={styles.input}
|
||||
/>
|
||||
<span style={styles.value}>{initialSize}%</span>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<div style={styles.sliderContainer}>
|
||||
<label style={styles.label}>
|
||||
QR Code Position X:
|
||||
<div style={styles.sliderWrapper}>
|
||||
<span style={styles.labelStart}>0%</span>
|
||||
<input
|
||||
type="range"
|
||||
step="0.25"
|
||||
name="left"
|
||||
min="0"
|
||||
max="100"
|
||||
value={initialPos.left}
|
||||
onChange={handlePositionChange}
|
||||
style={styles.input}
|
||||
/>
|
||||
<span style={styles.value}>{initialPos.left}%</span>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<div style={styles.sliderContainer}>
|
||||
<label style={styles.label}>
|
||||
QR Code Position Y:
|
||||
<div style={styles.sliderWrapper}>
|
||||
<span style={styles.labelStart}>0%</span>
|
||||
<input
|
||||
type="range"
|
||||
step="0.25"
|
||||
name="top"
|
||||
min="0"
|
||||
max="100"
|
||||
value={initialPos.top}
|
||||
onChange={handlePositionChange}
|
||||
style={styles.input}
|
||||
/>
|
||||
<span style={styles.value}>{initialPos.top}%</span>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</>}
|
||||
<p style={styles.description} onClick={() => setIsViewTables(!isViewTables)}>
|
||||
{isViewTables ? '˅' : '˃'} Daftar meja
|
||||
</p>
|
||||
<Switch
|
||||
onChange={handleChange}
|
||||
checked={isNeedConfirmationState === 1} // Convert to boolean
|
||||
offColor="#888"
|
||||
onColor="#4CAF50"
|
||||
uncheckedIcon={false}
|
||||
checkedIcon={false}
|
||||
height={25}
|
||||
width={50}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div style={styles.buttonContainer}>
|
||||
@@ -174,6 +212,7 @@ const SetPaymentQr = ({ shopId }) => {
|
||||
// Styles
|
||||
const styles = {
|
||||
container: {
|
||||
width: '100%',
|
||||
backgroundColor: "white",
|
||||
padding: "20px",
|
||||
borderRadius: "8px",
|
||||
@@ -230,13 +269,25 @@ const styles = {
|
||||
transition: "background-color 0.3s",
|
||||
},
|
||||
switchContainer: {
|
||||
marginTop: "20px",
|
||||
textAlign: "left",
|
||||
},
|
||||
description: {
|
||||
margin: "10px 0",
|
||||
fontSize: "14px",
|
||||
color: "#666",
|
||||
},
|
||||
sliderContainer: {
|
||||
marginBottom: "20px",
|
||||
},
|
||||
label: {
|
||||
display: "block",
|
||||
marginBottom: "10px",
|
||||
},
|
||||
sliderWrapper: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
},
|
||||
input: {
|
||||
flex: "1",
|
||||
margin: "0 10px",
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -19,8 +19,10 @@ import {
|
||||
|
||||
import Switch from "react-switch"; // Import the Switch component
|
||||
import Carousel from '../components/Carousel'
|
||||
import styles from './MaterialList.module.css'; // Import the CSS Module
|
||||
|
||||
const SetPaymentQr = ({ cafeId }) => {
|
||||
// All your state and logic goes here (unchanged)
|
||||
const [materials, setMaterials] = useState([]);
|
||||
const [mutations, setMutations] = useState([]);
|
||||
const [newMaterialName, setNewMaterialName] = useState("");
|
||||
@@ -34,6 +36,7 @@ const SetPaymentQr = ({ cafeId }) => {
|
||||
const [latestMutation, setLatestMutation] = useState([]);
|
||||
const [currentQuantity, setCurrentQuantity] = useState(-1);
|
||||
const [currentPrice, setCurrentPrice] = useState(0);
|
||||
const [newPrice, setNewPrice] = useState(0);
|
||||
const [quantityChange, setQuantityChange] = useState(0);
|
||||
const [sortOrder, setSortOrder] = useState("desc");
|
||||
const [isEditCurrentPrice, setIsEditCurrentPrice] = useState(false);
|
||||
@@ -53,7 +56,7 @@ const SetPaymentQr = ({ cafeId }) => {
|
||||
|
||||
const handleChange = (e) => {
|
||||
const formattedValue = formatCurrency(e.target.value);
|
||||
setCurrentPrice(formattedValue);
|
||||
setNewPrice(formattedValue);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@@ -87,11 +90,7 @@ const SetPaymentQr = ({ cafeId }) => {
|
||||
fetchMutations();
|
||||
}, [cafeId]);
|
||||
|
||||
const handleSortChange = (e) => {
|
||||
setSortOrder(e.target.value);
|
||||
};
|
||||
|
||||
const filteredMutations = mutations.filter((mutation) => mutation.materialId === materials[selectedMaterialIndex].materialId) || [];
|
||||
const filteredMutations = mutations.filter((mutation) => mutation.materialId === materials[selectedMaterialIndex]?.materialId) || [];
|
||||
|
||||
const sortedMutations = filteredMutations
|
||||
.filter((mutation) => mutation.materialId === materials[selectedMaterialIndex].materialId)
|
||||
@@ -156,32 +155,11 @@ const SetPaymentQr = ({ cafeId }) => {
|
||||
}
|
||||
};
|
||||
|
||||
const handlePrevious = () => {
|
||||
if (selectedMaterialIndex) {
|
||||
setQuantityChange(0);
|
||||
const currentIndex = materials.findIndex(
|
||||
(material) => material.materialId === selectedMaterialIndex
|
||||
);
|
||||
if (currentIndex > 0) {
|
||||
setSelectedMaterialIndex(materials[currentIndex - 1].materialId);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleNext = () => {
|
||||
if (selectedMaterialIndex) {
|
||||
setQuantityChange(0);
|
||||
const currentIndex = materials.findIndex(
|
||||
(material) => material.materialId === selectedMaterialIndex
|
||||
);
|
||||
if (currentIndex < materials.length - 1) {
|
||||
setSelectedMaterialIndex(materials[currentIndex + 1].materialId);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleQuantityChange = (change) => {
|
||||
setQuantityChange((prev) => prev + change);
|
||||
if (quantityChange + change < 1) setNewPrice(currentPrice);
|
||||
|
||||
setIsEditCurrentPrice(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@@ -202,24 +180,27 @@ const SetPaymentQr = ({ cafeId }) => {
|
||||
setLatestMutation(latestMutation);
|
||||
setCurrentQuantity(latestMutation.newStock);
|
||||
setCurrentPrice(formatCurrency(latestMutation.priceAtp));
|
||||
setNewPrice(formatCurrency(latestMutation.priceAtp));
|
||||
} else {
|
||||
setCurrentQuantity(0); // Default value if no mutations exist
|
||||
setLatestMutation({ newStock: 0 });
|
||||
setCurrentPrice(0);
|
||||
setNewPrice(0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
setIsViewingHistory(false);
|
||||
}, [materials, mutations, selectedMaterialIndex]);
|
||||
|
||||
const handleUpdateStock = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const newPrice = convertToInteger(currentPrice)
|
||||
const newprice = convertToInteger(newPrice)
|
||||
const newStock = currentQuantity + quantityChange;
|
||||
const formData = new FormData();
|
||||
formData.append("newStock", newStock);
|
||||
formData.append("priceAtp", newPrice);
|
||||
formData.append("priceAtp", newprice);
|
||||
formData.append("reason", "Stock update");
|
||||
|
||||
await createMaterialMutation(materials[selectedMaterialIndex].materialId, formData);
|
||||
@@ -244,103 +225,106 @@ const SetPaymentQr = ({ cafeId }) => {
|
||||
return date.toLocaleString();
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<div style={styles.container}>
|
||||
{loading ?
|
||||
<div className={styles.container}>
|
||||
{loading ? (
|
||||
<></>
|
||||
) : (
|
||||
<>
|
||||
</>
|
||||
:
|
||||
<>
|
||||
<h3 style={styles.title}>Bahan baku</h3>
|
||||
<h3 className={styles.title}>Bahan baku</h3>
|
||||
<Carousel items={materials} onSelect={(e) => setSelectedMaterialIndex(e)} selectedIndex={selectedMaterialIndex} />
|
||||
{selectedMaterialIndex != -1 ?
|
||||
{selectedMaterialIndex !== -1 ? (
|
||||
<>
|
||||
<div style={styles.switchContainer}>
|
||||
<div className={styles.switchContainer}>
|
||||
<h3>Stok sekarang {currentQuantity}</h3>
|
||||
</div>
|
||||
|
||||
<div style={styles.stokContainer}>
|
||||
<button onClick={() => handleQuantityChange(currentQuantity + quantityChange > 0 ? -1 : 0)} style={styles.stockButton}>
|
||||
<div className={styles.stokContainer}>
|
||||
<button onClick={() => handleQuantityChange(currentQuantity + quantityChange > 0 ? -1 : 0)} className={styles.stockButton}>
|
||||
-
|
||||
</button>
|
||||
<p>{currentQuantity + quantityChange}</p>
|
||||
<button onClick={() => handleQuantityChange(1)} style={styles.stockButton}>
|
||||
<button onClick={() => handleQuantityChange(1)} className={styles.stockButton}>
|
||||
+
|
||||
</button>
|
||||
</div>
|
||||
<div style={styles.uploadMessage}>
|
||||
<div className={styles.uploadMessage}>
|
||||
<p>harga per {materials && materials[selectedMaterialIndex]?.unit} sekarang</p>
|
||||
</div>
|
||||
<div style={styles.resultMessage}>
|
||||
|
||||
<div className={styles.resultMessage}>
|
||||
<input
|
||||
|
||||
style={{
|
||||
width: "200px",
|
||||
border: isEditCurrentPrice ? "1px solid #ccc" : "1px solid transparent",
|
||||
backgroundColor: isEditCurrentPrice ? "white" : "transparent",
|
||||
}}
|
||||
className={styles.resultMessageInput} // Replace inline style with CSS module class
|
||||
disabled={!isEditCurrentPrice || quantityChange < 1}
|
||||
value={currentPrice}
|
||||
value={newPrice}
|
||||
onChange={handleChange}
|
||||
placeholder="Enter amount"
|
||||
/>
|
||||
<div onClick={() => quantityChange < 1 ? null : setIsEditCurrentPrice(!isEditCurrentPrice)} style={quantityChange < 1 ? styles.changeButtonDisabled : styles.changeButtonEnabled}>{isEditCurrentPrice ? 'Terapkan' : 'Ganti'}</div>
|
||||
<div onClick={() => quantityChange < 1 ? null : setIsEditCurrentPrice(!isEditCurrentPrice)} className={quantityChange < 1 ? styles.changeButtonDisabled : styles.changeButtonEnabled}>
|
||||
{isEditCurrentPrice ? 'Terapkan' : 'Ganti'}
|
||||
</div>
|
||||
</div>
|
||||
<div style={styles.buttonContainer}>
|
||||
<button onClick={handleUpdateStock} style={styles.saveButton}>
|
||||
<div className={styles.buttonContainer}>
|
||||
<button onClick={handleUpdateStock} className={styles.saveButton}>
|
||||
Laporkan {quantityChange > 0 ? 'penambahan' : 'stok sekarang'} {quantityChange < 1 ? currentQuantity + quantityChange : quantityChange} {materials[selectedMaterialIndex]?.unit}
|
||||
</button>
|
||||
</div>
|
||||
<div onClick={() => setIsViewingHistory(!isViewingHistory)} style={styles.historyTab}>
|
||||
<h3> {isViewingHistory ? '˅' : '˃'} Riwayat stok</h3>
|
||||
<div style={styles.historyContainer}>
|
||||
{selectedMaterialIndex != -1 && isViewingHistory && !loading && (
|
||||
<div style={styles.mutationContainer}>
|
||||
{sortedMutations.length > 0 ? (
|
||||
sortedMutations.map((mutation) => (
|
||||
<div key={mutation.id} style={styles.mutationCard}>
|
||||
<h4 style={styles.mutationTitle}>
|
||||
{formatDate(mutation.createdAt)}
|
||||
</h4>
|
||||
<p>Details: {mutation.reason}</p>
|
||||
<p>stok {mutation.newStock}</p>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<p>No mutations available.</p>
|
||||
)}
|
||||
<div className={styles.historyTab}>
|
||||
<h3 onClick={() => setIsViewingHistory(!isViewingHistory)}> {isViewingHistory ? '˅' : '˃'} Riwayat stok</h3>
|
||||
{selectedMaterialIndex !== -1 && isViewingHistory && !loading && (
|
||||
<>
|
||||
<div className={styles.sorter} onClick={() => setSortOrder(sortOrder == 'asc' ? 'desc' : 'asc')}>
|
||||
Urutkan: {sortOrder === 'asc' ? "terlama" : "terbaru"} <div style={{ transform: 'rotate(90deg)' }}><></div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.historyContainer}>
|
||||
<div className={styles.mutationContainer}>
|
||||
{sortedMutations.length > 0 ? (
|
||||
sortedMutations.map((mutation) => (
|
||||
<div key={mutation.id} className={styles.mutationCard}>
|
||||
<div style={{ width: '42px', backgroundColor: '#b9b9b9', borderRadius: '10px', padding: '3px', paddingBottom: '0' }}>
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="SVGRepo_bgCarrier" stroke-width="0"></g>
|
||||
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g>
|
||||
<g id="SVGRepo_iconCarrier">
|
||||
<g id="Interface / Book">
|
||||
<path id="Vector" d="M5 19.5002V6.2002C5 5.08009 5 4.51962 5.21799 4.0918C5.40973 3.71547 5.71547 3.40973 6.0918 3.21799C6.51962 3 7.08009 3 8.2002 3H17.4002C17.9602 3 18.2407 3 18.4546 3.10899C18.6427 3.20487 18.7948 3.35774 18.8906 3.5459C18.9996 3.75981 19 4.04005 19 4.6001V16.4001C19 16.9601 18.9996 17.2398 18.8906 17.4537C18.7948 17.6419 18.6429 17.7952 18.4548 17.8911C18.2411 18 17.961 18 17.402 18H7.25C6.00736 18 5 19.0074 5 20.25C5 20.6642 5.33579 21 5.75 21H16.402C16.961 21 17.2411 21 17.4548 20.8911C17.6429 20.7952 17.7948 20.642 17.8906 20.4538C17.9996 20.2399 18 19.9601 18 19.4V18" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
<div className={styles.mutationTitle}>
|
||||
<h4>{formatDate(mutation.createdAt)}</h4>
|
||||
<p>Total stok: {mutation.newStock} || +{mutation.newStock - mutation.oldStock} || {formatCurrency((mutation.newStock - mutation.oldStock) * mutation.priceAtp)}</p>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<p>Tidak ada laporan perubahan stok.</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</> :
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div
|
||||
style={{ display: "flex", alignItems: "center", margin: "10px", marginTop: '17px', marginBottom: '34px' }}
|
||||
>
|
||||
<div className={styles.description}>
|
||||
<div style={{ marginRight: "5px", fontSize: "1.2em" }}>ⓘ</div>
|
||||
<h6 style={{ margin: 0, textAlign: "left" }}>
|
||||
<h6 style={{ margin: 0, textAlign: "left", fontSize: '12px' }}>
|
||||
Fitur ini mempermudah mengelola biaya dan memantau pengeluaran bahan.
|
||||
</h6>
|
||||
</div>
|
||||
|
||||
<div style={styles.switchContainer}>
|
||||
<div className={styles.switchContainer}>
|
||||
<h3>Buat bahan baru</h3>
|
||||
</div>
|
||||
<div style={styles.resultMessage}>
|
||||
|
||||
<div className={styles.resultMessage}>
|
||||
<input
|
||||
|
||||
style={{
|
||||
width: "100%",
|
||||
height: '31px',
|
||||
border: "1px solid #ccc"
|
||||
}}
|
||||
className={styles.resultMessageInput}
|
||||
value={newMaterialName}
|
||||
onChange={(event) => setNewMaterialName(event.target.value)}
|
||||
placeholder="Masukkan nama barang"
|
||||
style={{width: '100%', height: '31px'}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -348,7 +332,8 @@ const SetPaymentQr = ({ cafeId }) => {
|
||||
id="materialUnit"
|
||||
value={newMaterialUnit}
|
||||
onChange={(e) => setNewMaterialUnit(e.target.value)}
|
||||
style={styles.unit}
|
||||
className={styles.unit}
|
||||
style={{height: '37px'}}
|
||||
>
|
||||
<option value="gram">Satuan: gram</option>
|
||||
<option value="ons">Satuan: ons</option>
|
||||
@@ -362,131 +347,17 @@ const SetPaymentQr = ({ cafeId }) => {
|
||||
<option value="box">Satuan: box</option>
|
||||
</select>
|
||||
|
||||
<div style={styles.buttonContainer}>
|
||||
<button style={styles.saveButton}>
|
||||
<div className={styles.buttonContainer}>
|
||||
<button className={styles.saveButton} onClick={handleCreateMaterial}>
|
||||
Buat bahan baku
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
)}
|
||||
</>
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Styles
|
||||
const styles = {
|
||||
container: {
|
||||
width: '100%',
|
||||
minHeight: '50vh',
|
||||
backgroundColor: "white",
|
||||
padding: "20px",
|
||||
borderRadius: "8px",
|
||||
boxShadow: "0 2px 10px rgba(0, 0, 0, 0.1)",
|
||||
textAlign: "center", // Center text and children
|
||||
},
|
||||
title: {
|
||||
marginBottom: "20px",
|
||||
fontWeight: "bold",
|
||||
},
|
||||
qrCodeContainer: {
|
||||
backgroundColor: '#999999',
|
||||
borderRadius: '20px',
|
||||
position: "relative",
|
||||
width: "100%",
|
||||
height: "200px",
|
||||
backgroundSize: "contain",
|
||||
overflow: "hidden",
|
||||
margin: "0 auto", // Center the QR code container
|
||||
},
|
||||
uploadMessage: {
|
||||
fontWeight: 600,
|
||||
textAlign: "left",
|
||||
},
|
||||
changeButtonEnabled: {
|
||||
paddingRight: '10px',
|
||||
backgroundColor: 'green',
|
||||
borderRadius: '30px',
|
||||
color: 'white',
|
||||
fontWeight: 700,
|
||||
height: '36px',
|
||||
lineHeight: '36px',
|
||||
paddingLeft: '10px',
|
||||
paddingHeight: '10px',
|
||||
},
|
||||
changeButtonDisabled: {
|
||||
paddingRight: '10px',
|
||||
backgroundColor: '#a1a1a1',
|
||||
borderRadius: '30px',
|
||||
color: 'white',
|
||||
fontWeight: 700,
|
||||
height: '36px',
|
||||
lineHeight: '36px',
|
||||
paddingLeft: '10px',
|
||||
paddingHeight: '10px',
|
||||
},
|
||||
resultMessage: {
|
||||
marginTop: "-13px",
|
||||
textAlign: "left",
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between'
|
||||
},
|
||||
stokContainer: {
|
||||
display: 'flex',
|
||||
justifyContent: 'space-evenly',
|
||||
alignItems: 'center',
|
||||
marginTop: '18px',
|
||||
marginBottom: "6px",
|
||||
textAlign: "left",
|
||||
},
|
||||
buttonContainer: {
|
||||
marginTop: "11px",
|
||||
textAlign: "left",
|
||||
},
|
||||
stockButton: {
|
||||
padding: "10px 20px",
|
||||
fontSize: "3.5vw",
|
||||
backgroundColor: "#28a745",
|
||||
color: "#fff",
|
||||
border: "none",
|
||||
borderRadius: "30px",
|
||||
cursor: "pointer",
|
||||
transition: "background-color 0.3s",
|
||||
},
|
||||
saveButton: {
|
||||
width: '100%',
|
||||
padding: "10px 20px",
|
||||
fontSize: "3.5vw",
|
||||
backgroundColor: "#28a745",
|
||||
color: "#fff",
|
||||
border: "none",
|
||||
borderRadius: "30px",
|
||||
cursor: "pointer",
|
||||
transition: "background-color 0.3s",
|
||||
},
|
||||
switchContainer: {
|
||||
marginTop: "20px",
|
||||
textAlign: "left",
|
||||
},
|
||||
historyTab: {
|
||||
textAlign: "left",
|
||||
},
|
||||
historyContainer: {
|
||||
textAlign: "left",
|
||||
maxHeight: '200px',
|
||||
overflowY: 'auto'
|
||||
},
|
||||
description: {
|
||||
margin: "10px 0",
|
||||
fontSize: "14px",
|
||||
color: "#666",
|
||||
},
|
||||
unit: {
|
||||
marginTop: '11px',
|
||||
width: '100%',
|
||||
height: '31px',
|
||||
},
|
||||
};
|
||||
|
||||
export default SetPaymentQr;
|
||||
|
||||
144
src/pages/MaterialList.module.css
Normal file
144
src/pages/MaterialList.module.css
Normal file
@@ -0,0 +1,144 @@
|
||||
/* SetPaymentQr.module.css */
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
min-height: 47vh;
|
||||
background-color: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-bottom: 20px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.uploadMessage {
|
||||
font-weight: 600;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.changeButtonEnabled {
|
||||
padding-right: 10px;
|
||||
background-color: green;
|
||||
border-radius: 30px;
|
||||
color: white;
|
||||
font-weight: 700;
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
padding-left: 10px;
|
||||
padding-height: 10px;
|
||||
}
|
||||
|
||||
.changeButtonDisabled {
|
||||
padding-right: 10px;
|
||||
background-color: #a1a1a1;
|
||||
border-radius: 30px;
|
||||
color: white;
|
||||
font-weight: 700;
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
padding-left: 10px;
|
||||
padding-height: 10px;
|
||||
}
|
||||
|
||||
.resultMessage {
|
||||
margin-top: -13px;
|
||||
text-align: left;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.stokContainer {
|
||||
display: flex;
|
||||
justify-content: space-evenly;
|
||||
align-items: center;
|
||||
margin-top: -20px;
|
||||
margin-bottom: -15px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.buttonContainer {
|
||||
margin-top: 11px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.stockButton {
|
||||
padding: 10px 20px;
|
||||
font-size: 3.5vw;
|
||||
background-color: #28a745;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 30px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.saveButton {
|
||||
width: 100%;
|
||||
padding: 10px 20px;
|
||||
font-size: 3.5vw;
|
||||
background-color: #28a745;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 30px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.switchContainer {
|
||||
margin-top: 20px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.historyTab {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.historyContainer {
|
||||
text-align: left;
|
||||
max-height: 15vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.description {
|
||||
display: flex;
|
||||
margin: 10px 0;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.unit {
|
||||
margin-top: 11px;
|
||||
width: 100%;
|
||||
height: 31px;
|
||||
}
|
||||
|
||||
.sorter {
|
||||
border: 1px solid #c3c3c3;
|
||||
padding: 5px;
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: -10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.mutationCard {
|
||||
display: flex;
|
||||
margin-bottom: 7px;
|
||||
margin-top: 7px;
|
||||
}
|
||||
|
||||
.mutationTitle {
|
||||
margin-left: 5px;
|
||||
}
|
||||
.mutationTitle h4 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.mutationTitle p {
|
||||
margin: 0;
|
||||
}
|
||||
Reference in New Issue
Block a user