Fix global warranties view, add Model Number field, and enhance modal tab responsiveness

* **Fixed:**

  * Global view on Index page now correctly shows warranties from all users, including archived ones.
  * Added `GET /api/warranties/global/archived` and unified global queries with correlated subqueries to avoid missing or collapsed rows.
  * Updated frontend logic to merge archived warranties from the new endpoint when Global scope and Status = “All.”
  * Bumped `script.js` and service worker cache to ensure clients receive updated logic.
  * Updated files: `backend/warranties_routes.py`, `frontend/script.js`, `frontend/sw.js`, `frontend/index.html`, `frontend/status.html`.

* **Added:**

  * Introduced **Model Number** field to warranties.
  * Backend: Added `model_number` column, integrated into GET/POST/PUT routes.
  * Frontend: Added Model Number input in New/Edit modals and display on warranty cards.
  * Updated files: `backend/migrations/047_add_model_number_to_warranties.sql`, `backend/warranties_routes.py`, `frontend/index.html`, `frontend/status.html`, `frontend/script.js`, `locales/en/translation.json`.

* **Enhanced:**

  * Improved **Add Warranty modal** tab alignment for responsive layouts (≤740px).
  * Adjusted tab label size and spacing to prevent wrapping while keeping icons and labels visible.
  * Ensured consistent five-step progress indicator across all breakpoints.
  * Updated file: `frontend/style.css`.
This commit is contained in:
sassanix
2025-10-09 15:04:13 -03:00
parent 9007c9c23a
commit 96f2859975
12 changed files with 308 additions and 68 deletions

View File

@@ -323,7 +323,7 @@
<!-- Hero Section -->
<div class="about-hero">
<h1><i class="fas fa-shield-alt"></i> Warracker</h1>
<div class="version" id="versionDisplay" data-i18n="about.version">Version v0.10.1.14</div>
<div class="version" id="versionDisplay" data-i18n="about.version">Version v0.10.1.15</div>
<p class="description" data-i18n="about.description">
A comprehensive warranty management system designed to help you track, organize, and manage all your product warranties in one secure, user-friendly platform.
</p>
@@ -421,7 +421,7 @@
// Update version display dynamically
const versionDisplay = document.getElementById('versionDisplay');
if (versionDisplay && window.i18next) {
const currentVersion = '0.10.1.14'; // This should match version-checker.js
const currentVersion = '0.10.1.15'; // This should match version-checker.js
versionDisplay.textContent = window.i18next.t('about.version') + ' v' + currentVersion;
}

View File

