mirror of
https://github.com/sassanix/Warracker.git
synced 2026-01-06 05:29:39 -06:00
Grid view, manuals, prices, table view, export option, filters
## [0.5.0] - 2025-03-07 ### Added - Enhanced filtering and sorting capabilities - Status filter (All, Active, Expiring Soon, Expired) - Multiple sorting options (Expiration Date, Purchase Date, Name) - Export filtered warranties as CSV - Improved filter controls layout - Mobile-responsive filter design - Multiple view options for warranty display - Grid view with card layout (default) - List view for compact horizontal display - Table view for structured data presentation - View preference saved between sessions - Responsive design for all view types - Optional purchase price tracking - Users can now add purchase prices to warranties - Price information displayed in warranty cards - Currency formatting with dollar sign - Included in warranty summary and exports ### Changed - Completely redesigned user interface - Modern card-based layout for warranties - Enhanced filter controls with improved styling - Better visual hierarchy with labeled filter groups - Custom dropdown styling with intuitive icons - Improved spacing and alignment throughout - Consistent color scheme and visual feedback - Responsive grid layout for warranty cards ### Fixed - Status indicator borders now correctly displayed for all warranty states - Green border for active warranties - Orange border for warranties expiring soon - Red border for expired warranties - Consistent status styling across all warranty cards - Form now resets to first tab after successful warranty submission - Manual filename now properly cleared when form is reset ## [0.4.0] - 2025-03-07 ### Added - Improved warranty creation process - Multi-step form with intuitive navigation - Progress indicator showing completion status - Enhanced validation with clear error messages - Summary review step before submission - Expiration date preview in summary - Responsive design for all device sizes ### Fixed - Progress indicator alignment issue in multi-step form - Contained indicator within form boundaries - Prevented overflow with improved CSS approach - Ensured consistent tab widths for better alignment - Improved tab navigation visual feedback ## [0.3.0] - 2025-03-07 ### Added - Product manual upload support - Users can now upload a second document for product manuals - Manual documents are displayed alongside invoices in the warranty details - Both add and edit forms support manual uploads - Product URL support - Users can now add website URLs for products - Links to product websites displayed in warranty cards - Easy access to product support and information pages ### Changed - Improved document link styling for consistency - Enhanced visual appearance of document links - Consistent styling between invoice and manual links - Better hover effects for document links - Fixed styling inconsistencies between document links - Improved warranty card layout - Document links now displayed side by side for better space utilization - Responsive design adapts to different screen sizes - More compact and organized appearance ### Fixed - Styling inconsistency between View Invoice and View Manual buttons - Removed unused CSS file to prevent styling conflicts
This commit is contained in:
@@ -3,6 +3,27 @@ const warrantyForm = document.getElementById('warrantyForm');
|
||||
const settingsBtn = document.getElementById('settingsBtn');
|
||||
const settingsMenu = document.getElementById('settingsMenu');
|
||||
const darkModeToggle = document.getElementById('darkModeToggle');
|
||||
const warrantiesList = document.getElementById('warrantiesList');
|
||||
const refreshBtn = document.getElementById('refreshBtn');
|
||||
const searchInput = document.getElementById('searchWarranties');
|
||||
const statusFilter = document.getElementById('statusFilter');
|
||||
const sortBySelect = document.getElementById('sortBy');
|
||||
const exportBtn = document.getElementById('exportBtn');
|
||||
const gridViewBtn = document.getElementById('gridViewBtn');
|
||||
const listViewBtn = document.getElementById('listViewBtn');
|
||||
const tableViewBtn = document.getElementById('tableViewBtn');
|
||||
const tableViewHeader = document.querySelector('.table-view-header');
|
||||
const fileInput = document.getElementById('invoice');
|
||||
const fileName = document.getElementById('fileName');
|
||||
const manualInput = document.getElementById('manual');
|
||||
const manualFileName = document.getElementById('manualFileName');
|
||||
const editModal = document.getElementById('editModal');
|
||||
const deleteModal = document.getElementById('deleteModal');
|
||||
const editWarrantyForm = document.getElementById('editWarrantyForm');
|
||||
const saveWarrantyBtn = document.getElementById('saveWarrantyBtn');
|
||||
const confirmDeleteBtn = document.getElementById('confirmDeleteBtn');
|
||||
const loadingContainer = document.getElementById('loadingContainer');
|
||||
const toastContainer = document.getElementById('toastContainer');
|
||||
|
||||
// Theme Management
|
||||
function setTheme(isDark) {
|
||||
@@ -73,27 +94,18 @@ function addSerialNumberInput(container = serialNumbersContainer) {
|
||||
container.appendChild(newInput);
|
||||
}
|
||||
|
||||
const warrantiesList = document.getElementById('warrantiesList');
|
||||
const refreshBtn = document.getElementById('refreshBtn');
|
||||
const searchInput = document.getElementById('searchWarranties');
|
||||
const fileInput = document.getElementById('invoice');
|
||||
const fileName = document.getElementById('fileName');
|
||||
const manualInput = document.getElementById('manual');
|
||||
const manualFileName = document.getElementById('manualFileName');
|
||||
const editModal = document.getElementById('editModal');
|
||||
const deleteModal = document.getElementById('deleteModal');
|
||||
const editWarrantyForm = document.getElementById('editWarrantyForm');
|
||||
const saveWarrantyBtn = document.getElementById('saveWarrantyBtn');
|
||||
const confirmDeleteBtn = document.getElementById('confirmDeleteBtn');
|
||||
const loadingContainer = document.getElementById('loadingContainer');
|
||||
const toastContainer = document.getElementById('toastContainer');
|
||||
|
||||
// Variables
|
||||
let warranties = [];
|
||||
let currentWarrantyId = null;
|
||||
let currentFilters = {
|
||||
search: '',
|
||||
status: 'all',
|
||||
sort: 'expiration'
|
||||
};
|
||||
let currentView = 'grid'; // Default view
|
||||
|
||||
// API URL
|
||||
const API_URL = '/api/warranties'; // CORRECTED API_URL (relative URL)
|
||||
const API_URL = '/api/warranties';
|
||||
|
||||
// Event Listeners
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
@@ -114,6 +126,36 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
e.stopPropagation();
|
||||
});
|
||||
});
|
||||
|
||||
// Initialize form tabs
|
||||
initFormTabs();
|
||||
|
||||
// Filter event listeners
|
||||
searchInput.addEventListener('input', () => {
|
||||
currentFilters.search = searchInput.value.toLowerCase();
|
||||
applyFilters();
|
||||
});
|
||||
|
||||
statusFilter.addEventListener('change', () => {
|
||||
currentFilters.status = statusFilter.value;
|
||||
applyFilters();
|
||||
});
|
||||
|
||||
sortBySelect.addEventListener('change', () => {
|
||||
currentFilters.sort = sortBySelect.value;
|
||||
applyFilters();
|
||||
});
|
||||
|
||||
// View switcher event listeners
|
||||
gridViewBtn.addEventListener('click', () => switchView('grid'));
|
||||
listViewBtn.addEventListener('click', () => switchView('list'));
|
||||
tableViewBtn.addEventListener('click', () => switchView('table'));
|
||||
|
||||
// Export button event listener
|
||||
exportBtn.addEventListener('click', exportWarranties);
|
||||
|
||||
// Load saved view preference
|
||||
loadViewPreference();
|
||||
});
|
||||
|
||||
// File input change event
|
||||
@@ -132,9 +174,6 @@ warrantyForm.addEventListener('submit', addWarranty);
|
||||
// Refresh button
|
||||
refreshBtn.addEventListener('click', loadWarranties);
|
||||
|
||||
// Search input
|
||||
searchInput.addEventListener('input', filterWarranties);
|
||||
|
||||
// Save warranty changes
|
||||
saveWarrantyBtn.addEventListener('click', updateWarranty);
|
||||
|
||||
@@ -226,22 +265,30 @@ function renderWarranties(filteredWarranties = null) {
|
||||
|
||||
warrantiesList.innerHTML = '';
|
||||
|
||||
// Sort warranties: expiring soon first, then active, then expired
|
||||
warrantiesToRender.sort((a, b) => {
|
||||
const dateA = new Date(a.expiration_date);
|
||||
const dateB = new Date(b.expiration_date);
|
||||
|
||||
const isExpiredA = dateA < today;
|
||||
const isExpiredB = dateB < today;
|
||||
|
||||
if (isExpiredA && !isExpiredB) return 1;
|
||||
if (!isExpiredA && isExpiredB) return -1;
|
||||
|
||||
// Both active or both expired, sort by date
|
||||
return dateA - dateB;
|
||||
// Apply sorting based on current sort selection
|
||||
const sortedWarranties = [...warrantiesToRender].sort((a, b) => {
|
||||
switch (currentFilters.sort) {
|
||||
case 'name':
|
||||
return a.product_name.localeCompare(b.product_name);
|
||||
case 'purchase':
|
||||
return new Date(b.purchase_date) - new Date(a.purchase_date);
|
||||
case 'expiration':
|
||||
default:
|
||||
const dateA = new Date(a.expiration_date);
|
||||
const dateB = new Date(b.expiration_date);
|
||||
|
||||
const isExpiredA = dateA < today;
|
||||
const isExpiredB = dateB < today;
|
||||
|
||||
if (isExpiredA && !isExpiredB) return 1;
|
||||
if (!isExpiredA && isExpiredB) return -1;
|
||||
|
||||
// Both active or both expired, sort by date
|
||||
return dateA - dateB;
|
||||
}
|
||||
});
|
||||
|
||||
warrantiesToRender.forEach(warranty => {
|
||||
sortedWarranties.forEach(warranty => {
|
||||
const purchaseDate = new Date(warranty.purchase_date);
|
||||
const expirationDate = new Date(warranty.expiration_date);
|
||||
const daysRemaining = Math.floor((expirationDate - today) / (1000 * 60 * 60 * 24));
|
||||
@@ -259,13 +306,17 @@ function renderWarranties(filteredWarranties = null) {
|
||||
statusText = `${daysRemaining} days remaining`;
|
||||
}
|
||||
|
||||
// Add status to warranty object for filtering
|
||||
warranty.status = statusClass;
|
||||
warranty.daysRemaining = daysRemaining;
|
||||
|
||||
// Make sure serial numbers array exists and is valid
|
||||
const validSerialNumbers = Array.isArray(warranty.serial_numbers)
|
||||
? warranty.serial_numbers.filter(sn => sn && typeof sn === 'string' && sn.trim() !== '')
|
||||
: [];
|
||||
|
||||
const cardElement = document.createElement('div');
|
||||
cardElement.className = `warranty-card ${statusClass === 'expired' ? 'expired' : statusClass === 'expiring' ? 'expiring-soon' : ''}`;
|
||||
cardElement.className = `warranty-card ${statusClass === 'expired' ? 'expired' : statusClass === 'expiring' ? 'expiring-soon' : 'active'}`;
|
||||
cardElement.innerHTML = `
|
||||
<div class="warranty-header">
|
||||
<h3 class="warranty-title">${warranty.product_name}</h3>
|
||||
@@ -282,6 +333,7 @@ function renderWarranties(filteredWarranties = null) {
|
||||
<div>Purchased: <span>${formatDate(purchaseDate)}</span></div>
|
||||
<div>Warranty: <span>${warranty.warranty_years} ${warranty.warranty_years > 1 ? 'years' : 'year'}</span></div>
|
||||
<div>Expires: <span>${formatDate(expirationDate)}</span></div>
|
||||
${warranty.purchase_price ? `<div>Price: <span>$${parseFloat(warranty.purchase_price).toFixed(2)}</span></div>` : ''}
|
||||
<span class="warranty-status status-${statusClass}">${statusText}</span>
|
||||
${validSerialNumbers.length > 0 ? `
|
||||
<div class="serial-numbers">
|
||||
@@ -291,20 +343,23 @@ function renderWarranties(filteredWarranties = null) {
|
||||
</ul>
|
||||
</div>
|
||||
` : ''}
|
||||
${warranty.invoice_path ? `
|
||||
<div class="document-link-container">
|
||||
<div class="document-links-row">
|
||||
${warranty.product_url ? `
|
||||
<a href="${warranty.product_url}" class="product-link" target="_blank">
|
||||
<i class="fas fa-globe"></i> Product Website
|
||||
</a>
|
||||
` : ''}
|
||||
${warranty.invoice_path ? `
|
||||
<a href="${warranty.invoice_path}" class="invoice-link" target="_blank">
|
||||
<i class="fas fa-file-invoice"></i> View Invoice
|
||||
<i class="fas fa-file-invoice"></i> Invoice
|
||||
</a>
|
||||
</div>
|
||||
` : ''}
|
||||
${warranty.manual_path ? `
|
||||
<div class="document-link-container">
|
||||
` : ''}
|
||||
${warranty.manual_path ? `
|
||||
<a href="${warranty.manual_path}" class="manual-link" target="_blank">
|
||||
<i class="fas fa-book"></i> View Manual
|
||||
<i class="fas fa-book"></i> Manual
|
||||
</a>
|
||||
</div>
|
||||
` : ''}
|
||||
` : ''}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -381,6 +436,7 @@ async function addWarranty(event) {
|
||||
// Reset form
|
||||
warrantyForm.reset();
|
||||
fileName.textContent = '';
|
||||
manualFileName.textContent = '';
|
||||
|
||||
// Completely reset serial number inputs
|
||||
serialNumbersContainer.innerHTML = '';
|
||||
@@ -402,6 +458,9 @@ async function addWarranty(event) {
|
||||
|
||||
serialNumbersContainer.appendChild(initialInput);
|
||||
|
||||
// Switch back to the first tab
|
||||
switchToTab(0);
|
||||
|
||||
// Reload warranties
|
||||
loadWarranties();
|
||||
} catch (error) {
|
||||
@@ -416,10 +475,11 @@ function openEditModal(warranty) {
|
||||
currentWarrantyId = warranty.id;
|
||||
|
||||
// Populate form fields
|
||||
document.getElementById('editWarrantyId').value = warranty.id;
|
||||
document.getElementById('editProductName').value = warranty.product_name;
|
||||
document.getElementById('editPurchaseDate').value = new Date(warranty.purchase_date).toISOString().split('T')[0];
|
||||
document.getElementById('editProductUrl').value = warranty.product_url || '';
|
||||
document.getElementById('editPurchaseDate').value = warranty.purchase_date.split('T')[0];
|
||||
document.getElementById('editWarrantyYears').value = warranty.warranty_years;
|
||||
document.getElementById('editPurchasePrice').value = warranty.purchase_price || '';
|
||||
|
||||
// Clear existing serial number inputs
|
||||
const editSerialNumbersContainer = document.getElementById('editSerialNumbersContainer');
|
||||
@@ -585,4 +645,359 @@ async function deleteWarranty() {
|
||||
} finally {
|
||||
hideLoading();
|
||||
}
|
||||
}
|
||||
|
||||
// Form tab navigation variables
|
||||
const formTabs = Array.from(document.querySelectorAll('.form-tab'));
|
||||
const tabContents = Array.from(document.querySelectorAll('.tab-content'));
|
||||
let currentTabIndex = 0;
|
||||
|
||||
// Initialize form tabs
|
||||
function initFormTabs() {
|
||||
// Hide the submit button initially
|
||||
const submitButton = document.querySelector('button[type="submit"]');
|
||||
if (submitButton) {
|
||||
submitButton.style.display = 'none';
|
||||
}
|
||||
|
||||
// Show/hide navigation buttons based on current tab
|
||||
updateNavigationButtons();
|
||||
|
||||
// Add event listeners for tab navigation
|
||||
document.querySelector('.next-tab').addEventListener('click', () => {
|
||||
if (validateTab(currentTabIndex)) {
|
||||
switchToTab(currentTabIndex + 1);
|
||||
} else {
|
||||
showValidationErrors(currentTabIndex);
|
||||
}
|
||||
});
|
||||
|
||||
document.querySelector('.prev-tab').addEventListener('click', () => {
|
||||
switchToTab(currentTabIndex - 1);
|
||||
});
|
||||
|
||||
// Add click event for tab headers
|
||||
formTabs.forEach((tab, index) => {
|
||||
tab.addEventListener('click', () => {
|
||||
// Only allow clicking on previous tabs or current tab
|
||||
if (index <= currentTabIndex) {
|
||||
switchToTab(index);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Switch to a specific tab
|
||||
function switchToTab(index) {
|
||||
// Ensure index is within bounds
|
||||
if (index < 0 || index >= formTabs.length) return;
|
||||
|
||||
// Update active tab
|
||||
formTabs.forEach(tab => tab.classList.remove('active'));
|
||||
tabContents.forEach(content => content.classList.remove('active'));
|
||||
|
||||
formTabs[index].classList.add('active');
|
||||
tabContents[index].classList.add('active');
|
||||
|
||||
// Update current tab index
|
||||
currentTabIndex = index;
|
||||
|
||||
// Update progress indicator
|
||||
document.querySelector('.form-tabs').setAttribute('data-step', currentTabIndex);
|
||||
|
||||
// Update completed tabs
|
||||
updateCompletedTabs();
|
||||
|
||||
// Update navigation buttons
|
||||
updateNavigationButtons();
|
||||
|
||||
// Update summary if on summary tab
|
||||
if (index === formTabs.length - 1) {
|
||||
updateSummary();
|
||||
}
|
||||
}
|
||||
|
||||
// Update navigation buttons based on current tab
|
||||
function updateNavigationButtons() {
|
||||
const prevButton = document.querySelector('.prev-tab');
|
||||
const nextButton = document.querySelector('.next-tab');
|
||||
const submitButton = document.querySelector('button[type="submit"]');
|
||||
|
||||
// Hide/show previous button
|
||||
prevButton.style.display = currentTabIndex === 0 ? 'none' : 'block';
|
||||
|
||||
// Hide/show next button and submit button
|
||||
if (currentTabIndex === formTabs.length - 1) {
|
||||
nextButton.style.display = 'none';
|
||||
submitButton.style.display = 'block';
|
||||
} else {
|
||||
nextButton.style.display = 'block';
|
||||
submitButton.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// Update completed tabs
|
||||
function updateCompletedTabs() {
|
||||
formTabs.forEach((tab, index) => {
|
||||
if (index < currentTabIndex) {
|
||||
tab.classList.add('completed');
|
||||
} else {
|
||||
tab.classList.remove('completed');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Validate a specific tab
|
||||
function validateTab(tabIndex) {
|
||||
const tabContent = tabContents[tabIndex];
|
||||
const requiredInputs = tabContent.querySelectorAll('input[required]');
|
||||
let isValid = true;
|
||||
|
||||
requiredInputs.forEach(input => {
|
||||
if (!input.value.trim()) {
|
||||
isValid = false;
|
||||
input.classList.add('invalid');
|
||||
} else {
|
||||
input.classList.remove('invalid');
|
||||
}
|
||||
});
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
// Show validation errors for a specific tab
|
||||
function showValidationErrors(tabIndex) {
|
||||
const tabContent = tabContents[tabIndex];
|
||||
const requiredInputs = tabContent.querySelectorAll('input[required]');
|
||||
|
||||
requiredInputs.forEach(input => {
|
||||
if (!input.value.trim()) {
|
||||
input.classList.add('invalid');
|
||||
|
||||
// Add validation message if not already present
|
||||
let validationMessage = input.nextElementSibling;
|
||||
if (!validationMessage || !validationMessage.classList.contains('validation-message')) {
|
||||
validationMessage = document.createElement('div');
|
||||
validationMessage.className = 'validation-message';
|
||||
validationMessage.textContent = 'This field is required';
|
||||
input.parentNode.insertBefore(validationMessage, input.nextSibling);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Show toast message
|
||||
showToast('Please fill in all required fields', 'error');
|
||||
}
|
||||
|
||||
// Update summary tab with current form values
|
||||
function updateSummary() {
|
||||
// Product information
|
||||
document.getElementById('summary-product-name').textContent =
|
||||
document.getElementById('productName').value || '-';
|
||||
|
||||
document.getElementById('summary-product-url').textContent =
|
||||
document.getElementById('productUrl').value || '-';
|
||||
|
||||
// Serial numbers
|
||||
const serialNumbers = [];
|
||||
document.querySelectorAll('input[name="serial_numbers[]"]').forEach(input => {
|
||||
if (input.value.trim()) {
|
||||
serialNumbers.push(input.value.trim());
|
||||
}
|
||||
});
|
||||
|
||||
const serialNumbersContainer = document.getElementById('summary-serial-numbers');
|
||||
if (serialNumbers.length > 0) {
|
||||
serialNumbersContainer.innerHTML = '<ul>' +
|
||||
serialNumbers.map(sn => `<li>${sn}</li>`).join('') +
|
||||
'</ul>';
|
||||
} else {
|
||||
serialNumbersContainer.textContent = 'None';
|
||||
}
|
||||
|
||||
// Warranty details
|
||||
const purchaseDate = document.getElementById('purchaseDate').value;
|
||||
document.getElementById('summary-purchase-date').textContent = purchaseDate ?
|
||||
new Date(purchaseDate).toLocaleDateString() : '-';
|
||||
|
||||
const warrantyYears = document.getElementById('warrantyYears').value;
|
||||
document.getElementById('summary-warranty-years').textContent = warrantyYears ?
|
||||
`${warrantyYears} ${warrantyYears > 1 ? 'years' : 'year'}` : '-';
|
||||
|
||||
// Calculate and display expiration date
|
||||
if (purchaseDate && warrantyYears) {
|
||||
const expirationDate = new Date(purchaseDate);
|
||||
expirationDate.setFullYear(expirationDate.getFullYear() + parseInt(warrantyYears));
|
||||
document.getElementById('summary-expiration-date').textContent =
|
||||
expirationDate.toLocaleDateString();
|
||||
} else {
|
||||
document.getElementById('summary-expiration-date').textContent = '-';
|
||||
}
|
||||
|
||||
// Purchase price
|
||||
const purchasePrice = document.getElementById('purchasePrice').value;
|
||||
document.getElementById('summary-purchase-price').textContent = purchasePrice ?
|
||||
`$${parseFloat(purchasePrice).toFixed(2)}` : 'Not specified';
|
||||
|
||||
// Documents
|
||||
const invoiceFile = document.getElementById('invoice').files[0];
|
||||
document.getElementById('summary-invoice').textContent = invoiceFile ?
|
||||
invoiceFile.name : 'No file selected';
|
||||
|
||||
const manualFile = document.getElementById('manual').files[0];
|
||||
document.getElementById('summary-manual').textContent = manualFile ?
|
||||
manualFile.name : 'No file selected';
|
||||
}
|
||||
|
||||
// Add input event listeners to remove validation errors when user types
|
||||
document.addEventListener('input', (e) => {
|
||||
if (e.target.hasAttribute('required') && e.target.classList.contains('invalid')) {
|
||||
if (e.target.value.trim()) {
|
||||
e.target.classList.remove('invalid');
|
||||
|
||||
// Remove validation message if exists
|
||||
const validationMessage = e.target.nextElementSibling;
|
||||
if (validationMessage && validationMessage.classList.contains('validation-message')) {
|
||||
validationMessage.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function applyFilters() {
|
||||
let filteredWarranties = [...warranties];
|
||||
|
||||
// Apply search filter
|
||||
if (currentFilters.search) {
|
||||
filteredWarranties = filteredWarranties.filter(warranty =>
|
||||
warranty.product_name.toLowerCase().includes(currentFilters.search)
|
||||
);
|
||||
}
|
||||
|
||||
// Apply status filter
|
||||
if (currentFilters.status !== 'all') {
|
||||
filteredWarranties = filteredWarranties.filter(warranty => {
|
||||
// We need to calculate the status if it's not already set
|
||||
if (!warranty.status) {
|
||||
const today = new Date();
|
||||
const expirationDate = new Date(warranty.expiration_date);
|
||||
const daysRemaining = Math.floor((expirationDate - today) / (1000 * 60 * 60 * 24));
|
||||
|
||||
if (daysRemaining < 0) {
|
||||
warranty.status = 'expired';
|
||||
} else if (daysRemaining < 30) {
|
||||
warranty.status = 'expiring';
|
||||
} else {
|
||||
warranty.status = 'active';
|
||||
}
|
||||
}
|
||||
|
||||
return warranty.status === currentFilters.status;
|
||||
});
|
||||
}
|
||||
|
||||
renderWarranties(filteredWarranties);
|
||||
}
|
||||
|
||||
function exportWarranties() {
|
||||
// Get filtered warranties
|
||||
let warrantiesToExport = [...warranties];
|
||||
|
||||
// Apply current filters
|
||||
if (currentFilters.search) {
|
||||
warrantiesToExport = warrantiesToExport.filter(warranty =>
|
||||
warranty.product_name.toLowerCase().includes(currentFilters.search)
|
||||
);
|
||||
}
|
||||
|
||||
if (currentFilters.status !== 'all') {
|
||||
warrantiesToExport = warrantiesToExport.filter(warranty =>
|
||||
warranty.status === currentFilters.status
|
||||
);
|
||||
}
|
||||
|
||||
// Create CSV content
|
||||
let csvContent = "data:text/csv;charset=utf-8,";
|
||||
|
||||
// Add headers
|
||||
csvContent += "Product Name,Purchase Date,Warranty Period,Expiration Date,Status,Serial Numbers\n";
|
||||
|
||||
// Add data rows
|
||||
warrantiesToExport.forEach(warranty => {
|
||||
const purchaseDate = new Date(warranty.purchase_date).toLocaleDateString();
|
||||
const expirationDate = new Date(warranty.expiration_date).toLocaleDateString();
|
||||
const serialNumbers = Array.isArray(warranty.serial_numbers)
|
||||
? warranty.serial_numbers.join('; ')
|
||||
: '';
|
||||
|
||||
// Calculate status if not already set
|
||||
if (!warranty.status) {
|
||||
const today = new Date();
|
||||
const expDate = new Date(warranty.expiration_date);
|
||||
const daysRemaining = Math.floor((expDate - today) / (1000 * 60 * 60 * 24));
|
||||
|
||||
if (daysRemaining < 0) {
|
||||
warranty.status = 'Expired';
|
||||
} else if (daysRemaining < 30) {
|
||||
warranty.status = 'Expiring Soon';
|
||||
} else {
|
||||
warranty.status = 'Active';
|
||||
}
|
||||
}
|
||||
|
||||
// Format status for CSV
|
||||
let statusText = warranty.status.charAt(0).toUpperCase() + warranty.status.slice(1);
|
||||
if (statusText === 'Expiring') statusText = 'Expiring Soon';
|
||||
|
||||
// Create CSV row
|
||||
csvContent += `"${warranty.product_name}",${purchaseDate},${warranty.warranty_years} years,${expirationDate},${statusText},"${serialNumbers}"\n`;
|
||||
});
|
||||
|
||||
// Create download link
|
||||
const encodedUri = encodeURI(csvContent);
|
||||
const link = document.createElement("a");
|
||||
link.setAttribute("href", encodedUri);
|
||||
link.setAttribute("download", "warranties.csv");
|
||||
document.body.appendChild(link);
|
||||
|
||||
// Trigger download
|
||||
link.click();
|
||||
|
||||
// Clean up
|
||||
document.body.removeChild(link);
|
||||
|
||||
showToast('Warranties exported successfully', 'success');
|
||||
}
|
||||
|
||||
// Function to switch between views
|
||||
function switchView(viewType) {
|
||||
// Update current view
|
||||
currentView = viewType;
|
||||
|
||||
// Update view buttons
|
||||
gridViewBtn.classList.toggle('active', viewType === 'grid');
|
||||
listViewBtn.classList.toggle('active', viewType === 'list');
|
||||
tableViewBtn.classList.toggle('active', viewType === 'table');
|
||||
|
||||
// Update warranties list class
|
||||
warrantiesList.className = `warranties-list ${viewType}-view`;
|
||||
|
||||
// Show/hide table header for table view
|
||||
if (tableViewHeader) {
|
||||
tableViewHeader.classList.toggle('visible', viewType === 'table');
|
||||
}
|
||||
|
||||
// Re-render warranties with the new view
|
||||
renderWarranties();
|
||||
|
||||
// Save view preference to localStorage
|
||||
localStorage.setItem('warrantyView', viewType);
|
||||
}
|
||||
|
||||
// Load saved view preference
|
||||
function loadViewPreference() {
|
||||
const savedView = localStorage.getItem('warrantyView');
|
||||
if (savedView) {
|
||||
switchView(savedView);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user