| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Sens Gruppen Hardware Inventory</title> |
| <script src="https://cdn.tailwindcss.com"></script> |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script> |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf-autotable/3.5.28/jspdf.plugin.autotable.min.js"></script> |
| <script> |
| tailwind.config = { |
| theme: { |
| extend: { |
| colors: { |
| sens: { |
| blue: '#005b96', |
| light: '#e1f0ff', |
| dark: '#003d66' |
| } |
| } |
| } |
| } |
| } |
| </script> |
| <style> |
| .form-view { |
| transition: all 0.3s ease; |
| } |
| .table-view { |
| transition: all 0.3s ease; |
| } |
| .hidden-view { |
| display: none; |
| opacity: 0; |
| } |
| .visible-view { |
| display: block; |
| opacity: 1; |
| } |
| .toast { |
| animation: fadeInOut 3s ease-in-out; |
| } |
| @keyframes fadeInOut { |
| 0% { opacity: 0; transform: translateY(20px); } |
| 15% { opacity: 1; transform: translateY(0); } |
| 85% { opacity: 1; transform: translateY(0); } |
| 100% { opacity: 0; transform: translateY(-20px); } |
| } |
| </style> |
| </head> |
| <body class="bg-gray-50 min-h-screen"> |
| <div class="container mx-auto px-4 py-8"> |
| |
| <header class="mb-8"> |
| <div class="flex justify-between items-center"> |
| <div> |
| <h1 class="text-3xl font-bold text-sens-blue"> |
| <i class="fas fa-laptop-house mr-2"></i> Sens Hardware Inventory |
| </h1> |
| <p class="text-gray-600">Manage all hardware assets across the Sens Gruppen companies</p> |
| </div> |
| <img src="https://via.placeholder.com/150x50?text=Sens+Logo" alt="Sens Gruppen Logo" class="h-12"> |
| </div> |
| </header> |
|
|
| |
| <main> |
| |
| <div class="bg-white rounded-lg shadow-md p-4 mb-6 sticky top-0 z-10"> |
| <div class="flex flex-wrap gap-3 items-center justify-between"> |
| <div class="flex flex-wrap gap-2"> |
| <button id="newItemBtn" class="bg-green-500 hover:bg-green-600 text-white px-4 py-2 rounded-lg flex items-center"> |
| <i class="fas fa-plus-circle mr-2"></i> New Item |
| </button> |
| <button id="searchBtn" class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-lg flex items-center"> |
| <i class="fas fa-search mr-2"></i> Search |
| </button> |
| <button id="queryBtn" class="bg-purple-500 hover:bg-purple-600 text-white px-4 py-2 rounded-lg flex items-center"> |
| <i class="fas fa-filter mr-2"></i> Query |
| </button> |
| </div> |
| <div class="flex flex-wrap gap-2"> |
| <button id="exportBtn" class="bg-yellow-500 hover:bg-yellow-600 text-white px-4 py-2 rounded-lg flex items-center"> |
| <i class="fas fa-file-export mr-2"></i> Export |
| </button> |
| <button id="printBtn" class="bg-indigo-500 hover:bg-indigo-600 text-white px-4 py-2 rounded-lg flex items-center"> |
| <i class="fas fa-print mr-2"></i> Print |
| </button> |
| <button id="toggleViewBtn" class="bg-gray-500 hover:bg-gray-600 text-white px-4 py-2 rounded-lg flex items-center"> |
| <i class="fas fa-table mr-2"></i> Spreadsheet View |
| </button> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div id="searchQueryPanel" class="hidden bg-white rounded-lg shadow-md p-4 mb-6"> |
| <div class="grid grid-cols-1 md:grid-cols-3 gap-4"> |
| <div> |
| <label class="block text-gray-700 mb-1">Hardware Type</label> |
| <select id="searchType" class="w-full p-2 border rounded-lg"> |
| <option value="">All Types</option> |
| <option value="Laptop">Laptop</option> |
| <option value="Docking stations">Docking Station</option> |
| <option value="Monitors">Monitor</option> |
| <option value="Keyboards and mice">Keyboard/Mouse</option> |
| <option value="Headphones">Headphones</option> |
| <option value="Mobile telephones">Mobile Phone</option> |
| <option value="TV">TV</option> |
| <option value="Printers">Printer</option> |
| </select> |
| </div> |
| <div> |
| <label class="block text-gray-700 mb-1">Company</label> |
| <select id="searchCompany" class="w-full p-2 border rounded-lg"> |
| <option value="">All Companies</option> |
| <option value="Sens Utvikling AS">Sens Utvikling AS</option> |
| <option value="Sens Arbeidsinkludering AS">Sens Arbeidsinkludering AS</option> |
| <option value="Sens Gruppen As">Sens Gruppen As</option> |
| </select> |
| </div> |
| <div> |
| <label class="block text-gray-700 mb-1">Department</label> |
| <select id="searchDepartment" class="w-full p-2 border rounded-lg"> |
| <option value="">All Departments</option> |
| <option value="Moss">Moss</option> |
| <option value="Sarpsborg">Sarpsborg</option> |
| <option value="Halden">Halden</option> |
| <option value="Askim">Askim</option> |
| <option value="JMO">JMO</option> |
| <option value="SJF">SJF</option> |
| <option value="OMS">OMS</option> |
| <option value="AFT">AFT</option> |
| <option value="VTA">VTA</option> |
| <option value="Jobb+">Jobb+</option> |
| <option value="Miljoårena">Miljoårena</option> |
| </select> |
| </div> |
| <div> |
| <label class="block text-gray-700 mb-1">Make</label> |
| <select id="searchMake" class="w-full p-2 border rounded-lg"> |
| <option value="">All Makes</option> |
| <option value="Lenovo">Lenovo</option> |
| <option value="Benq">Benq</option> |
| <option value="Philips">Philips</option> |
| <option value="HP">HP</option> |
| <option value="Asus">Asus</option> |
| <option value="Samsung">Samsung</option> |
| <option value="Sony">Sony</option> |
| </select> |
| </div> |
| <div> |
| <label class="block text-gray-700 mb-1">Employee Name</label> |
| <input id="searchEmployee" type="text" class="w-full p-2 border rounded-lg" placeholder="Search by employee..."> |
| </div> |
| <div class="flex items-end"> |
| <button id="applySearchBtn" class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-lg flex items-center"> |
| <i class="fas fa-check mr-2"></i> Apply Filters |
| </button> |
| <button id="clearSearchBtn" class="ml-2 bg-gray-500 hover:bg-gray-600 text-white px-4 py-2 rounded-lg flex items-center"> |
| <i class="fas fa-times mr-2"></i> Clear |
| </button> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div id="formView" class="form-view visible-view bg-white rounded-lg shadow-md p-6 mb-6"> |
| <h2 class="text-xl font-semibold mb-4 text-sens-blue border-b pb-2"> |
| <i class="fas fa-edit mr-2"></i> Hardware Details |
| </h2> |
| <form id="hardwareForm"> |
| <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"> |
| |
| <div class="space-y-4"> |
| <div> |
| <label class="block text-gray-700 mb-1">Hardware Type *</label> |
| <select id="type" name="type" required class="w-full p-2 border rounded-lg"> |
| <option value="">Select Type</option> |
| <option value="Laptop">Laptop</option> |
| <option value="Docking stations">Docking Station</option> |
| <option value="Monitors">Monitor</option> |
| <option value="Keyboards and mice">Keyboard/Mouse</option> |
| <option value="Headphones">Headphones</option> |
| <option value="Mobile telephones">Mobile Phone</option> |
| <option value="TV">TV</option> |
| <option value="Printers">Printer</option> |
| </select> |
| </div> |
| <div> |
| <label class="block text-gray-700 mb-1">Company *</label> |
| <select id="company" name="company" required class="w-full p-2 border rounded-lg"> |
| <option value="">Select Company</option> |
| <option value="Sens Utvikling AS">Sens Utvikling AS</option> |
| <option value="Sens Arbeidsinkludering AS">Sens Arbeidsinkludering AS</option> |
| <option value="Sens Gruppen As">Sens Gruppen As</option> |
| </select> |
| </div> |
| <div> |
| <label class="block text-gray-700 mb-1">Department *</label> |
| <select id="department" name="department" required class="w-full p-2 border rounded-lg"> |
| <option value="">Select Department</option> |
| <option value="Moss">Moss</option> |
| <option value="Sarpsborg">Sarpsborg</option> |
| <option value="Halden">Halden</option> |
| <option value="Askim">Askim</option> |
| <option value="JMO">JMO</option> |
| <option value="SJF">SJF</option> |
| <option value="OMS">OMS</option> |
| <option value="AFT">AFT</option> |
| <option value="VTA">VTA</option> |
| <option value="Jobb+">Jobb+</option> |
| <option value="Miljoårena">Miljoårena</option> |
| </select> |
| </div> |
| <div> |
| <label class="block text-gray-700 mb-1">Department Manager</label> |
| <input id="departmentManager" name="departmentManager" type="text" class="w-full p-2 border rounded-lg" value="Gunnar" readonly> |
| </div> |
| </div> |
| |
| |
| <div class="space-y-4"> |
| <div> |
| <label class="block text-gray-700 mb-1">Employee Name</label> |
| <input id="employeeName" name="employeeName" type="text" class="w-full p-2 border rounded-lg" placeholder="e.g. Sandra Johnson"> |
| </div> |
| <div> |
| <label class="block text-gray-700 mb-1">Item Description</label> |
| <input id="itemDescription" name="itemDescription" type="text" class="w-full p-2 border rounded-lg" placeholder="e.g. Screen"> |
| </div> |
| <div> |
| <label class="block text-gray-700 mb-1">Make *</label> |
| <select id="make" name="make" required class="w-full p-2 border rounded-lg"> |
| <option value="">Select Make</option> |
| <option value="Lenovo">Lenovo</option> |
| <option value="Benq">Benq</option> |
| <option value="Philips">Philips</option> |
| <option value="HP">HP</option> |
| <option value="Asus">Asus</option> |
| <option value="Samsung">Samsung</option> |
| <option value="Sony">Sony</option> |
| </select> |
| </div> |
| <div> |
| <label class="block text-gray-700 mb-1">Model *</label> |
| <input id="model" name="model" type="text" required class="w-full p-2 border rounded-lg" placeholder="e.g. Lenovo L14"> |
| </div> |
| </div> |
| |
| |
| <div class="space-y-4"> |
| <div> |
| <label class="block text-gray-700 mb-1">Serial Number *</label> |
| <input id="serialNumber" name="serialNumber" type="text" required class="w-full p-2 border rounded-lg" placeholder="e.g. PHE20212"> |
| </div> |
| <div> |
| <label class="block text-gray-700 mb-1">Full Specifications</label> |
| <textarea id="specifications" name="specifications" rows="3" class="w-full p-2 border rounded-lg" placeholder="e.g. Lenovo laptop, 14" screen, 256GB Hard disk, 16GB Ram"></textarea> |
| </div> |
| <div> |
| <label class="block text-gray-700 mb-1">Date Purchased *</label> |
| <input id="datePurchased" name="datePurchased" type="date" required class="w-full p-2 border rounded-lg"> |
| </div> |
| <div> |
| <label class="block text-gray-700 mb-1">Warranty Expiration *</label> |
| <input id="warrantyExpiration" name="warrantyExpiration" type="date" required class="w-full p-2 border rounded-lg"> |
| </div> |
| <div> |
| <label class="block text-gray-700 mb-1">Price (Kr)</label> |
| <input id="price" name="price" type="number" class="w-full p-2 border rounded-lg" placeholder="e.g. 9909"> |
| </div> |
| <input type="hidden" id="itemId" name="itemId"> |
| <input type="hidden" id="uniqueIdentifier" name="uniqueIdentifier"> |
| </div> |
| </div> |
| |
| <div class="mt-6 flex justify-end gap-3"> |
| <button type="button" id="cancelBtn" class="bg-gray-500 hover:bg-gray-600 text-white px-4 py-2 rounded-lg"> |
| Cancel |
| </button> |
| <button type="submit" id="saveBtn" class="bg-sens-blue hover:bg-sens-dark text-white px-4 py-2 rounded-lg"> |
| Save Item |
| </button> |
| </div> |
| </form> |
| </div> |
|
|
| |
| <div id="tableView" class="table-view hidden-view bg-white rounded-lg shadow-md p-4 overflow-x-auto"> |
| <h2 class="text-xl font-semibold mb-4 text-sens-blue border-b pb-2"> |
| <i class="fas fa-table mr-2"></i> Inventory Overview |
| </h2> |
| <table class="min-w-full divide-y divide-gray-200"> |
| <thead class="bg-gray-50"> |
| <tr> |
| <th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">ID</th> |
| <th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Type</th> |
| <th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Company</th> |
| <th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Department</th> |
| <th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Employee</th> |
| <th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Make/Model</th> |
| <th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Warranty</th> |
| <th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th> |
| </tr> |
| </thead> |
| <tbody id="inventoryTableBody" class="bg-white divide-y divide-gray-200"> |
| |
| </tbody> |
| </table> |
| </div> |
|
|
| |
| <div class="bg-white rounded-lg shadow-md p-4 mt-6"> |
| <h2 class="text-xl font-semibold mb-4 text-sens-blue border-b pb-2"> |
| <i class="fas fa-bell mr-2"></i> Warranty Expiration Alerts |
| </h2> |
| <div class="grid grid-cols-1 md:grid-cols-2 gap-4"> |
| <div class="bg-yellow-100 border-l-4 border-yellow-500 p-4"> |
| <h3 class="font-bold text-yellow-800">Approaching Expiry (1 month or less)</h3> |
| <ul id="monthExpiryList" class="mt-2 space-y-1"> |
| |
| </ul> |
| </div> |
| <div class="bg-red-100 border-l-4 border-red-500 p-4"> |
| <h3 class="font-bold text-red-800">Imminent Expiry (2 weeks or less)</h3> |
| <ul id="weekExpiryList" class="mt-2 space-y-1"> |
| |
| </ul> |
| </div> |
| </div> |
| <div class="mt-4"> |
| <button id="sendAlertsBtn" class="bg-sens-blue hover:bg-sens-dark text-white px-4 py-2 rounded-lg flex items-center"> |
| <i class="fas fa-envelope mr-2"></i> Email Alerts Now |
| </button> |
| </div> |
| </div> |
| </main> |
| </div> |
|
|
| |
| <div id="toastNotification" class="toast fixed bottom-4 right-4 bg-green-500 text-white px-6 py-3 rounded-lg shadow-lg hidden"> |
| <div class="flex items-center"> |
| <i class="fas fa-check-circle mr-2"></i> |
| <span id="toastMessage">Item saved successfully!</span> |
| </div> |
| </div> |
|
|
| <script> |
| |
| let inventoryData = [ |
| { |
| id: 1, |
| uniqueIdentifier: "L-0001", |
| type: "Laptop", |
| company: "Sens Utvikling AS", |
| department: "Moss", |
| departmentManager: "Gunnar", |
| employeeName: "Sandra Johnson", |
| itemDescription: "Work laptop", |
| make: "Lenovo", |
| model: "ThinkPad L14", |
| serialNumber: "LEN202101", |
| specifications: "Lenovo laptop, 14\" screen, 256GB SSD, 16GB RAM", |
| datePurchased: "2023-01-15", |
| warrantyExpiration: "2026-01-15", |
| price: 9909 |
| }, |
| { |
| id: 2, |
| uniqueIdentifier: "M-0001", |
| type: "Monitors", |
| company: "Sens Arbeidsinkludering AS", |
| department: "Sarpsborg", |
| departmentManager: "Gunnar", |
| employeeName: "John Doe", |
| itemDescription: "Main monitor", |
| make: "Benq", |
| model: "PD2700U", |
| serialNumber: "BEN202102", |
| specifications: "27-inch 4K UHD IPS monitor", |
| datePurchased: "2023-03-20", |
| warrantyExpiration: "2025-03-20", |
| price: 5499 |
| }, |
| { |
| id: 3, |
| uniqueIdentifier: "H-0001", |
| type: "Headphones", |
| company: "Sens Gruppen As", |
| department: "Halden", |
| departmentManager: "Gunnar", |
| employeeName: "Anna Smith", |
| itemDescription: "Noise-cancelling headphones", |
| make: "Sony", |
| model: "WH-1000XM4", |
| serialNumber: "SON202103", |
| specifications: "Wireless noise cancelling headphones", |
| datePurchased: "2023-05-10", |
| warrantyExpiration: "2024-05-10", |
| price: 2499 |
| } |
| ]; |
| |
| |
| const formView = document.getElementById('formView'); |
| const tableView = document.getElementById('tableView'); |
| const toggleViewBtn = document.getElementById('toggleViewBtn'); |
| const hardwareForm = document.getElementById('hardwareForm'); |
| const inventoryTableBody = document.getElementById('inventoryTableBody'); |
| const searchQueryPanel = document.getElementById('searchQueryPanel'); |
| const searchBtn = document.getElementById('searchBtn'); |
| const newItemBtn = document.getElementById('newItemBtn'); |
| const cancelBtn = document.getElementById('cancelBtn'); |
| const saveBtn = document.getElementById('saveBtn'); |
| const applySearchBtn = document.getElementById('applySearchBtn'); |
| const clearSearchBtn = document.getElementById('clearSearchBtn'); |
| const exportBtn = document.getElementById('exportBtn'); |
| const printBtn = document.getElementById('printBtn'); |
| const sendAlertsBtn = document.getElementById('sendAlertsBtn'); |
| const toastNotification = document.getElementById('toastNotification'); |
| const toastMessage = document.getElementById('toastMessage'); |
| const monthExpiryList = document.getElementById('monthExpiryList'); |
| const weekExpiryList = document.getElementById('weekExpiryList'); |
| |
| |
| let currentView = 'form'; |
| let currentItemId = null; |
| let isEditMode = false; |
| |
| |
| const prefixMap = { |
| 'Laptop': 'L', |
| 'Docking stations': 'D', |
| 'Monitors': 'M', |
| 'Keyboards and mice': 'K', |
| 'Headphones': 'H', |
| 'Mobile telephones': 'MT', |
| 'TV': 'TV', |
| 'Printers': 'P' |
| }; |
| |
| |
| function init() { |
| |
| const today = new Date().toISOString().split('T')[0]; |
| document.getElementById('datePurchased').value = today; |
| |
| |
| const warrantyDate = new Date(); |
| warrantyDate.setFullYear(warrantyDate.getFullYear() + 3); |
| document.getElementById('warrantyExpiration').value = warrantyDate.toISOString().split('T')[0]; |
| |
| renderInventoryTable(); |
| checkWarrantyAlerts(); |
| |
| |
| toggleViewBtn.addEventListener('click', toggleView); |
| newItemBtn.addEventListener('click', newItem); |
| cancelBtn.addEventListener('click', resetForm); |
| hardwareForm.addEventListener('submit', saveItem); |
| searchBtn.addEventListener('click', toggleSearchPanel); |
| applySearchBtn.addEventListener('click', applySearch); |
| clearSearchBtn.addEventListener('click', clearSearch); |
| exportBtn.addEventListener('click', exportToPDF); |
| printBtn.addEventListener('click', printInventory); |
| sendAlertsBtn.addEventListener('click', sendAlerts); |
| |
| |
| document.getElementById('type').addEventListener('change', generateUniqueIdentifier); |
| } |
| |
| |
| function toggleView() { |
| if (currentView === 'form') { |
| formView.classList.remove('visible-view'); |
| formView.classList.add('hidden-view'); |
| tableView.classList.remove('hidden-view'); |
| tableView.classList.add('visible-view'); |
| toggleViewBtn.innerHTML = '<i class="fas fa-edit mr-2"></i> Form View'; |
| currentView = 'table'; |
| } else { |
| formView.classList.remove('hidden-view'); |
| formView.classList.add('visible-view'); |
| tableView.classList.remove('visible-view'); |
| tableView.classList.add('hidden-view'); |
| toggleViewBtn.innerHTML = '<i class="fas fa-table mr-2"></i> Spreadsheet View'; |
| currentView = 'form'; |
| } |
| } |
| |
| |
| function newItem() { |
| resetForm(); |
| isEditMode = false; |
| currentItemId = null; |
| generateUniqueIdentifier(); |
| scrollToTop(); |
| } |
| |
| |
| function resetForm() { |
| hardwareForm.reset(); |
| document.getElementById('itemId').value = ''; |
| document.getElementById('uniqueIdentifier').value = ''; |
| document.getElementById('departmentManager').value = 'Gunnar'; |
| |
| |
| const today = new Date().toISOString().split('T')[0]; |
| document.getElementById('datePurchased').value = today; |
| |
| |
| const warrantyDate = new Date(); |
| warrantyDate.setFullYear(warrantyDate.getFullYear() + 3); |
| document.getElementById('warrantyExpiration').value = warrantyDate.toISOString().split('T')[0]; |
| } |
| |
| |
| function generateUniqueIdentifier() { |
| const type = document.getElementById('type').value; |
| if (!type) return; |
| |
| const prefix = prefixMap[type] || 'X'; |
| const itemsOfType = inventoryData.filter(item => item.type === type); |
| const nextNumber = (itemsOfType.length + 1).toString().padStart(4, '0'); |
| const uniqueId = `${prefix}-${nextNumber}`; |
| |
| document.getElementById('uniqueIdentifier').value = uniqueId; |
| } |
| |
| |
| function saveItem(e) { |
| e.preventDefault(); |
| |
| const formData = new FormData(hardwareForm); |
| const itemData = {}; |
| formData.forEach((value, key) => { |
| itemData[key] = value; |
| }); |
| |
| if (isEditMode && currentItemId) { |
| |
| const index = inventoryData.findIndex(item => item.id === currentItemId); |
| if (index !== -1) { |
| inventoryData[index] = { |
| ...inventoryData[index], |
| ...itemData, |
| id: currentItemId |
| }; |
| showToast('Item updated successfully!'); |
| } |
| } else { |
| |
| const newId = inventoryData.length > 0 ? Math.max(...inventoryData.map(item => item.id)) + 1 : 1; |
| itemData.id = newId; |
| inventoryData.push(itemData); |
| showToast('Item added successfully!'); |
| } |
| |
| renderInventoryTable(); |
| checkWarrantyAlerts(); |
| resetForm(); |
| } |
| |
| |
| function editItem(id) { |
| const item = inventoryData.find(item => item.id === id); |
| if (!item) return; |
| |
| |
| for (const key in item) { |
| if (document.getElementById(key)) { |
| document.getElementById(key).value = item[key]; |
| } |
| } |
| |
| currentItemId = id; |
| isEditMode = true; |
| scrollToTop(); |
| } |
| |
| |
| function deleteItem(id) { |
| if (confirm('Are you sure you want to delete this item?')) { |
| inventoryData = inventoryData.filter(item => item.id !== id); |
| renderInventoryTable(); |
| checkWarrantyAlerts(); |
| showToast('Item deleted successfully!'); |
| |
| if (isEditMode && currentItemId === id) { |
| resetForm(); |
| isEditMode = false; |
| currentItemId = null; |
| } |
| } |
| } |
| |
| |
| function renderInventoryTable(filteredData = null) { |
| const data = filteredData || inventoryData; |
| inventoryTableBody.innerHTML = ''; |
| |
| if (data.length === 0) { |
| const row = document.createElement('tr'); |
| row.innerHTML = ` |
| <td colspan="8" class="px-4 py-4 text-center text-gray-500"> |
| No items found. Click "New Item" to add one. |
| </td> |
| `; |
| inventoryTableBody.appendChild(row); |
| return; |
| } |
| |
| data.forEach(item => { |
| const row = document.createElement('tr'); |
| row.className = 'hover:bg-gray-50'; |
| |
| |
| const warrantyDate = new Date(item.warrantyExpiration); |
| const today = new Date(); |
| const daysLeft = Math.floor((warrantyDate - today) / (1000 * 60 * 60 * 24)); |
| let warrantyStatus = ''; |
| let statusClass = ''; |
| |
| if (daysLeft < 0) { |
| warrantyStatus = 'Expired'; |
| statusClass = 'bg-red-100 text-red-800'; |
| } else if (daysLeft <= 14) { |
| warrantyStatus = `${daysLeft} days`; |
| statusClass = 'bg-red-100 text-red-800'; |
| } else if (daysLeft <= 30) { |
| warrantyStatus = `${daysLeft} days`; |
| statusClass = 'bg-yellow-100 text-yellow-800'; |
| } else { |
| warrantyStatus = 'Valid'; |
| statusClass = 'bg-green-100 text-green-800'; |
| } |
| |
| row.innerHTML = ` |
| <td class="px-4 py-4 whitespace-nowrap text-sm font-medium text-gray-900">${item.uniqueIdentifier}</td> |
| <td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500">${item.type}</td> |
| <td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500">${item.company}</td> |
| <td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500">${item.department}</td> |
| <td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500">${item.employeeName || '-'}</td> |
| <td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500">${item.make} ${item.model}</td> |
| <td class="px-4 py-4 whitespace-nowrap"> |
| <span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full ${statusClass}"> |
| ${warrantyStatus} |
| </span> |
| </td> |
| <td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500"> |
| <button onclick="editItem(${item.id})" class="text-blue-600 hover:text-blue-900 mr-2"> |
| <i class="fas fa-edit"></i> |
| </button> |
| <button onclick="deleteItem(${item.id})" class="text-red-600 hover:text-red-900"> |
| <i class="fas fa-trash-alt"></i> |
| </button> |
| </td> |
| `; |
| |
| inventoryTableBody.appendChild(row); |
| }); |
| } |
| |
| |
| function toggleSearchPanel() { |
| searchQueryPanel.classList.toggle('hidden'); |
| if (!searchQueryPanel.classList.contains('hidden')) { |
| searchBtn.innerHTML = '<i class="fas fa-times mr-2"></i> Close Search'; |
| } else { |
| searchBtn.innerHTML = '<i class="fas fa-search mr-2"></i> Search'; |
| } |
| } |
| |
| |
| function applySearch() { |
| const type = document.getElementById('searchType').value; |
| const company = document.getElementById('searchCompany').value; |
| const department = document.getElementById('searchDepartment').value; |
| const make = document.getElementById('searchMake').value; |
| const employee = document.getElementById('searchEmployee').value.toLowerCase(); |
| |
| let filteredData = inventoryData; |
| |
| if (type) { |
| filteredData = filteredData.filter(item => item.type === type); |
| } |
| |
| if (company) { |
| filteredData = filteredData.filter(item => item.company === company); |
| } |
| |
| if (department) { |
| filteredData = filteredData.filter(item => item.department === department); |
| } |
| |
| if (make) { |
| filteredData = filteredData.filter(item => item.make === make); |
| } |
| |
| if (employee) { |
| filteredData = filteredData.filter(item => |
| item.employeeName && item.employeeName.toLowerCase().includes(employee) |
| ); |
| } |
| |
| renderInventoryTable(filteredData); |
| toggleSearchPanel(); |
| } |
| |
| |
| function clearSearch() { |
| document.getElementById('searchType').value = ''; |
| document.getElementById('searchCompany').value = ''; |
| document.getElementById('searchDepartment').value = ''; |
| document.getElementById('searchMake').value = ''; |
| document.getElementById('searchEmployee').value = ''; |
| |
| renderInventoryTable(); |
| } |
| |
| |
| function checkWarrantyAlerts() { |
| const today = new Date(); |
| const oneMonthFromNow = new Date(); |
| oneMonthFromNow.setMonth(today.getMonth() + 1); |
| |
| const twoWeeksFromNow = new Date(); |
| twoWeeksFromNow.setDate(today.getDate() + 14); |
| |
| |
| monthExpiryList.innerHTML = ''; |
| weekExpiryList.innerHTML = ''; |
| |
| inventoryData.forEach(item => { |
| const expiryDate = new Date(item.warrantyExpiration); |
| |
| if (expiryDate <= twoWeeksFromNow && expiryDate >= today) { |
| |
| const li = document.createElement('li'); |
| li.innerHTML = ` |
| <div class="flex items-start"> |
| <span class="flex-shrink-0 bg-red-500 w-2 h-2 mt-1.5 rounded-full"></span> |
| <span class="ml-2">${item.uniqueIdentifier} - ${item.type} (${item.company}/${item.department}) - Expires in ${Math.floor((expiryDate - today) / (1000 * 60 * 60 * 24))} days</span> |
| </div> |
| `; |
| weekExpiryList.appendChild(li); |
| } else if (expiryDate <= oneMonthFromNow && expiryDate > twoWeeksFromNow) { |
| |
| const li = document.createElement('li'); |
| li.innerHTML = ` |
| <div class="flex items-start"> |
| <span class="flex-shrink-0 bg-yellow-500 w-2 h-2 mt-1.5 rounded-full"></span> |
| <span class="ml-2">${item.uniqueIdentifier} - ${item.type} (${item.company}/${item.department}) - Expires in ${Math.floor((expiryDate - today) / (1000 * 60 * 60 * 24))} days</span> |
| </div> |
| `; |
| monthExpiryList.appendChild(li); |
| } |
| }); |
| |
| |
| if (monthExpiryList.children.length === 0) { |
| monthExpiryList.innerHTML = '<li class="text-gray-500 ml-4">No items expiring within the next month</li>'; |
| } |
| |
| if (weekExpiryList.children.length === 0) { |
| weekExpiryList.innerHTML = '<li class="text-gray-500 ml-4">No items expiring within the next 2 weeks</li>'; |
| } |
| } |
| |
| |
| function sendAlerts() { |
| |
| |
| |
| const alerts = []; |
| |
| const monthItems = [...monthExpiryList.querySelectorAll('li')] |
| .filter(li => !li.textContent.includes('No items')); |
| |
| const weekItems = [...weekExpiryList.querySelectorAll('li')] |
| .filter(li => !li.textContent.includes('No items')); |
| |
| if (monthItems.length === 0 && weekItems.length === 0) { |
| showToast('No warranty alerts to send'); |
| return; |
| } |
| |
| let message = 'Warranty Alert Emails Sent:\n\n'; |
| |
| if (monthItems.length > 0) { |
| message += '1 Month Alerts:\n'; |
| monthItems.forEach(li => { |
| message += `- ${li.textContent.trim()}\n`; |
| }); |
| message += '\n'; |
| } |
| |
| if (weekItems.length > 0) { |
| message += '2 Week Alerts:\n'; |
| weekItems.forEach(li => { |
| message += `- ${li.textContent.trim()}\n`; |
| }); |
| } |
| |
| |
| if (confirm(`${message}\nConfirm sending these alerts?`)) { |
| showToast('Warranty alerts sent successfully!'); |
| } |
| } |
| |
| |
| function exportToPDF() { |
| |
| const { jsPDF } = window.jspdf; |
| const doc = new jsPDF(); |
| |
| |
| doc.setFontSize(18); |
| doc.text('Sens Gruppen Hardware Inventory', 14, 22); |
| doc.setFontSize(12); |
| doc.text(`Generated on ${new Date().toLocaleDateString()}`, 14, 30); |
| |
| |
| const headers = [ |
| 'ID', 'Type', 'Company', 'Department', 'Employee', 'Make/Model', |
| 'Serial No', 'Purchase Date', 'Warranty Expiry', 'Price (Kr)' |
| ]; |
| |
| const data = inventoryData.map(item => [ |
| item.uniqueIdentifier, |
| item.type, |
| item.company, |
| item.department, |
| item.employeeName || '-', |
| `${item.make} ${item.model}`, |
| item.serialNumber, |
| formatDate(item.datePurchased), |
| formatDate(item.warrantyExpiration), |
| item.price ? item.price.toString() : '-' |
| ]); |
| |
| |
| doc.autoTable({ |
| head: [headers], |
| body: data, |
| startY: 40, |
| styles: { |
| fontSize: 8, |
| cellPadding: 2, |
| overflow: 'linebreak' |
| }, |
| columnStyles: { |
| 0: { cellWidth: 15 }, |
| 1: { cellWidth: 20 }, |
| 2: { cellWidth: 25 }, |
| 3: { cellWidth: 15 }, |
| 4: { cellWidth: 20 }, |
| 5: { cellWidth: 30 }, |
| 6: { cellWidth: 20 }, |
| 7: { cellWidth: 20 }, |
| 8: { cellWidth: 20 }, |
| 9: { cellWidth: 15 } |
| }, |
| margin: { top: 40 } |
| }); |
| |
| |
| doc.save('Sens_Hardware_Inventory.pdf'); |
| showToast('Inventory exported to PDF'); |
| } |
| |
| |
| function printInventory() { |
| |
| const printWindow = window.open('', '_blank'); |
| |
| let html = ` |
| <html> |
| <head> |
| <title>Sens Gruppen Hardware Inventory</title> |
| <style> |
| body { font-family: Arial, sans-serif; margin: 20px; } |
| h1 { color: #005b96; margin-bottom: 5px; } |
| .date { color: #666; margin-bottom: 20px; } |
| table { width: 100%; border-collapse: collapse; margin-top: 20px; } |
| th { background-color: #005b96; color: white; text-align: left; padding: 8px; } |
| td { border: 1px solid #ddd; padding: 8px; } |
| tr:nth-child(even) { background-color: #f2f2f2; } |
| .expired { background-color: #ffdddd; } |
| .warning { background-color: #fff3cd; } |
| </style> |
| </head> |
| <body> |
| <h1>Sens Gruppen Hardware Inventory</h1> |
| <div class="date">Generated on ${new Date().toLocaleDateString()}</div> |
| <table> |
| <thead> |
| <tr> |
| <th>ID</th> |
| <th>Type</th> |
| <th>Company</th> |
| <th>Department</th> |
| <th>Employee</th> |
| <th>Make/Model</th> |
| <th>Serial No</th> |
| <th>Purchase Date</th> |
| <th>Warranty Expiry</th> |
| <th>Price (Kr)</th> |
| </tr> |
| </thead> |
| <tbody> |
| `; |
| |
| |
| inventoryData.forEach(item => { |
| const today = new Date(); |
| const warrantyDate = new Date(item.warrantyExpiration); |
| const daysLeft = Math.floor((warrantyDate - today) / (1000 * 60 * 60 * 24)); |
| let rowClass = ''; |
| |
| if (daysLeft < 0) { |
| rowClass = 'expired'; |
| } else if (daysLeft <= 30) { |
| rowClass = 'warning'; |
| } |
| |
| html += ` |
| <tr class="${rowClass}"> |
| <td>${item.uniqueIdentifier}</td> |
| <td>${item.type}</td> |
| <td>${item.company}</td> |
| <td>${item.department}</td> |
| <td>${item.employeeName || '-'}</td> |
| <td>${item.make} ${item.model}</td> |
| <td>${item.serialNumber}</td> |
| <td>${formatDate(item.datePurchased)}</td> |
| <td>${formatDate(item.warrantyExpiration)}</td> |
| <td>${item.price || '-'}</td> |
| </tr> |
| `; |
| }); |
| |
| html += ` |
| </tbody> |
| </table> |
| <script> |
| window.onload = function() { |
| window.print(); |
| window.close(); |
| }; |
| </script> |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=tlynn/hardware" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
| </html> |