Download index.html from tlynn/hardware: direct link, hf CLI and curl.
- Browser
- Download file 47.4 kB
-
https://huggingface.co/spaces/tlynn/hardware/resolve/main/index.html
- Command line
-
hf download hf://spaces/tlynn/hardware/index.html
-
curl -L -o index.html https://huggingface.co/spaces/tlynn/hardware/resolve/main/index.html
47.4 kB
| <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 --> | |
| <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 Content Area --> | |
| <main> | |
| <!-- Controls Bar --> | |
| <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> | |
| <!-- Search/Query Panel (Initially hidden) --> | |
| <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> | |
| <!-- Form View --> | |
| <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"> | |
| <!-- Column 1 --> | |
| <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> | |
| <!-- Column 2 --> | |
| <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> | |
| <!-- Column 3 --> | |
| <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> | |
| <!-- Table View --> | |
| <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"> | |
| <!-- Data will be populated here by JavaScript --> | |
| </tbody> | |
| </table> | |
| </div> | |
| <!-- Warranty Alerts --> | |
| <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"> | |
| <!-- Items will be added here by JavaScript --> | |
| </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"> | |
| <!-- Items will be added here by JavaScript --> | |
| </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> | |
| <!-- Toast Notification --> | |
| <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> | |
| // Sample data - in a real app, this would come from a database | |
| 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 | |
| } | |
| ]; | |
| // DOM Elements | |
| 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'); | |
| // Current state | |
| let currentView = 'form'; | |
| let currentItemId = null; | |
| let isEditMode = false; | |
| // Prefix map for unique identifiers | |
| const prefixMap = { | |
| 'Laptop': 'L', | |
| 'Docking stations': 'D', | |
| 'Monitors': 'M', | |
| 'Keyboards and mice': 'K', | |
| 'Headphones': 'H', | |
| 'Mobile telephones': 'MT', | |
| 'TV': 'TV', | |
| 'Printers': 'P' | |
| }; | |
| // Initialize the app | |
| function init() { | |
| // Set current date as default for date fields | |
| const today = new Date().toISOString().split('T')[0]; | |
| document.getElementById('datePurchased').value = today; | |
| // Calculate warranty expiration (3 years from today by default) | |
| const warrantyDate = new Date(); | |
| warrantyDate.setFullYear(warrantyDate.getFullYear() + 3); | |
| document.getElementById('warrantyExpiration').value = warrantyDate.toISOString().split('T')[0]; | |
| renderInventoryTable(); | |
| checkWarrantyAlerts(); | |
| // Event listeners | |
| 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); | |
| // Generate unique ID when type changes | |
| document.getElementById('type').addEventListener('change', generateUniqueIdentifier); | |
| } | |
| // Toggle between form and table view | |
| 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'; | |
| } | |
| } | |
| // Create new item | |
| function newItem() { | |
| resetForm(); | |
| isEditMode = false; | |
| currentItemId = null; | |
| generateUniqueIdentifier(); | |
| scrollToTop(); | |
| } | |
| // Reset form | |
| function resetForm() { | |
| hardwareForm.reset(); | |
| document.getElementById('itemId').value = ''; | |
| document.getElementById('uniqueIdentifier').value = ''; | |
| document.getElementById('departmentManager').value = 'Gunnar'; | |
| // Set current date as default for date fields | |
| const today = new Date().toISOString().split('T')[0]; | |
| document.getElementById('datePurchased').value = today; | |
| // Calculate warranty expiration (3 years from today by default) | |
| const warrantyDate = new Date(); | |
| warrantyDate.setFullYear(warrantyDate.getFullYear() + 3); | |
| document.getElementById('warrantyExpiration').value = warrantyDate.toISOString().split('T')[0]; | |
| } | |
| // Generate unique identifier based on type | |
| 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; | |
| } | |
| // Save item (create or update) | |
| function saveItem(e) { | |
| e.preventDefault(); | |
| const formData = new FormData(hardwareForm); | |
| const itemData = {}; | |
| formData.forEach((value, key) => { | |
| itemData[key] = value; | |
| }); | |
| if (isEditMode && currentItemId) { | |
| // Update existing item | |
| const index = inventoryData.findIndex(item => item.id === currentItemId); | |
| if (index !== -1) { | |
| inventoryData[index] = { | |
| ...inventoryData[index], | |
| ...itemData, | |
| id: currentItemId | |
| }; | |
| showToast('Item updated successfully!'); | |
| } | |
| } else { | |
| // Create new item | |
| 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(); | |
| } | |
| // Edit item | |
| function editItem(id) { | |
| const item = inventoryData.find(item => item.id === id); | |
| if (!item) return; | |
| // Populate form with item data | |
| for (const key in item) { | |
| if (document.getElementById(key)) { | |
| document.getElementById(key).value = item[key]; | |
| } | |
| } | |
| currentItemId = id; | |
| isEditMode = true; | |
| scrollToTop(); | |
| } | |
| // Delete item | |
| 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; | |
| } | |
| } | |
| } | |
| // Render inventory table | |
| 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'; | |
| // Calculate warranty status | |
| 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); | |
| }); | |
| } | |
| // Toggle search panel | |
| 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'; | |
| } | |
| } | |
| // Apply search filters | |
| 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(); | |
| } | |
| // Clear search filters | |
| function clearSearch() { | |
| document.getElementById('searchType').value = ''; | |
| document.getElementById('searchCompany').value = ''; | |
| document.getElementById('searchDepartment').value = ''; | |
| document.getElementById('searchMake').value = ''; | |
| document.getElementById('searchEmployee').value = ''; | |
| renderInventoryTable(); | |
| } | |
| // Check for warranty alerts | |
| function checkWarrantyAlerts() { | |
| const today = new Date(); | |
| const oneMonthFromNow = new Date(); | |
| oneMonthFromNow.setMonth(today.getMonth() + 1); | |
| const twoWeeksFromNow = new Date(); | |
| twoWeeksFromNow.setDate(today.getDate() + 14); | |
| // Clear previous alerts | |
| monthExpiryList.innerHTML = ''; | |
| weekExpiryList.innerHTML = ''; | |
| inventoryData.forEach(item => { | |
| const expiryDate = new Date(item.warrantyExpiration); | |
| if (expiryDate <= twoWeeksFromNow && expiryDate >= today) { | |
| // Within 2 weeks expiry | |
| 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) { | |
| // Within 1 month expiry | |
| 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); | |
| } | |
| }); | |
| // Show empty state if no alerts | |
| 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>'; | |
| } | |
| } | |
| // Send alerts via email (simulated) | |
| function sendAlerts() { | |
| // In a real app, this would connect to an email service | |
| // For demo purposes, we'll just show a toast notification | |
| 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`; | |
| }); | |
| } | |
| // Show confirmation dialog with the message that would be sent | |
| if (confirm(`${message}\nConfirm sending these alerts?`)) { | |
| showToast('Warranty alerts sent successfully!'); | |
| } | |
| } | |
| // Export to PDF | |
| function exportToPDF() { | |
| // Using jsPDF with autoTable plugin | |
| const { jsPDF } = window.jspdf; | |
| const doc = new jsPDF(); | |
| // Add title | |
| doc.setFontSize(18); | |
| doc.text('Sens Gruppen Hardware Inventory', 14, 22); | |
| doc.setFontSize(12); | |
| doc.text(`Generated on ${new Date().toLocaleDateString()}`, 14, 30); | |
| // Prepare data for the table | |
| 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() : '-' | |
| ]); | |
| // Add table to PDF | |
| 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 } | |
| }); | |
| // Save the PDF | |
| doc.save('Sens_Hardware_Inventory.pdf'); | |
| showToast('Inventory exported to PDF'); | |
| } | |
| // Print inventory | |
| function printInventory() { | |
| // Create a printable version of the inventory | |
| 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> | |
| `; | |
| // Add table rows | |
| 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> |