updates
This commit is contained in:
@@ -7,9 +7,9 @@
|
||||
"mounts": [
|
||||
"type=bind,source=${localEnv:HOME}/.codex,target=/home/node/.codex"
|
||||
],
|
||||
"forwardPorts": [5173, 5000, 8999, 27017],
|
||||
"forwardPorts": [5174, 5000, 8999, 27017],
|
||||
"portsAttributes": {
|
||||
"5173": {
|
||||
"5174": {
|
||||
"label": "Vite frontend",
|
||||
"onAutoForward": "notify"
|
||||
},
|
||||
|
||||
@@ -10,9 +10,7 @@ services:
|
||||
DB_USERNAME: devroot
|
||||
DB_PASSWORD: devroot
|
||||
PORT: 5000
|
||||
VITE_API_HOST: localhost
|
||||
VITE_API_PORT: 5000
|
||||
VITE_API_PROTOCOL: http
|
||||
CORS_ORIGIN: http://localhost:5174
|
||||
networks:
|
||||
- mongo-network
|
||||
volumes:
|
||||
|
||||
+2
-1
@@ -1,3 +1,4 @@
|
||||
/.vs
|
||||
/.vscode
|
||||
.env
|
||||
.env
|
||||
node_modules/
|
||||
|
||||
@@ -30,6 +30,7 @@ DB_PORT=27017
|
||||
DB_USERNAME=devroot
|
||||
DB_PASSWORD=devroot
|
||||
PORT=5000
|
||||
CORS_ORIGIN=http://localhost:5174
|
||||
```
|
||||
|
||||
Start the app from two terminals inside the container:
|
||||
@@ -45,7 +46,7 @@ npm run dev
|
||||
```
|
||||
|
||||
Forwarded services:
|
||||
- Client: http://localhost:5173
|
||||
- Client: http://localhost:5174
|
||||
- API: http://localhost:5000
|
||||
- Mongo Express: http://localhost:8999
|
||||
- MongoDB: localhost:27017
|
||||
@@ -73,6 +74,7 @@ DB_PASSWORD=<password>
|
||||
DB_HOST=<hostname> //server
|
||||
DB_PORT=<db port> //27017
|
||||
PORT=<server port> //5000
|
||||
CORS_ORIGIN=<allowed client origin> //http://localhost:5174
|
||||
```
|
||||
|
||||
- Run `npm i` to install required node packages
|
||||
|
||||
@@ -2,11 +2,14 @@ import js from "@eslint/js";
|
||||
import globals from "globals";
|
||||
import reactHooks from "eslint-plugin-react-hooks";
|
||||
import reactRefresh from "eslint-plugin-react-refresh";
|
||||
import tseslint from "typescript-eslint";
|
||||
|
||||
export default [
|
||||
{ ignores: ["dist"] },
|
||||
js.configs.recommended,
|
||||
...tseslint.configs.recommended,
|
||||
{
|
||||
files: ["**/*.{js,jsx}"],
|
||||
files: ["**/*.{js,jsx,ts,tsx}"],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2020,
|
||||
globals: globals.browser,
|
||||
@@ -21,9 +24,9 @@ export default [
|
||||
"react-refresh": reactRefresh,
|
||||
},
|
||||
rules: {
|
||||
...js.configs.recommended.rules,
|
||||
...reactHooks.configs.recommended.rules,
|
||||
"no-unused-vars": ["error", { varsIgnorePattern: "^[A-Z_]" }],
|
||||
"no-unused-vars": "off",
|
||||
"@typescript-eslint/no-unused-vars": ["error", { varsIgnorePattern: "^[A-Z_]", argsIgnorePattern: "^_" }],
|
||||
"react-refresh/only-export-components": [
|
||||
"warn",
|
||||
{ allowConstantExport: true },
|
||||
|
||||
Generated
-3630
File diff suppressed because it is too large
Load Diff
+20
-17
@@ -5,28 +5,31 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"build": "tsc --noEmit && vite build",
|
||||
"lint": "eslint .",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tailwindcss/vite": "^4.1.3",
|
||||
"axios": "^1.8.4",
|
||||
"color": "^5.0.0",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"tailwindcss": "^4.1.3"
|
||||
"@tailwindcss/vite": "^4.3.1",
|
||||
"axios": "^1.18.0",
|
||||
"color": "^5.0.3",
|
||||
"react": "^19.2.7",
|
||||
"react-dom": "^19.2.7",
|
||||
"tailwindcss": "^4.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@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",
|
||||
"prettier-plugin-tailwindcss": "^0.6.11",
|
||||
"vite": "^6.2.0"
|
||||
"@eslint/js": "^10.0.1",
|
||||
"@types/react": "^19.2.17",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@vitejs/plugin-react": "^6.0.2",
|
||||
"eslint": "^10.5.0",
|
||||
"eslint-plugin-react-hooks": "^7.1.1",
|
||||
"eslint-plugin-react-refresh": "^0.5.3",
|
||||
"globals": "^17.6.0",
|
||||
"prettier-plugin-tailwindcss": "^0.8.0",
|
||||
"typescript": "^6.0.3",
|
||||
"typescript-eslint": "^8.61.0",
|
||||
"vite": "^8.0.16"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
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}`;
|
||||
const legacyApiHost = import.meta.env.VITE_API_HOST;
|
||||
const legacyApiPort = import.meta.env.VITE_API_PORT;
|
||||
const legacyApiProtocol = import.meta.env.VITE_API_PROTOCOL || "http";
|
||||
|
||||
const baseApiUrl =
|
||||
import.meta.env.VITE_API_URL ||
|
||||
(legacyApiHost
|
||||
? `${legacyApiProtocol}://${legacyApiHost}${legacyApiPort ? `:${legacyApiPort}` : ""}`
|
||||
: "");
|
||||
|
||||
export const api = axios.create({
|
||||
baseURL: baseApiUrl,
|
||||
|
||||
+47
-14
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useState, useReducer } from "react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { getAllBoxes, Box, BoxCard, BoxFormModal } from "./features/box";
|
||||
import { Item, moveItem } from "./features/item";
|
||||
|
||||
@@ -6,8 +6,6 @@ import { Plus, Search } from "./icons";
|
||||
|
||||
const App = () => {
|
||||
const [boxes, setBoxes] = useState<Box[]>([]);
|
||||
const [filteredBoxes, setFilteredBoxes] = useState<Box[]>([]);
|
||||
|
||||
const [isItemMoving, setIsItemMoving] = useState(false);
|
||||
|
||||
const [itemToBeMoved, setItemToBeMoved] = useState<Item>();
|
||||
@@ -15,18 +13,24 @@ const App = () => {
|
||||
|
||||
|
||||
const handleSelectMoveToBox = async (targetBox: Box) => {
|
||||
if (!itemToBeMoved?._id || !targetBox._id || !itemToBeMovedSourceBox) {
|
||||
setIsItemMoving(false);
|
||||
return;
|
||||
}
|
||||
|
||||
setIsItemMoving(false);
|
||||
|
||||
await moveItem(itemToBeMoved?._id!, targetBox._id!);
|
||||
|
||||
const response = await moveItem(itemToBeMoved._id, targetBox._id);
|
||||
if (!response.success) {
|
||||
console.error(response.error ?? "Could not move item");
|
||||
setIsItemMoving(false);
|
||||
return;
|
||||
}
|
||||
|
||||
setBoxes(prev => {
|
||||
const updatedBoxes = prev.map(box => {
|
||||
if (box._id === itemToBeMovedSourceBox) {
|
||||
return {
|
||||
...box,
|
||||
items: box.items?.filter(item => item._id !== itemToBeMoved?._id) ?? []
|
||||
items: box.items?.filter(item => item._id !== itemToBeMoved._id) ?? []
|
||||
};
|
||||
}
|
||||
if (box._id === targetBox._id && itemToBeMoved) {
|
||||
@@ -40,7 +44,9 @@ const App = () => {
|
||||
return updatedBoxes;
|
||||
});
|
||||
|
||||
|
||||
setIsItemMoving(false);
|
||||
setItemToBeMoved(undefined);
|
||||
setItemToBeMovedSourceBox(undefined);
|
||||
}
|
||||
|
||||
const handleMoveItem = (item: Item, boxId: string) => {
|
||||
@@ -52,9 +58,33 @@ const App = () => {
|
||||
const handleCreateBox = (newBox: Box) => {
|
||||
setBoxes((boxes) => [...boxes, newBox]);
|
||||
};
|
||||
const handleUpdateBox = (updatedBox: Box) => {
|
||||
setBoxes((boxes) => boxes.map((box) => box._id === updatedBox._id ? updatedBox : box));
|
||||
};
|
||||
const handleDeleteBox = (id: string) => {
|
||||
setBoxes((boxes) => boxes.filter((box) => box._id !== id));
|
||||
};
|
||||
const handleCreateItem = (boxId: string, newItem: Item) => {
|
||||
setBoxes((boxes) => boxes.map((box) => box._id === boxId
|
||||
? { ...box, items: [...(box.items ?? []), newItem] }
|
||||
: box
|
||||
));
|
||||
};
|
||||
const handleUpdateItem = (boxId: string, updatedItem: Item) => {
|
||||
setBoxes((boxes) => boxes.map((box) => box._id === boxId
|
||||
? {
|
||||
...box,
|
||||
items: box.items?.map((item) => item._id === updatedItem._id ? updatedItem : item) ?? [],
|
||||
}
|
||||
: box
|
||||
));
|
||||
};
|
||||
const handleDeleteItem = (boxId: string, itemId: string) => {
|
||||
setBoxes((boxes) => boxes.map((box) => box._id === boxId
|
||||
? { ...box, items: box.items?.filter((item) => item._id !== itemId) ?? [] }
|
||||
: box
|
||||
));
|
||||
};
|
||||
const [isBoxFormModalOpen, setBoxFormModalOpen] = useState(false);
|
||||
const handleBoxFormModalOpen = () => {
|
||||
setBoxFormModalOpen(true);
|
||||
@@ -70,9 +100,9 @@ const App = () => {
|
||||
};
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const filteredBoxes = useMemo(() => {
|
||||
const ss = searchString.toLowerCase();
|
||||
const result = boxes.filter((box) => {
|
||||
return boxes.filter((box) => {
|
||||
|
||||
const matchingItems = box.items?.filter(
|
||||
(item) =>
|
||||
@@ -86,7 +116,6 @@ const App = () => {
|
||||
(matchingItems && matchingItems.length > 0)
|
||||
);
|
||||
})
|
||||
setFilteredBoxes(result);
|
||||
}, [boxes, searchString]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -106,12 +135,12 @@ const App = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<BoxFormModal
|
||||
{isBoxFormModalOpen && <BoxFormModal
|
||||
mode="create"
|
||||
isOpen={isBoxFormModalOpen}
|
||||
onSubmitBox={handleCreateBox}
|
||||
onClose={handleBoxFormModalClose}
|
||||
/>
|
||||
/>}
|
||||
<div className='page-content'>
|
||||
|
||||
<div className="utility-container">
|
||||
@@ -142,7 +171,11 @@ const App = () => {
|
||||
<BoxCard
|
||||
key={box._id}
|
||||
initialBox={box}
|
||||
onUpdateBox={handleUpdateBox}
|
||||
onDeleteBox={handleDeleteBox}
|
||||
onCreateItem={handleCreateItem}
|
||||
onUpdateItem={handleUpdateItem}
|
||||
onDeleteItem={handleDeleteItem}
|
||||
onMoveItem={handleMoveItem}
|
||||
onClickMoveToBox={handleSelectMoveToBox}
|
||||
isItemMoving={box._id !== itemToBeMovedSourceBox && isItemMoving}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
import React, { useState } from "react";
|
||||
|
||||
const ColorSelect = ({ colorData, handler, boxColor }) => {
|
||||
const [style, setStyle] = useState(colorData.defaultStyle);
|
||||
|
||||
const handleHover = (isHover) => {
|
||||
setStyle(isHover ? colorData.lightStyle : colorData.defaultStyle);
|
||||
};
|
||||
|
||||
return (
|
||||
<label
|
||||
onMouseEnter={() => handleHover(true)}
|
||||
onMouseLeave={() => handleHover(false)}
|
||||
style={style}
|
||||
>
|
||||
<input
|
||||
checked={boxColor === colorData.title}
|
||||
onChange={handler}
|
||||
type="radio"
|
||||
value={colorData.title}
|
||||
></input>
|
||||
</label>
|
||||
);
|
||||
};
|
||||
|
||||
export default ColorSelect;
|
||||
@@ -29,6 +29,10 @@ export const GenericMenu = ({
|
||||
const menuRef = useRef<HTMLDivElement>(null);
|
||||
const menuButtonRef = useRef<HTMLButtonElement>(null);
|
||||
useEffect(() => {
|
||||
if (!isOpen) {
|
||||
return;
|
||||
}
|
||||
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (
|
||||
menuRef.current &&
|
||||
@@ -44,7 +48,7 @@ export const GenericMenu = ({
|
||||
return () => {
|
||||
document.removeEventListener("click", handleClickOutside);
|
||||
};
|
||||
}, []);
|
||||
}, [isOpen, onToggle]);
|
||||
|
||||
return (
|
||||
<div className="menu">
|
||||
|
||||
@@ -5,4 +5,8 @@ export interface Box {
|
||||
name: string;
|
||||
description: string;
|
||||
items?: Item[];
|
||||
sort?: number;
|
||||
color?: string;
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { useState } from "react";
|
||||
import { Box, BoxFormModal, deleteBox } from "../";
|
||||
import { Item, ItemCard, ItemFormModal } from "../../item";
|
||||
import { Plus, Ellipsis, Garbage, Pencil } from "../../../icons";
|
||||
@@ -6,24 +6,33 @@ import { GenericMenu } from '../../../components/GenericMenu';
|
||||
|
||||
interface BoxCardProps {
|
||||
initialBox: Box;
|
||||
onUpdateBox: (box: Box) => void;
|
||||
onDeleteBox: (id: string) => void;
|
||||
onCreateItem: (boxId: string, item: Item) => void;
|
||||
onUpdateItem: (boxId: string, item: Item) => void;
|
||||
onDeleteItem: (boxId: string, itemId: string) => void;
|
||||
onMoveItem: (item: Item, boxId: string) => void;
|
||||
isItemMoving: boolean;
|
||||
onClickMoveToBox: (box: Box) => void;
|
||||
}
|
||||
|
||||
export const BoxCard: React.FC<BoxCardProps> = ({ initialBox, onDeleteBox, onMoveItem, onClickMoveToBox, isItemMoving }) => {
|
||||
const [box, setBox] = useState<Box>(initialBox);
|
||||
|
||||
export const BoxCard: React.FC<BoxCardProps> = ({
|
||||
initialBox: box,
|
||||
onUpdateBox,
|
||||
onDeleteBox,
|
||||
onCreateItem,
|
||||
onUpdateItem,
|
||||
onDeleteItem,
|
||||
onMoveItem,
|
||||
onClickMoveToBox,
|
||||
isItemMoving,
|
||||
}) => {
|
||||
const boxId = box._id;
|
||||
const [isBoxFormModalOpen, setBoxFormModalOpen] = useState(false);
|
||||
const [isItemFormModalOpen, setItemFormModalOpen] = useState(false);
|
||||
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setBox(initialBox);
|
||||
}, [initialBox]);
|
||||
|
||||
const toggleMenu = () => {
|
||||
setIsMenuOpen((prevState) => !prevState);
|
||||
};
|
||||
@@ -35,26 +44,21 @@ export const BoxCard: React.FC<BoxCardProps> = ({ initialBox, onDeleteBox, onMov
|
||||
setItemFormModalOpen(false);
|
||||
};
|
||||
|
||||
const handleOpenBoxFormModal = () => {
|
||||
setBoxFormModalOpen(true);
|
||||
};
|
||||
const handleCloseBoxFormModal = () => {
|
||||
setBoxFormModalOpen(false);
|
||||
};
|
||||
|
||||
const handleCreateItem = (newItem: Item) => {
|
||||
const updatedBox: Box = {
|
||||
...box,
|
||||
items: [...(box.items ?? []), newItem],
|
||||
};
|
||||
setBox(updatedBox);
|
||||
if (boxId) {
|
||||
onCreateItem(boxId, newItem);
|
||||
}
|
||||
};
|
||||
|
||||
const handleClickEditBox = () => {
|
||||
setBoxFormModalOpen(true);
|
||||
};
|
||||
const handleUpdateBox = (updatedBox: Box) => {
|
||||
setBox(updatedBox);
|
||||
onUpdateBox(updatedBox);
|
||||
};
|
||||
const handleDeleteBox = async () => {
|
||||
if (box._id != null) {
|
||||
@@ -66,15 +70,16 @@ export const BoxCard: React.FC<BoxCardProps> = ({ initialBox, onDeleteBox, onMov
|
||||
}
|
||||
}
|
||||
};
|
||||
const handleUpdateItem = (updatedItem: Item) => {
|
||||
if (boxId) {
|
||||
onUpdateItem(boxId, updatedItem);
|
||||
}
|
||||
};
|
||||
const handleDeleteItem = (id: string) => {
|
||||
const updatedItems = (box.items ?? []).filter(item => item._id !== id);
|
||||
const updatedBox: Box = {
|
||||
...box,
|
||||
items: updatedItems,
|
||||
};
|
||||
setBox(updatedBox);
|
||||
if (boxId) {
|
||||
onDeleteItem(boxId, id);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const menuItems = [
|
||||
{
|
||||
@@ -93,20 +98,20 @@ export const BoxCard: React.FC<BoxCardProps> = ({ initialBox, onDeleteBox, onMov
|
||||
|
||||
return (
|
||||
<>
|
||||
<BoxFormModal
|
||||
{isBoxFormModalOpen && <BoxFormModal
|
||||
mode="edit"
|
||||
initialData={box}
|
||||
isOpen={isBoxFormModalOpen}
|
||||
onSubmitBox={handleUpdateBox}
|
||||
onClose={handleCloseBoxFormModal}
|
||||
/>
|
||||
<ItemFormModal
|
||||
/>}
|
||||
{isItemFormModalOpen && boxId && <ItemFormModal
|
||||
mode="create"
|
||||
isOpen={isItemFormModalOpen}
|
||||
onSubmitItem={handleCreateItem}
|
||||
onClose={handleCloseItemFormModal}
|
||||
boxId={box._id!}
|
||||
/>
|
||||
boxId={boxId}
|
||||
/>}
|
||||
<div className={`box-card-container relative ${isItemMoving ? 'target' : ''}`} >
|
||||
{isItemMoving && <div onClick={() => onClickMoveToBox(box)} className="overlay"></div>}
|
||||
<div className={`box-card`}>
|
||||
@@ -126,10 +131,11 @@ export const BoxCard: React.FC<BoxCardProps> = ({ initialBox, onDeleteBox, onMov
|
||||
<div className="item-card-list-container scrollbar-minimal">
|
||||
<ul className="item-card-list">
|
||||
{box.items?.map((item) => (
|
||||
<ItemCard
|
||||
boxId && <ItemCard
|
||||
key={item._id}
|
||||
boxId={box._id!}
|
||||
boxId={boxId}
|
||||
initialItem={item}
|
||||
onUpdateItem={handleUpdateItem}
|
||||
onDeleteItem={handleDeleteItem}
|
||||
onMoveItem={onMoveItem}
|
||||
/>
|
||||
@@ -137,10 +143,11 @@ export const BoxCard: React.FC<BoxCardProps> = ({ initialBox, onDeleteBox, onMov
|
||||
</ul>
|
||||
</div>
|
||||
<div className="actions">
|
||||
<button
|
||||
className="group button-icon"
|
||||
onClick={handleOpenItemFormModal}
|
||||
>
|
||||
<button
|
||||
className="group button-icon"
|
||||
onClick={handleOpenItemFormModal}
|
||||
disabled={!boxId}
|
||||
>
|
||||
<Plus height="24" width="24" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useState } from "react";
|
||||
import { Modal } from '../../../components/Modal';
|
||||
import { Box, createBox, updateBox } from "..";
|
||||
import { ApiResponse } from "../../../api";
|
||||
|
||||
interface BoxFormModalProps {
|
||||
mode: string;
|
||||
@@ -31,9 +32,18 @@ export const BoxFormModal: React.FC<BoxFormModalProps> = ({
|
||||
const isEdit = mode === "edit";
|
||||
|
||||
try {
|
||||
const response = isEdit
|
||||
? await updateBox(initialData?._id!, boxData)
|
||||
: await createBox(boxData);
|
||||
let response: ApiResponse<Box>;
|
||||
if (isEdit) {
|
||||
const boxId = initialData?._id;
|
||||
if (!boxId) {
|
||||
console.error("Cannot update box without an ID");
|
||||
return;
|
||||
}
|
||||
|
||||
response = await updateBox(boxId, boxData);
|
||||
} else {
|
||||
response = await createBox(boxData);
|
||||
}
|
||||
|
||||
if (!response.success || !response.data) {
|
||||
console.error(
|
||||
@@ -44,10 +54,9 @@ export const BoxFormModal: React.FC<BoxFormModalProps> = ({
|
||||
}
|
||||
|
||||
onSubmitBox(response.data);
|
||||
onClose();
|
||||
} catch (err) {
|
||||
console.error(`Error during ${isEdit ? "update" : "create"}:`, err);
|
||||
} finally {
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -55,7 +64,7 @@ export const BoxFormModal: React.FC<BoxFormModalProps> = ({
|
||||
<Modal isOpen={isOpen}>
|
||||
<form onSubmit={handleSubmit} id="form">
|
||||
<input value="" id="id" name="id" readOnly={true} hidden={true} />
|
||||
<h1 className="modal-header">Edit Box</h1>
|
||||
<h1 className="modal-header">{mode === "edit" ? "Edit" : "Create"} Box</h1>
|
||||
<fieldset>
|
||||
<label className="input-label" htmlFor="box-name">
|
||||
name
|
||||
|
||||
@@ -47,7 +47,7 @@ const updateBox = async (
|
||||
};
|
||||
const deleteBox = async (id: string): Promise<ApiResponse<null>> => {
|
||||
try {
|
||||
const response = await api.delete<null>(`boxes/${id}`);
|
||||
await api.delete<null>(`/boxes/${id}`);
|
||||
return {
|
||||
success: true,
|
||||
};
|
||||
|
||||
@@ -6,18 +6,19 @@ import { GenericMenu } from "../../../components/GenericMenu";
|
||||
interface ItemCardProps {
|
||||
boxId: string;
|
||||
initialItem: Item;
|
||||
onUpdateItem: (item: Item) => void;
|
||||
onDeleteItem: (id: string) => void;
|
||||
onMoveItem: (item: Item, boxId: string) => void;
|
||||
}
|
||||
|
||||
export const ItemCard: React.FC<ItemCardProps> = ({
|
||||
boxId,
|
||||
initialItem,
|
||||
initialItem: item,
|
||||
onUpdateItem,
|
||||
onDeleteItem,
|
||||
onMoveItem
|
||||
}) => {
|
||||
const [isItemFormModalOpen, setIsItemFormModalOpen] = useState(false);
|
||||
const [item, setItem] = useState(initialItem);
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||
const [formModalMode, setFormModalMode] = useState("edit");
|
||||
|
||||
@@ -38,7 +39,7 @@ export const ItemCard: React.FC<ItemCardProps> = ({
|
||||
|
||||
}
|
||||
const handleUpdateItem = (updatedItem: Item) => {
|
||||
setItem(updatedItem);
|
||||
onUpdateItem(updatedItem);
|
||||
};
|
||||
|
||||
const handleDeleteItem = async () => {
|
||||
@@ -62,14 +63,14 @@ export const ItemCard: React.FC<ItemCardProps> = ({
|
||||
];
|
||||
return (
|
||||
<>
|
||||
<ItemFormModal
|
||||
{isItemFormModalOpen && <ItemFormModal
|
||||
mode={formModalMode}
|
||||
isOpen={isItemFormModalOpen}
|
||||
onSubmitItem={handleUpdateItem}
|
||||
onClose={handleCloseItemFormModal}
|
||||
boxId={boxId}
|
||||
initialData={item}
|
||||
/>
|
||||
/>}
|
||||
<li>
|
||||
<div className="item-container">
|
||||
<div className='item' onClick={handleClickViewItem}>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useState } from "react";
|
||||
import { Modal } from '../../../components/Modal';
|
||||
import { Item, createItem, updateItem } from "..";
|
||||
import { ApiResponse } from "../../../api";
|
||||
|
||||
interface ItemFormModalProps {
|
||||
mode: string;
|
||||
@@ -33,18 +34,30 @@ export const ItemFormModal: React.FC<ItemFormModalProps> = ({
|
||||
|
||||
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
if (mode === "readonly") {
|
||||
return;
|
||||
}
|
||||
|
||||
const itemData: Item = {
|
||||
name: itemName as string,
|
||||
description: itemDescription as string,
|
||||
quantity: itemQuantity as number,
|
||||
name: itemName,
|
||||
description: itemDescription,
|
||||
quantity: Number.isFinite(itemQuantity) ? itemQuantity : 0,
|
||||
};
|
||||
const isEdit = mode === 'edit';
|
||||
|
||||
try {
|
||||
const response = isEdit
|
||||
? await updateItem(boxId!, initialData?._id!, itemData)
|
||||
: await createItem(itemData, boxId);
|
||||
let response: ApiResponse<Item>;
|
||||
if (isEdit) {
|
||||
const itemId = initialData?._id;
|
||||
if (!itemId) {
|
||||
console.error("Cannot update item without an ID");
|
||||
return;
|
||||
}
|
||||
|
||||
response = await updateItem(boxId, itemId, itemData);
|
||||
} else {
|
||||
response = await createItem(itemData, boxId);
|
||||
}
|
||||
|
||||
if (!response.success || !response.data) {
|
||||
console.error(
|
||||
@@ -55,10 +68,9 @@ export const ItemFormModal: React.FC<ItemFormModalProps> = ({
|
||||
}
|
||||
|
||||
onSubmitItem(response.data);
|
||||
onClose();
|
||||
} catch (err) {
|
||||
console.error(`Error during ${isEdit ? "update" : "create"}:`, err);
|
||||
} finally {
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -102,7 +114,7 @@ export const ItemFormModal: React.FC<ItemFormModalProps> = ({
|
||||
className="input"
|
||||
value={itemQuantity}
|
||||
onChange={(e) =>
|
||||
setItemQuantity(parseInt(e.currentTarget.value, 10))
|
||||
setItemQuantity(e.currentTarget.valueAsNumber)
|
||||
}
|
||||
id="item-quantity"
|
||||
name="quantity"
|
||||
|
||||
@@ -3,4 +3,6 @@ export interface Item {
|
||||
name: string;
|
||||
description: string;
|
||||
quantity: number;
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ const deleteItem = async (
|
||||
itemId: string,
|
||||
): Promise<ApiResponse<null>> => {
|
||||
try {
|
||||
const response = await api.delete<null>(`boxes/${boxId}/items/${itemId}`);
|
||||
await api.delete<null>(`/boxes/${boxId}/items/${itemId}`);
|
||||
return {
|
||||
success: true,
|
||||
};
|
||||
@@ -63,7 +63,7 @@ const deleteItem = async (
|
||||
|
||||
const moveItem = async (itemId: string, boxId: string): Promise<ApiResponse<null>> => {
|
||||
try {
|
||||
const response = await api.patch<null>(`items/${itemId}`, { boxId });
|
||||
await api.patch<null>(`/items/${itemId}`, { boxId });
|
||||
return {
|
||||
success: true,
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"jsx": "react-jsx",
|
||||
"module": "esnext",
|
||||
"target": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"moduleResolution": "bundler",
|
||||
"esModuleInterop": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true
|
||||
|
||||
Vendored
+1
@@ -1,6 +1,7 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
interface ImportMetaEnv {
|
||||
VITE_API_URL: string;
|
||||
VITE_API_HOST: string;
|
||||
VITE_API_PORT: string;
|
||||
VITE_API_PROTOCOL: string;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"jsx": "react-jsx",
|
||||
"module": "esnext",
|
||||
"target": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"moduleResolution": "bundler",
|
||||
"esModuleInterop": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true
|
||||
|
||||
@@ -5,4 +5,13 @@ import tailwindcss from "@tailwindcss/vite";
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react(), tailwindcss()],
|
||||
server: {
|
||||
host: "0.0.0.0",
|
||||
port: 5174,
|
||||
strictPort: true,
|
||||
proxy: {
|
||||
"/boxes": "http://localhost:5000",
|
||||
"/items": "http://localhost:5000",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
Generated
+4876
-1
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "storage-manager",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "concurrently -n server,client -c blue,green \"npm run dev --workspace server\" \"npm run dev --workspace client\"",
|
||||
"build": "npm run build --workspace client",
|
||||
"lint": "npm run lint --workspace client",
|
||||
"typecheck": "npm run typecheck --workspace client",
|
||||
"start": "npm run start --workspace server"
|
||||
},
|
||||
"workspaces": [
|
||||
"client",
|
||||
"server"
|
||||
],
|
||||
"devDependencies": {
|
||||
"concurrently": "^10.0.3"
|
||||
}
|
||||
}
|
||||
Generated
-1380
File diff suppressed because it is too large
Load Diff
+8
-7
@@ -4,17 +4,18 @@
|
||||
"description": "",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
"dev": "nodemon server.js",
|
||||
"start": "node server.js",
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"start": "nodemon --inspect server.js"
|
||||
"start:inspect": "nodemon --inspect server.js"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"body-parser": "^2.2.0",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.4.7",
|
||||
"express": "^5.1.0",
|
||||
"mongoose": "^8.13.2",
|
||||
"nodemon": "^3.1.9"
|
||||
"cors": "^2.8.6",
|
||||
"dotenv": "^17.4.2",
|
||||
"express": "^5.2.1",
|
||||
"mongoose": "^9.7.0",
|
||||
"nodemon": "^3.1.14"
|
||||
}
|
||||
}
|
||||
|
||||
+201
-151
@@ -1,216 +1,275 @@
|
||||
require('dotenv').config();
|
||||
require('dotenv').config({ quiet: true });
|
||||
const express = require('express');
|
||||
const mongoose = require('mongoose');
|
||||
const app = express();
|
||||
const cors = require('cors');
|
||||
|
||||
app.use(cors(), express.json());
|
||||
const corsOrigins = (process.env.CORS_ORIGIN || 'http://localhost:5174')
|
||||
.split(',')
|
||||
.map((origin) => origin.trim())
|
||||
.filter(Boolean);
|
||||
|
||||
const isAllowedCorsOrigin = (origin) => {
|
||||
if (!origin) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (corsOrigins.includes(origin)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
const { hostname } = new URL(origin);
|
||||
return hostname === 'localhost' || hostname === '127.0.0.1';
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
app.use(cors({
|
||||
origin: (origin, callback) => {
|
||||
callback(null, isAllowedCorsOrigin(origin));
|
||||
},
|
||||
}), express.json());
|
||||
|
||||
const port = process.env.PORT || 5000;
|
||||
const { ObjectId } = mongoose.Types;
|
||||
|
||||
const DB_HOST = process.env.DB_HOST || 'server';
|
||||
const DB_HOST = process.env.DB_HOST || 'localhost';
|
||||
const DB_PORT = process.env.DB_PORT || 27017;
|
||||
const DB_USERNAME = process.env.DB_USERNAME;
|
||||
const DB_PASSWORD = process.env.DB_PASSWORD;
|
||||
const DB_NAME = process.env.DB_NAME;
|
||||
|
||||
const connectionString = `mongodb://${DB_USERNAME}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}?authMechanism=DEFAULT`;
|
||||
mongoose.connect(connectionString);
|
||||
const credentials =
|
||||
DB_USERNAME && DB_PASSWORD
|
||||
? `${encodeURIComponent(DB_USERNAME)}:${encodeURIComponent(DB_PASSWORD)}@`
|
||||
: '';
|
||||
const database = DB_NAME ? `/${DB_NAME}` : '';
|
||||
const authSource = credentials && DB_NAME ? '?authSource=admin' : '';
|
||||
const connectionString = `mongodb://${credentials}${DB_HOST}:${DB_PORT}${database}${authSource}`;
|
||||
|
||||
mongoose.connect(connectionString).catch((error) => {
|
||||
console.error('Unable to connect to MongoDB.');
|
||||
console.error(`Check DB_HOST, DB_PORT, DB_USERNAME, DB_PASSWORD, and DB_NAME. Current host: ${DB_HOST}:${DB_PORT}`);
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
const db = mongoose.connection;
|
||||
|
||||
db.on('error', console.error.bind(console, 'connection error:'));
|
||||
db.once('open', function () {
|
||||
console.log(`connected to mongodb: ${DB_HOST}`);
|
||||
});
|
||||
const asyncHandler = (handler) => async (request, response, next) => {
|
||||
try {
|
||||
await handler(request, response, next);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
const isValidObjectId = (id) => ObjectId.isValid(id);
|
||||
|
||||
const removeUndefinedValues = (payload) => Object.fromEntries(
|
||||
Object.entries(payload).filter(([, value]) => value !== undefined)
|
||||
);
|
||||
|
||||
const getBoxPayload = (body) => removeUndefinedValues({
|
||||
name: body.name,
|
||||
description: body.description,
|
||||
color: body.color,
|
||||
});
|
||||
|
||||
const getItemPayload = (body) => removeUndefinedValues({
|
||||
name: body.name,
|
||||
description: body.description,
|
||||
quantity: body.quantity,
|
||||
});
|
||||
|
||||
const validateId = (id, response, label = 'ID') => {
|
||||
if (isValidObjectId(id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
response.status(400).json({ error: `Invalid ${label}` });
|
||||
return false;
|
||||
};
|
||||
|
||||
/*------------MODELS------------*/
|
||||
//create schema
|
||||
var itemSchema = new mongoose.Schema({
|
||||
name: String,
|
||||
description: String,
|
||||
quantity: Number
|
||||
});
|
||||
const itemSchema = new mongoose.Schema({
|
||||
name: { type: String, required: true, trim: true },
|
||||
description: { type: String, default: '', trim: true },
|
||||
quantity: { type: Number, required: true, min: 0, default: 1 },
|
||||
}, { timestamps: true });
|
||||
|
||||
var boxSchema = new mongoose.Schema({
|
||||
name: String,
|
||||
description: String,
|
||||
const boxSchema = new mongoose.Schema({
|
||||
name: { type: String, required: true, trim: true },
|
||||
description: { type: String, default: '', trim: true },
|
||||
items: [itemSchema],
|
||||
sort: Number,
|
||||
color: String,
|
||||
});
|
||||
sort: { type: Number, required: true, min: 0 },
|
||||
color: { type: String, default: '#ffffff', trim: true },
|
||||
}, { timestamps: true });
|
||||
|
||||
//compile schema into model
|
||||
var Box = mongoose.model('box', boxSchema, 'box');
|
||||
var Item = mongoose.model('item', itemSchema, 'item');
|
||||
const Box = mongoose.model('box', boxSchema, 'box');
|
||||
/*------------/MODELS------------*/
|
||||
|
||||
/*------------CONTROLLER------------*/
|
||||
app.listen(port, () => console.log(`Listening on port ${port}`));
|
||||
|
||||
// get all boxes
|
||||
app.get('/boxes', async (request, response) => {
|
||||
app.get('/boxes', asyncHandler(async (request, response) => {
|
||||
const boxes = await getAllBoxes();
|
||||
response.send(boxes);
|
||||
});
|
||||
}));
|
||||
|
||||
// create box
|
||||
app.post('/boxes', async (request, response) => {
|
||||
try {
|
||||
const numBoxes = await Box.countDocuments();
|
||||
const newBox = await Box.create({
|
||||
...request.body,
|
||||
sort: numBoxes,
|
||||
});
|
||||
app.post('/boxes', asyncHandler(async (request, response) => {
|
||||
const lastBox = await Box.findOne().sort({ sort: -1 }).select('sort');
|
||||
const newBox = await Box.create({
|
||||
...getBoxPayload(request.body),
|
||||
sort: lastBox ? lastBox.sort + 1 : 0,
|
||||
});
|
||||
|
||||
response.status(201).json(newBox);
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Error creating box:', error);
|
||||
response.status(500).json({ error: 'Failed to create box' });
|
||||
}
|
||||
});
|
||||
response.status(201).json(newBox);
|
||||
}));
|
||||
|
||||
// update box
|
||||
app.put('/boxes/:boxId', async (request, response) => {
|
||||
try {
|
||||
const { boxId } = request.params;
|
||||
const updatedBox = await Box.findByIdAndUpdate(
|
||||
boxId,
|
||||
{ ...request.body },
|
||||
{ new: true, runValidators: true }
|
||||
);
|
||||
|
||||
if (!updatedBox) {
|
||||
return response.status(404).json({ error: 'Box not found' });
|
||||
}
|
||||
|
||||
response.status(200).json(updatedBox);
|
||||
app.put('/boxes/:boxId', asyncHandler(async (request, response) => {
|
||||
const { boxId } = request.params;
|
||||
if (!validateId(boxId, response, 'box ID')) {
|
||||
return;
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Error updating box:', error);
|
||||
response.status(500).json({ error: 'Failed to update box' });
|
||||
|
||||
const updatedBox = await Box.findByIdAndUpdate(
|
||||
boxId,
|
||||
getBoxPayload(request.body),
|
||||
{ returnDocument: 'after', runValidators: true }
|
||||
);
|
||||
|
||||
if (!updatedBox) {
|
||||
return response.status(404).json({ error: 'Box not found' });
|
||||
}
|
||||
});
|
||||
|
||||
response.status(200).json(updatedBox);
|
||||
}));
|
||||
|
||||
|
||||
// delete box
|
||||
app.delete('/boxes/:boxId', async (request, response) => {
|
||||
try {
|
||||
const { boxId } = request.params;
|
||||
const result = await Box.findByIdAndDelete(boxId);
|
||||
|
||||
if (!result) {
|
||||
return response.status(404).json({ success: false, message: 'Box not found' });
|
||||
}
|
||||
|
||||
response.status(200).json({ success: true, message: 'Box deleted', data: result });
|
||||
app.delete('/boxes/:boxId', asyncHandler(async (request, response) => {
|
||||
const { boxId } = request.params;
|
||||
if (!validateId(boxId, response, 'box ID')) {
|
||||
return;
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Error deleting box:', error);
|
||||
response.status(500).json({ success: false, error: 'Failed to delete box' });
|
||||
|
||||
const result = await Box.findByIdAndDelete(boxId);
|
||||
|
||||
if (!result) {
|
||||
return response.status(404).json({ success: false, message: 'Box not found' });
|
||||
}
|
||||
});
|
||||
|
||||
response.status(200).json({ success: true, message: 'Box deleted', data: result });
|
||||
}));
|
||||
|
||||
// create item
|
||||
app.post('/boxes/:boxId/items', async (request, response) => {
|
||||
try {
|
||||
const item = new Item(request.body);
|
||||
const savedItem = await item.save();
|
||||
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;
|
||||
app.post('/boxes/:boxId/items', asyncHandler(async (request, response) => {
|
||||
const { boxId } = request.params;
|
||||
if (!validateId(boxId, response, 'box ID')) {
|
||||
return;
|
||||
}
|
||||
|
||||
});
|
||||
const box = await Box.findById(boxId);
|
||||
if (!box) {
|
||||
return response.status(404).json({ error: 'Box not found' });
|
||||
}
|
||||
|
||||
const item = box.items.create(getItemPayload(request.body));
|
||||
box.items.push(item);
|
||||
await box.save();
|
||||
response.status(201).json(item);
|
||||
}));
|
||||
// edit item
|
||||
app.put('/boxes/:boxId/items/:itemId', async (request, response) => {
|
||||
app.put('/boxes/:boxId/items/:itemId', asyncHandler(async (request, response) => {
|
||||
const { boxId, itemId } = request.params;
|
||||
const { name, description, quantity } = request.body;
|
||||
try {
|
||||
const updatedBox = await Box.findOneAndUpdate(
|
||||
{ _id: boxId, 'items._id': itemId },
|
||||
{
|
||||
$set: {
|
||||
'items.$.name': name,
|
||||
'items.$.description': description,
|
||||
'items.$.quantity': quantity,
|
||||
},
|
||||
},
|
||||
{ new: true }
|
||||
);
|
||||
const updatedItem = updatedBox.items.find(item => item._id.toString() === itemId);
|
||||
|
||||
if (!updatedItem) {
|
||||
return response.status(404).json({ error: 'Box or item not found' });
|
||||
}
|
||||
|
||||
response.status(200).json(updatedItem);
|
||||
if (!validateId(boxId, response, 'box ID') || !validateId(itemId, response, 'item ID')) {
|
||||
return;
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Error updating item:', error);
|
||||
response.status(500).json({ error: 'Failed to update item' });
|
||||
|
||||
const box = await Box.findById(boxId);
|
||||
if (!box) {
|
||||
return response.status(404).json({ error: 'Box not found' });
|
||||
}
|
||||
});
|
||||
|
||||
const item = box.items.id(itemId);
|
||||
if (!item) {
|
||||
return response.status(404).json({ error: 'Item not found' });
|
||||
}
|
||||
|
||||
item.set(getItemPayload(request.body));
|
||||
await box.save();
|
||||
response.status(200).json(item);
|
||||
}));
|
||||
|
||||
// delete item from box
|
||||
app.delete('/boxes/:boxId/items/:itemId', async (request, response) => {
|
||||
app.delete('/boxes/:boxId/items/:itemId', asyncHandler(async (request, response) => {
|
||||
const { boxId, itemId } = request.params;
|
||||
|
||||
try {
|
||||
const updatedBox = await Box.findByIdAndUpdate(
|
||||
boxId,
|
||||
{
|
||||
$pull: { items: { _id: itemId } }
|
||||
},
|
||||
{ new: true }
|
||||
);
|
||||
|
||||
if (!updatedBox) {
|
||||
return response.status(404).json({ error: 'Box not found' });
|
||||
}
|
||||
|
||||
response.json(updatedBox);
|
||||
if (!validateId(boxId, response, 'box ID') || !validateId(itemId, response, 'item ID')) {
|
||||
return;
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Error deleting item:', error);
|
||||
response.status(500).json({ error: 'Failed to delete item' });
|
||||
|
||||
const box = await Box.findById(boxId);
|
||||
if (!box) {
|
||||
return response.status(404).json({ error: 'Box not found' });
|
||||
}
|
||||
});
|
||||
|
||||
const item = box.items.id(itemId);
|
||||
if (!item) {
|
||||
return response.status(404).json({ error: 'Item not found' });
|
||||
}
|
||||
|
||||
item.deleteOne();
|
||||
await box.save();
|
||||
response.json(box);
|
||||
}));
|
||||
|
||||
//move item
|
||||
app.patch('/items/:itemId', async (req, res) => {
|
||||
app.patch('/items/:itemId', asyncHandler(async (req, res) => {
|
||||
const { itemId } = req.params;
|
||||
const { boxId: targetBoxId } = req.body;
|
||||
|
||||
if (!targetBoxId) return res.status(400).json({ error: 'Target boxId is required' });
|
||||
if (!validateId(itemId, res, 'item ID') || !validateId(targetBoxId, res, 'target box ID')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const item = await Item.findById(itemId);
|
||||
if (!item) return res.status(404).json({ error: 'Item not found' });
|
||||
console.log(item);
|
||||
|
||||
const sourceBox = await Box.findOne({ "items._id": item._id });
|
||||
const sourceBox = await Box.findOne({ "items._id": itemId });
|
||||
const targetBox = await Box.findById(targetBoxId);
|
||||
if (!targetBox) return res.status(404).json({ error: 'Target box not found' });
|
||||
if (!sourceBox) return res.status(404).json({ error: 'Source box not found' });
|
||||
if (sourceBox._id.equals(targetBox._id)) return res.status(204).send();
|
||||
|
||||
const item = sourceBox.items.id(itemId);
|
||||
if (!item) return res.status(404).json({ error: 'Item not found' });
|
||||
const itemToMove = item.toObject();
|
||||
|
||||
await Box.updateOne(
|
||||
{ _id: sourceBox._id },
|
||||
{ $pull: { items: item } }
|
||||
);
|
||||
|
||||
await Box.updateOne(
|
||||
{ _id: targetBoxId },
|
||||
{ $push: { items: item } }
|
||||
);
|
||||
item.deleteOne();
|
||||
targetBox.items.push(itemToMove);
|
||||
await sourceBox.save();
|
||||
await targetBox.save();
|
||||
|
||||
res.status(204).send();
|
||||
}));
|
||||
|
||||
app.use((error, request, response, next) => {
|
||||
console.error(error);
|
||||
|
||||
if (error.name === 'ValidationError') {
|
||||
return response.status(400).json({ error: error.message });
|
||||
}
|
||||
|
||||
response.status(500).json({ error: 'Internal server error' });
|
||||
});
|
||||
|
||||
/*--------/CONTROLLER------------*/
|
||||
@@ -220,12 +279,3 @@ const getAllBoxes = async (queryParameters) => {
|
||||
const data = await Box.find(queryParameters).sort({ sort: 1 });
|
||||
return data;
|
||||
}
|
||||
|
||||
const getBox = async (id) => {
|
||||
const data = await Box.findById(id);
|
||||
return data;
|
||||
}
|
||||
const getItem = async (id) => {
|
||||
const data = await Item.findById(id);
|
||||
return data;
|
||||
}
|
||||
Reference in New Issue
Block a user