nicholas/storage-manager#1: new directory structure, rewrite add box, item functionality

This commit is contained in:
2025-04-08 17:52:26 -05:00
parent dbcd1bfff5
commit d00615b197
45 changed files with 4265 additions and 15594 deletions
+2
View File
@@ -1 +1,3 @@
/.vs
/.vscode
.env
+23 -5
View File
@@ -1,4 +1,22 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
# dependencies
/node_modules
@@ -17,7 +35,7 @@
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
*.local
dist
dist-ssr
*.env
+12
View File
@@ -0,0 +1,12 @@
# React + Vite
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
Currently, two official plugins are available:
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
## Expanding the ESLint configuration
If you are developing a production application, we recommend using TypeScript and enable type-aware lint rules. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project.
+33
View File
@@ -0,0 +1,33 @@
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
export default [
{ ignores: ['dist'] },
{
files: ['**/*.{js,jsx}'],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
parserOptions: {
ecmaVersion: 'latest',
ecmaFeatures: { jsx: true },
sourceType: 'module',
},
},
plugins: {
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
},
rules: {
...js.configs.recommended.rules,
...reactHooks.configs.recommended.rules,
'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
},
]
@@ -12,6 +12,7 @@
<body class="scroll-container">
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script type="module" src="/src/index.tsx"></script>
</body>
</html>
+2553 -13615
View File
File diff suppressed because it is too large Load Diff
+21 -35
View File
@@ -1,43 +1,29 @@
{
"name": "storage",
"version": "0.1.0",
"name": "client",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^13.3.0",
"@testing-library/user-event": "^14.2.1",
"axios": "^0.27.2",
"body-parser": "^1.20.0",
"color": "^4.2.3",
"express": "^4.18.1",
"mongoose": "^6.4.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-scripts": "^5.0.1"
},
"version": "0.0.0",
"type": "module",
"scripts": {
"start": "react-scripts start",
"server": "nodemon ../server/server.js",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
"dev": "vite",
"build": "vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
"dependencies": {
"axios": "^1.8.4",
"color": "^5.0.0",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
"devDependencies": {
"nodemon": "^2.0.16"
"@eslint/js": "^9.21.0",
"@types/react": "^19.0.10",
"@types/react-dom": "^19.0.4",
"@vitejs/plugin-react": "^4.3.4",
"eslint": "^9.21.0",
"eslint-plugin-react-hooks": "^5.1.0",
"eslint-plugin-react-refresh": "^0.4.19",
"globals": "^16.0.0",
"vite": "^6.2.0"
}
}
-9
View File
@@ -1,9 +0,0 @@
const SERVER_IP_ADDRESS = 'localhost';
const SERVER_PORT = '5000';
const PROTOCOL = 'http';
const databaseName = 'box';
const baseApiUrl = `${PROTOCOL}://${SERVER_IP_ADDRESS}:${SERVER_PORT}/${databaseName}`;
export default {
baseApiUrl
}
+23
View File
@@ -0,0 +1,23 @@
import axios from 'axios';
const config = {
SERVER_IP_ADDRESS: import.meta.env.VITE_API_HOST || 'localhost',
SERVER_PORT: import.meta.env.VITE_API_PORT || '5000',
PROTOCOL: import.meta.env.VITE_API_PROTOCOL || 'http',
};
const baseApiUrl = `${config.PROTOCOL}://${config.SERVER_IP_ADDRESS}:${config.SERVER_PORT}`;
export const api = axios.create({
baseURL: baseApiUrl,
headers: {
'Content-Type': 'application/json',
},
});
api.interceptors.response.use(
response => response,
error => {
console.error('API error:', error);
return Promise.reject(error);
}
);
+6
View File
@@ -0,0 +1,6 @@
export interface ApiResponse<T> {
success: boolean;
data?: T;
message?: string;
error?: string;
}
+2
View File
@@ -0,0 +1,2 @@
export { api } from './api';
export * from './apiResponse.types';
-465
View File
@@ -1,465 +0,0 @@
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import Box from './box';
import api from './api';
import Modal from './modal';
import Button from './button';
import InputBox from './inputBox';
import TextArea from './textArea';
import ColorSelect from './color';
import Color from 'color';
import './styles/app.css';
function App() {
const [data, setData] = useState([]);
const [boxID, setBoxID] = useState(undefined);
const [boxName, setBoxName] = useState('');
const [boxDescription, setBoxDescription] = useState('');
const [boxColor, setBoxColor] = useState('WHITE');
const [showModalBox, setShowModalBox] = useState(false);
const [showModalItem, setShowModalItem] = useState(false);
const [showModalItemReadOnly, setShowModalItemReadOnly] = useState(false);
const [editBoxBool, setEditBoxBool] = useState(false);
const [editItemBool, setEditItemBool] = useState(false);
const [itemID, setItemID] = useState(undefined);
const [itemName, setItemName] = useState('');
const [itemDescription, setItemDescription] = useState('');
const [itemQuantity, setItemQuantity] = useState(1);
const [filter, setFilter] = useState('');
const [filteredData, setFilteredData] = useState([]);
const [dragSource, setDragSource] = useState({});
const [dragSourceElement, setDragSourceElement] = useState(undefined);
const [isItemMoving, setIsItemMoving] = useState(false);
const [moveItemID, setMoveItemID] = useState(null);
const [moveItemBoxID, setMoveItemBoxID] = useState(null)
//fetch all box data
useEffect(() => {
const fetchData = async () => {
await fetchDataAsync();
}
fetchData();
}, []);
//set filtered data on filter or original data change
useEffect(() => {
const finalFilter = filter.trim().toLowerCase();
const filtered = data.filter((box) =>
box.name.toLowerCase().includes(finalFilter) ||
box.description.toLowerCase().includes(finalFilter) ||
box.item.some(item =>
(item.name && item.name.toLowerCase().includes(finalFilter)) ||
(item.description && item.description.toLowerCase().includes(finalFilter))
)
)
setFilteredData(filtered);
}, [filter, data]);
const fetchDataAsync = async () => {
try {
const resp = await axios.get(api.baseApiUrl);
setData(resp.data);
setFilteredData(resp.data);
}
catch (error) {
console.log(error);
}
}
// BOX FUNCTIONS ///////////////////////////////////////////////////////////////
const createBox = async (boxData) => {
try {
const response = await axios.post(api.baseApiUrl, boxData);
setData(response.data);
}
catch (error) {
console.log(error);
}
}
const editBox = async (boxData) => {
try {
const response = await axios.put(`${api.baseApiUrl}/${boxData.id}`, boxData);
setData(response.data);
}
catch (error) {
console.log(error);
}
}
const removeBox = async (id) => {
try {
const response = await axios.delete(`${api.baseApiUrl}/${id}`);
const storageData = response.data;
setData(storageData);
}
catch (error) {
console.log(error);
}
}
const sortBox = async (target, source) => {
try {
const response = await axios.put(`${api.baseApiUrl}/${source._id}/sort`, target);
setData(response.data);
}
catch (error) {
}
}
const handleNameBox = (e) => {
setBoxName(e.target.value);
}
const handleBoxDescription = (e) => {
setBoxDescription(e.target.value);
}
const handleColorBox = (event) => {
setBoxColor(event.target.value);
}
const handleSubmitBox = (event) => {
event.preventDefault();
setShowModalBox(false);
setEditBoxBool(false);
const boxData = {
id: boxID,
name: boxName,
description: boxDescription,
color: boxColor
};
if (editBoxBool)
editBox(boxData);
else
createBox(boxData);
clearFormBox();
return false;
}
const handleRemoveBox = (id) => {
removeBox(id);
}
const handleAddBox = () => {
clearFormBox();
setShowModalBox(true);
setEditBoxBool(false);
}
const handleEditBox = (id) => {
const box = data.find(box => box._id === id);
setEditBoxBool(true);
setBoxName(box.name);
setBoxDescription(box.description);
setBoxColor(box.color);
setBoxID(id);
setShowModalBox(true);
}
const clearFormBox = () => {
setBoxName('');
setBoxDescription('');
setBoxID(undefined);
}
const handleDragStartBox = (dragSource, event) => {
setDragSource(dragSource);
setDragSourceElement(event.currentTarget);
event.currentTarget.style.opacity = 0.3;
var rect = event.currentTarget.getBoundingClientRect();
var x = event.clientX - rect.left;
var y = event.clientY - rect.top;
event.dataTransfer.setDragImage(event.currentTarget,x,y);
event.dataTransfer.dropEffect = 'move';
}
const handleDragOverBox = (event) => {
event.preventDefault();
event.dataTransfer.dropEffect = 'move';
}
const handleDropBox = async (dragTarget, event) => {
event.preventDefault();
const sourceElement = dragSourceElement;
const targetElement = event.currentTarget;
sourceElement.style.opacity = 1;
const elements = [...event.currentTarget.parentElement.children];
const targetIndex = elements.indexOf(targetElement);
const sourceIndex = elements.indexOf(sourceElement);
if (dragTarget._id !== dragSource._id) {
const target = Object.assign(dragTarget, { sort: targetIndex });
const source = Object.assign(dragSource, { sort: sourceIndex });
await sortBox(target, source);
}
return false;
}
const handleSelectMoveBox = async (boxData, event) => {
setMoveItemBoxID(null);
setMoveItemID(null);
setIsItemMoving(false);
await moveItem(moveItemID, boxData._id);
}
const boxHandler = {
handleSubmit: handleSubmitBox,
handleRemove: handleRemoveBox,
handleName: handleNameBox,
handleDescription: handleBoxDescription,
handleColor: handleColorBox,
handleEdit: handleEditBox,
handleDragStart: handleDragStartBox,
handleDragOver: handleDragOverBox,
handleDrop: handleDropBox,
handleSelectMove: handleSelectMoveBox,
};
const handleModalCloseBox = () => {
setShowModalBox(false);
}
// ITEM FUNCTIONS ///////////////////////////////////////////////////////////////
const moveItem = async (itemID, boxIDTarget) => {
try {
const response = await axios.post(`${api.baseApiUrl}/item/${itemID}/${boxIDTarget}`);
setData(response.data);
}
catch (error) {
console.log(error);
}
}
const createItem = async (boxID, itemData) => {
try {
const response = await axios.post(`${api.baseApiUrl}/${boxID}/item`, itemData);
const boxData = [...data];
boxData.splice(boxData.findIndex(box => box._id === boxID), 1, response.data);
setData(boxData);
}
catch (error) {
console.log(error);
}
}
const editItem = async (boxID, itemID, itemData) => {
try {
const response = await axios.put(`${api.baseApiUrl}/${boxID}/item/${itemID}`, itemData);
const boxData = [...data];
boxData.splice(boxData.findIndex(box => box._id === boxID), 1, response.data);
setData(boxData);
}
catch (error) {
console.log(error);
}
}
const removeItem = async (boxID, itemID) => {
try {
const boxData = [...data];
const response = await axios.delete(`${api.baseApiUrl}/${boxID}/item/${itemID}`);
boxData.splice(boxData.findIndex(box => box._id === boxID), 1, response.data);
setData(boxData);
}
catch (error) {
console.log(error);
}
}
const handleAddItem = (id) => {
clearFormItem();
setBoxID(id);
setEditItemBool(false);
setShowModalItem(true);
}
const setItemInfo = (boxID, itemID) => {
const box = data.find(box => box._id === boxID);
const item = box.item.find(item => item._id === itemID);
setBoxID(boxID);
setItemID(itemID);
setItemName(item.name || '');
setItemDescription(item.description || '');
setItemQuantity(item.quantity || 1);
}
const handleEditItem = (boxID, itemID) => {
clearFormItem();
setItemInfo(boxID, itemID);
setEditItemBool(true);
setShowModalItem(true);
}
const handleMoveItem = (boxID, itemID) => {
setMoveItemBoxID(boxID);
setMoveItemID(itemID);
setIsItemMoving(true);
/*highlight boxes available*/
/*...*/
}
const handleViewItem = (boxID, itemID) => {
setItemInfo(boxID, itemID);
setShowModalItemReadOnly(true);
}
const handleRemoveItem = (boxID, itemID) => {
removeItem(boxID, itemID);
}
const handleSubmitItem = (event) => {
event.preventDefault();
setShowModalItem(false);
const itemData = {
id: itemID,
name: itemName,
description: itemDescription,
quantity: itemQuantity
}
if (editItemBool)
editItem(boxID, itemID, itemData);
else
createItem(boxID, itemData);
clearFormItem();
return false;
}
const handleModalCloseItem = () => {
setShowModalItem(false);
setShowModalItemReadOnly(false);
}
const clearFormItem = () => {
setItemID(undefined);
setItemName('');
setItemDescription('');
setItemQuantity(1);
}
const handleNameItem = (e) => {
setItemName(e.target.value)
}
const handleDescriptionItem = (e) => {
setItemDescription(e.target.value)
}
const handleQuantityItem = (e) => {
const num = parseInt(e.target.value);
if (Number.isInteger(num)) {
setItemQuantity(num);
}
if (e.target.value === '')
setItemQuantity('');
}
const itemHandler = {
handleSubmit: handleSubmitItem,
handleRemove: handleRemoveItem,
handleAdd: handleAddItem,
handleEdit: handleEditItem,
handleMove: handleMoveItem,
handleView: handleViewItem,
handleName: handleNameItem,
handleDescription: handleDescriptionItem,
handleQuantity: handleQuantityItem
};
const handleFilterChange = (event) => {
const filterText = event.target.value;
setFilter(filterText);
}
const handleClearFilter = () => {
setFilter('');
}
const baseColors = {
'WHITE': 'white',
'RED': '#FF9AA2',
'PINK': '#FFB7B2',
'ORANGE': '#FFDAC1',
'GREEN': '#E2F0CB',
'EMERALD': '#B5EAD7',
'PURPLE': '#C7CEEA'
}
const colorData = Object.entries(baseColors).map((color) => {
const data = {
title: color[0],
defaultStyle: { backgroundColor: color[1] },
lightStyle: { backgroundColor: Color(color[1]).lighten(0.05).string() },
darkStyle: { backgroundColor: Color(color[1]).darken(0.1).string() }
}
return data;
});
const colorSelector =
<>
<label>color</label>
<div className='color-selector-container'>
{colorData.map((data) => {
return <ColorSelect colorData={data} handler={boxHandler.handleColor} boxColor={boxColor} />
})
}
</div>
</>
const boxForm =
<form id='form'>
<InputBox id='id' name='id' value={boxID} readOnly={true} hidden={true} />
<h1>Edit Box</h1>
<fieldset>
<InputBox focus={true} id='box-name' label='name' onChange={boxHandler.handleName} value={boxName} name='name' type='text' />
<InputBox id='box-description' label='description' onChange={boxHandler.handleDescription} value={boxDescription} name='description' type='text' />
{colorSelector}
</fieldset>
</form>
const itemForm = <form id='form'>
<h1>Edit Item</h1>
<fieldset>
<InputBox id='id' name='id' value={itemID} readOnly={true} hidden={true} />
<InputBox focus={true} id='item-name' label='name' onChange={itemHandler.handleName} value={itemName} name='name' type='text' />
<TextArea id='item-description' label='description' onChange={itemHandler.handleDescription} value={itemDescription} name='description' />
<InputBox id='item-quantity' label='quantity' onChange={itemHandler.handleQuantity} value={itemQuantity} name='quantity' type='text' />
</fieldset>
</form>
//
const itemView = <div>
<h1>{itemName}</h1>
<h2>{itemDescription}</h2>
<h3>{itemQuantity}</h3>
</div>
return (
<>
{<Modal show={showModalItem} content={itemForm} handleSubmit={itemHandler.handleSubmit} handleClose={handleModalCloseItem} />}
{<Modal show={showModalBox} content={boxForm} handleSubmit={boxHandler.handleSubmit} handleClose={handleModalCloseBox} />}
{<Modal show={showModalItemReadOnly} content={itemView} handleClose={handleModalCloseItem} />}
<div className='header-container'>
<span className='filter-container'>
<span className='material-icons icon'>search</span>
<input spellCheck='false' className='filter' type='search' id='box-filter' name='filter' value={filter} onChange={handleFilterChange} />
<span className='material-icons icon clear' onClick={handleClearFilter}>clear</span>
</span>
{<Button label='new box' onClick={handleAddBox} />}
</div>
<div className='body-container scroll-container'>
<ul className='list-box'>
{filteredData.map((box, index) => {
return (
<Box
style={colorData.find(color => color.title === box.color)}
isItemMoving={box._id !== moveItemBoxID && isItemMoving}
key={index}
boxData={box}
boxHandler={boxHandler}
itemHandler={itemHandler}
/>
);
})}
</ul>
</div>
</>
);
}
export default App;
+68
View File
@@ -0,0 +1,68 @@
import { useEffect, useState } from 'react';
import { getAllBoxes, Box, BoxCard, BoxFormModal } from './features/box';
import './styles/app.css';
import './styles/button.css';
import './styles/inputBox.css';
import './styles/textArea.css';
import './index.css';
const App = () => {
const [boxes, setBoxes] = useState<Box[]>([]);
const [isModalOpen, setModalOpen] = useState(false);
const handleCreateBox = (newBox: Box) => {
setBoxes((boxes) => [...boxes, newBox]);
};
const toggleModal = () => {
setModalOpen(!isModalOpen);
};
const handleSubmit = () => {
setModalOpen(false);
};
const handleClose = () => {
setModalOpen(false);
};
useEffect(() => {
const fetchData = async () => {
const response = await getAllBoxes();
if (response.success) {
if (response.data != null) {
setBoxes(response.data);
} else {
console.error('No data returned in the response');
}
}
};
fetchData();
}, []);
return (
<div>
<BoxFormModal isOpen={isModalOpen} onCreateBox={handleCreateBox} onClose={handleClose} />
<div className="filter-container">
<span className='material-icons icon'>search</span>
<input spellCheck='false' className='filter' type='search' id='box-filter' name='filter' />
<span className='material-icons icon clear'>clear</span>
</div>
<div className='header-container'>
<button className='button' onClick={toggleModal}>new box</button>
</div>
<div className='body-container scroll-container'>
<ul className='list-box'>
{boxes.map((item) => (
<BoxCard key={item._id} box={item} />
))}
</ul>
</div>
</div>
);
};
export default App;
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

-76
View File
@@ -1,76 +0,0 @@
import React, { useState, useRef, useEffect } from 'react';
import Item from './item';
const Box = ({ boxData, boxHandler, itemHandler, isItemMoving, style }) => {
const menu = useRef(null);
useEffect(() => {
document.addEventListener('mousedown', handleClick);
return () => {
document.removeEventListener('mousedown', handleClick);
};
}, []);
const handleClick = (event) => {
if (menu.current.contains(event.target)) return;
setShowMenu(false);
}
const handleClickRemove = () => {
boxHandler.handleRemove(boxData._id);
setShowMenu(false);
}
const handleClickEdit = () => {
boxHandler.handleEdit(boxData._id);
setShowMenu(false);
}
const [showMenu, setShowMenu] = useState(false);
const handleBoxMenuClick = () => {
setShowMenu(true);
}
const moreMenu = () => {
return (
<ul className='menu-more-list box'>
<li className='menu-action' onClick={handleClickEdit}>
<span className='material-icons icon'>edit</span>
<span className='label'>edit</span>
</li>
<li className='menu-action' onClick={handleClickRemove}>
<span className='material-icons icon'>delete</span>
<span className='label'>remove</span>
</li>
</ul>
)
}
return (
<li className={`${isItemMoving ? 'target ' : ''}box`}
style={style.defaultStyle}
draggable='true'
onDragStart={(event) => boxHandler.handleDragStart(boxData, event)}
onDragOver={boxHandler.handleDragOver}
onDrop={(event) => boxHandler.handleDrop(boxData, event)}
onClick={(event) => isItemMoving && boxHandler.handleSelectMove(boxData, event)}>
{false && <span>{boxData.sort}</span>}
<div ref={menu} className='menu-more-container'>{showMenu && moreMenu()}</div>
<div className='box-header'>
<div className='box-title-container'>
<h2 className='box-title'>{boxData.name}</h2>
<h3 className='box-subtitle'>{boxData.description}</h3>
</div>
<span className='menu-btn box material-icons' onClick={handleBoxMenuClick}>more_vert</span>
</div>
<div style={style.darkStyle} className='head-divider'></div>
<ul className={`list-box-item scroll-container ${boxData.item.length === 0 ? "empty": ""}`}>
{boxData.item.map(((item, index) => {
return <Item
key={index}
data={item}
boxID={boxData._id}
itemHandler={itemHandler} />
}))}
</ul>
<div className='menu-bottom'>
<span style={style.lightStyle} className='material-icons add' onClick={() => itemHandler.handleAdd(boxData._id)}>add</span>
</div>
</li>
)
}
export default Box;
-8
View File
@@ -1,8 +0,0 @@
import React, {useState} from 'react';
import './styles/button.css';
const Button = ({style, form, onClick, label, className}) => {
return <button style={style} form={form} onClick={onClick} className={`button ${className}`}>{label}</button>
}
export default Button;
@@ -1,6 +1,6 @@
import React, { useState } from 'react';
const Color = ({ colorData, handler, boxColor }) => {
const ColorSelect = ({ colorData, handler, boxColor }) => {
const [style, setStyle] = useState(colorData.defaultStyle);
const handleHover = (isHover) => {
@@ -14,4 +14,4 @@ const Color = ({ colorData, handler, boxColor }) => {
)
}
export default Color;
export default ColorSelect;
+8
View File
@@ -0,0 +1,8 @@
import { Item } from '../item/item.types';
export interface Box {
_id?: string;
name: string;
description: string;
items: Item[];
}
@@ -0,0 +1,51 @@
import { useState } from 'react';
import { Box } from '../';
import { Item, ItemCard, ItemFormModal } from '../../item';
interface BoxCardProps {
box: Box;
}
export const BoxCard: React.FC<BoxCardProps> = ({ box }) => {
const [items, setItems] = useState<Item[]>(box.items);
const [modalOpen, setModalOpen] = useState(false);
const handleOpenItemFormModal = () => {
setModalOpen(true);
}
const handleClose = () => {
setModalOpen(false);
};
const handleCreateItem = (newItem: Item) => {
setItems((items) => [...items, newItem]);
};
return (
<li className="box">
<ItemFormModal isOpen={modalOpen} onCreateItem={handleCreateItem} onClose={handleClose} boxId={box._id} />
<div className='menu-more-container'></div>
<div className="box-header">
<div className="box-title-container">
<h2 className="box-title">{box.name}</h2>
<h3 className="box-subtitle">{box.description}</h3>
</div>
<span className='menu-btn box material-icons'>more_vert</span>
</div>
<div className="head-divider" />
<ul className={`list-box-item scroll-container ${box.items?.length === 0 ? 'empty' : ''}`}>
{items?.map((item) => (
<ItemCard key={item._id} item={item}></ItemCard>
))}
</ul>
<div className="menu-bottom">
<span
className="material-icons add"
onClick={handleOpenItemFormModal}>
add
</span>
</div>
</li>
);
};
@@ -0,0 +1,65 @@
import Modal from '../../../modal';
import { Box, createBox } from '..';
import { Item } from '../../item';
interface BoxFormModalProps {
isOpen: boolean;
onClose: () => void;
onCreateBox: (box: Box) => void;
initialData?: Partial<Box>;
}
export const BoxFormModal: React.FC<BoxFormModalProps> = ({ isOpen, onClose, onCreateBox, initialData }) => {
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
const form = event.currentTarget;
const formData = new FormData(form);
const boxData: Box = {
name: formData.get('name') as string,
description: formData.get('description') as string,
items: initialData?.items as Item[]
};
try {
const response = await createBox(boxData);
if (response.success) {
if (response.data != null) {
onCreateBox(response.data);
} else {
console.error('No data returned in the response');
}
} else {
console.log(response.error || 'Unknown error');
}
} catch (err) {
console.error(err);
}
finally {
onClose();
}
};
return (
<Modal isOpen={isOpen}>
<form onSubmit={handleSubmit} id='form'>
<input value="" id='id' name='id' readOnly={true} hidden={true} />
<h1>Edit Box</h1>
<fieldset>
<label className='input' htmlFor="box-name">name</label>
<input className='input' id='box-name' name='name' type='text' />
<label className='input' htmlFor="box-description">description</label>
<input className='input' id='box-description' name='description' type='text' />
<div className="modal-actions">
<button className='button' type="submit">Create</button>
<button className='button' type="button" onClick={onClose}>Cancel</button>
</div>
</fieldset>
</form>
</Modal>
);
};
+5
View File
@@ -0,0 +1,5 @@
export * from './box.types';
export * from './components/BoxCard';
export * from './components/BoxFormModal';
export * from './services/BoxService';
@@ -0,0 +1,32 @@
import { api, ApiResponse } from '../../../api';
import { Box } from '..';
const getAllBoxes = async (): Promise<ApiResponse<Box[]>> => {
try {
const response = await api.get<Box[]>('/boxes');
return { success: true, data: response.data };
} catch (error) {
const message =
error instanceof Error ? error.message : 'Could not fetch boxes';
return { success: false, data: [], error: message };
}
};
const createBox = async (boxData: Box): Promise<ApiResponse<Box>> => {
try {
const response = await api.post<Box>(`/boxes`, boxData);
return {
success: true,
data: response.data,
};
} catch (error) {
console.error('Error creating box:', error);
return {
success: false,
error: 'Could not create box',
};
}
}
export { getAllBoxes, createBox }
@@ -0,0 +1,21 @@
import { Item } from '../item.types';
interface ItemCardProps {
item: Item;
}
export const ItemCard: React.FC<ItemCardProps> = ({ item }) => {
return (
<li className='item'>
<div className='item-menu-more-container'></div>
<span className='item-name'>{item.name}</span>
<span className='menu-btn item material-icons'>
remove_red_eye
</span>
<span className='menu-btn item material-icons'>
more_vert
</span>
</li>
);
};
@@ -0,0 +1,66 @@
import Modal from '../../../modal';
import { Item, createItem } from '..';
interface ItemFormModalProps {
isOpen: boolean;
onClose: () => void;
onCreateItem: (item: Item) => void;
boxId?: string;
initialData?: Partial<Item>;
}
export const ItemFormModal: React.FC<ItemFormModalProps> = ({ isOpen, onClose, onCreateItem, boxId, initialData }) => {
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
const form = event.currentTarget;
const formData = new FormData(form);
const itemData: Item = {
name: formData.get('name') as string,
description: formData.get('description') as string,
quantity: parseInt(formData.get('quantity') as string, 10) as number,
};
try {
const response = await createItem(itemData, boxId);
if (response.success) {
onCreateItem(response.data as Item);
} else {
console.error(response.error);
}
} catch (err) {
console.error(err);
}
finally {
onClose();
}
};
return (
<Modal isOpen={isOpen}>
<form onSubmit={handleSubmit} id='form'>
<input value="" id='id' name='id' readOnly={true} hidden={true} />
<h1>Edit Item</h1>
<fieldset>
<label className='input' htmlFor="item-name">name</label>
<input className='input' id='item-name' name='name' type='text' />
<label className='input' htmlFor="item-description">description</label>
<input className='input' id='item-description' name='description' type='text' />
<label className='input' htmlFor="item-quantity">quantity</label>
<input className='input' id='item-quantity' name='quantity' type='number' />
<div className="modal-actions">
<button className='button' type="submit">Create</button>
<button className='button' type="button" onClick={onClose}>Cancel</button>
</div>
</fieldset>
</form>
</Modal>
);
};
+5
View File
@@ -0,0 +1,5 @@
export * from './item.types';
export * from './components/ItemCard';
export * from './components/ItemFormModal';
export * from './services/ItemService';
+6
View File
@@ -0,0 +1,6 @@
export interface Item {
_id?: string;
name: string;
description: string;
quantity: number;
}
@@ -0,0 +1,21 @@
import { api, ApiResponse } from '../../../api';
import { Item } from '..';
const createItem = async (itemData: Item, boxId?: string,): Promise<ApiResponse<Item | null>> => {
try {
const response = await api.post<Item>(`/boxes/${boxId}/items`, itemData);
return {
success: true,
data: response.data,
};
} catch (error) {
console.error('Error creating item:', error);
return {
success: false,
data: null,
error: 'Could not create item',
};
}
}
export { createItem }
-11
View File
@@ -1,11 +0,0 @@
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './app';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
+12
View File
@@ -0,0 +1,12 @@
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from './app';
import './index.css';
const rootElement = document.getElementById('root') as HTMLElement;
createRoot(rootElement).render(
<StrictMode>
<App />
</StrictMode>
);
-17
View File
@@ -1,17 +0,0 @@
import React, {useEffect, useRef} from 'react';
import './styles/inputBox.css';
const InputBox = ({ id, name, label, onChange, value, type, readOnly, hidden, focus}) => {
const focusInput = useRef(null);
useEffect(()=>{
if(focus) focusInput.current.focus();
},[])
return (
<>
<label className='input' htmlFor={id}>{label}</label>
<input ref={focusInput} className='input' readOnly={readOnly} hidden={hidden} onChange={onChange} value={value} id={id} name={name} type={type} />
</>
)
}
export default InputBox;
-68
View File
@@ -1,68 +0,0 @@
import React, { useState, useRef, useEffect } from 'react';
const Item = ({ data, itemHandler, boxID }) => {
const menu = useRef(null);
const handleClick = (event) => {
if (menu.current.contains(event.target))
return;
setShowMenu(false);
}
const handleClickEdit = () => {
itemHandler.handleEdit(boxID,data._id);
setShowMenu(false);
}
const handleClickMove = () => {
itemHandler.handleMove(boxID,data._id);
setShowMenu(false);
}
const handleClickRemove = () => {
itemHandler.handleRemove(boxID, data._id);
setShowMenu(false);
}
useEffect(() => {
document.addEventListener('mousedown', handleClick);
return () => {
document.removeEventListener('mousedown', handleClick);
};
}, []);
const [showMenu, setShowMenu] = useState(false);
const handleItemMenuClick = () => {
setShowMenu(true);
}
const handleItemViewClick = () => {
itemHandler.handleView(boxID,data._id);
}
const moreMenu = () => {
return (
<ul className='menu-more-list item'>
<li className='menu-action' onClick={handleClickEdit}>
<span className='material-icons icon'>edit</span>
<span className='label'>edit</span>
</li>
<li className='menu-action' onClick={handleClickMove}>
<span className='material-icons icon'>open_with</span>
<span className='label'>move</span>
</li>
<li className='menu-action' onClick={handleClickRemove}>
<span className='material-icons icon'>delete</span>
<span className='label'>remove</span>
</li>
</ul>
)
}
return (
<li className='item'>
<div ref={menu} className='item-menu-more-container'>{showMenu && moreMenu()}</div>
<span className='item-name'>{data.name}</span>
<span className='menu-btn item material-icons' onClick={handleItemViewClick}>
remove_red_eye
</span>
<span className='menu-btn item material-icons' onClick={handleItemMenuClick}>
more_vert
</span>
</li>
);
};
export default Item;
-29
View File
@@ -1,29 +0,0 @@
import React from 'react';
import Button from './button';
import './styles/modal.css';
import './styles/app.css';
const confirmStyle = {
backgroundColor: '#0495FC',
color: 'white'
}
const Modal = ({ show, content, handleSubmit, handleClose }) => {
const handleClickOff = (event) => {
if (event.target.classList.contains('modal-container'))
handleClose();
}
return show ?
<div className='modal-container' onClick={handleClickOff}>
<div className='modal-content'>
<div className='modal-body'>
{content}
</div>
<div className='modal-action'>
<Button label='cancel' onClick={handleClose} className='btn btn-close' />
{handleSubmit && <Button style={confirmStyle} form='id' label='submit' onClick={handleSubmit} className='btn btn-submit' />}
</div>
</div>
</div>
: null;
}
export default Modal;
+23
View File
@@ -0,0 +1,23 @@
import { ReactNode } from 'react';
import './styles/modal.css';
interface ModalProps {
isOpen: boolean;
children: ReactNode;
}
const Modal: React.FC<ModalProps> = ({ isOpen, children }) => {
if (!isOpen) return null;
return (
<div className='modal-container'>
<div className='modal-content'>
<div className='modal-body'>
{children}
</div>
</div>
</div>
);
};
export default Modal;
+16 -15
View File
@@ -1,26 +1,27 @@
.modal-container {
z-index: 1;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
justify-content: center;
align-items: center;
height: 100%;
width: 100%;
background-color: rgba(0, 0, 0, 0.32)
background-color: rgba(0, 0, 0, 0.32);
z-index: 1000;
padding: 20px;
overflow: auto;
}
.modal-content {
display: grid;
padding: 0.8rem;
grid-template-rows: 1fr auto;
width: 50%;
height: auto;
border-radius: 4px;
box-shadow: 0 11px 15px -7px
rgba(0,0,0,.2),
0 24px 38px 3px rgba(0,0,0,.14),
0 9px 46px 8px rgba(0,0,0,.12);
background: #fff;
background-color: white;
border-radius: 1em;
padding: 20px;
max-width: 500px;
width: 100%;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.modal-action {
display: grid;
gap: 0.2rem;
-13
View File
@@ -1,13 +0,0 @@
import React from 'react';
import './styles/textArea.css';
const TextArea = ({ id, name, label, onChange, value, readOnly, hidden}) => {
return (
<>
<label className='textarea' htmlFor={id}>{label}</label>
<textarea className='textarea' readOnly={readOnly} hidden={hidden} onChange={onChange} value={value} id={id} name={name} />
</>
)
}
export default TextArea;
+11
View File
@@ -0,0 +1,11 @@
{
"compilerOptions": {
"jsx": "react-jsx",
"module": "esnext",
"target": "esnext",
"moduleResolution": "node",
"esModuleInterop": true,
"strict": true,
"skipLibCheck": true
}
}
+11
View File
@@ -0,0 +1,11 @@
/// <reference types="vite/client" />
interface ImportMetaEnv {
VITE_API_HOST: string;
VITE_API_PORT: string;
VITE_API_PROTOCOL: string
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}
+14
View File
@@ -0,0 +1,14 @@
{
"compilerOptions": {
"jsx": "react-jsx",
"module": "esnext",
"target": "esnext",
"moduleResolution": "node",
"esModuleInterop": true,
"strict": true,
"skipLibCheck": true
},
"include": [
"src"
],
}
+7
View File
@@ -0,0 +1,7 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https://vite.dev/config/
export default defineConfig({
plugins: [react()],
})
+1
View File
@@ -0,0 +1 @@
.env
+30
View File
@@ -0,0 +1,30 @@
services:
mongo:
container_name: mongo-db
image: mongo:4.4.6
networks:
- mongo-network
restart: always
environment:
MONGO_INITDB_ROOT_USERNAME: ${MONGO_INITDB_ROOT_USERNAME}
MONGO_INITDB_ROOT_PASSWORD: ${MONGO_INITDB_ROOT_PASSWORD}
ports:
- 27017:27017
mongo-express:
container_name: mongo-express
image: mongo-express
networks:
- mongo-network
restart: always
ports:
- 8999:8081
environment:
ME_CONFIG_MONGODB_ADMINUSERNAME: ${ME_CONFIG_MONGODB_ADMINUSERNAME}
ME_CONFIG_MONGODB_ADMINPASSWORD: ${ME_CONFIG_MONGODB_ADMINPASSWORD}
ME_CONFIG_MONGODB_URL: mongodb://${MONGO_INITDB_ROOT_USERNAME}:${MONGO_INITDB_ROOT_PASSWORD}@mongo:27017/
ME_CONFIG_BASICAUTH: false
networks:
mongo-network:
name: mongo-network
+1
View File
@@ -1 +1,2 @@
/node_modules
.env
+1057 -1175
View File
File diff suppressed because it is too large Load Diff
+6 -5
View File
@@ -5,15 +5,16 @@
"main": "server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "nodemon server.js"
"start": "nodemon --inspect server.js"
},
"author": "",
"license": "ISC",
"dependencies": {
"body-parser": "^1.20.0",
"body-parser": "^2.2.0",
"cors": "^2.8.5",
"express": "^4.18.1",
"mongoose": "^6.4.0",
"nodemon": "^2.0.16"
"dotenv": "^16.4.7",
"express": "^5.1.0",
"mongoose": "^8.13.2",
"nodemon": "^3.1.9"
}
}
+49 -46
View File
@@ -1,24 +1,26 @@
require('dotenv').config();
const express = require('express');
const mongoose = require('mongoose');
const app = express();
const cors = require('cors');
const { response } = require('express');
app.use(cors(), express.json());
const port = process.env.PORT || 5000;
const MONGO_IP_ADDRESS = '192.168.100.102';
mongoose.connect('mongodb://root:exampleok@nas.local.net:27017/?authMechanism=DEFAULT', { useNewUrlParser: true });
const DB_HOST = 'server';
const DB_PORT = 27017;
const DB_USERNAME = process.env.DB_USERNAME;
const DB_PASSWORD = process.env.DB_PASSWORD;
const connectionString = `mongodb://${DB_USERNAME}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}?authMechanism=DEFAULT`;
mongoose.connect(connectionString);
const db = mongoose.connection;
db.on('error', console.error.bind(console, 'connection error:'));
db.once('open', function () {
console.log(`connected to mongodb: ${MONGO_IP_ADDRESS}`);
console.log(`connected to mongodb: ${DB_HOST}`);
});
/*------------MODELS------------*/
//create schema
var itemSchema = new mongoose.Schema({
@@ -30,7 +32,7 @@ var itemSchema = new mongoose.Schema({
var boxSchema = new mongoose.Schema({
name: String,
description: String,
item: [itemSchema],
items: [itemSchema],
sort: Number,
color: String,
});
@@ -44,50 +46,47 @@ var Item = mongoose.model('item', itemSchema, 'item');
app.listen(port, () => console.log(`Listening on port ${port}`));
// get all boxes
app.get('/box', async (request, response) => {
app.get('/boxes', async (request, response) => {
const boxes = await getAllBoxes();
response.send(boxes);
});
// create box
app.post('/box', async (request, response) => {
const numBoxes = await Box.countDocuments({});
const query = Object.assign({ sort: numBoxes }, request.body);
await Box.create(query);
const updatedBoxes = await getAllBoxes();
response.send(updatedBoxes);
app.post('/boxes', async (request, response) => {
try {
const numBoxes = await Box.countDocuments();
const newBox = await Box.create({
...request.body,
sort: numBoxes,
});
response.status(201).json(newBox);
} catch (error) {
console.error('Error creating box:', error);
response.status(500).json({ error: 'Failed to create box' });
}
});
//edit box
app.put('/box/:id', async (request, response) => {
const id = request.params.id;
await Box.updateOne({ _id: id }, { $set: request.body });
const updatedBoxes = await getAllBoxes();
response.send(updatedBoxes);
});
//delete box
app.delete('/box/:id', async (request, response) => {
await Box.deleteOne({ _id: request.params.id });
const updatedBoxes = await getAllBoxes();
response.send(updatedBoxes);
});
//change box sort
app.put('/box/:id/sort', async (request, response) => {
const source = await getBox(request.params.id);
const target = request.body;
await Box.updateOne({ _id: source._id }, { $set: { sort: target.sort } });
await Box.updateOne({ _id: target._id }, { $set: { sort: source.sort } });
const updatedBoxes = await getAllBoxes();
response.send(updatedBoxes);
})
//create item
app.post('/box/:id/item', async (request, response) => {
const id = request.params.id;
const query = { _id: id };
await Box.updateOne(query, {
$push: { item: request.body }
});
const updatedBox = await getBox(id);
response.send(updatedBox);
app.post('/boxes/:boxId/items', async (request, response) => {
try {
const item = new Item(request.body);
const savedItem = await item.save();
console.log(item)
const box = await Box.findById(request.params.boxId);
if (!box) {
throw new Error('Box not found');
}
box.items.push(savedItem);
await box.save();
response.send(item);
} catch (error) {
console.error('Error adding item to Box:', error);
throw error; // or handle it according to your needs
}
});
//edit item
app.put('/box/:boxId/item/:itemId', async (request, response) => {
@@ -146,3 +145,7 @@ const getBox = async (id) => {
const data = await Box.findById(id);
return data;
}
const getItem = async (id) => {
const data = await Item.findById(id);
return data;
}