ok
This commit is contained in:
@@ -96,6 +96,7 @@ function App() {
|
||||
if (response.status === 200) {
|
||||
setShop(cafe);
|
||||
setShopItems(data);
|
||||
console.log(data)
|
||||
// Filter out unavailable items
|
||||
const filteredData = data
|
||||
.map((itemType) => ({
|
||||
@@ -424,6 +425,7 @@ function App() {
|
||||
shopName={shop.name}
|
||||
shopOwnerId={shop.ownerId}
|
||||
shopItems={shopItems}
|
||||
setShopItems={setShopItems}
|
||||
shopClerks={shopClerks}
|
||||
socket={socket}
|
||||
user={user}
|
||||
|
||||
@@ -63,14 +63,14 @@ export default function Footer({
|
||||
</div>
|
||||
|
||||
{/* Search Icon */}
|
||||
<div onClick={goToSearch} className={styles["footer-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>
|
||||
</div> */}
|
||||
|
||||
{/* Cart Icon */}
|
||||
<div onClick={goToCart} className={styles["footer-icon"]}>
|
||||
|
||||
@@ -330,11 +330,11 @@ const Header = ({
|
||||
)}
|
||||
{user.username !== undefined && (
|
||||
<Child onClick={() => setModal("edit_account")}>
|
||||
Edit profile
|
||||
Ubah profil
|
||||
</Child>
|
||||
)}
|
||||
{shopId && user.roleId == 1 && (
|
||||
<Child onClick={goToAdminCafes}>see your {user.userId == shopOwnerId ? 'other' : ''} cafes</Child>)}
|
||||
<Child onClick={goToAdminCafes}>Dashboard</Child>)}
|
||||
{shopId &&
|
||||
user.userId == shopOwnerId &&
|
||||
user.username !== undefined &&
|
||||
@@ -349,7 +349,7 @@ const Header = ({
|
||||
|
||||
<div class="toggle-switch">
|
||||
<label class="toggle-switch-label" for="toggleSwitch">
|
||||
Edit Mode
|
||||
Mode edit
|
||||
</label>
|
||||
<Switch
|
||||
checked={isEditMode}
|
||||
@@ -358,18 +358,18 @@ const Header = ({
|
||||
</div>
|
||||
|
||||
<Child onClick={() => setModal("welcome_config")}>
|
||||
welcoming page
|
||||
Halaman sambutan
|
||||
</Child>
|
||||
<Child onClick={() => setModal("add_material")}>
|
||||
stock
|
||||
Stok
|
||||
</Child>
|
||||
<Child onClick={() => setModal("edit_tables")}>
|
||||
table maps
|
||||
Denah meja
|
||||
</Child>
|
||||
<Child hasChildren>
|
||||
clerks
|
||||
Kasir
|
||||
<Child onClick={() => setModal("create_clerk")}>
|
||||
+ Add clerk
|
||||
+ Tambah
|
||||
</Child>
|
||||
{shopClerks &&
|
||||
shopClerks.map((key, index) => (
|
||||
@@ -388,10 +388,10 @@ const Header = ({
|
||||
))}
|
||||
</Child>
|
||||
<Child onClick={() => setModal("payment_option")}>
|
||||
payment options
|
||||
Opsi pembayaran
|
||||
</Child>
|
||||
|
||||
<Child onClick={() => setModal("reports")}>reports</Child>
|
||||
<Child onClick={() => setModal("reports")}>Laporan</Child>
|
||||
</Child>
|
||||
</>
|
||||
)}
|
||||
@@ -403,7 +403,7 @@ const Header = ({
|
||||
|
||||
<div class="toggle-switch">
|
||||
<label class="toggle-switch-label" for="toggleSwitch">
|
||||
Edit Mode
|
||||
Mode edit
|
||||
</label>
|
||||
<Switch
|
||||
checked={isEditMode}
|
||||
@@ -411,15 +411,15 @@ const Header = ({
|
||||
/>
|
||||
</div>
|
||||
<Child onClick={() => setModal("add_material")}>
|
||||
stock
|
||||
stok
|
||||
</Child>
|
||||
{user.username !== undefined &&
|
||||
user.roleId == 2 &&
|
||||
user.cafeId == shopId && (
|
||||
<Child hasChildren>
|
||||
connected guest sides
|
||||
Tablet tamu
|
||||
<Child onClick={goToGuestSideLogin}>
|
||||
+ Add guest side
|
||||
+ Tambah
|
||||
</Child>
|
||||
{guestSides &&
|
||||
guestSides.map((key, index) => (
|
||||
@@ -439,7 +439,7 @@ const Header = ({
|
||||
</Child>
|
||||
)}
|
||||
|
||||
<Child onClick={() => setModal("reports")}>reports</Child>
|
||||
<Child onClick={() => setModal("reports")}>Laporan</Child>
|
||||
</Child>
|
||||
)}
|
||||
{user.username !== undefined && (
|
||||
|
||||
@@ -35,8 +35,8 @@
|
||||
}
|
||||
|
||||
.itemImage {
|
||||
width: 139px;
|
||||
height: 149px;
|
||||
width: 119px;
|
||||
height: 129px;
|
||||
border-radius: 10px;
|
||||
margin-right: 10px;
|
||||
object-fit: cover;
|
||||
@@ -45,8 +45,8 @@
|
||||
|
||||
.imageContainer {
|
||||
position: relative;
|
||||
width: 139px;
|
||||
height: 149px;
|
||||
width: 119px;
|
||||
height: 129px;
|
||||
}
|
||||
|
||||
.overlay {
|
||||
@@ -124,7 +124,7 @@
|
||||
font-weight: 600;
|
||||
width: calc(100% - 15px); /* Adjust the width to prevent overflow */
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: 35px;
|
||||
/* margin-bottom: 35px; */
|
||||
margin-left: 5px;
|
||||
color: #d9c61c;
|
||||
background-color: transparent;
|
||||
@@ -143,19 +143,19 @@
|
||||
|
||||
.itemQty {
|
||||
display: flex;
|
||||
align-items: end;
|
||||
align-items: center;
|
||||
font-size: 0.9rem;
|
||||
margin-left: 5px;
|
||||
color: rgb(115, 165, 133);
|
||||
fill: rgb(115, 165, 133);
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.itemQtyValue {
|
||||
margin-bottom: 8px;
|
||||
font-family: "Poppins", sans-serif;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
margin-top: 5px;
|
||||
margin-top: 19px;
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
width: 25px;
|
||||
@@ -197,7 +197,7 @@
|
||||
.plusNegative {
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
margin-top: -10px;
|
||||
margin: 2.5px 0 -0.5px 0px;
|
||||
}
|
||||
|
||||
.remove {
|
||||
|
||||
@@ -18,6 +18,8 @@ import ItemType from "./ItemType.js";
|
||||
import { createItemType } from "../helpers/itemHelper.js";
|
||||
|
||||
const ItemLister = ({
|
||||
index,
|
||||
indexTotal,
|
||||
itemTypeId,
|
||||
typeVisibility = true,
|
||||
refreshTotal,
|
||||
@@ -26,9 +28,12 @@ const ItemLister = ({
|
||||
user,
|
||||
typeName,
|
||||
typeImage,
|
||||
setShopItems,
|
||||
itemList,
|
||||
forCart,
|
||||
forInvoice,
|
||||
moveItemTypeUp,
|
||||
moveItemTypeDown,
|
||||
isEditMode,
|
||||
handleCreateItem,
|
||||
handleUpdateItem,
|
||||
@@ -155,7 +160,7 @@ const ItemLister = ({
|
||||
setPreviewUrl(previewUrl);
|
||||
};
|
||||
|
||||
const onCreateItem = (itemName, itemPrice, selectedImage, previewUrl) => {
|
||||
const onCreateItem = async (itemName, itemPrice, selectedImage, previewUrl) => {
|
||||
if (isEdit)
|
||||
setItemsToCreate((prevItems) => [
|
||||
...prevItems,
|
||||
@@ -165,9 +170,28 @@ const ItemLister = ({
|
||||
price: itemPrice,
|
||||
selectedImage,
|
||||
image: previewUrl,
|
||||
availability: true
|
||||
},
|
||||
]);
|
||||
else handleCreateItem(itemTypeId, itemName, itemPrice, selectedImage);
|
||||
else {
|
||||
const newItem = await handleCreateItem(itemTypeId, itemName, itemPrice, selectedImage);
|
||||
|
||||
console.log(newItem)
|
||||
if (newItem) {
|
||||
setShopItems((prevShopItems) => {
|
||||
return prevShopItems.map((itemType) => {
|
||||
// Check if the itemTypeId matches
|
||||
if (itemType.itemTypeId === itemTypeId) {
|
||||
return {
|
||||
...itemType,
|
||||
itemList: [...itemType.itemList, newItem], // Add the new item to the itemList
|
||||
};
|
||||
}
|
||||
return itemType; // Return the unchanged item type
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
console.log(items);
|
||||
console.log(itemsToCreate);
|
||||
@@ -197,7 +221,7 @@ const ItemLister = ({
|
||||
console.log(itemsToUpdate);
|
||||
};
|
||||
|
||||
const handleChange = (itemId) => {
|
||||
const handleChange = async (itemId) => {
|
||||
console.log(itemId);
|
||||
const itemIndex = items.findIndex((item) => item.itemId === itemId);
|
||||
if (itemIndex === -1) return; // Item not found
|
||||
@@ -220,13 +244,29 @@ const ItemLister = ({
|
||||
// If isEdit, add item to the list of items to update
|
||||
setItemsToUpdate((prev) => [...prev, { itemId, newAvailability }]);
|
||||
} else {
|
||||
// If not isEdit, immediately execute the update
|
||||
executeUpdateAvailability(
|
||||
itemId,
|
||||
newAvailability,
|
||||
updatedItems,
|
||||
itemIndex
|
||||
);
|
||||
|
||||
await executeUpdateAvailability(itemId, newAvailability);
|
||||
|
||||
// Update shopItems state
|
||||
setShopItems((prevShopItems) => {
|
||||
return prevShopItems.map((itemType) => {
|
||||
// Map through the itemList to find the item by itemId and update its availability
|
||||
const updatedItemList = itemType.itemList.map((item) => {
|
||||
if (item.itemId === itemId) {
|
||||
return {
|
||||
...item,
|
||||
availability: newAvailability, // Update the availability
|
||||
};
|
||||
}
|
||||
return item; // Return unchanged item
|
||||
});
|
||||
|
||||
return {
|
||||
...itemType,
|
||||
itemList: updatedItemList, // Update the itemList with modified item
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
console.log(itemsToUpdate);
|
||||
};
|
||||
@@ -260,7 +300,8 @@ const ItemLister = ({
|
||||
try {
|
||||
console.log(isVisible);
|
||||
if (itemTypeId) {
|
||||
await updateItemType(
|
||||
// Call the updateItemType function
|
||||
const updatedItemType = await updateItemType(
|
||||
shopId,
|
||||
itemTypeId,
|
||||
typeNameInputRef.current.value,
|
||||
@@ -269,6 +310,21 @@ const ItemLister = ({
|
||||
isVisible
|
||||
);
|
||||
|
||||
// Update shopItems state
|
||||
setShopItems((prevShopItems) => {
|
||||
return prevShopItems.map((itemType) => {
|
||||
if (itemType.itemTypeId === itemTypeId) {
|
||||
return {
|
||||
...itemType,
|
||||
name: updatedItemType.name || typeNameInputRef.current.value, // Update name if provided
|
||||
image: updatedItemType.image || itemType.image, // Update image if provided
|
||||
visibility: updatedItemType.visibility !== undefined ? updatedItemType.visibility : isVisible, // Update visibility if provided
|
||||
};
|
||||
}
|
||||
return itemType; // Return unchanged item type
|
||||
});
|
||||
});
|
||||
|
||||
// Iterate through itemsToUpdate and call the API
|
||||
for (const {
|
||||
itemId,
|
||||
@@ -277,33 +333,129 @@ const ItemLister = ({
|
||||
price,
|
||||
image,
|
||||
} of itemsToUpdate) {
|
||||
if (newAvailability != undefined)
|
||||
if (newAvailability != undefined) {
|
||||
await executeUpdateAvailability(
|
||||
itemId,
|
||||
newAvailability,
|
||||
items,
|
||||
items.findIndex((item) => item.itemId === itemId)
|
||||
);
|
||||
else await handleUpdateItem(itemId, name, price, image);
|
||||
|
||||
// Update shopItems state
|
||||
setShopItems((prevShopItems) => {
|
||||
return prevShopItems.map((itemType) => {
|
||||
// Map through the itemList to find the item by itemId and update its availability
|
||||
const updatedItemList = itemType.itemList.map((item) => {
|
||||
if (item.itemId === itemId) {
|
||||
return {
|
||||
...item,
|
||||
availability: newAvailability, // Update the availability
|
||||
};
|
||||
}
|
||||
return item; // Return unchanged item
|
||||
});
|
||||
|
||||
return {
|
||||
...itemType,
|
||||
itemList: updatedItemList, // Update the itemList with modified item
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
else {
|
||||
|
||||
// Call the handleUpdateItem function
|
||||
const updatedItem = await handleUpdateItem(itemId, name, price, image);
|
||||
|
||||
// Update shopItems state
|
||||
setShopItems((prevShopItems) => {
|
||||
return prevShopItems.map((itemType) => {
|
||||
// Map through the itemList to find the item by itemId and update it
|
||||
const updatedItemList = itemType.itemList.map((item) => {
|
||||
if (item.itemId === itemId) {
|
||||
return {
|
||||
...item,
|
||||
name: updatedItem.name || name, // Update fields as needed
|
||||
price: updatedItem.price || price,
|
||||
image: updatedItem.image || image,
|
||||
};
|
||||
}
|
||||
return item; // Return unchanged item
|
||||
});
|
||||
|
||||
return {
|
||||
...itemType,
|
||||
itemList: updatedItemList, // Update the itemList with modified item
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
console.log(itemsToCreate)
|
||||
for (const { name, price, selectedImage } of itemsToCreate) {
|
||||
handleCreateItem(itemTypeId, name, price, selectedImage);
|
||||
const newItem = await handleCreateItem(itemTypeId, name, price, selectedImage);
|
||||
console.log(newItem)
|
||||
if (newItem) {
|
||||
setShopItems((prevShopItems) => {
|
||||
return prevShopItems.map((itemType) => {
|
||||
// Check if the itemTypeId matches
|
||||
if (itemType.itemTypeId === itemTypeId) {
|
||||
return {
|
||||
...itemType,
|
||||
itemList: [...itemType.itemList, newItem], // Add the new item to the itemList
|
||||
};
|
||||
}
|
||||
return itemType; // Return the unchanged item type
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log(selectedImage)
|
||||
console.log(previewUrl)
|
||||
const itemType = await createItemType(
|
||||
shopId,
|
||||
editedTypeName,
|
||||
selectedImage,
|
||||
previewUrl
|
||||
);
|
||||
console.log(itemType);
|
||||
console.log(selectedImage);
|
||||
console.log(previewUrl);
|
||||
|
||||
try {
|
||||
// Call the createItemType function
|
||||
const newItemType = await createItemType(shopId, editedTypeName, selectedImage, previewUrl);
|
||||
|
||||
// Update shopItems state with the new item type
|
||||
setShopItems((prevShopItems) => [
|
||||
...prevShopItems,
|
||||
{
|
||||
itemTypeId: newItemType.itemTypeId, // API should return this
|
||||
name: newItemType.name,
|
||||
image: newItemType.image,
|
||||
cafeId: shopId, // Adjust as necessary
|
||||
itemList: [], // Start with an empty itemList
|
||||
},
|
||||
]);
|
||||
|
||||
// Loop through itemsToCreate and create each item
|
||||
for (const { name, price, selectedImage } of itemsToCreate) {
|
||||
handleCreateItem(itemType.itemTypeId, name, price, selectedImage);
|
||||
// Call handleCreateItem to create a new item
|
||||
const newItem = await handleCreateItem(newItemType.itemTypeId, name, price, selectedImage);
|
||||
|
||||
// If the item was created successfully, update the shopItems state
|
||||
if (newItem) {
|
||||
setShopItems((prevShopItems) =>
|
||||
prevShopItems.map((itemType) => {
|
||||
if (itemType.itemTypeId === newItemType.itemTypeId) {
|
||||
return {
|
||||
...itemType,
|
||||
itemList: [...itemType.itemList, newItem], // Add the new item to the itemList
|
||||
};
|
||||
}
|
||||
return itemType; // Return unchanged item type
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error creating item type or items:", error);
|
||||
}
|
||||
}
|
||||
// Clear the itemsToUpdate after saving
|
||||
setItemsToCreate([]);
|
||||
setItemsToUpdate([]);
|
||||
setIsEditing(false);
|
||||
if (handleUnEdit) handleUnEdit();
|
||||
@@ -338,8 +490,8 @@ const ItemLister = ({
|
||||
{(items.length > 0 ||
|
||||
(user && (user.cafeId == shopId || user.userId == shopOwnerId))) && (
|
||||
<div
|
||||
className={`${styles["item-lister"]} ${
|
||||
isEdit ? styles["fullscreen"] : ""
|
||||
key={itemTypeId}
|
||||
className={`${styles["item-lister"]} ${isEdit ? styles["fullscreen"] : ""
|
||||
}`}
|
||||
style={{ paddingBottom: isEdit ? "28vh" : "" }}
|
||||
>
|
||||
@@ -347,16 +499,65 @@ const ItemLister = ({
|
||||
{isEdit && <ItemType blank={true} imageUrl={previewUrl} />}
|
||||
<input
|
||||
ref={typeNameInputRef}
|
||||
className={`${styles.title} ${
|
||||
isEdit ? styles.border : styles.noborder
|
||||
className={`${styles.title} ${isEdit ? styles.border : styles.noborder
|
||||
}`}
|
||||
value={editedTypeName}
|
||||
placeholder="type name"
|
||||
placeholder="Nama tipe"
|
||||
onChange={(e) => setEditedTypeName(e.target.value)}
|
||||
disabled={!isEdit}
|
||||
/>
|
||||
{isEditMode && !isEdit && (
|
||||
<><div
|
||||
<>
|
||||
<div
|
||||
style={{
|
||||
width: '32px',
|
||||
height: '32px', // Add a height to the div
|
||||
display: 'flex', // Use flexbox
|
||||
justifyContent: 'center', // Center horizontally
|
||||
alignItems: 'center', // Center vertically
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
onClick={index==0?null:()=>moveItemTypeUp(itemTypeId)} // Move onClick here for the whole div
|
||||
>
|
||||
<svg
|
||||
viewBox="0 0 16 16"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="#000000"
|
||||
style={{ width: '100%', height: '100%' }} // Ensure SVG fits the div
|
||||
>
|
||||
<g id="SVGRepo_bgCarrier" strokeWidth="0"></g>
|
||||
<g id="SVGRepo_tracerCarrier" strokeLinecap="round" strokeLinejoin="round"></g>
|
||||
<g id="SVGRepo_iconCarrier">
|
||||
<path d="m 1 11 c 0 -0.265625 0.105469 -0.519531 0.292969 -0.707031 l 6 -6 c 0.390625 -0.390625 1.023437 -0.390625 1.414062 0 l 6 6 c 0.1875 0.1875 0.292969 0.441406 0.292969 0.707031 s -0.105469 0.519531 -0.292969 0.707031 c -0.390625 0.390625 -1.023437 0.390625 -1.414062 0 l -5.292969 -5.292969 l -5.292969 5.292969 c -0.390625 0.390625 -1.023437 0.390625 -1.414062 0 c -0.1875 -0.1875 -0.292969 -0.441406 -0.292969 -0.707031 z m 0 0" fill={index===0?"gray":"#2e3436"}></path>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
width: '32px',
|
||||
height: '32px', // Add a height to the div
|
||||
display: 'flex', // Use flexbox
|
||||
justifyContent: 'center', // Center horizontally
|
||||
alignItems: 'center', // Center vertically
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
onClick={index==indexTotal-1?null:()=>moveItemTypeDown(itemTypeId)} // Move onClick here for the whole div
|
||||
>
|
||||
<svg
|
||||
viewBox="0 0 16 16"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="#000000"
|
||||
style={{ width: '100%', height: '100%' }} // Ensure SVG fits the div
|
||||
>
|
||||
<g id="SVGRepo_bgCarrier" strokeWidth="0"></g>
|
||||
<g id="SVGRepo_tracerCarrier" strokeLinecap="round" strokeLinejoin="round"></g>
|
||||
<g id="SVGRepo_iconCarrier">
|
||||
<path d="m 1 5 c 0 -0.265625 0.105469 -0.519531 0.292969 -0.707031 c 0.390625 -0.390625 1.023437 -0.390625 1.414062 0 l 5.292969 5.292969 l 5.292969 -5.292969 c 0.390625 -0.390625 1.023437 -0.390625 1.414062 0 c 0.1875 0.1875 0.292969 0.441406 0.292969 0.707031 s -0.105469 0.519531 -0.292969 0.707031 l -6 6 c -0.390625 0.390625 -1.023437 0.390625 -1.414062 0 l -6 -6 c -0.1875 -0.1875 -0.292969 -0.441406 -0.292969 -0.707031 z m 0 0" fill={index===indexTotal-1?"gray":"#2e3436"}></path>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
width: '32px',
|
||||
height: '32px', // Add a height to the div
|
||||
@@ -529,6 +730,19 @@ const ItemLister = ({
|
||||
}
|
||||
imageUrl={getImageUrl("uploads/snack4.png")}
|
||||
/>
|
||||
{Array.from({ length: 16 }, (_, index) => {
|
||||
const sampleNumber = index + 1; // To get numbers from 1 to 16
|
||||
return (
|
||||
<ItemType
|
||||
key={sampleNumber}
|
||||
rectangular={true}
|
||||
onClick={(previewUrl, selectedImage) =>
|
||||
handleImageChange(previewUrl, selectedImage)
|
||||
}
|
||||
imageUrl={getImageUrl(`uploads/samples/sample (${sampleNumber}).png`)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -557,7 +771,7 @@ const ItemLister = ({
|
||||
onClick={toggleAddNewItem}
|
||||
style={{ display: "inline-block" }}
|
||||
>
|
||||
cancel
|
||||
batal
|
||||
</button>
|
||||
<Item blank={true} handleCreateItem={onCreateItem} />
|
||||
</>
|
||||
@@ -574,7 +788,7 @@ const ItemLister = ({
|
||||
onClick={() => editItem(0)}
|
||||
style={{ display: "inline-block" }}
|
||||
>
|
||||
cancel
|
||||
batal
|
||||
</button>
|
||||
)}
|
||||
<div className={styles["itemWrapper"]}>
|
||||
@@ -587,7 +801,7 @@ const ItemLister = ({
|
||||
/>
|
||||
)}
|
||||
<h3>
|
||||
{item.availability ? "available" : "unavailable"}
|
||||
{item.availability ? "tersedia" : "tidak tersedia"}
|
||||
</h3>
|
||||
<div
|
||||
style={{
|
||||
@@ -653,7 +867,7 @@ const ItemLister = ({
|
||||
onClick={() => editItem(0)}
|
||||
style={{ display: "inline-block" }}
|
||||
>
|
||||
cancel
|
||||
batal
|
||||
</button>
|
||||
)}
|
||||
<div className={styles["itemWrapper"]}>
|
||||
@@ -666,7 +880,7 @@ const ItemLister = ({
|
||||
/>
|
||||
)}
|
||||
<h3>
|
||||
{item.availability ? "available" : "unavailable"}
|
||||
{item.availability ? "tersedia" : "tidak tersedia"}
|
||||
</h3>
|
||||
<div
|
||||
style={{
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
font-family: "Poppins", sans-serif;
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
font-size: 28px;
|
||||
font-size: 22px;
|
||||
color: rgba(88, 55, 50, 1);
|
||||
text-align: left;
|
||||
width: calc(70% - 10px);
|
||||
|
||||
@@ -5,16 +5,21 @@ export default function ItemType({
|
||||
onClick,
|
||||
onCreate,
|
||||
blank,
|
||||
name: initialName = "",
|
||||
name,
|
||||
imageUrl,
|
||||
selected,
|
||||
rectangular,
|
||||
}) {
|
||||
const inputRef = useRef(null);
|
||||
const [name, setName] = useState(initialName);
|
||||
const [namee, setName] = useState(name);
|
||||
const [selectedImage, setSelectedImage] = useState(null);
|
||||
const [previewUrl, setPreviewUrl] = useState(imageUrl);
|
||||
|
||||
// Effect to update local state when name prop changes
|
||||
useEffect(() => {
|
||||
setName(name);
|
||||
}, [name]);
|
||||
|
||||
useEffect(() => {
|
||||
if (blank && inputRef.current) {
|
||||
inputRef.current.focus();
|
||||
@@ -26,12 +31,10 @@ export default function ItemType({
|
||||
const reader = new FileReader();
|
||||
reader.onloadend = () => {
|
||||
onClick(reader.result, selectedImage);
|
||||
// setPreviewUrl(reader.result);
|
||||
};
|
||||
reader.readAsDataURL(selectedImage);
|
||||
} else {
|
||||
setPreviewUrl(imageUrl);
|
||||
// onClick(getImageUrl(imageUrl));
|
||||
}
|
||||
}, [selectedImage, imageUrl]);
|
||||
|
||||
@@ -51,14 +54,14 @@ export default function ItemType({
|
||||
return;
|
||||
}
|
||||
|
||||
onCreate(name, selectedImage);
|
||||
onCreate(namee, selectedImage);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
styles[
|
||||
name
|
||||
namee
|
||||
? "item-type"
|
||||
: rectangular
|
||||
? "item-type-rectangular"
|
||||
@@ -78,10 +81,10 @@ export default function ItemType({
|
||||
>
|
||||
<img
|
||||
src={previewUrl}
|
||||
alt={name}
|
||||
alt={namee}
|
||||
className={styles["item-type-image"]}
|
||||
/>
|
||||
{blank && (
|
||||
{blank && rectangular && (
|
||||
<div className={styles["item-type-image-container"]}>
|
||||
<input
|
||||
type="file"
|
||||
@@ -97,7 +100,7 @@ export default function ItemType({
|
||||
<input
|
||||
ref={inputRef}
|
||||
className={`${styles["item-type-name"]} ${styles.noborder}`}
|
||||
value={name}
|
||||
value={namee}
|
||||
onChange={handleNameChange}
|
||||
disabled={true}
|
||||
style={{
|
||||
@@ -106,11 +109,6 @@ export default function ItemType({
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{/* {blank && (
|
||||
<button className={styles["item-type-create"]} onClick={handleCreate}>
|
||||
create
|
||||
</button>
|
||||
)} */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
.item-type {
|
||||
width: calc(25vw - 20px);
|
||||
height: calc(39vw - 20px);
|
||||
margin: 1px 10px -5px;
|
||||
height: calc(30vw - 20px);
|
||||
margin: 1px 10px 0px;
|
||||
overflow: visible;
|
||||
text-align: center;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
position: relative; /* Ensure absolute positioning inside works */
|
||||
position: relative;
|
||||
}
|
||||
.item-type-rectangular {
|
||||
width: calc(25vw - 20px);
|
||||
@@ -34,8 +34,8 @@
|
||||
}
|
||||
.item-type-rect {
|
||||
position: relative;
|
||||
height: 20vw;
|
||||
width: 20vw;
|
||||
height: 13vw;
|
||||
width: 13vw;
|
||||
object-fit: cover;
|
||||
border-radius: 15px;
|
||||
background-color: #fff;
|
||||
@@ -46,7 +46,6 @@
|
||||
font-family: "Poppins", sans-serif;
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
margin-bottom: 10px; /* Adjust margin for spacing */
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
width: calc(25vw - 30px);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
overflow-x: auto;
|
||||
white-space: nowrap;
|
||||
padding: 3px 0px;
|
||||
margin-bottom: -5px;
|
||||
}
|
||||
|
||||
.item-type-list {
|
||||
|
||||
@@ -9,6 +9,7 @@ const ItemTypeLister = ({
|
||||
shopId,
|
||||
shopOwnerId,
|
||||
user,
|
||||
setShopItems,
|
||||
itemTypes,
|
||||
onFilterChange,
|
||||
filterId,
|
||||
@@ -102,7 +103,7 @@ const ItemTypeLister = ({
|
||||
user.userId == shopOwnerId || user.cafeId == shopId) && (
|
||||
<ItemType
|
||||
onClick={toggleAddNewItem}
|
||||
name={"create"}
|
||||
name={"buat baru"}
|
||||
imageUrl={getImageUrl("uploads/addnew.png")}
|
||||
/>
|
||||
)}
|
||||
@@ -115,6 +116,7 @@ const ItemTypeLister = ({
|
||||
shopOwnerId={shopOwnerId}
|
||||
user={user}
|
||||
typeName={""}
|
||||
setShopItems={setShopItems}
|
||||
itemList={items}
|
||||
isEditMode={true}
|
||||
handleCreateItem={(itemTypeId, name, price, selectedImage) => createItem(shopId, name, price, selectedImage,itemTypeId)}
|
||||
@@ -127,9 +129,9 @@ const ItemTypeLister = ({
|
||||
)}
|
||||
{itemTypes && itemTypes.length > 0 && (
|
||||
<ItemType
|
||||
name={"All"}
|
||||
name={"semua"}
|
||||
onClick={() => onFilterChange(0)}
|
||||
imageUrl={getImageUrl("uploads/1718732420960.png")}
|
||||
imageUrl={getImageUrl("uploads/assets/All.png")}
|
||||
/>
|
||||
)}
|
||||
{itemTypes &&
|
||||
|
||||
@@ -106,7 +106,7 @@ const SetPaymentQr = ({ shopId }) => {
|
||||
|
||||
return (
|
||||
<div style={styles.container}>
|
||||
<h2 style={styles.title}>Payment QRIS</h2>
|
||||
<h2 style={styles.title}>QR pembayaran</h2>
|
||||
<div
|
||||
id="qr-code-container"
|
||||
ref={qrCodeContainerRef}
|
||||
@@ -133,7 +133,7 @@ const SetPaymentQr = ({ shopId }) => {
|
||||
/>
|
||||
</div>
|
||||
<div style={styles.resultMessage}>
|
||||
{qrCodeDetected ? <p>QR Code Detected</p> : <p>No QR Code Detected</p>}
|
||||
{qrCodeDetected ? <p>QR Code Detected</p> : <p>Tidak ada qr yang terdeteksi</p>}
|
||||
</div>
|
||||
<div style={styles.buttonContainer}>
|
||||
<button onClick={handleSave} style={styles.saveButton}>
|
||||
@@ -141,9 +141,9 @@ const SetPaymentQr = ({ shopId }) => {
|
||||
</button>
|
||||
</div>
|
||||
<div style={styles.switchContainer}>
|
||||
<h1>Double Check tem Availability</h1>
|
||||
<h1>Pengecekan ketersediaan ganda</h1>
|
||||
<p style={styles.description}>
|
||||
Turn on the switch for the clerk to double check before customer pay.
|
||||
Nyalakan agar kasir memeriksa kembali ketersediaan produk sebelum pelanggan membayar.
|
||||
</p>
|
||||
<Switch onChange={handleChange} checked={isNeedConfirmation} />
|
||||
</div>
|
||||
|
||||
@@ -120,7 +120,7 @@ export default function SearchInput({
|
||||
<Searchinput
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
placeholder="Search..."
|
||||
placeholder="Cari apa ?"
|
||||
value={songName}
|
||||
onChange={handleChange}
|
||||
onBlur={handleBlur}
|
||||
|
||||
@@ -245,6 +245,24 @@ export async function updateItemType(
|
||||
}
|
||||
}
|
||||
|
||||
export const moveItemType = async (itemTypeId, targetItemTypeId, fromOrder, toOrder) => {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/item/moveType/${itemTypeId}/${targetItemTypeId}/${fromOrder}/${toOrder}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
Authorization: `Bearer ${getAuthToken()}`,
|
||||
},
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to move item type');
|
||||
}
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export async function deleteItemType(shopId, itemTypeId) {
|
||||
try {
|
||||
const response = await fetch(
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
|
||||
import "../App.css";
|
||||
|
||||
import { getImageUrl, createItem, updateItem } from "../helpers/itemHelper.js";
|
||||
import { getImageUrl, createItem, updateItem, moveItemType } from "../helpers/itemHelper.js";
|
||||
import SearchInput from "../components/SearchInput";
|
||||
import ItemTypeLister from "../components/ItemTypeLister";
|
||||
import { MusicPlayer } from "../components/MusicPlayer";
|
||||
@@ -30,6 +30,7 @@ function CafePage({
|
||||
welcomePageConfig,
|
||||
shopName,
|
||||
shopOwnerId,
|
||||
setShopItems,
|
||||
shopItems,
|
||||
shopClerks,
|
||||
socket,
|
||||
@@ -149,6 +150,40 @@ function CafePage({
|
||||
|
||||
document.body.style.overflow = "auto";
|
||||
};
|
||||
|
||||
const moveItemTypeHandler = async (itemTypeId, direction, index) => {
|
||||
const previousItems = [...shopItems];
|
||||
|
||||
// Update local state immediately
|
||||
const newItems = [...shopItems];
|
||||
|
||||
let targetIndex;
|
||||
if (direction === 'up' && index > 0) {
|
||||
targetIndex = index - 1;
|
||||
} else if (direction === 'down' && index < newItems.length - 1) {
|
||||
targetIndex = index + 1;
|
||||
}
|
||||
console.log(index);
|
||||
console.log(targetIndex);
|
||||
if (targetIndex !== undefined) {
|
||||
// Swap items
|
||||
[newItems[index], newItems[targetIndex]] = [newItems[targetIndex], newItems[index]];
|
||||
newItems[index].order = targetIndex;
|
||||
newItems[targetIndex].order = index;
|
||||
|
||||
setShopItems(newItems);
|
||||
|
||||
// Call the API to move the item type
|
||||
try {
|
||||
await moveItemType(itemTypeId, previousItems[targetIndex].itemTypeId, index, targetIndex);
|
||||
} catch (error) {
|
||||
console.error('Error moving item type:', error);
|
||||
// Revert the changes if the backend fails
|
||||
setShopItems(previousItems);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (loading)
|
||||
return (
|
||||
<div className="Loader">
|
||||
@@ -204,6 +239,7 @@ function CafePage({
|
||||
shopOwnerId={shopOwnerId}
|
||||
shopId={shopId}
|
||||
itemTypes={shopItems}
|
||||
setShopItems={setShopItems}
|
||||
isEditMode={isEditMode}
|
||||
onFilterChange={(e) => setFilterId(e)}
|
||||
filterId={filterId}
|
||||
@@ -217,8 +253,10 @@ function CafePage({
|
||||
(itemType) =>
|
||||
filterId == 0 || itemType.itemTypeId === filterId
|
||||
)
|
||||
.map((itemType) => (
|
||||
.map((itemType, index) => (
|
||||
<ItemLister
|
||||
index={index}
|
||||
indexTotal={shopItems.length}
|
||||
shopId={shopId}
|
||||
shopOwnerId={shopOwnerId}
|
||||
user={user}
|
||||
@@ -226,8 +264,11 @@ function CafePage({
|
||||
itemTypeId={itemType.itemTypeId}
|
||||
typeName={itemType.name}
|
||||
typeImage={itemType.image}
|
||||
setShopItems={setShopItems}
|
||||
itemList={itemType.itemList}
|
||||
typeVisibility={itemType.visibility}
|
||||
moveItemTypeUp={(e)=>moveItemTypeHandler(e,'up', index)}
|
||||
moveItemTypeDown={(e)=>moveItemTypeHandler(e, 'down', index)}
|
||||
isEditMode={isEditMode}
|
||||
beingEditedType={beingEditedType}
|
||||
setBeingEditedType={setBeingEditedType}
|
||||
|
||||
@@ -150,7 +150,7 @@ export default function Invoice({ table, sendParam, deviceType, socket }) {
|
||||
return (
|
||||
<div className={styles.Invoice}>
|
||||
<div style={{ marginTop: "30px" }}></div>
|
||||
<h2 className={styles["Invoice-title"]}>Cart</h2>
|
||||
<h2 className={styles["Invoice-title"]}>Keranjang</h2>
|
||||
<div style={{ marginTop: "30px" }}></div>
|
||||
<div className={styles.RoundedRectangle}>
|
||||
{cartItems.map((itemType) => (
|
||||
@@ -165,7 +165,7 @@ export default function Invoice({ table, sendParam, deviceType, socket }) {
|
||||
|
||||
{table.tableNo != null && (
|
||||
<div className={styles.OrderTypeContainer}>
|
||||
<span htmlFor="orderType">Serve to table {table.tableNo}</span>
|
||||
<span htmlFor="orderType">Diantar ke meja {table.tableNo}</span>
|
||||
{/* <select
|
||||
id="orderType"
|
||||
value={orderType}
|
||||
@@ -193,7 +193,7 @@ export default function Invoice({ table, sendParam, deviceType, socket }) {
|
||||
)}
|
||||
|
||||
<div className={styles.NoteContainer}>
|
||||
<span>Note :</span>
|
||||
<span>Catatan :</span>
|
||||
<span></span>
|
||||
</div>
|
||||
|
||||
@@ -201,7 +201,7 @@ export default function Invoice({ table, sendParam, deviceType, socket }) {
|
||||
<textarea
|
||||
ref={textareaRef}
|
||||
className={styles.NoteInput}
|
||||
placeholder="Add a note..."
|
||||
placeholder="Tambahkan catatan..."
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.TotalContainer}>
|
||||
@@ -211,21 +211,21 @@ export default function Invoice({ table, sendParam, deviceType, socket }) {
|
||||
</div>
|
||||
<div className={styles.PaymentOption}>
|
||||
<div className={styles.TotalContainer}>
|
||||
<span>Payment Option</span>
|
||||
<span>Opsi pembayaran</span>
|
||||
<span></span>
|
||||
</div>
|
||||
<button className={styles.PayButton} onClick={() => handlePay(false)}>
|
||||
{isPaymentLoading ? (
|
||||
<ColorRing height="50" width="50" color="white" />
|
||||
) : (
|
||||
"Cashless"
|
||||
"Nontunai"
|
||||
)}
|
||||
</button>
|
||||
<div className={styles.Pay2Button} onClick={() => handlePay(true)}>
|
||||
{isPaymentLoading ? (
|
||||
<ColorRing height="12" width="12" color="white" />
|
||||
) : (
|
||||
"Cash"
|
||||
"Tunai"
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -80,6 +80,31 @@ const Dashboard = ({ user, setModal }) => {
|
||||
});
|
||||
}
|
||||
};
|
||||
// function calculateCafeMetrics(cafes) {
|
||||
// let totalIncomeThisMonth = 0;
|
||||
// let totalIncomeLastMonth = 0;
|
||||
|
||||
// cafes.forEach(cafe => {
|
||||
// const currentIncome = cafe.totalIncome;
|
||||
// const growth = cafe.growthIncome / 100;
|
||||
|
||||
// // Hitung keuntungan bulan lalu
|
||||
// const lastMonthIncome = currentIncome / (1 + growth);
|
||||
|
||||
// // Tambahkan ke total
|
||||
// totalIncomeThisMonth += currentIncome;
|
||||
// totalIncomeLastMonth += lastMonthIncome;
|
||||
// });
|
||||
|
||||
// // Hitung growth total
|
||||
// const totalGrowth = ((totalIncomeThisMonth - totalIncomeLastMonth) / totalIncomeLastMonth) * 100;
|
||||
|
||||
// return {
|
||||
// totalIncomeThisMonth,
|
||||
// totalIncomeLastMonth: totalIncomeLastMonth.toFixed(2),
|
||||
// totalGrowth: totalGrowth.toFixed(2)
|
||||
// };
|
||||
// }
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -100,7 +125,9 @@ const Dashboard = ({ user, setModal }) => {
|
||||
onClick={() => navigate("/" + item.cafeId)}
|
||||
className={styles.rectangle}
|
||||
>
|
||||
{item.name || item.username}
|
||||
<h1>{item.name || item.username}</h1>
|
||||
|
||||
<div><h1>{item.report.totalIncome}</h1><h1>{item.report.totalIncome}</h1></div>
|
||||
</div>
|
||||
))}
|
||||
{user && user.roleId < 1 ? (
|
||||
|
||||
@@ -211,10 +211,11 @@ const App = ({ cafeId,
|
||||
right: 0,
|
||||
backgroundColor: 'rgb(207, 207, 207)'}}
|
||||
>
|
||||
<h2 className={styles["Transactions-title"]}>Reports</h2>
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<h2 className={styles["Transactions-title"]}>Laporan</h2>
|
||||
<div style={{ textAlign: "center",
|
||||
marginTop: '30px' }}>
|
||||
<MultiSwitch
|
||||
texts={["Yesterday", "This week", "This Month", "This year"]}
|
||||
texts={["kemarin", "minggu ini", "bulan ini", "tahun ini"]}
|
||||
selectedSwitch={["daily", "weekly", "monthly", "yearly"].indexOf(
|
||||
filter
|
||||
)}
|
||||
|
||||
@@ -43,7 +43,7 @@ function SearchResult({ user, shopItems, sendParam }) {
|
||||
return (
|
||||
<div className="App">
|
||||
<header className="App-header">
|
||||
<Header HeaderText={"Search"} />
|
||||
<Header HeaderText={"Pencarian"} />
|
||||
<div style={{ marginTop: "5px" }}></div>
|
||||
<SearchInput
|
||||
shopId={shopId}
|
||||
|
||||
@@ -229,7 +229,7 @@ export default function Transactions({
|
||||
: handleDecline(transaction.transactionId)
|
||||
}
|
||||
>
|
||||
{isPaymentOpen ? "back" : "cancel"}
|
||||
{isPaymentOpen ? "kembali" : "batalkan"}
|
||||
</h5>
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user