diff --git a/src/modules/it-inventory/components/it-inventory-module.tsx b/src/modules/it-inventory/components/it-inventory-module.tsx index 843408e..50ad9f5 100644 --- a/src/modules/it-inventory/components/it-inventory-module.tsx +++ b/src/modules/it-inventory/components/it-inventory-module.tsx @@ -1,43 +1,86 @@ -'use client'; +"use client"; -import { useState } from 'react'; -import { Plus, Pencil, Trash2, Search } from 'lucide-react'; -import { Button } from '@/shared/components/ui/button'; -import { Input } from '@/shared/components/ui/input'; -import { Label } from '@/shared/components/ui/label'; -import { Textarea } from '@/shared/components/ui/textarea'; -import { Badge } from '@/shared/components/ui/badge'; -import { Card, CardContent, CardHeader, CardTitle } from '@/shared/components/ui/card'; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/shared/components/ui/select'; -import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@/shared/components/ui/dialog'; -import type { CompanyId } from '@/core/auth/types'; -import type { InventoryItem, InventoryItemType, InventoryItemStatus } from '../types'; -import { useInventory } from '../hooks/use-inventory'; +import { useState, useMemo } from "react"; +import { Plus, Pencil, Trash2, Search } from "lucide-react"; +import { Button } from "@/shared/components/ui/button"; +import { Input } from "@/shared/components/ui/input"; +import { Label } from "@/shared/components/ui/label"; +import { Textarea } from "@/shared/components/ui/textarea"; +import { Badge } from "@/shared/components/ui/badge"; +import { + Card, + CardContent, + CardHeader, + CardTitle, +} from "@/shared/components/ui/card"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/shared/components/ui/select"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogFooter, +} from "@/shared/components/ui/dialog"; +import type { CompanyId } from "@/core/auth/types"; +import type { + InventoryItem, + InventoryItemType, + InventoryItemStatus, +} from "../types"; +import { useInventory } from "../hooks/use-inventory"; +import { useContacts } from "@/modules/address-book/hooks/use-contacts"; const TYPE_LABELS: Record = { - laptop: 'Laptop', desktop: 'Desktop', monitor: 'Monitor', printer: 'Imprimantă', - phone: 'Telefon', tablet: 'Tabletă', network: 'Rețea', peripheral: 'Periferic', other: 'Altele', + laptop: "Laptop", + desktop: "Desktop", + monitor: "Monitor", + printer: "Imprimantă", + phone: "Telefon", + tablet: "Tabletă", + network: "Rețea", + peripheral: "Periferic", + other: "Altele", }; const STATUS_LABELS: Record = { - active: 'Activ', 'in-repair': 'În reparație', storage: 'Depozitat', decommissioned: 'Dezafectat', + active: "Activ", + "in-repair": "În reparație", + storage: "Depozitat", + decommissioned: "Dezafectat", }; -type ViewMode = 'list' | 'add' | 'edit'; +type ViewMode = "list" | "add" | "edit"; export function ItInventoryModule() { - const { items, allItems, loading, filters, updateFilter, addItem, updateItem, removeItem } = useInventory(); - const [viewMode, setViewMode] = useState('list'); + const { + items, + allItems, + loading, + filters, + updateFilter, + addItem, + updateItem, + removeItem, + } = useInventory(); + const [viewMode, setViewMode] = useState("list"); const [editingItem, setEditingItem] = useState(null); const [deletingId, setDeletingId] = useState(null); - const handleSubmit = async (data: Omit) => { - if (viewMode === 'edit' && editingItem) { + const handleSubmit = async ( + data: Omit, + ) => { + if (viewMode === "edit" && editingItem) { await updateItem(editingItem.id, data); } else { await addItem(data); } - setViewMode('list'); + setViewMode("list"); setEditingItem(null); }; @@ -52,81 +95,180 @@ export function ItInventoryModule() {
{/* Stats */}
-

Total

{allItems.length}

-

Active

{allItems.filter((i) => i.status === 'active').length}

-

În reparație

{allItems.filter((i) => i.status === 'in-repair').length}

-

Dezafectate

{allItems.filter((i) => i.status === 'decommissioned').length}

+ + +

Total

+

{allItems.length}

+
+
+ + +

Active

+

+ {allItems.filter((i) => i.status === "active").length} +

+
+
+ + +

În reparație

+

+ {allItems.filter((i) => i.status === "in-repair").length} +

+
+
+ + +

Dezafectate

+

+ {allItems.filter((i) => i.status === "decommissioned").length} +

