nicholas/storage-manager#1: rewrite 'move' feature; style tweaks
This commit is contained in:
+87
-35
@@ -1,16 +1,60 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect, useState, useReducer } from "react";
|
||||
import { getAllBoxes, Box, BoxCard, BoxFormModal } from "./features/box";
|
||||
import { Item, moveItem } from "./features/item";
|
||||
|
||||
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>();
|
||||
const [itemToBeMovedSourceBox, setItemToBeMovedSourceBox] = useState<string>();
|
||||
|
||||
|
||||
const handleSelectMoveToBox = async (targetBox: Box) => {
|
||||
|
||||
setIsItemMoving(false);
|
||||
|
||||
await moveItem(itemToBeMoved?._id!, targetBox._id!);
|
||||
|
||||
|
||||
setBoxes(prev => {
|
||||
const updatedBoxes = prev.map(box => {
|
||||
if (box._id === itemToBeMovedSourceBox) {
|
||||
return {
|
||||
...box,
|
||||
items: box.items?.filter(item => item._id !== itemToBeMoved?._id) ?? []
|
||||
};
|
||||
}
|
||||
if (box._id === targetBox._id && itemToBeMoved) {
|
||||
return {
|
||||
...box,
|
||||
items: [...(box.items ?? []), itemToBeMoved]
|
||||
};
|
||||
}
|
||||
return box;
|
||||
});
|
||||
return updatedBoxes;
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
const handleMoveItem = (item: Item, boxId: string) => {
|
||||
setItemToBeMoved(item);
|
||||
setItemToBeMovedSourceBox(boxId);
|
||||
setIsItemMoving(true);
|
||||
}
|
||||
|
||||
const handleCreateBox = (newBox: Box) => {
|
||||
setBoxes((boxes) => [...boxes, newBox]);
|
||||
};
|
||||
const handleDeleteBox = (id: string) => {
|
||||
setBoxes((boxes) => boxes.filter((box) => box._id !== id));
|
||||
};
|
||||
|
||||
const [isBoxFormModalOpen, setBoxFormModalOpen] = useState(false);
|
||||
const handleBoxFormModalOpen = () => {
|
||||
setBoxFormModalOpen(true);
|
||||
@@ -24,21 +68,26 @@ const App = () => {
|
||||
const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setSearch(event.target.value);
|
||||
};
|
||||
const filteredBoxes = boxes.filter((box) => {
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const ss = searchString.toLowerCase();
|
||||
const result = boxes.filter((box) => {
|
||||
|
||||
const matchingItems = box.items?.filter(
|
||||
(item) =>
|
||||
item.name.toLowerCase().includes(ss) ||
|
||||
item.description.toLowerCase().includes(ss),
|
||||
);
|
||||
const matchingItems = box.items?.filter(
|
||||
(item) =>
|
||||
item.name && item.name.toLowerCase().includes(ss) ||
|
||||
item.description && item.description.toLowerCase().includes(ss),
|
||||
);
|
||||
|
||||
return (
|
||||
box.name.toLowerCase().includes(ss) ||
|
||||
box.description.toLowerCase().includes(ss) ||
|
||||
(matchingItems && matchingItems.length > 0)
|
||||
);
|
||||
});
|
||||
return (
|
||||
box.name.toLowerCase().includes(ss) ||
|
||||
box.description.toLowerCase().includes(ss) ||
|
||||
(matchingItems && matchingItems.length > 0)
|
||||
);
|
||||
})
|
||||
setFilteredBoxes(result);
|
||||
}, [boxes, searchString]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
@@ -65,37 +114,40 @@ const App = () => {
|
||||
/>
|
||||
|
||||
<div className="utility-container">
|
||||
<button onClick={handleBoxFormModalOpen} className="group button-icon">
|
||||
{<Plus height="32" width="32" />}
|
||||
<button onClick={handleBoxFormModalOpen} className="group button-icon">
|
||||
{<Plus height="36" width="36" />}
|
||||
</button>
|
||||
<div className="flex justify-center p-2 align-middle">
|
||||
<div className="search-container group">
|
||||
<input
|
||||
onChange={handleSearchChange}
|
||||
spellCheck="false"
|
||||
type="text"
|
||||
id="box-filter"
|
||||
name="filter"
|
||||
className="input-search"
|
||||
/>
|
||||
<Search viewBox="0 0 24 24" className="search-icon" />
|
||||
<div className="flex justify-center p-2 align-middle max-w-lg w-full">
|
||||
<div className="search-container group">
|
||||
<input
|
||||
onChange={handleSearchChange}
|
||||
spellCheck="false"
|
||||
type="text"
|
||||
id="box-filter"
|
||||
name="filter"
|
||||
className="input-search"
|
||||
/>
|
||||
<Search viewBox="0 0 24 24" className="search-icon" />
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div className="box-card-list-container">
|
||||
<ul className="box-card-list">
|
||||
{filteredBoxes.map((item) => (
|
||||
<div className="box-card-list">
|
||||
{filteredBoxes.map((box) => (
|
||||
<BoxCard
|
||||
key={item._id}
|
||||
initialBox={item}
|
||||
onDelete={handleDeleteBox}
|
||||
key={box._id}
|
||||
initialBox={box}
|
||||
onDeleteBox={handleDeleteBox}
|
||||
onMoveItem={handleMoveItem}
|
||||
onClickMoveToBox={handleSelectMoveToBox}
|
||||
isItemMoving={box._id !== itemToBeMovedSourceBox && isItemMoving}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -47,7 +47,7 @@ export const GenericMenu = ({
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<div className="menu">
|
||||
<button
|
||||
className="group button-icon"
|
||||
ref={menuButtonRef}
|
||||
@@ -56,8 +56,8 @@ export const GenericMenu = ({
|
||||
<Icon {...iconProps} />
|
||||
</button>
|
||||
{isOpen && (
|
||||
<div ref={menuRef} className="menu-container">
|
||||
<ul className={`menu`}>
|
||||
<div ref={menuRef} className="menu-list-container">
|
||||
<ul className={`menu-list`}>
|
||||
{menuItems.map((item, index) => (
|
||||
<li className={`menu-item`} key={index}>
|
||||
<button onClick={item.onClick} className={`menu-button`}>
|
||||
|
||||
@@ -6,11 +6,13 @@ import { GenericMenu } from '../../../components/GenericMenu';
|
||||
|
||||
interface BoxCardProps {
|
||||
initialBox: Box;
|
||||
onDelete: (id: string) => void;
|
||||
onDeleteBox: (id: string) => void;
|
||||
onMoveItem: (item: Item, boxId: string) => void;
|
||||
isItemMoving: boolean;
|
||||
onClickMoveToBox: (box: Box) => void;
|
||||
}
|
||||
|
||||
export const BoxCard: React.FC<BoxCardProps> = ({ initialBox, onDelete }) => {
|
||||
const [items, setItems] = useState<Item[]>(initialBox.items!);
|
||||
export const BoxCard: React.FC<BoxCardProps> = ({ initialBox, onDeleteBox, onMoveItem, onClickMoveToBox, isItemMoving }) => {
|
||||
const [box, setBox] = useState<Box>(initialBox);
|
||||
|
||||
const [isBoxFormModalOpen, setBoxFormModalOpen] = useState(false);
|
||||
@@ -18,6 +20,10 @@ export const BoxCard: React.FC<BoxCardProps> = ({ initialBox, onDelete }) => {
|
||||
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setBox(initialBox);
|
||||
}, [initialBox]);
|
||||
|
||||
const toggleMenu = () => {
|
||||
setIsMenuOpen((prevState) => !prevState);
|
||||
};
|
||||
@@ -37,7 +43,11 @@ export const BoxCard: React.FC<BoxCardProps> = ({ initialBox, onDelete }) => {
|
||||
};
|
||||
|
||||
const handleCreateItem = (newItem: Item) => {
|
||||
setItems((items) => [...items, newItem]);
|
||||
const updatedBox: Box = {
|
||||
...box,
|
||||
items: [...(box.items ?? []), newItem],
|
||||
};
|
||||
setBox(updatedBox);
|
||||
};
|
||||
|
||||
const handleClickEditBox = () => {
|
||||
@@ -50,16 +60,22 @@ export const BoxCard: React.FC<BoxCardProps> = ({ initialBox, onDelete }) => {
|
||||
if (box._id != null) {
|
||||
const response = await deleteBox(box._id);
|
||||
if (response.success) {
|
||||
onDelete(box._id);
|
||||
onDeleteBox(box._id);
|
||||
} else {
|
||||
console.error("No data returned in the response");
|
||||
}
|
||||
}
|
||||
};
|
||||
const handleDeleteItem = (id: string) => {
|
||||
setItems((items) => items.filter((item) => item._id !== id));
|
||||
const updatedItems = (box.items ?? []).filter(item => item._id !== id);
|
||||
const updatedBox: Box = {
|
||||
...box,
|
||||
items: updatedItems,
|
||||
};
|
||||
setBox(updatedBox);
|
||||
};
|
||||
|
||||
|
||||
const menuItems = [
|
||||
{
|
||||
label: "Edit",
|
||||
@@ -91,41 +107,45 @@ export const BoxCard: React.FC<BoxCardProps> = ({ initialBox, onDelete }) => {
|
||||
onClose={handleCloseItemFormModal}
|
||||
boxId={box._id!}
|
||||
/>
|
||||
<li className="box-card">
|
||||
<div className="box-card-header">
|
||||
<div className="box-card-title-container">
|
||||
<h1 className="box-card-title">{box.name}</h1>
|
||||
<h2 className="box-card-subtitle">{box.description}</h2>
|
||||
<div className={`box-card-container relative ${isItemMoving ? 'target' : ''}`} >
|
||||
{isItemMoving && <div onClick={() => onClickMoveToBox(box)} className="overlay"></div>}
|
||||
<div className={`box-card`}>
|
||||
<div className="box-card-header">
|
||||
<div className="box-card-title-container">
|
||||
<h1 className="box-card-title">{box.name}</h1>
|
||||
<h2 className="box-card-subtitle">{box.description}</h2>
|
||||
</div>
|
||||
<GenericMenu
|
||||
onToggle={toggleMenu}
|
||||
menuItems={menuItems}
|
||||
isOpen={isMenuOpen}
|
||||
Icon={Ellipsis}
|
||||
iconProps={{ height: 24, width: 24 }}
|
||||
/>
|
||||
</div>
|
||||
<div className="item-card-list-container scrollbar-minimal">
|
||||
<ul className="item-card-list">
|
||||
{box.items?.map((item) => (
|
||||
<ItemCard
|
||||
key={item._id}
|
||||
boxId={box._id!}
|
||||
initialItem={item}
|
||||
onDeleteItem={handleDeleteItem}
|
||||
onMoveItem={onMoveItem}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<div className="actions">
|
||||
<button
|
||||
className="group button-icon"
|
||||
onClick={handleOpenItemFormModal}
|
||||
>
|
||||
<Plus height="24" width="24" />
|
||||
</button>
|
||||
</div>
|
||||
<GenericMenu
|
||||
onToggle={toggleMenu}
|
||||
menuItems={menuItems}
|
||||
isOpen={isMenuOpen}
|
||||
Icon={Ellipsis}
|
||||
iconProps={{ height: 24, width: 24 }}
|
||||
/>
|
||||
</div>
|
||||
<div className="item-card-list-container">
|
||||
<ul className="item-card-list">
|
||||
{items?.map((item) => (
|
||||
<ItemCard
|
||||
key={item._id}
|
||||
boxId={box._id!}
|
||||
initialItem={item}
|
||||
onDelete={handleDeleteItem}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
className="group button-icon"
|
||||
onClick={handleOpenItemFormModal}
|
||||
>
|
||||
<Plus height="24" width="24" />
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
</div >
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -6,17 +6,22 @@ import { GenericMenu } from "../../../components/GenericMenu";
|
||||
interface ItemCardProps {
|
||||
boxId: string;
|
||||
initialItem: Item;
|
||||
onDelete: (id: string) => void;
|
||||
onDeleteItem: (id: string) => void;
|
||||
onMoveItem: (item: Item, boxId: string) => void;
|
||||
}
|
||||
|
||||
export const ItemCard: React.FC<ItemCardProps> = ({
|
||||
boxId,
|
||||
initialItem,
|
||||
onDelete,
|
||||
onDeleteItem,
|
||||
onMoveItem
|
||||
}) => {
|
||||
const [isItemFormModalOpen, setIsItemFormModalOpen] = useState(false);
|
||||
const [item, setItem] = useState(initialItem);
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||
const [formModalMode, setFormModalMode] = useState("edit");
|
||||
|
||||
|
||||
const toggleMenu = () => {
|
||||
setIsMenuOpen((prevState) => !prevState);
|
||||
};
|
||||
@@ -24,8 +29,14 @@ export const ItemCard: React.FC<ItemCardProps> = ({
|
||||
setIsItemFormModalOpen(false);
|
||||
};
|
||||
const handleClickEditItem = async () => {
|
||||
setFormModalMode("edit");
|
||||
setIsItemFormModalOpen(true);
|
||||
};
|
||||
const handleClickViewItem = async () => {
|
||||
setFormModalMode("readonly");
|
||||
setIsItemFormModalOpen(true);
|
||||
|
||||
}
|
||||
const handleUpdateItem = (updatedItem: Item) => {
|
||||
setItem(updatedItem);
|
||||
};
|
||||
@@ -34,20 +45,25 @@ export const ItemCard: React.FC<ItemCardProps> = ({
|
||||
if (item._id != null) {
|
||||
const response = await deleteItem(boxId, item._id);
|
||||
if (response.success) {
|
||||
onDelete(item._id);
|
||||
onDeleteItem(item._id);
|
||||
} else {
|
||||
console.error("No data returned in the response");
|
||||
}
|
||||
}
|
||||
};
|
||||
const handleClickMoveItem = async () => {
|
||||
setIsMenuOpen(false);
|
||||
onMoveItem(item, boxId);
|
||||
}
|
||||
const menuItems = [
|
||||
{ label: "Edit", icon: Pencil, onClick: handleClickEditItem },
|
||||
{ label: "Delete", icon: Garbage, onClick: handleDeleteItem },
|
||||
{ label: "Move", icon: Pencil, onClick: handleClickMoveItem }
|
||||
];
|
||||
return (
|
||||
<>
|
||||
<ItemFormModal
|
||||
mode="edit"
|
||||
mode={formModalMode}
|
||||
isOpen={isItemFormModalOpen}
|
||||
onSubmitItem={handleUpdateItem}
|
||||
onClose={handleCloseItemFormModal}
|
||||
@@ -56,7 +72,9 @@ export const ItemCard: React.FC<ItemCardProps> = ({
|
||||
/>
|
||||
<li>
|
||||
<div className="item-container">
|
||||
<h3 className="item-name">{item.name}</h3>
|
||||
<div className='item' onClick={handleClickViewItem}>
|
||||
<h3 className="item-name">{item.name}</h3>
|
||||
</div>
|
||||
<GenericMenu
|
||||
isOpen={isMenuOpen}
|
||||
menuItems={menuItems}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState } from "react";
|
||||
import {Modal} from '../../../components/Modal';
|
||||
import { Modal } from '../../../components/Modal';
|
||||
import { Item, createItem, updateItem } from "..";
|
||||
|
||||
interface ItemFormModalProps {
|
||||
@@ -24,16 +24,22 @@ export const ItemFormModal: React.FC<ItemFormModalProps> = ({
|
||||
);
|
||||
const [itemQuantity, setItemQuantity] = useState(initialData?.quantity ?? 0);
|
||||
|
||||
const titles: Record<string, string> = {
|
||||
readonly: 'View',
|
||||
edit: 'Edit',
|
||||
create: 'Create',
|
||||
};
|
||||
const itemTitle = titles[mode] ?? 'Item';
|
||||
|
||||
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
|
||||
const isEdit = mode === "edit";
|
||||
|
||||
const itemData: Item = {
|
||||
name: itemName as string,
|
||||
description: itemDescription as string,
|
||||
quantity: itemQuantity as number,
|
||||
};
|
||||
const isEdit = mode === 'edit';
|
||||
|
||||
try {
|
||||
const response = isEdit
|
||||
@@ -60,12 +66,13 @@ export const ItemFormModal: React.FC<ItemFormModalProps> = ({
|
||||
<Modal isOpen={isOpen}>
|
||||
<form onSubmit={handleSubmit} id="form">
|
||||
<input value="" id="id" name="id" readOnly={true} hidden={true} />
|
||||
<h1>Edit Item</h1>
|
||||
<h1>{itemTitle} Item</h1>
|
||||
<fieldset>
|
||||
<label className="input-label" htmlFor="item-name">
|
||||
name
|
||||
</label>
|
||||
<input
|
||||
readOnly={mode === "readonly"}
|
||||
className="input"
|
||||
value={itemName}
|
||||
onChange={(e) => setItemName(e.currentTarget.value)}
|
||||
@@ -78,6 +85,7 @@ export const ItemFormModal: React.FC<ItemFormModalProps> = ({
|
||||
description
|
||||
</label>
|
||||
<input
|
||||
readOnly={mode === "readonly"}
|
||||
className="input"
|
||||
value={itemDescription}
|
||||
onChange={(e) => setItemDescription(e.currentTarget.value)}
|
||||
@@ -90,6 +98,7 @@ export const ItemFormModal: React.FC<ItemFormModalProps> = ({
|
||||
quantity
|
||||
</label>
|
||||
<input
|
||||
readOnly={mode === "readonly"}
|
||||
className="input"
|
||||
value={itemQuantity}
|
||||
onChange={(e) =>
|
||||
@@ -101,9 +110,9 @@ export const ItemFormModal: React.FC<ItemFormModalProps> = ({
|
||||
/>
|
||||
|
||||
<div>
|
||||
<button className="button" type="submit">
|
||||
{mode !== "readonly" && <button className="button" type="submit">
|
||||
Submit
|
||||
</button>
|
||||
</button>}
|
||||
<button className="button" type="button" onClick={onClose}>
|
||||
Cancel
|
||||
</button>
|
||||
|
||||
@@ -50,7 +50,8 @@ const deleteItem = async (
|
||||
return {
|
||||
success: true,
|
||||
};
|
||||
} catch (error) {
|
||||
}
|
||||
catch (error) {
|
||||
console.error("Error deleting item:", error);
|
||||
|
||||
return {
|
||||
@@ -59,4 +60,21 @@ const deleteItem = async (
|
||||
};
|
||||
}
|
||||
};
|
||||
export { createItem, updateItem, deleteItem };
|
||||
|
||||
const moveItem = async (itemId: string, boxId: string): Promise<ApiResponse<null>> => {
|
||||
try {
|
||||
const response = await api.patch<null>(`items/${itemId}`, { boxId });
|
||||
return {
|
||||
success: true,
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
console.error("Error moving item:", error);
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: "Could not move item",
|
||||
};
|
||||
}
|
||||
}
|
||||
export { createItem, updateItem, deleteItem, moveItem };
|
||||
|
||||
+47
-12
@@ -24,16 +24,28 @@ code {
|
||||
--color-subdued: #b3b3b3;
|
||||
}
|
||||
|
||||
.scrollbar-minimal::-webkit-scrollbar {
|
||||
@apply h-1 w-1;
|
||||
}
|
||||
|
||||
.scrollbar-minimal::-webkit-scrollbar-thumb {
|
||||
@apply bg-highlight rounded-full;
|
||||
}
|
||||
|
||||
.scrollbar-minimal::-webkit-scrollbar-track {
|
||||
@apply bg-transparent;
|
||||
}
|
||||
|
||||
html {
|
||||
@apply bg-black;
|
||||
}
|
||||
|
||||
.utility-container {
|
||||
@apply flex py-4 w-full justify-center;
|
||||
@apply flex py-4 w-full justify-center items-center;
|
||||
}
|
||||
|
||||
.button-icon {
|
||||
@apply cursor-pointer rounded-full p-2 hover:bg-highlight;
|
||||
@apply h-min cursor-pointer rounded-full p-2 hover:bg-highlight;
|
||||
}
|
||||
.icon {
|
||||
@apply fill-subdued group-hover:fill-white;
|
||||
@@ -43,10 +55,16 @@ html {
|
||||
@apply flex p-4;
|
||||
}
|
||||
.box-card-list {
|
||||
@apply m-0 flex flex-wrap space-x-4 p-0;
|
||||
@apply m-0 flex flex-wrap space-x-4 p-0 items-start;
|
||||
}
|
||||
.box-card-container {
|
||||
@apply box-border;
|
||||
}
|
||||
.box-card {
|
||||
@apply m-2 max-h-128 min-w-sm rounded-md bg-base p-6 text-subdued;
|
||||
@apply grid grid-rows-[auto_1fr_auto] m-2 min-h-64 max-h-128 min-w-sm rounded-md bg-base p-4 text-subdued;
|
||||
}
|
||||
.overlay {
|
||||
@apply absolute inset-0 bg-black opacity-50 z-10 border-dashed border-2 border-subdued hover:border-green-300 hover:cursor-pointer;
|
||||
}
|
||||
.box-card-header {
|
||||
@apply flex justify-between;
|
||||
@@ -57,11 +75,13 @@ html {
|
||||
.box-card-subtitle {
|
||||
@apply mb-4 text-sm font-semibold text-subdued;
|
||||
}
|
||||
|
||||
.menu-container {
|
||||
@apply absolute right-0 z-10 mt-2 w-36 rounded bg-highlight p-1 shadow-[0_16px_24px_rgba(0,0,0,0.3),_0_6px_8px_rgba(0,0,0,0.2)];
|
||||
}
|
||||
.menu {
|
||||
@apply z-50 flex items-center relative;
|
||||
}
|
||||
.menu-list-container {
|
||||
@apply absolute right-0 z-10 mt-2 w-36 rounded bg-highlight p-1 shadow-lg;
|
||||
}
|
||||
.menu-list {
|
||||
@apply text-sm;
|
||||
}
|
||||
.menu-button {
|
||||
@@ -75,10 +95,21 @@ html {
|
||||
@apply block w-full cursor-pointer text-left;
|
||||
}
|
||||
|
||||
.item-card-list-container {
|
||||
@apply max-h-64 overflow-y-auto;
|
||||
}
|
||||
.item-card-list {
|
||||
@apply mr-2;
|
||||
}
|
||||
.item-container {
|
||||
@apply flex justify-between p-2 hover:bg-highlight;
|
||||
@apply flex justify-between align-middle rounded-sm px-2 hover:bg-highlight;
|
||||
}
|
||||
.item {
|
||||
@apply flex flex-auto hover:cursor-pointer p-2;
|
||||
}
|
||||
.item-name {
|
||||
@apply cursor-default;
|
||||
}
|
||||
|
||||
.modal-container {
|
||||
@apply fixed inset-0 z-20 flex items-center justify-center bg-black/80;
|
||||
}
|
||||
@@ -97,13 +128,17 @@ html {
|
||||
}
|
||||
|
||||
.search-container {
|
||||
@apply relative w-full max-w-sm focus-within:border-white focus-within:fill-white;
|
||||
@apply relative w-full max-w-lg focus-within:fill-white;
|
||||
}
|
||||
|
||||
.input-search {
|
||||
@apply box-border w-full rounded-full border-1 border-subdued py-2 pr-4 pl-10 group-focus-within:border-1 group-focus-within:border-white focus:outline-none;
|
||||
@apply border-2 border-subdued bg-highlight group-hover:bg-highlight-2 w-full rounded-full py-2 pr-4 pl-10 focus:outline-none group-focus-within:border-white;
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
@apply absolute top-1/2 left-3 h-5 w-5 -translate-y-1/2 transform fill-subdued group-focus-within:fill-white;
|
||||
}
|
||||
|
||||
.actions {
|
||||
@apply mt-4;
|
||||
}
|
||||
|
||||
+28
-21
@@ -184,29 +184,36 @@ app.delete('/boxes/:boxId/items/:itemId', async (request, response) => {
|
||||
});
|
||||
|
||||
//move item
|
||||
app.post('/box/item/:itemId/:boxIdTarget', async (request, response) => {
|
||||
const itemID = request.params.itemId;
|
||||
const targetBoxID = request.params.boxIdTarget;
|
||||
//the way I am doing this cannot be the best way...fix me later
|
||||
app.patch('/items/:itemId', async (req, res) => {
|
||||
const { itemId } = req.params;
|
||||
const { boxId: targetBoxId } = req.body;
|
||||
|
||||
try {
|
||||
//find source box
|
||||
const box = await Box.find({ "item._id": itemID });
|
||||
//get item in source box
|
||||
const item = await Box.find({ "item._id": itemID }, { item: { $elemMatch: { _id: itemID } } });
|
||||
//remove item from source box
|
||||
await Box.updateOne({ _id: box[0]._id }, { $pull: { item: { _id: itemID } } });
|
||||
//add item to target box
|
||||
await Box.updateOne({ _id: targetBoxID }, { $push: { item: item[0].item[0] } });
|
||||
if (!targetBoxId) return res.status(400).json({ error: 'Target boxId is required' });
|
||||
|
||||
const boxes = await getAllBoxes();
|
||||
response.send(boxes);
|
||||
}
|
||||
catch (error) {
|
||||
response.json(error);
|
||||
}
|
||||
})
|
||||
/*------------/CONTROLLER------------*/
|
||||
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 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' });
|
||||
|
||||
|
||||
await Box.updateOne(
|
||||
{ _id: sourceBox._id },
|
||||
{ $pull: { items: item } }
|
||||
);
|
||||
|
||||
await Box.updateOne(
|
||||
{ _id: targetBoxId },
|
||||
{ $push: { items: item } }
|
||||
);
|
||||
|
||||
res.status(204).send();
|
||||
});
|
||||
|
||||
/*--------/CONTROLLER------------*/
|
||||
|
||||
const getAllBoxes = async (queryParameters) => {
|
||||
queryParameters = queryParameters ?? {};
|
||||
|
||||
Reference in New Issue
Block a user