This commit is contained in:
zadit
2024-11-01 11:33:26 +07:00
parent 5a2b9b2f86
commit 32e8ebd69b
20 changed files with 812 additions and 509 deletions

View File

@@ -96,6 +96,7 @@ function App() {
if (response.status === 200) { if (response.status === 200) {
setShop(cafe); setShop(cafe);
setShopItems(data); setShopItems(data);
console.log(data)
// Filter out unavailable items // Filter out unavailable items
const filteredData = data const filteredData = data
.map((itemType) => ({ .map((itemType) => ({
@@ -424,6 +425,7 @@ function App() {
shopName={shop.name} shopName={shop.name}
shopOwnerId={shop.ownerId} shopOwnerId={shop.ownerId}
shopItems={shopItems} shopItems={shopItems}
setShopItems={setShopItems}
shopClerks={shopClerks} shopClerks={shopClerks}
socket={socket} socket={socket}
user={user} user={user}

View File

@@ -63,14 +63,14 @@ export default function Footer({
</div> </div>
{/* Search Icon */} {/* Search Icon */}
<div onClick={goToSearch} className={styles["footer-icon"]}> {/* <div onClick={goToSearch} className={styles["footer-icon"]}>
<svg <svg
viewBox="0 0 34 34" viewBox="0 0 34 34"
style={{ fill: selectedPage === 1 ? "black" : "#8F8787" }} 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" /> <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> </svg>
</div> </div> */}
{/* Cart Icon */} {/* Cart Icon */}
<div onClick={goToCart} className={styles["footer-icon"]}> <div onClick={goToCart} className={styles["footer-icon"]}>

View File

@@ -330,11 +330,11 @@ const Header = ({
)} )}
{user.username !== undefined && ( {user.username !== undefined && (
<Child onClick={() => setModal("edit_account")}> <Child onClick={() => setModal("edit_account")}>
Edit profile Ubah profil
</Child> </Child>
)} )}
{shopId && user.roleId == 1 && ( {shopId && user.roleId == 1 && (
<Child onClick={goToAdminCafes}>see your {user.userId == shopOwnerId ? 'other' : ''} cafes</Child>)} <Child onClick={goToAdminCafes}>Dashboard</Child>)}
{shopId && {shopId &&
user.userId == shopOwnerId && user.userId == shopOwnerId &&
user.username !== undefined && user.username !== undefined &&
@@ -349,7 +349,7 @@ const Header = ({
<div class="toggle-switch"> <div class="toggle-switch">
<label class="toggle-switch-label" for="toggleSwitch"> <label class="toggle-switch-label" for="toggleSwitch">
Edit Mode Mode edit
</label> </label>
<Switch <Switch
checked={isEditMode} checked={isEditMode}
@@ -358,18 +358,18 @@ const Header = ({
</div> </div>
<Child onClick={() => setModal("welcome_config")}> <Child onClick={() => setModal("welcome_config")}>
welcoming page Halaman sambutan
</Child> </Child>
<Child onClick={() => setModal("add_material")}> <Child onClick={() => setModal("add_material")}>
stock Stok
</Child> </Child>
<Child onClick={() => setModal("edit_tables")}> <Child onClick={() => setModal("edit_tables")}>
table maps Denah meja
</Child> </Child>
<Child hasChildren> <Child hasChildren>
clerks Kasir
<Child onClick={() => setModal("create_clerk")}> <Child onClick={() => setModal("create_clerk")}>
+ Add clerk + Tambah
</Child> </Child>
{shopClerks && {shopClerks &&
shopClerks.map((key, index) => ( shopClerks.map((key, index) => (
@@ -388,10 +388,10 @@ const Header = ({
))} ))}
</Child> </Child>
<Child onClick={() => setModal("payment_option")}> <Child onClick={() => setModal("payment_option")}>
payment options Opsi pembayaran
</Child> </Child>
<Child onClick={() => setModal("reports")}>reports</Child> <Child onClick={() => setModal("reports")}>Laporan</Child>
</Child> </Child>
</> </>
)} )}
@@ -403,7 +403,7 @@ const Header = ({
<div class="toggle-switch"> <div class="toggle-switch">
<label class="toggle-switch-label" for="toggleSwitch"> <label class="toggle-switch-label" for="toggleSwitch">
Edit Mode Mode edit
</label> </label>
<Switch <Switch
checked={isEditMode} checked={isEditMode}
@@ -411,15 +411,15 @@ const Header = ({
/> />
</div> </div>
<Child onClick={() => setModal("add_material")}> <Child onClick={() => setModal("add_material")}>
stock stok
</Child> </Child>
{user.username !== undefined && {user.username !== undefined &&
user.roleId == 2 && user.roleId == 2 &&
user.cafeId == shopId && ( user.cafeId == shopId && (
<Child hasChildren> <Child hasChildren>
connected guest sides Tablet tamu
<Child onClick={goToGuestSideLogin}> <Child onClick={goToGuestSideLogin}>
+ Add guest side + Tambah
</Child> </Child>
{guestSides && {guestSides &&
guestSides.map((key, index) => ( guestSides.map((key, index) => (
@@ -439,7 +439,7 @@ const Header = ({
</Child> </Child>
)} )}
<Child onClick={() => setModal("reports")}>reports</Child> <Child onClick={() => setModal("reports")}>Laporan</Child>
</Child> </Child>
)} )}
{user.username !== undefined && ( {user.username !== undefined && (

View File

@@ -35,8 +35,8 @@
} }
.itemImage { .itemImage {
width: 139px; width: 119px;
height: 149px; height: 129px;
border-radius: 10px; border-radius: 10px;
margin-right: 10px; margin-right: 10px;
object-fit: cover; object-fit: cover;
@@ -45,8 +45,8 @@
.imageContainer { .imageContainer {
position: relative; position: relative;
width: 139px; width: 119px;
height: 149px; height: 129px;
} }
.overlay { .overlay {
@@ -124,7 +124,7 @@
font-weight: 600; font-weight: 600;
width: calc(100% - 15px); /* Adjust the width to prevent overflow */ width: calc(100% - 15px); /* Adjust the width to prevent overflow */
font-size: 0.9rem; font-size: 0.9rem;
margin-bottom: 35px; /* margin-bottom: 35px; */
margin-left: 5px; margin-left: 5px;
color: #d9c61c; color: #d9c61c;
background-color: transparent; background-color: transparent;
@@ -143,19 +143,19 @@
.itemQty { .itemQty {
display: flex; display: flex;
align-items: end; align-items: center;
font-size: 0.9rem; font-size: 0.9rem;
margin-left: 5px; margin-left: 5px;
color: rgb(115, 165, 133); color: rgb(115, 165, 133);
fill: rgb(115, 165, 133); fill: rgb(115, 165, 133);
height: 40px;
} }
.itemQtyValue { .itemQtyValue {
margin-bottom: 8px;
font-family: "Poppins", sans-serif; font-family: "Poppins", sans-serif;
font-style: normal; font-style: normal;
font-weight: 600; font-weight: 600;
margin-top: 5px; margin-top: 19px;
margin-left: 5px; margin-left: 5px;
margin-right: 5px; margin-right: 5px;
width: 25px; width: 25px;
@@ -197,7 +197,7 @@
.plusNegative { .plusNegative {
width: 35px; width: 35px;
height: 35px; height: 35px;
margin-top: -10px; margin: 2.5px 0 -0.5px 0px;
} }
.remove { .remove {

View File

@@ -18,6 +18,8 @@ import ItemType from "./ItemType.js";
import { createItemType } from "../helpers/itemHelper.js"; import { createItemType } from "../helpers/itemHelper.js";
const ItemLister = ({ const ItemLister = ({
index,
indexTotal,
itemTypeId, itemTypeId,
typeVisibility = true, typeVisibility = true,
refreshTotal, refreshTotal,
@@ -26,9 +28,12 @@ const ItemLister = ({
user, user,
typeName, typeName,
typeImage, typeImage,
setShopItems,
itemList, itemList,
forCart, forCart,
forInvoice, forInvoice,
moveItemTypeUp,
moveItemTypeDown,
isEditMode, isEditMode,
handleCreateItem, handleCreateItem,
handleUpdateItem, handleUpdateItem,
@@ -155,7 +160,7 @@ const ItemLister = ({
setPreviewUrl(previewUrl); setPreviewUrl(previewUrl);
}; };
const onCreateItem = (itemName, itemPrice, selectedImage, previewUrl) => { const onCreateItem = async (itemName, itemPrice, selectedImage, previewUrl) => {
if (isEdit) if (isEdit)
setItemsToCreate((prevItems) => [ setItemsToCreate((prevItems) => [
...prevItems, ...prevItems,
@@ -165,9 +170,28 @@ const ItemLister = ({
price: itemPrice, price: itemPrice,
selectedImage, selectedImage,
image: previewUrl, 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(items);
console.log(itemsToCreate); console.log(itemsToCreate);
@@ -197,7 +221,7 @@ const ItemLister = ({
console.log(itemsToUpdate); console.log(itemsToUpdate);
}; };
const handleChange = (itemId) => { const handleChange = async (itemId) => {
console.log(itemId); console.log(itemId);
const itemIndex = items.findIndex((item) => item.itemId === itemId); const itemIndex = items.findIndex((item) => item.itemId === itemId);
if (itemIndex === -1) return; // Item not found if (itemIndex === -1) return; // Item not found
@@ -220,13 +244,29 @@ const ItemLister = ({
// If isEdit, add item to the list of items to update // If isEdit, add item to the list of items to update
setItemsToUpdate((prev) => [...prev, { itemId, newAvailability }]); setItemsToUpdate((prev) => [...prev, { itemId, newAvailability }]);
} else { } else {
// If not isEdit, immediately execute the update
executeUpdateAvailability( await executeUpdateAvailability(itemId, newAvailability);
itemId,
newAvailability, // Update shopItems state
updatedItems, setShopItems((prevShopItems) => {
itemIndex 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); console.log(itemsToUpdate);
}; };
@@ -260,7 +300,8 @@ const ItemLister = ({
try { try {
console.log(isVisible); console.log(isVisible);
if (itemTypeId) { if (itemTypeId) {
await updateItemType( // Call the updateItemType function
const updatedItemType = await updateItemType(
shopId, shopId,
itemTypeId, itemTypeId,
typeNameInputRef.current.value, typeNameInputRef.current.value,
@@ -269,6 +310,21 @@ const ItemLister = ({
isVisible 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 // Iterate through itemsToUpdate and call the API
for (const { for (const {
itemId, itemId,
@@ -277,33 +333,129 @@ const ItemLister = ({
price, price,
image, image,
} of itemsToUpdate) { } of itemsToUpdate) {
if (newAvailability != undefined) if (newAvailability != undefined) {
await executeUpdateAvailability( await executeUpdateAvailability(
itemId, itemId,
newAvailability, newAvailability,
items, items,
items.findIndex((item) => item.itemId === itemId) 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) { 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 { } else {
console.log(selectedImage) console.log(selectedImage);
console.log(previewUrl) console.log(previewUrl);
const itemType = await createItemType(
shopId, try {
editedTypeName, // Call the createItemType function
selectedImage, const newItemType = await createItemType(shopId, editedTypeName, selectedImage, previewUrl);
previewUrl
); // Update shopItems state with the new item type
console.log(itemType); 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) { 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 // Clear the itemsToUpdate after saving
setItemsToCreate([]);
setItemsToUpdate([]); setItemsToUpdate([]);
setIsEditing(false); setIsEditing(false);
if (handleUnEdit) handleUnEdit(); if (handleUnEdit) handleUnEdit();
@@ -338,8 +490,8 @@ const ItemLister = ({
{(items.length > 0 || {(items.length > 0 ||
(user && (user.cafeId == shopId || user.userId == shopOwnerId))) && ( (user && (user.cafeId == shopId || user.userId == shopOwnerId))) && (
<div <div
className={`${styles["item-lister"]} ${ key={itemTypeId}
isEdit ? styles["fullscreen"] : "" className={`${styles["item-lister"]} ${isEdit ? styles["fullscreen"] : ""
}`} }`}
style={{ paddingBottom: isEdit ? "28vh" : "" }} style={{ paddingBottom: isEdit ? "28vh" : "" }}
> >
@@ -347,16 +499,65 @@ const ItemLister = ({
{isEdit && <ItemType blank={true} imageUrl={previewUrl} />} {isEdit && <ItemType blank={true} imageUrl={previewUrl} />}
<input <input
ref={typeNameInputRef} ref={typeNameInputRef}
className={`${styles.title} ${ className={`${styles.title} ${isEdit ? styles.border : styles.noborder
isEdit ? styles.border : styles.noborder
}`} }`}
value={editedTypeName} value={editedTypeName}
placeholder="type name" placeholder="Nama tipe"
onChange={(e) => setEditedTypeName(e.target.value)} onChange={(e) => setEditedTypeName(e.target.value)}
disabled={!isEdit} disabled={!isEdit}
/> />
{isEditMode && !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={{ style={{
width: '32px', width: '32px',
height: '32px', // Add a height to the div height: '32px', // Add a height to the div
@@ -529,6 +730,19 @@ const ItemLister = ({
} }
imageUrl={getImageUrl("uploads/snack4.png")} 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> </div>
)} )}
@@ -557,7 +771,7 @@ const ItemLister = ({
onClick={toggleAddNewItem} onClick={toggleAddNewItem}
style={{ display: "inline-block" }} style={{ display: "inline-block" }}
> >
cancel batal
</button> </button>
<Item blank={true} handleCreateItem={onCreateItem} /> <Item blank={true} handleCreateItem={onCreateItem} />
</> </>
@@ -574,7 +788,7 @@ const ItemLister = ({
onClick={() => editItem(0)} onClick={() => editItem(0)}
style={{ display: "inline-block" }} style={{ display: "inline-block" }}
> >
cancel batal
</button> </button>
)} )}
<div className={styles["itemWrapper"]}> <div className={styles["itemWrapper"]}>
@@ -587,7 +801,7 @@ const ItemLister = ({
/> />
)} )}
<h3> <h3>
&nbsp;{item.availability ? "available" : "unavailable"} &nbsp; &nbsp;{item.availability ? "tersedia" : "tidak tersedia"} &nbsp;
</h3> </h3>
<div <div
style={{ style={{
@@ -653,7 +867,7 @@ const ItemLister = ({
onClick={() => editItem(0)} onClick={() => editItem(0)}
style={{ display: "inline-block" }} style={{ display: "inline-block" }}
> >
cancel batal
</button> </button>
)} )}
<div className={styles["itemWrapper"]}> <div className={styles["itemWrapper"]}>
@@ -666,7 +880,7 @@ const ItemLister = ({
/> />
)} )}
<h3> <h3>
&nbsp;{item.availability ? "available" : "unavailable"}&nbsp; &nbsp;{item.availability ? "tersedia" : "tidak tersedia"}&nbsp;
</h3> </h3>
<div <div
style={{ style={{

View File

@@ -38,7 +38,7 @@
font-family: "Poppins", sans-serif; font-family: "Poppins", sans-serif;
font-weight: 500; font-weight: 500;
font-style: normal; font-style: normal;
font-size: 28px; font-size: 22px;
color: rgba(88, 55, 50, 1); color: rgba(88, 55, 50, 1);
text-align: left; text-align: left;
width: calc(70% - 10px); width: calc(70% - 10px);

View File

@@ -5,16 +5,21 @@ export default function ItemType({
onClick, onClick,
onCreate, onCreate,
blank, blank,
name: initialName = "", name,
imageUrl, imageUrl,
selected, selected,
rectangular, rectangular,
}) { }) {
const inputRef = useRef(null); const inputRef = useRef(null);
const [name, setName] = useState(initialName); const [namee, setName] = useState(name);
const [selectedImage, setSelectedImage] = useState(null); const [selectedImage, setSelectedImage] = useState(null);
const [previewUrl, setPreviewUrl] = useState(imageUrl); const [previewUrl, setPreviewUrl] = useState(imageUrl);
// Effect to update local state when name prop changes
useEffect(() => {
setName(name);
}, [name]);
useEffect(() => { useEffect(() => {
if (blank && inputRef.current) { if (blank && inputRef.current) {
inputRef.current.focus(); inputRef.current.focus();
@@ -26,12 +31,10 @@ export default function ItemType({
const reader = new FileReader(); const reader = new FileReader();
reader.onloadend = () => { reader.onloadend = () => {
onClick(reader.result, selectedImage); onClick(reader.result, selectedImage);
// setPreviewUrl(reader.result);
}; };
reader.readAsDataURL(selectedImage); reader.readAsDataURL(selectedImage);
} else { } else {
setPreviewUrl(imageUrl); setPreviewUrl(imageUrl);
// onClick(getImageUrl(imageUrl));
} }
}, [selectedImage, imageUrl]); }, [selectedImage, imageUrl]);
@@ -51,14 +54,14 @@ export default function ItemType({
return; return;
} }
onCreate(name, selectedImage); onCreate(namee, selectedImage);
}; };
return ( return (
<div <div
className={ className={
styles[ styles[
name namee
? "item-type" ? "item-type"
: rectangular : rectangular
? "item-type-rectangular" ? "item-type-rectangular"
@@ -78,10 +81,10 @@ export default function ItemType({
> >
<img <img
src={previewUrl} src={previewUrl}
alt={name} alt={namee}
className={styles["item-type-image"]} className={styles["item-type-image"]}
/> />
{blank && ( {blank && rectangular && (
<div className={styles["item-type-image-container"]}> <div className={styles["item-type-image-container"]}>
<input <input
type="file" type="file"
@@ -97,7 +100,7 @@ export default function ItemType({
<input <input
ref={inputRef} ref={inputRef}
className={`${styles["item-type-name"]} ${styles.noborder}`} className={`${styles["item-type-name"]} ${styles.noborder}`}
value={name} value={namee}
onChange={handleNameChange} onChange={handleNameChange}
disabled={true} disabled={true}
style={{ style={{
@@ -106,11 +109,6 @@ export default function ItemType({
}} }}
/> />
)} )}
{/* {blank && (
<button className={styles["item-type-create"]} onClick={handleCreate}>
create
</button>
)} */}
</div> </div>
); );
} }

View File

@@ -1,14 +1,14 @@
.item-type { .item-type {
width: calc(25vw - 20px); width: calc(25vw - 20px);
height: calc(39vw - 20px); height: calc(30vw - 20px);
margin: 1px 10px -5px; margin: 1px 10px 0px;
overflow: visible; overflow: visible;
text-align: center; text-align: center;
align-items: center; align-items: center;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
position: relative; /* Ensure absolute positioning inside works */ position: relative;
} }
.item-type-rectangular { .item-type-rectangular {
width: calc(25vw - 20px); width: calc(25vw - 20px);
@@ -34,8 +34,8 @@
} }
.item-type-rect { .item-type-rect {
position: relative; position: relative;
height: 20vw; height: 13vw;
width: 20vw; width: 13vw;
object-fit: cover; object-fit: cover;
border-radius: 15px; border-radius: 15px;
background-color: #fff; background-color: #fff;
@@ -46,7 +46,6 @@
font-family: "Poppins", sans-serif; font-family: "Poppins", sans-serif;
font-weight: 500; font-weight: 500;
font-style: normal; font-style: normal;
margin-bottom: 10px; /* Adjust margin for spacing */
font-size: 14px; font-size: 14px;
color: #333; color: #333;
width: calc(25vw - 30px); width: calc(25vw - 30px);

View File

@@ -3,6 +3,7 @@
overflow-x: auto; overflow-x: auto;
white-space: nowrap; white-space: nowrap;
padding: 3px 0px; padding: 3px 0px;
margin-bottom: -5px;
} }
.item-type-list { .item-type-list {

View File

@@ -9,6 +9,7 @@ const ItemTypeLister = ({
shopId, shopId,
shopOwnerId, shopOwnerId,
user, user,
setShopItems,
itemTypes, itemTypes,
onFilterChange, onFilterChange,
filterId, filterId,
@@ -102,7 +103,7 @@ const ItemTypeLister = ({
user.userId == shopOwnerId || user.cafeId == shopId) && ( user.userId == shopOwnerId || user.cafeId == shopId) && (
<ItemType <ItemType
onClick={toggleAddNewItem} onClick={toggleAddNewItem}
name={"create"} name={"buat baru"}
imageUrl={getImageUrl("uploads/addnew.png")} imageUrl={getImageUrl("uploads/addnew.png")}
/> />
)} )}
@@ -115,6 +116,7 @@ const ItemTypeLister = ({
shopOwnerId={shopOwnerId} shopOwnerId={shopOwnerId}
user={user} user={user}
typeName={""} typeName={""}
setShopItems={setShopItems}
itemList={items} itemList={items}
isEditMode={true} isEditMode={true}
handleCreateItem={(itemTypeId, name, price, selectedImage) => createItem(shopId, name, price, selectedImage,itemTypeId)} handleCreateItem={(itemTypeId, name, price, selectedImage) => createItem(shopId, name, price, selectedImage,itemTypeId)}
@@ -127,9 +129,9 @@ const ItemTypeLister = ({
)} )}
{itemTypes && itemTypes.length > 0 && ( {itemTypes && itemTypes.length > 0 && (
<ItemType <ItemType
name={"All"} name={"semua"}
onClick={() => onFilterChange(0)} onClick={() => onFilterChange(0)}
imageUrl={getImageUrl("uploads/1718732420960.png")} imageUrl={getImageUrl("uploads/assets/All.png")}
/> />
)} )}
{itemTypes && {itemTypes &&

View File

@@ -106,7 +106,7 @@ const SetPaymentQr = ({ shopId }) => {
return ( return (
<div style={styles.container}> <div style={styles.container}>
<h2 style={styles.title}>Payment QRIS</h2> <h2 style={styles.title}>QR pembayaran</h2>
<div <div
id="qr-code-container" id="qr-code-container"
ref={qrCodeContainerRef} ref={qrCodeContainerRef}
@@ -133,7 +133,7 @@ const SetPaymentQr = ({ shopId }) => {
/> />
</div> </div>
<div style={styles.resultMessage}> <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>
<div style={styles.buttonContainer}> <div style={styles.buttonContainer}>
<button onClick={handleSave} style={styles.saveButton}> <button onClick={handleSave} style={styles.saveButton}>
@@ -141,9 +141,9 @@ const SetPaymentQr = ({ shopId }) => {
</button> </button>
</div> </div>
<div style={styles.switchContainer}> <div style={styles.switchContainer}>
<h1>Double Check tem Availability</h1> <h1>Pengecekan ketersediaan ganda</h1>
<p style={styles.description}> <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> </p>
<Switch onChange={handleChange} checked={isNeedConfirmation} /> <Switch onChange={handleChange} checked={isNeedConfirmation} />
</div> </div>

View File

@@ -120,7 +120,7 @@ export default function SearchInput({
<Searchinput <Searchinput
ref={inputRef} ref={inputRef}
type="text" type="text"
placeholder="Search..." placeholder="Cari apa ?"
value={songName} value={songName}
onChange={handleChange} onChange={handleChange}
onBlur={handleBlur} onBlur={handleBlur}

View File

@@ -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) { export async function deleteItemType(shopId, itemTypeId) {
try { try {
const response = await fetch( const response = await fetch(

View File

@@ -10,7 +10,7 @@ import {
import "../App.css"; 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 SearchInput from "../components/SearchInput";
import ItemTypeLister from "../components/ItemTypeLister"; import ItemTypeLister from "../components/ItemTypeLister";
import { MusicPlayer } from "../components/MusicPlayer"; import { MusicPlayer } from "../components/MusicPlayer";
@@ -30,6 +30,7 @@ function CafePage({
welcomePageConfig, welcomePageConfig,
shopName, shopName,
shopOwnerId, shopOwnerId,
setShopItems,
shopItems, shopItems,
shopClerks, shopClerks,
socket, socket,
@@ -149,6 +150,40 @@ function CafePage({
document.body.style.overflow = "auto"; 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) if (loading)
return ( return (
<div className="Loader"> <div className="Loader">
@@ -204,6 +239,7 @@ function CafePage({
shopOwnerId={shopOwnerId} shopOwnerId={shopOwnerId}
shopId={shopId} shopId={shopId}
itemTypes={shopItems} itemTypes={shopItems}
setShopItems={setShopItems}
isEditMode={isEditMode} isEditMode={isEditMode}
onFilterChange={(e) => setFilterId(e)} onFilterChange={(e) => setFilterId(e)}
filterId={filterId} filterId={filterId}
@@ -217,8 +253,10 @@ function CafePage({
(itemType) => (itemType) =>
filterId == 0 || itemType.itemTypeId === filterId filterId == 0 || itemType.itemTypeId === filterId
) )
.map((itemType) => ( .map((itemType, index) => (
<ItemLister <ItemLister
index={index}
indexTotal={shopItems.length}
shopId={shopId} shopId={shopId}
shopOwnerId={shopOwnerId} shopOwnerId={shopOwnerId}
user={user} user={user}
@@ -226,8 +264,11 @@ function CafePage({
itemTypeId={itemType.itemTypeId} itemTypeId={itemType.itemTypeId}
typeName={itemType.name} typeName={itemType.name}
typeImage={itemType.image} typeImage={itemType.image}
setShopItems={setShopItems}
itemList={itemType.itemList} itemList={itemType.itemList}
typeVisibility={itemType.visibility} typeVisibility={itemType.visibility}
moveItemTypeUp={(e)=>moveItemTypeHandler(e,'up', index)}
moveItemTypeDown={(e)=>moveItemTypeHandler(e, 'down', index)}
isEditMode={isEditMode} isEditMode={isEditMode}
beingEditedType={beingEditedType} beingEditedType={beingEditedType}
setBeingEditedType={setBeingEditedType} setBeingEditedType={setBeingEditedType}

View File

@@ -150,7 +150,7 @@ export default function Invoice({ table, sendParam, deviceType, socket }) {
return ( return (
<div className={styles.Invoice}> <div className={styles.Invoice}>
<div style={{ marginTop: "30px" }}></div> <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 style={{ marginTop: "30px" }}></div>
<div className={styles.RoundedRectangle}> <div className={styles.RoundedRectangle}>
{cartItems.map((itemType) => ( {cartItems.map((itemType) => (
@@ -165,7 +165,7 @@ export default function Invoice({ table, sendParam, deviceType, socket }) {
{table.tableNo != null && ( {table.tableNo != null && (
<div className={styles.OrderTypeContainer}> <div className={styles.OrderTypeContainer}>
<span htmlFor="orderType">Serve to table {table.tableNo}</span> <span htmlFor="orderType">Diantar ke meja {table.tableNo}</span>
{/* <select {/* <select
id="orderType" id="orderType"
value={orderType} value={orderType}
@@ -193,7 +193,7 @@ export default function Invoice({ table, sendParam, deviceType, socket }) {
)} )}
<div className={styles.NoteContainer}> <div className={styles.NoteContainer}>
<span>Note :</span> <span>Catatan :</span>
<span></span> <span></span>
</div> </div>
@@ -201,7 +201,7 @@ export default function Invoice({ table, sendParam, deviceType, socket }) {
<textarea <textarea
ref={textareaRef} ref={textareaRef}
className={styles.NoteInput} className={styles.NoteInput}
placeholder="Add a note..." placeholder="Tambahkan catatan..."
/> />
</div> </div>
<div className={styles.TotalContainer}> <div className={styles.TotalContainer}>
@@ -211,21 +211,21 @@ export default function Invoice({ table, sendParam, deviceType, socket }) {
</div> </div>
<div className={styles.PaymentOption}> <div className={styles.PaymentOption}>
<div className={styles.TotalContainer}> <div className={styles.TotalContainer}>
<span>Payment Option</span> <span>Opsi pembayaran</span>
<span></span> <span></span>
</div> </div>
<button className={styles.PayButton} onClick={() => handlePay(false)}> <button className={styles.PayButton} onClick={() => handlePay(false)}>
{isPaymentLoading ? ( {isPaymentLoading ? (
<ColorRing height="50" width="50" color="white" /> <ColorRing height="50" width="50" color="white" />
) : ( ) : (
"Cashless" "Nontunai"
)} )}
</button> </button>
<div className={styles.Pay2Button} onClick={() => handlePay(true)}> <div className={styles.Pay2Button} onClick={() => handlePay(true)}>
{isPaymentLoading ? ( {isPaymentLoading ? (
<ColorRing height="12" width="12" color="white" /> <ColorRing height="12" width="12" color="white" />
) : ( ) : (
"Cash" "Tunai"
)} )}
</div> </div>
</div> </div>

View File

@@ -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 ( return (
<> <>
@@ -100,7 +125,9 @@ const Dashboard = ({ user, setModal }) => {
onClick={() => navigate("/" + item.cafeId)} onClick={() => navigate("/" + item.cafeId)}
className={styles.rectangle} 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> </div>
))} ))}
{user && user.roleId < 1 ? ( {user && user.roleId < 1 ? (

View File

@@ -211,10 +211,11 @@ const App = ({ cafeId,
right: 0, right: 0,
backgroundColor: 'rgb(207, 207, 207)'}} backgroundColor: 'rgb(207, 207, 207)'}}
> >
<h2 className={styles["Transactions-title"]}>Reports</h2> <h2 className={styles["Transactions-title"]}>Laporan</h2>
<div style={{ textAlign: "center" }}> <div style={{ textAlign: "center",
marginTop: '30px' }}>
<MultiSwitch <MultiSwitch
texts={["Yesterday", "This week", "This Month", "This year"]} texts={["kemarin", "minggu ini", "bulan ini", "tahun ini"]}
selectedSwitch={["daily", "weekly", "monthly", "yearly"].indexOf( selectedSwitch={["daily", "weekly", "monthly", "yearly"].indexOf(
filter filter
)} )}

View File

@@ -43,7 +43,7 @@ function SearchResult({ user, shopItems, sendParam }) {
return ( return (
<div className="App"> <div className="App">
<header className="App-header"> <header className="App-header">
<Header HeaderText={"Search"} /> <Header HeaderText={"Pencarian"} />
<div style={{ marginTop: "5px" }}></div> <div style={{ marginTop: "5px" }}></div>
<SearchInput <SearchInput
shopId={shopId} shopId={shopId}

View File

@@ -229,7 +229,7 @@ export default function Transactions({
: handleDecline(transaction.transactionId) : handleDecline(transaction.transactionId)
} }
> >
{isPaymentOpen ? "back" : "cancel"} {isPaymentOpen ? "kembali" : "batalkan"}
</h5> </h5>
</div> </div>
)} )}