@@ -415,6 +415,10 @@
<input type="text" name="serial_numbers[]" class="form-control" style="margin-bottom: 8px;" data-i18n-placeholder="warranties.enter_serial_number" placeholder="Enter serial number">
</div>
</div>
<div class="form-group">
<label for="modelNumber" data-i18n="warranties.model_number_optional">Model Number (Optional)</label>
<input type="text" id="modelNumber" name="model_number" class="form-control" data-i18n-placeholder="warranties.model_number_placeholder" placeholder="e.g. SM-G991U1">
</div>
<div class="form-group">
<label for="vendor" data-i18n="warranties.vendor_optional">Vendor (Optional)</label>
<input type="text" id="vendor" name="vendor" class="form-control" data-i18n-placeholder="warranties.vendor_placeholder" placeholder="e.g. Amazon, Best Buy, etc.">
@@ -819,6 +823,10 @@
<!-- Serial number inputs will be added dynamically -->
</div>
</div>
<div class="form-group">
<label for="editModelNumber" data-i18n="warranties.model_number_optional">Model Number (Optional)</label>
<input type="text" id="editModelNumber" name="model_number" class="form-control" data-i18n-placeholder="warranties.model_number_placeholder" placeholder="e.g. SM-G991U1">
</div>
<div class="form-group">
<label for="editVendor" data-i18n="warranties.vendor_optional">Vendor (Optional)</label>
<input type="text" id="editVendor" name="vendor" class="form-control" data-i18n-placeholder="warranties.vendor_placeholder" placeholder="e.g. Amazon, Best Buy, etc.">
@@ -1318,7 +1326,7 @@
</div>
<script src="auth.js?v=20250119001"></script>
<script src="script.js?v=20250119001"></script>
<script src="script.js?v=20250119002"></script>
<script>
// Lightweight UI logic for Filter/Sort popovers (no change to core logic)
document.addEventListener('DOMContentLoaded', function () {

View File

@@ -936,6 +936,9 @@ let formTabs = []; // Changed from const to let, initialized as empty
if (notesModalWarrantyObj.warranty_type) {
formData.append('warranty_type', notesModalWarrantyObj.warranty_type);
}
if (typeof notesModalWarrantyObj.model_number !== 'undefined' && notesModalWarrantyObj.model_number !== null) {
formData.append('model_number', notesModalWarrantyObj.model_number);
}
if (notesModalWarrantyObj.serial_numbers && Array.isArray(notesModalWarrantyObj.serial_numbers)) {
notesModalWarrantyObj.serial_numbers.forEach(sn => {
if (sn && sn.trim() !== '') {
@@ -953,6 +956,11 @@ let formTabs = []; // Changed from const to let, initialized as empty
formData.append('tag_ids', JSON.stringify([]));
}
formData.append('notes', notesValue);
// Also include model number value from edit input if present on DOM
const editModelNumberInput = document.getElementById('editModelNumber');
if (editModelNumberInput && editModelNumberInput.value.trim() !== '') {
formData.set('model_number', editModelNumberInput.value.trim());
}
const response = await fetch(`/api/warranties/${warrantyId}`, {
method: 'PUT',
headers: {
@@ -2182,10 +2190,10 @@ async function loadWarranties(isAuthenticated) { // Added isAuthenticated parame
// Use the appropriate API endpoint based on saved preference
const baseUrl = window.location.origin;
// If status is 'archived', use archived endpoint (user-scoped only for now)
// If status is 'archived', use archived endpoint (support global vs personal)
const isArchivedView = currentFilters && currentFilters.status === 'archived';
const apiUrl = isArchivedView
? `${baseUrl}/api/warranties/archived`
? (shouldUseGlobalView ? `${baseUrl}/api/warranties/global/archived` : `${baseUrl}/api/warranties/archived`)
: (shouldUseGlobalView ? `${baseUrl}/api/warranties/global` : `${baseUrl}/api/warranties`);
console.log(`[DEBUG] Using API endpoint based on saved preference '${savedScope}', archivedView=${isArchivedView}: ${apiUrl}`);
@@ -2235,9 +2243,9 @@ async function loadWarranties(isAuthenticated) { // Added isAuthenticated parame
// Optionally merge archived items into the "All" view (only in personal scope)
let combinedData = Array.isArray(data) ? data : [];
lastLoadedIncludesArchived = false;
if (!shouldUseGlobalView && !isArchivedView && currentFilters && currentFilters.status === 'all') {
if (!isArchivedView && currentFilters && currentFilters.status === 'all') {
try {
const archivedUrl = `${baseUrl}/api/warranties/archived`;
const archivedUrl = shouldUseGlobalView ? `${baseUrl}/api/warranties/global/archived` : `${baseUrl}/api/warranties/archived`;
const archivedResp = await fetch(archivedUrl, options);
if (archivedResp.ok) {
const archivedData = await archivedResp.json();
@@ -2709,6 +2717,7 @@ async function renderWarranties(warrantiesToRender) {
</div>
` : ''}
` : ''}
${warranty.model_number ? `<div><i class="fas fa-tag"></i> ${window.i18next ? window.i18next.t('warranties.model_number') : 'Model Number'}: <span>${warranty.model_number}</span></div>` : ''}
${warranty.vendor ? `<div><i class="fas fa-store"></i> ${window.i18next ? window.i18next.t('warranties.vendor') : 'Vendor'}: <span>${warranty.vendor}</span></div>` : ''}
${warranty.warranty_type ? `<div><i class="fas fa-shield-alt"></i> ${window.i18next ? window.i18next.t('warranties.type') : 'Type'}: <span>${warranty.warranty_type}</span></div>` : ''}
</div>
@@ -2771,6 +2780,7 @@ async function renderWarranties(warrantiesToRender) {
</div>
` : ''}
` : ''}
${warranty.model_number ? `<div><i class=\"fas fa-tag\"></i> ${window.i18next ? window.i18next.t('warranties.model_number') : 'Model Number'}: <span>${warranty.model_number}</span></div>` : ''}
${warranty.vendor ? `<div><i class="fas fa-store"></i> ${window.i18next ? window.i18next.t('warranties.vendor') : 'Vendor'}: <span>${warranty.vendor}</span></div>` : ''}
${warranty.warranty_type ? `<div><i class="fas fa-shield-alt"></i> ${window.i18next ? window.i18next.t('warranties.type') : 'Type'}: <span>${warranty.warranty_type}</span></div>` : ''}
</div>
@@ -2833,6 +2843,7 @@ async function renderWarranties(warrantiesToRender) {
</div>
` : ''}
` : ''}
${warranty.model_number ? `<div><i class=\"fas fa-tag\"></i> ${window.i18next ? window.i18next.t('warranties.model_number') : 'Model Number'}: <span>${warranty.model_number}</span></div>` : ''}
${warranty.vendor ? `<div><i class="fas fa-store"></i> ${window.i18next ? window.i18next.t('warranties.vendor') : 'Vendor'}: <span>${warranty.vendor}</span></div>` : ''}
${warranty.warranty_type ? `<div><i class="fas fa-shield-alt"></i> ${window.i18next ? window.i18next.t('warranties.type') : 'Type'}: <span>${warranty.warranty_type}</span></div>` : ''}
</div>
@@ -3205,6 +3216,10 @@ async function openEditModal(warranty) {
// Populate form fields
document.getElementById('editProductName').value = warranty.product_name;
document.getElementById('editProductUrl').value = warranty.product_url || '';
const editModelNumberInput = document.getElementById('editModelNumber');
if (editModelNumberInput) {
editModelNumberInput.value = warranty.model_number || '';
}
document.getElementById('editPurchaseDate').value = warranty.purchase_date.split('T')[0];
// Populate new duration fields
document.getElementById('editWarrantyDurationYears').value = warranty.warranty_duration_years || 0;
@@ -3890,6 +3905,11 @@ async function handleFormSubmit(event) { // Made async to properly await paperle
// Create form data object
const formData = new FormData(warrantyForm);
// Ensure model_number is included if present
const modelNumberInput = document.getElementById('modelNumber');
if (modelNumberInput && modelNumberInput.value.trim() !== '') {
formData.set('model_number', modelNumberInput.value.trim());
}
// Handle warranty type - use custom value if "other" is selected
const warrantyTypeSelect = document.getElementById('warrantyType');
@@ -6359,6 +6379,15 @@ function saveWarranty() {
formData.append('notes', '');
}
// Add model number to form data (optional)
const editModelNumber = document.getElementById('editModelNumber');
if (editModelNumber && editModelNumber.value.trim() !== '') {
formData.append('model_number', editModelNumber.value.trim());
} else {
// Explicitly clear if empty
formData.append('model_number', '');
}
// Add vendor/retailer to form data
const editVendorInput = document.getElementById('editVendor'); // Use the correct ID
formData.append('vendor', editVendorInput ? editVendorInput.value.trim() : ''); // Use the correct variable

View File

@@ -37,7 +37,7 @@
<script src="js/i18n.js?v=20250119001"></script>
<!-- Load fix for auth buttons -->
<script src="fix-auth-buttons-loader.js?v=20250119001"></script>
<script src="script.js?v=20250119001" defer></script> <!-- Added script.js -->
<script src="script.js?v=20250119002" defer></script> <!-- Added script.js -->
<script src="status.js?v=20250119001" defer></script> <!-- Status page specific functionality -->
<style>
.user-menu {
@@ -551,6 +551,10 @@
<!-- Serial number inputs will be added dynamically -->
</div>
</div>
<div class="form-group">
<label for="editModelNumber" data-i18n="warranties.model_number_optional">Model Number (Optional)</label>
<input type="text" id="editModelNumber" name="model_number" class="form-control" data-i18n-placeholder="warranties.model_number_placeholder" placeholder="e.g. SM-G991U1">
</div>
<div class="form-group">
<label for="editVendor" data-i18n="warranties.vendor_optional">Vendor (Optional)</label>
<input type="text" id="editVendor" name="vendor" class="form-control" data-i18n-placeholder="warranties.vendor_placeholder" placeholder="e.g. Amazon, Best Buy, etc.">

View File

@@ -304,35 +304,16 @@ header .container {
display: none;
}
.form-tabs::before {
content: '';
position: absolute;
bottom: -2px;
height: 2px;
background-color: var(--secondary-color);
transition: all 0.3s ease;
width: 25%;
z-index: 1;
}
.form-tabs::before { display: none; }
.form-tabs[data-step="0"]::before {
left: 0;
}
.form-tabs[data-step="1"]::before {
left: 25%;
}
.form-tabs[data-step="2"]::before {
left: 50%;
}
.form-tabs[data-step="3"]::before {
left: 75%;
}
/* Progress handled via ::after below */
.form-tab {
flex: 1;
flex: 1 1 20%;
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
text-align: center;
padding: 15px 10px;
cursor: pointer;
@@ -340,7 +321,9 @@ header .container {
color: var(--dark-gray);
transition: color 0.3s ease;
box-sizing: border-box;
width: 25%; /* Ensure each tab is exactly 25% width */
width: 20%; /* Five tabs fit on one row */
min-width: 0; /* Allow shrinking without wrapping inner content */
white-space: nowrap; /* Prevent label wrapping */
}
.form-tab:hover {
@@ -430,16 +413,22 @@ input.invalid {
transition: width 0.3s ease;
}
.form-tabs[data-step="0"]::after {
width: calc(100% / 3);
.form-tabs[data-step="0"]::after { width: calc(100% / 5); }
.form-tabs[data-step="1"]::after { width: calc(200% / 5); }
.form-tabs[data-step="2"]::after { width: calc(300% / 5); }
.form-tabs[data-step="3"]::after { width: calc(400% / 5); }
.form-tabs[data-step="4"]::after { width: 100%; }
@media (max-width: 788px) {
.form-tab { padding: 12px 6px; }
.form-tab i { margin-right: 6px; }
}
.form-tabs[data-step="1"]::after {
width: calc(200% / 3);
}
.form-tabs[data-step="2"]::after {
width: 100%;
/* On narrow screens, shrink labels to keep tabs uniform (keep text visible) */
@media (max-width: 740px) {
.form-tab { padding: 10px 4px; gap: 4px; }
.form-tab i { margin-right: 4px; font-size: 0.95rem; }
.form-tab span { display: inline; font-size: 0.85rem; }
}
@keyframes fade-in {

View File

@@ -1,4 +1,4 @@
const CACHE_NAME = 'warracker-cache-v20250119005';
const CACHE_NAME = 'warracker-cache-v20250119006';
const urlsToCache = [
'./',
'./index.html',
@@ -8,7 +8,7 @@ const urlsToCache = [
'./settings-styles.css?v=20250119001',
'./header-fix.css?v=20250119001',
'./mobile-header.css?v=20250119002',
'./script.js?v=20250119001',
'./script.js?v=20250119002',
'./auth.js?v=20250119001',
'./settings-new.js?v=20250119001',
'./status.js?v=20250119001',

View File

@@ -1,6 +1,6 @@
// Version checker for Warracker
document.addEventListener('DOMContentLoaded', () => {
const currentVersion = '0.10.1.14'; // Current version of the application
const currentVersion = '0.10.1.15'; // Current version of the application
const updateStatus = document.getElementById('updateStatus');
const updateLink = document.getElementById('updateLink');
const versionDisplay = document.getElementById('versionDisplay');