This commit is contained in:
zadit
2025-01-13 07:36:24 +07:00
parent 2864a00814
commit ba5c440a2a
7 changed files with 465 additions and 334 deletions

View File

@@ -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

View File

@@ -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'}}>&larr;</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}>

View File

@@ -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;

View File

@@ -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} />

View File

@@ -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",
},
};