+
+
- {viewMode === 'list' && ( + {viewMode === "list" && ( <>
- updateFilter('search', e.target.value)} className="pl-9" /> + updateFilter("search", e.target.value)} + className="pl-9" + />
- + updateFilter("type", v as InventoryItemType | "all") + } + > + + + Toate tipurile {(Object.keys(TYPE_LABELS) as InventoryItemType[]).map((t) => ( - {TYPE_LABELS[t]} + + {TYPE_LABELS[t]} + ))} - + updateFilter("status", v as InventoryItemStatus | "all") + } + > + + + Toate - {(Object.keys(STATUS_LABELS) as InventoryItemStatus[]).map((s) => ( - {STATUS_LABELS[s]} - ))} + {(Object.keys(STATUS_LABELS) as InventoryItemStatus[]).map( + (s) => ( + + {STATUS_LABELS[s]} + + ), + )} -
{loading ? ( -

Se încarcă...

+

+ Se încarcă... +

) : items.length === 0 ? ( -

Niciun echipament găsit.

+

+ Niciun echipament găsit. +

) : (
- - - - - - - - - - - + + + + + + + + + + + + + {items.map((item) => ( - + - + + + - - - - + +
NumeTipVendor/ModelS/NIPAtribuitLocațieStatusAcțiuni
NumeTip + Vendor/Model + S/NIP + Atribuit + LocațieStatus + Acțiuni +
{item.name}{TYPE_LABELS[item.type]} + + {TYPE_LABELS[item.type]} + + {item.vendor && {item.vendor}} - {item.vendor && item.model && / } - {item.model && {item.model}} + {item.vendor && item.model && ( + / + )} + {item.model && ( + + {item.model} + + )} + + {item.serialNumber} + + {item.ipAddress} {item.serialNumber}{item.ipAddress} {item.assignedTo}{item.rackLocation || item.location}{STATUS_LABELS[item.status]} + {item.rackLocation || item.location} + + + {STATUS_LABELS[item.status]} + +
- -
@@ -140,27 +282,48 @@ export function ItInventoryModule() { )} - {(viewMode === 'add' || viewMode === 'edit') && ( + {(viewMode === "add" || viewMode === "edit") && ( - {viewMode === 'edit' ? 'Editare echipament' : 'Echipament nou'} + + + {viewMode === "edit" ? "Editare echipament" : "Echipament nou"} + + { setViewMode('list'); setEditingItem(null); }} + onCancel={() => { + setViewMode("list"); + setEditingItem(null); + }} /> )} {/* Delete confirmation */} - { if (!open) setDeletingId(null); }}> + { + if (!open) setDeletingId(null); + }} + > - Confirmare ștergere -

Ești sigur că vrei să ștergi acest echipament? Acțiunea este ireversibilă.

+ + Confirmare ștergere + +

+ Ești sigur că vrei să ștergi acest echipament? Acțiunea este + ireversibilă. +

- - + +
@@ -168,60 +331,214 @@ export function ItInventoryModule() { ); } -function InventoryForm({ initial, onSubmit, onCancel }: { +function InventoryForm({ + initial, + onSubmit, + onCancel, +}: { initial?: InventoryItem; - onSubmit: (data: Omit) => void; + onSubmit: ( + data: Omit, + ) => void; onCancel: () => void; }) { - const [name, setName] = useState(initial?.name ?? ''); - const [type, setType] = useState(initial?.type ?? 'laptop'); - const [serialNumber, setSerialNumber] = useState(initial?.serialNumber ?? ''); - const [assignedTo, setAssignedTo] = useState(initial?.assignedTo ?? ''); - const [company, setCompany] = useState(initial?.company ?? 'beletage'); - const [location, setLocation] = useState(initial?.location ?? ''); - const [purchaseDate, setPurchaseDate] = useState(initial?.purchaseDate ?? ''); - const [status, setStatus] = useState(initial?.status ?? 'active'); - const [ipAddress, setIpAddress] = useState(initial?.ipAddress ?? ''); - const [macAddress, setMacAddress] = useState(initial?.macAddress ?? ''); - const [warrantyExpiry, setWarrantyExpiry] = useState(initial?.warrantyExpiry ?? ''); - const [purchaseCost, setPurchaseCost] = useState(initial?.purchaseCost ?? ''); - const [rackLocation, setRackLocation] = useState(initial?.rackLocation ?? ''); - const [vendor, setVendor] = useState(initial?.vendor ?? ''); - const [model, setModel] = useState(initial?.model ?? ''); - const [notes, setNotes] = useState(initial?.notes ?? ''); + const { allContacts } = useContacts(); + + const [name, setName] = useState(initial?.name ?? ""); + const [type, setType] = useState( + initial?.type ?? "laptop", + ); + const [serialNumber, setSerialNumber] = useState(initial?.serialNumber ?? ""); + const [assignedTo, setAssignedTo] = useState(initial?.assignedTo ?? ""); + const [assignedToContactId, setAssignedToContactId] = useState( + initial?.assignedToContactId ?? "", + ); + const [assignedToFocused, setAssignedToFocused] = useState(false); + const [company, setCompany] = useState( + initial?.company ?? "beletage", + ); + const [location, setLocation] = useState(initial?.location ?? ""); + const [purchaseDate, setPurchaseDate] = useState(initial?.purchaseDate ?? ""); + const [status, setStatus] = useState( + initial?.status ?? "active", + ); + const [ipAddress, setIpAddress] = useState(initial?.ipAddress ?? ""); + const [macAddress, setMacAddress] = useState(initial?.macAddress ?? ""); + const [warrantyExpiry, setWarrantyExpiry] = useState( + initial?.warrantyExpiry ?? "", + ); + const [purchaseCost, setPurchaseCost] = useState(initial?.purchaseCost ?? ""); + const [rackLocation, setRackLocation] = useState(initial?.rackLocation ?? ""); + const [vendor, setVendor] = useState(initial?.vendor ?? ""); + const [model, setModel] = useState(initial?.model ?? ""); + const [notes, setNotes] = useState(initial?.notes ?? ""); + + // Contact suggestions for assignedTo autocomplete + const assignedToSuggestions = useMemo(() => { + if (!assignedTo || assignedTo.length < 2) return []; + const q = assignedTo.toLowerCase(); + return allContacts + .filter( + (c) => + c.name.toLowerCase().includes(q) || + c.company.toLowerCase().includes(q), + ) + .slice(0, 5); + }, [allContacts, assignedTo]); return ( -
{ - e.preventDefault(); - onSubmit({ - name, type, serialNumber, assignedTo, company, location, purchaseDate, status, - ipAddress, macAddress, warrantyExpiry, purchaseCost, rackLocation, vendor, model, - notes, tags: initial?.tags ?? [], visibility: initial?.visibility ?? 'all', - }); - }} className="space-y-4"> + { + e.preventDefault(); + onSubmit({ + name, + type, + serialNumber, + assignedTo, + assignedToContactId, + company, + location, + purchaseDate, + status, + ipAddress, + macAddress, + warrantyExpiry, + purchaseCost, + rackLocation, + vendor, + model, + notes, + tags: initial?.tags ?? [], + visibility: initial?.visibility ?? "all", + }); + }} + className="space-y-4" + >
-
setName(e.target.value)} className="mt-1" required />
-
- setName(e.target.value)} + className="mt-1" + required + /> +
+
+ +
-
setVendor(e.target.value)} className="mt-1" placeholder="Dell, HP, Lenovo..." />
-
setModel(e.target.value)} className="mt-1" />
-
setSerialNumber(e.target.value)} className="mt-1" />
+
+ + setVendor(e.target.value)} + className="mt-1" + placeholder="Dell, HP, Lenovo..." + /> +
+
+ + setModel(e.target.value)} + className="mt-1" + /> +
+
+ + setSerialNumber(e.target.value)} + className="mt-1" + /> +
-
setIpAddress(e.target.value)} className="mt-1" placeholder="192.168.1.x" />
-
setMacAddress(e.target.value)} className="mt-1" placeholder="AA:BB:CC:DD:EE:FF" />
-
setAssignedTo(e.target.value)} className="mt-1" />
+
+ + setIpAddress(e.target.value)} + className="mt-1" + placeholder="192.168.1.x" + /> +
+
+ + setMacAddress(e.target.value)} + className="mt-1" + placeholder="AA:BB:CC:DD:EE:FF" + /> +
+
+ + { + setAssignedTo(e.target.value); + setAssignedToContactId(""); + }} + onFocus={() => setAssignedToFocused(true)} + onBlur={() => setTimeout(() => setAssignedToFocused(false), 200)} + className="mt-1" + placeholder="Caută după nume..." + /> + {assignedToFocused && assignedToSuggestions.length > 0 && ( +
+ {assignedToSuggestions.map((c) => ( + + ))} +
+ )} +
-
- setCompany(v as CompanyId)} + > + + + Beletage Urban Switch @@ -230,24 +547,86 @@ function InventoryForm({ initial, onSubmit, onCancel }: {
-
setLocation(e.target.value)} className="mt-1" />
-
setRackLocation(e.target.value)} className="mt-1" />
-
- setLocation(e.target.value)} + className="mt-1" + /> +
+
+ + setRackLocation(e.target.value)} + className="mt-1" + /> +
+
+ +
-
setPurchaseDate(e.target.value)} className="mt-1" />
-
setPurchaseCost(e.target.value)} className="mt-1" />
-
setWarrantyExpiry(e.target.value)} className="mt-1" />
+
+ + setPurchaseDate(e.target.value)} + className="mt-1" + /> +
+
+ + setPurchaseCost(e.target.value)} + className="mt-1" + /> +
+
+ + setWarrantyExpiry(e.target.value)} + className="mt-1" + /> +
+
+
+ +