diff --git a/backend/src/models/Item.ts b/backend/src/models/Item.ts
index bfd7dca..0e1e235 100644
--- a/backend/src/models/Item.ts
+++ b/backend/src/models/Item.ts
@@ -6,15 +6,17 @@ export interface IItem extends Document {
description: string;
tags: Types.Array;
quantity: number; // ← nuovo
+ used: number; // ← nuovo, opzionale
dateAdded: Date;
addedBy: string;
}
const itemSchema = new Schema({
name: { type: String, required: true },
- description: { type: String, required: true },
+ description: { type: String, required: false },
tags: [{ type: Schema.Types.ObjectId, ref: 'Tag' }],
quantity: { type: Number, default: 0, min: 0 }, // default 0, mai negativo
+ used: { type: Number, default: 0 }, // opzionale, default false
dateAdded: { type: Date, default: Date.now },
addedBy: { type: String },
});
diff --git a/backend/src/routes/items.ts b/backend/src/routes/items.ts
index 820d01a..c357da1 100644
--- a/backend/src/routes/items.ts
+++ b/backend/src/routes/items.ts
@@ -1,6 +1,6 @@
import { Router } from 'express';
import Item from '../models/Item.js';
-import Tag from '../models/Tag.js';
+import Tag from '../models/Tag.js';
import auth from '../middleware/auth.js';
const router = Router();
@@ -18,7 +18,7 @@ router.get('/search', async (req, res) => {
tagIds?: string;
};
- const regex = new RegExp(query, 'i');
+ const regex = new RegExp(query, 'i');
const filter: any = {
$and: [
{ $or: [{ name: regex }, { description: regex }] },
@@ -31,11 +31,12 @@ router.get('/search', async (req, res) => {
/* ──────────── POST: crea item ──────────── */
router.post('/', auth, async (req, res) => {
- const { name, description, tagIds, quantity = 0 } = req.body as {
+ const { name, description, tagIds, quantity = 0, used = false } = req.body as {
name: string;
description: string;
tagIds: string[];
quantity?: number;
+ used?: number;
};
const tags = await Tag.find({ _id: { $in: tagIds } });
@@ -44,6 +45,7 @@ router.post('/', auth, async (req, res) => {
description,
tags,
quantity,
+ used,
addedBy: req.user!.email,
});
@@ -61,10 +63,10 @@ router.put('/:id', auth, async (req, res) => {
};
const update: any = {};
- if (name !== undefined) update.name = name;
+ if (name !== undefined) update.name = name;
if (description !== undefined) update.description = description;
- if (quantity !== undefined) update.quantity = quantity;
- if (tagIds) update.tags = await Tag.find({ _id: { $in: tagIds } });
+ if (quantity !== undefined) update.quantity = quantity;
+ if (tagIds) update.tags = await Tag.find({ _id: { $in: tagIds } });
const item = await Item.findByIdAndUpdate(id, update, { new: true }).populate('tags');
if (!item) {
@@ -84,17 +86,46 @@ router.patch('/:id/quantity', auth, async (req, res) => {
return;
}
- const item = await Item.findByIdAndUpdate(
- id,
- { quantity },
- { new: true }
- ).populate('tags');
-
+ const item = await Item.findById(id);
if (!item) {
res.status(404).json({ message: 'Item non trovato' });
return;
}
- res.json(item);
+
+ if (item.used > quantity) {
+ res.status(400).json({ message: 'La quantità non può essere inferiore alla quantità utilizzata' });
+ return;
+ }
+
+ item.quantity = quantity;
+ await item.save();
+ res.json(await item.populate('tags'));
+});
+
+/* ──────────── PATCH: modifica solo la quantità utilizzata ──────────── */
+router.patch('/:id/usedquantity', auth, async (req, res) => {
+ const { id } = req.params;
+ const { quantity } = req.body as { quantity: number };
+
+ if (quantity < 0) {
+ res.status(400).json({ message: 'La quantità utilizzata non può essere negativa' });
+ return;
+ }
+
+ const item = await Item.findById(id);
+ if (!item) {
+ res.status(404).json({ message: 'Item non trovato' });
+ return;
+ }
+
+ if (quantity > item.quantity) {
+ res.status(400).json({ message: 'La quantità utilizzata non può essere maggiore della quantità totale' });
+ return;
+ }
+
+ item.used = quantity;
+ await item.save();
+ res.json(await item.populate('tags'));
});
/* ──────────── DELETE: elimina item ──────────── */
diff --git a/docker-compose.yaml b/docker-compose.yaml
index 9594a92..22bfed2 100644
--- a/docker-compose.yaml
+++ b/docker-compose.yaml
@@ -25,7 +25,7 @@ services:
depends_on:
- backend
ports:
- - "80:80"
+ - "2222:80"
volumes:
diff --git a/frontend/src/components/AddItemDialog.tsx b/frontend/src/components/AddItemDialog.tsx
index eccc1ee..6d29d42 100644
--- a/frontend/src/components/AddItemDialog.tsx
+++ b/frontend/src/components/AddItemDialog.tsx
@@ -3,6 +3,7 @@ import { useState } from 'react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Textarea } from '@/components/ui/textarea';
+import { Checkbox } from './ui/checkbox';
import { Badge } from '@/components/ui/badge';
import {
Dialog,
@@ -28,6 +29,8 @@ export const AddItemDialog = ({ open, onOpenChange, tags, onItemAdded }: AddItem
const [name, setName] = useState('');
const [description, setDescription] = useState('');
const [selectedTagIds, setSelectedTagIds] = useState([]);
+ const [qt, setQt] = useState(1); // Default quantity set to 1
+ const [used, setUsed] = useState(0); // Assuming this is for used tags, not implemented in the original code
const [isLoading, setIsLoading] = useState(false);
const { user } = useAuth();
@@ -51,7 +54,8 @@ export const AddItemDialog = ({ open, onOpenChange, tags, onItemAdded }: AddItem
const newItem = await inventoryService.createItem({
name: name.trim(),
description: description.trim(),
- quantity: 1, // Default quantity set to 1
+ quantity: qt, // Default quantity set to 1
+ used: used,
tagIds: selectedTagIds
});
@@ -119,8 +123,8 @@ export const AddItemDialog = ({ open, onOpenChange, tags, onItemAdded }: AddItem
key={tag._id}
variant="outline"
className={`cursor-pointer transition-all ${selectedTagIds.includes(tag._id)
- ? 'bg-blue-100 border-blue-300'
- : 'hover:bg-gray-100'
+ ? 'bg-blue-100 border-blue-300'
+ : 'hover:bg-gray-100'
}`}
onClick={() => toggleTag(tag._id)}
>
@@ -136,6 +140,34 @@ export const AddItemDialog = ({ open, onOpenChange, tags, onItemAdded }: AddItem
Clicca sui tag per selezionarli
+
+ Quantità *
+ {
+ const quantity = Math.max(1, parseInt(e.target.value, 10) || 1);
+ setQt(quantity);
+ setUsed(Math.min(used, quantity)); // Ensure used does not exceed quantity
+ }}
+ placeholder="Quantità totale"
+ required
+ />
+
+
+
+ Quantità Usata
+ {
+ const usedQuantity = Math.max(0, parseInt(e.target.value, 10) || 0);
+ setUsed(usedQuantity);
+ setQt(Math.max(qt, usedQuantity)); // Ensure quantity is at least as much as used
+ }}
+ placeholder="Quantità usata"
+ />
+
-
+ {/*
Quantità
setFormData(prev => ({ ...prev, quantity: parseInt(e.target.value) || 0 }))}
required
/>
-
+
*/}
Tag
diff --git a/frontend/src/components/InventoryDashboard.tsx b/frontend/src/components/InventoryDashboard.tsx
index 2857723..e132652 100644
--- a/frontend/src/components/InventoryDashboard.tsx
+++ b/frontend/src/components/InventoryDashboard.tsx
@@ -13,6 +13,7 @@ import { ManageTagsDialog } from '@/components/ManageTagsDialog';
import { DeleteItemDialog } from '@/components/DeleteItemDialog';
import { EditItemDialog } from '@/components/EditItemDialog';
import { QuantityControls } from '@/components/QuantityControls';
+import { QuantityUsedControls } from './QuantityUsedControls';
import { useToast } from '@/hooks/use-toast';
export const InventoryDashboard = () => {
@@ -304,11 +305,12 @@ export const InventoryDashboard = () => {
Nome
Descrizione
- Quantità
Tag
- Data Aggiunta
- Aggiunto da
- Azioni
+ Quantità
+ Utilizzato
+ Data Aggiunta
+ {/* Aggiunto da */}
+ Azioni
@@ -326,12 +328,6 @@ export const InventoryDashboard = () => {
{item.name}
{item.description}
-
-
-
{item.tags.map(tag => (
@@ -348,8 +344,20 @@ export const InventoryDashboard = () => {
))}
+
+
+
+
+
+
{formatDate(item.dateAdded)}
- {item.addedBy}
+ {/* {item.addedBy} */}
updateQuantity(item.quantity - 1)}
- disabled={isUpdating || item.quantity <= 0}
+ disabled={isUpdating || item.quantity <= 0 || item.used >= item.quantity}
className="h-8 w-8 p-0"
>
diff --git a/frontend/src/components/QuantityUsedControls.tsx b/frontend/src/components/QuantityUsedControls.tsx
new file mode 100644
index 0000000..051e31c
--- /dev/null
+++ b/frontend/src/components/QuantityUsedControls.tsx
@@ -0,0 +1,65 @@
+
+import { useState } from 'react';
+import { Button } from '@/components/ui/button';
+import { Plus, Minus } from 'lucide-react';
+import { InventoryItem } from '@/types/inventory';
+import { inventoryService } from '@/services/inventoryService';
+import { useToast } from '@/hooks/use-toast';
+
+interface QuantityControlsProps {
+ item: InventoryItem;
+ onQuantityUpdate: (updatedItem: InventoryItem) => void;
+}
+
+export const QuantityUsedControls = ({ item, onQuantityUpdate }: QuantityControlsProps) => {
+ const [isUpdating, setIsUpdating] = useState(false);
+ const { toast } = useToast();
+
+ const updateQuantity = async (newQuantity: number) => {
+ if (newQuantity < 0) return;
+
+ setIsUpdating(true);
+ try {
+ const updatedItem = await inventoryService.updateUsedItemQuantity(item._id, newQuantity);
+ onQuantityUpdate(updatedItem);
+ toast({
+ title: "Quantità aggiornata",
+ description: `${item.name}: ${newQuantity} pezzi`,
+ });
+ } catch (error) {
+ toast({
+ title: "Errore",
+ description: "Impossibile aggiornare la quantità",
+ variant: "destructive",
+ });
+ } finally {
+ setIsUpdating(false);
+ }
+ };
+
+ return (
+
+
updateQuantity(item.used - 1)}
+ disabled={isUpdating || item.used <= 0}
+ className="h-8 w-8 p-0"
+ >
+
+
+
+ {item.used}
+
+
updateQuantity(item.used + 1)}
+ disabled={isUpdating || item.used >= item.quantity}
+ className="h-8 w-8 p-0"
+ >
+
+
+
+ );
+};
diff --git a/frontend/src/services/inventoryService.ts b/frontend/src/services/inventoryService.ts
index 271839c..57f6558 100644
--- a/frontend/src/services/inventoryService.ts
+++ b/frontend/src/services/inventoryService.ts
@@ -80,6 +80,23 @@ export const inventoryService = {
return res.json();
},
+ async updateUsedItemQuantity(itemId: string, quantity: number): Promise {
+ const res = await fetch(`${API_URL}/items/${itemId}/usedquantity`, {
+ method: 'PATCH',
+ headers: {
+ 'Content-Type': 'application/json',
+ ...getAuthHeader(),
+ },
+ body: JSON.stringify({ quantity }),
+ });
+
+ if (!res.ok) {
+ const msg = (await res.json())?.message ?? 'Errore aggiornamento quantità usata';
+ throw new Error(msg);
+ }
+ return res.json();
+ },
+
async deleteItem(itemId: string): Promise {
const res = await fetch(`${API_URL}/items/${itemId}`, {
method: 'DELETE',
diff --git a/frontend/src/types/inventory.ts b/frontend/src/types/inventory.ts
index 9e684c3..7403737 100644
--- a/frontend/src/types/inventory.ts
+++ b/frontend/src/types/inventory.ts
@@ -10,6 +10,7 @@ export interface InventoryItem {
name: string;
description: string;
quantity: number;
+ used: number;
tags: Tag[];
dateAdded: string;
addedBy: string;
@@ -19,6 +20,7 @@ export interface CreateItemData {
name: string;
description: string;
quantity: number;
+ used: number;
tagIds: string[];
}