nicholas/storage-manager#1: new directory structure, rewrite add box, item functionality
This commit is contained in:
@@ -1 +1,3 @@
|
|||||||
/.vs
|
/.vs
|
||||||
|
/.vscode
|
||||||
|
.env
|
||||||
+23
-5
@@ -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
|
# dependencies
|
||||||
/node_modules
|
/node_modules
|
||||||
@@ -17,7 +35,7 @@
|
|||||||
.env.development.local
|
.env.development.local
|
||||||
.env.test.local
|
.env.test.local
|
||||||
.env.production.local
|
.env.production.local
|
||||||
|
*.local
|
||||||
npm-debug.log*
|
dist
|
||||||
yarn-debug.log*
|
dist-ssr
|
||||||
yarn-error.log*
|
*.env
|
||||||
@@ -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.
|
||||||
@@ -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">
|
<body class="scroll-container">
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/index.tsx"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
Generated
+2553
-13615
File diff suppressed because it is too large
Load Diff
+21
-35
@@ -1,43 +1,29 @@
|
|||||||
{
|
{
|
||||||
"name": "storage",
|
"name": "client",
|
||||||
"version": "0.1.0",
|
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"version": "0.0.0",
|
||||||
"@testing-library/jest-dom": "^5.16.4",
|
"type": "module",
|
||||||
"@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"
|
|
||||||
},
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "react-scripts start",
|
"dev": "vite",
|
||||||
"server": "nodemon ../server/server.js",
|
"build": "vite build",
|
||||||
"build": "react-scripts build",
|
"lint": "eslint .",
|
||||||
"test": "react-scripts test",
|
"preview": "vite preview"
|
||||||
"eject": "react-scripts eject"
|
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"dependencies": {
|
||||||
"extends": "react-app"
|
"axios": "^1.8.4",
|
||||||
},
|
"color": "^5.0.0",
|
||||||
"browserslist": {
|
"react": "^19.0.0",
|
||||||
"production": [
|
"react-dom": "^19.0.0"
|
||||||
">0.2%",
|
|
||||||
"not dead",
|
|
||||||
"not op_mini all"
|
|
||||||
],
|
|
||||||
"development": [
|
|
||||||
"last 1 chrome version",
|
|
||||||
"last 1 firefox version",
|
|
||||||
"last 1 safari version"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
);
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
export interface ApiResponse<T> {
|
||||||
|
success: boolean;
|
||||||
|
data?: T;
|
||||||
|
message?: string;
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export { api } from './api';
|
||||||
|
export * from './apiResponse.types';
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
||||||
@@ -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 |
@@ -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;
|
|
||||||
@@ -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';
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
const Color = ({ colorData, handler, boxColor }) => {
|
const ColorSelect = ({ colorData, handler, boxColor }) => {
|
||||||
const [style, setStyle] = useState(colorData.defaultStyle);
|
const [style, setStyle] = useState(colorData.defaultStyle);
|
||||||
|
|
||||||
const handleHover = (isHover) => {
|
const handleHover = (isHover) => {
|
||||||
@@ -14,4 +14,4 @@ const Color = ({ colorData, handler, boxColor }) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Color;
|
export default ColorSelect;
|
||||||
@@ -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>
|
||||||
|
|
||||||
|
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -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>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export * from './item.types';
|
||||||
|
|
||||||
|
export * from './components/ItemCard';
|
||||||
|
export * from './components/ItemFormModal';
|
||||||
|
export * from './services/ItemService';
|
||||||
@@ -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 }
|
||||||
@@ -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')
|
|
||||||
);
|
|
||||||
@@ -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>
|
||||||
|
);
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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
@@ -1,26 +1,27 @@
|
|||||||
.modal-container {
|
.modal-container {
|
||||||
z-index: 1;
|
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
height: 100%;
|
background-color: rgba(0, 0, 0, 0.32);
|
||||||
width: 100%;
|
z-index: 1000;
|
||||||
background-color: rgba(0, 0, 0, 0.32)
|
padding: 20px;
|
||||||
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-content {
|
.modal-content {
|
||||||
display: grid;
|
background-color: white;
|
||||||
padding: 0.8rem;
|
border-radius: 1em;
|
||||||
grid-template-rows: 1fr auto;
|
padding: 20px;
|
||||||
width: 50%;
|
max-width: 500px;
|
||||||
height: auto;
|
width: 100%;
|
||||||
border-radius: 4px;
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-action {
|
.modal-action {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 0.2rem;
|
gap: 0.2rem;
|
||||||
|
|||||||
@@ -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;
|
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"module": "esnext",
|
||||||
|
"target": "esnext",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"strict": true,
|
||||||
|
"skipLibCheck": true
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+11
@@ -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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"module": "esnext",
|
||||||
|
"target": "esnext",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"strict": true,
|
||||||
|
"skipLibCheck": true
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src"
|
||||||
|
],
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import react from '@vitejs/plugin-react'
|
||||||
|
|
||||||
|
// https://vite.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()],
|
||||||
|
})
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
.env
|
||||||
@@ -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 +1,2 @@
|
|||||||
/node_modules
|
/node_modules
|
||||||
|
.env
|
||||||
Generated
+1057
-1175
File diff suppressed because it is too large
Load Diff
+6
-5
@@ -5,15 +5,16 @@
|
|||||||
"main": "server.js",
|
"main": "server.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
"start": "nodemon server.js"
|
"start": "nodemon --inspect server.js"
|
||||||
},
|
},
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"body-parser": "^1.20.0",
|
"body-parser": "^2.2.0",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"express": "^4.18.1",
|
"dotenv": "^16.4.7",
|
||||||
"mongoose": "^6.4.0",
|
"express": "^5.1.0",
|
||||||
"nodemon": "^2.0.16"
|
"mongoose": "^8.13.2",
|
||||||
|
"nodemon": "^3.1.9"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+49
-46
@@ -1,24 +1,26 @@
|
|||||||
|
require('dotenv').config();
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const mongoose = require('mongoose');
|
const mongoose = require('mongoose');
|
||||||
const app = express();
|
const app = express();
|
||||||
const cors = require('cors');
|
const cors = require('cors');
|
||||||
const { response } = require('express');
|
|
||||||
|
|
||||||
app.use(cors(), express.json());
|
app.use(cors(), express.json());
|
||||||
|
|
||||||
const port = process.env.PORT || 5000;
|
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;
|
const db = mongoose.connection;
|
||||||
|
|
||||||
db.on('error', console.error.bind(console, 'connection error:'));
|
db.on('error', console.error.bind(console, 'connection error:'));
|
||||||
db.once('open', function () {
|
db.once('open', function () {
|
||||||
console.log(`connected to mongodb: ${MONGO_IP_ADDRESS}`);
|
console.log(`connected to mongodb: ${DB_HOST}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*------------MODELS------------*/
|
/*------------MODELS------------*/
|
||||||
//create schema
|
//create schema
|
||||||
var itemSchema = new mongoose.Schema({
|
var itemSchema = new mongoose.Schema({
|
||||||
@@ -30,7 +32,7 @@ var itemSchema = new mongoose.Schema({
|
|||||||
var boxSchema = new mongoose.Schema({
|
var boxSchema = new mongoose.Schema({
|
||||||
name: String,
|
name: String,
|
||||||
description: String,
|
description: String,
|
||||||
item: [itemSchema],
|
items: [itemSchema],
|
||||||
sort: Number,
|
sort: Number,
|
||||||
color: String,
|
color: String,
|
||||||
});
|
});
|
||||||
@@ -44,50 +46,47 @@ var Item = mongoose.model('item', itemSchema, 'item');
|
|||||||
app.listen(port, () => console.log(`Listening on port ${port}`));
|
app.listen(port, () => console.log(`Listening on port ${port}`));
|
||||||
|
|
||||||
// get all boxes
|
// get all boxes
|
||||||
app.get('/box', async (request, response) => {
|
app.get('/boxes', async (request, response) => {
|
||||||
const boxes = await getAllBoxes();
|
const boxes = await getAllBoxes();
|
||||||
response.send(boxes);
|
response.send(boxes);
|
||||||
});
|
});
|
||||||
|
|
||||||
// create box
|
// create box
|
||||||
app.post('/box', async (request, response) => {
|
app.post('/boxes', async (request, response) => {
|
||||||
const numBoxes = await Box.countDocuments({});
|
try {
|
||||||
const query = Object.assign({ sort: numBoxes }, request.body);
|
const numBoxes = await Box.countDocuments();
|
||||||
await Box.create(query);
|
const newBox = await Box.create({
|
||||||
const updatedBoxes = await getAllBoxes();
|
...request.body,
|
||||||
response.send(updatedBoxes);
|
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
|
//create item
|
||||||
app.post('/box/:id/item', async (request, response) => {
|
app.post('/boxes/:boxId/items', async (request, response) => {
|
||||||
const id = request.params.id;
|
try {
|
||||||
const query = { _id: id };
|
const item = new Item(request.body);
|
||||||
await Box.updateOne(query, {
|
const savedItem = await item.save();
|
||||||
$push: { item: request.body }
|
console.log(item)
|
||||||
});
|
const box = await Box.findById(request.params.boxId);
|
||||||
const updatedBox = await getBox(id);
|
if (!box) {
|
||||||
response.send(updatedBox);
|
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
|
//edit item
|
||||||
app.put('/box/:boxId/item/:itemId', async (request, response) => {
|
app.put('/box/:boxId/item/:itemId', async (request, response) => {
|
||||||
@@ -145,4 +144,8 @@ const getAllBoxes = async (queryParameters) => {
|
|||||||
const getBox = async (id) => {
|
const getBox = async (id) => {
|
||||||
const data = await Box.findById(id);
|
const data = await Box.findById(id);
|
||||||
return data;
|
return data;
|
||||||
|
}
|
||||||
|
const getItem = async (id) => {
|
||||||
|
const data = await Item.findById(id);
|
||||||
|
return data;
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user