@@ -2,6 +2,7 @@ import { loadFavourites } from './favourites.js';
2
2
import lastInStockTimes from './lastInStockTimes.js' ;
3
3
4
4
window . currentLocale = localStorage . getItem ( "selectedLocale" ) || "en-gb" ;
5
+ let isLocaleChanging = false ;
5
6
6
7
// Helper function to format display time
7
8
function formatDisplayTime ( isoString ) {
@@ -30,22 +31,58 @@ function updateLastInStockTime(productModel, locale) {
30
31
if ( ! window . fetchWorker ) {
31
32
window . fetchWorker = new Worker ( './scripts/fetchWorker.js' ) ;
32
33
window . fetchWorker . addEventListener ( 'message' , ( event ) => {
33
- const data = event . data ;
34
- if ( data ?. searchedProducts ?. productDetails ) {
35
- updateStockStatus ( data . searchedProducts . productDetails ) ;
34
+ if ( event . data ?. success ) {
35
+ updateStockStatus ( event . data . data ) ;
36
36
isApiDown = false ;
37
37
} else {
38
+ console . error ( 'Fetch error:' , event . data ?. error || 'Unknown error' ) ;
38
39
if ( ! isApiDown ) {
39
40
isApiDown = true ;
40
41
playNotificationSound ( ) ;
41
42
}
43
+ document . getElementById ( "fetch-time" ) . textContent = `Error: ${ event . data ?. error || 'API request failed' } ` ;
42
44
}
45
+ isLocaleChanging = false ;
46
+ } ) ;
47
+ }
48
+
49
+ function showLoadingSpinners ( ) {
50
+ const gpuRows = document . querySelectorAll ( "tbody tr" ) ;
51
+ const spinnerHTML = `
52
+ <svg class="loading-spinner" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
53
+ <g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
54
+ <path stroke-dasharray="16" stroke-dashoffset="16" d="M12 3c4.97 0 9 4.03 9 9">
55
+ <animate fill="freeze" attributeName="stroke-dashoffset" dur="0.3s" values="16;0"/>
56
+ <animateTransform attributeName="transform" dur="1.5s" repeatCount="indefinite" type="rotate" values="0 12 12;360 12 12"/>
57
+ </path>
58
+ <path stroke-dasharray="64" stroke-dashoffset="64" stroke-opacity="0.3" d="M12 3c4.97 0 9 4.03 9 9c0 4.97 -4.03 9 -9 9c-4.97 0 -9 -4.03 -9 -9c0 -4.97 4.03 -9 9 -9Z">
59
+ <animate fill="freeze" attributeName="stroke-dashoffset" dur="1.2s" values="64;0"/>
60
+ </path>
61
+ </g>
62
+ </svg>` ;
63
+
64
+ gpuRows . forEach ( row => {
65
+ const cells = [
66
+ row . querySelector ( ".stock-status" ) ,
67
+ row . querySelector ( ".product-price" ) ,
68
+ row . querySelector ( ".product-link" )
69
+ ] ;
70
+
71
+ cells . forEach ( cell => {
72
+ if ( cell ) {
73
+ cell . innerHTML = spinnerHTML ;
74
+ cell . style . color = "" ; // Reset to default
75
+ cell . className = cell . className . split ( ' ' ) [ 0 ] ; // Keep base class
76
+ }
77
+ } ) ;
43
78
} ) ;
44
79
}
45
80
46
- // Fetch stock data
47
81
export function fetchStockData ( ) {
48
82
if ( window . fetchWorker ) {
83
+ if ( isLocaleChanging ) {
84
+ showLoadingSpinners ( ) ;
85
+ }
49
86
window . fetchWorker . postMessage ( { type : 'fetch' , locale : window . currentLocale } ) ;
50
87
}
51
88
}
@@ -59,117 +96,109 @@ function formatLastFetchTime() {
59
96
let lastFetchTime = null ;
60
97
let isApiDown = false ;
61
98
62
- // Update table data
63
- export function updateStockStatus ( products ) {
64
- console . log ( "Updating table data:" , products ) ;
99
+ export function updateStockStatus ( inventoryData ) {
65
100
const gpuRows = document . querySelectorAll ( "tbody tr" ) ;
66
-
67
- // Update the last fetch time
68
101
lastFetchTime = formatLastFetchTime ( ) ;
69
102
document . getElementById ( "fetch-time" ) . textContent = `Last fetch: ${ lastFetchTime } ` ;
70
103
71
- // Create a set of GPU models from the fetched data
72
- const fetchedGpuModels = new Set ( products . map ( product => product . displayName ) ) ;
73
-
74
- // Clear previous table data
75
- gpuRows . forEach ( row => {
76
- const statusCell = row . querySelector ( ".stock-status" ) ;
77
- const priceCell = row . querySelector ( ".product-price" ) ;
78
- const linkCell = row . querySelector ( ".product-link" ) ;
79
- const skuSpan = row . querySelector ( ".product-sku" ) ;
80
-
81
- if ( statusCell ) {
82
- statusCell . textContent = "" ;
83
- statusCell . classList . remove ( "in-stock" , "out-of-stock" , "unknown-status" ) ;
84
- }
85
- if ( priceCell ) priceCell . textContent = "" ;
86
- if ( linkCell ) linkCell . innerHTML = "" ;
87
- if ( skuSpan ) skuSpan . textContent = "" ;
88
- } ) ;
89
-
90
- // Update the table with the fetched data
91
- products . forEach ( product => {
92
- if ( product . manufacturer === "NVIDIA" ) {
93
- gpuRows . forEach ( row => {
94
- const modelNameSpan = row . querySelector ( ".model-name" ) ;
95
- const rowModelName = modelNameSpan ?. textContent ;
96
-
97
- if ( rowModelName && product . displayName === rowModelName ) {
98
- const statusCell = row . querySelector ( ".stock-status" ) ;
99
- const priceCell = row . querySelector ( ".product-price" ) ;
100
- const linkCell = row . querySelector ( ".product-link" ) ;
101
- const skuSpan = row . querySelector ( ".product-sku" ) ;
102
- const alertIcon = row . querySelector ( ".alert-icon" ) ;
103
-
104
- const isFavourited = alertIcon ?. getAttribute ( "data-favourite" ) === "true" ;
105
- const previousStatus = statusCell ?. textContent ;
106
-
107
- // Update stock status
108
- if ( statusCell ) {
109
- let stockStatus = "" ;
110
- if ( product . prdStatus === "buy_now" ) {
111
- stockStatus = "In Stock" ;
112
- statusCell . classList . add ( "in-stock" ) ;
113
-
114
- // Update last in-stock time
115
- updateLastInStockTime ( product . displayName , window . currentLocale ) ;
116
-
117
- if ( isFavourited && previousStatus !== "In Stock" ) {
118
- playNotificationSound ( ) ;
119
- }
120
- } else if ( product . prdStatus === "out_of_stock" ) {
121
- stockStatus = "Out of Stock" ;
122
- statusCell . classList . add ( "out-of-stock" ) ;
123
- }
124
- statusCell . textContent = stockStatus ;
125
-
126
- // Set tooltip with last in-stock time
127
- const lastTime = lastInStockTimes [ product . displayName ] ?. [ window . currentLocale ] ;
128
- statusCell . setAttribute ( "data-tooltip" , `Last In Stock: ${ formatDisplayTime ( lastTime ) } ` ) ;
129
- }
130
-
131
- // Update other cells
132
- if ( priceCell && product . productPrice ) {
133
- // Create a temporary element to decode HTML entities
134
- const tempElement = document . createElement ( 'div' ) ;
135
- tempElement . innerHTML = product . productPrice ;
136
- // Set the decoded text content
137
- priceCell . textContent = tempElement . textContent ;
138
- priceCell . style . color = "" ;
139
- tempElement . remove ( ) ;
140
- }
141
- if ( linkCell && product . internalLink ) {
142
- linkCell . innerHTML = `<a href="${ product . internalLink } " target="_blank" rel="noopener noreferrer">View<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"><path fill="currentColor" d="M5 17.59L15.59 7H9V5h10v10h-2V8.41L6.41 19z"/></svg></a>` ;
143
- linkCell . style . color = "" ;
144
- }
145
- if ( skuSpan && product . productSKU ) {
146
- skuSpan . textContent = `SKU: ${ product . productSKU } ` ;
147
- }
148
- }
149
- } ) ;
150
- }
104
+ const inventoryMap = { } ;
105
+ inventoryData . forEach ( item => {
106
+ inventoryMap [ item . displayName ] = item ;
151
107
} ) ;
152
108
153
- // Set "Unknown" status for GPUs not found in API response
154
109
gpuRows . forEach ( row => {
155
110
const modelNameSpan = row . querySelector ( ".model-name" ) ;
156
111
const rowModelName = modelNameSpan ?. textContent ;
112
+ if ( ! rowModelName ) return ;
113
+
157
114
const statusCell = row . querySelector ( ".stock-status" ) ;
158
115
const priceCell = row . querySelector ( ".product-price" ) ;
159
116
const linkCell = row . querySelector ( ".product-link" ) ;
160
117
const skuSpan = row . querySelector ( ".product-sku" ) ;
118
+ const alertIcon = row . querySelector ( ".alert-icon" ) ;
119
+
120
+ const item = inventoryMap [ rowModelName ] ;
121
+ const isFavourited = alertIcon ?. getAttribute ( "data-favourite" ) === "true" ;
122
+ const previousStatus = statusCell ?. textContent ;
123
+
124
+ // Reset cell styling
125
+ [ statusCell , priceCell , linkCell , skuSpan ] . forEach ( cell => {
126
+ if ( cell ) {
127
+ cell . className = cell . className . split ( ' ' ) [ 0 ] ; // Keep base class
128
+ cell . style . color = "" ;
129
+ }
130
+ } ) ;
161
131
162
- if ( rowModelName && ! fetchedGpuModels . has ( rowModelName ) ) {
132
+ if ( ! item ) {
163
133
if ( statusCell ) {
164
134
statusCell . textContent = "Not Available" ;
165
135
statusCell . classList . add ( "unknown-status" ) ;
166
136
const lastTime = lastInStockTimes [ rowModelName ] ?. [ window . currentLocale ] ;
167
137
statusCell . setAttribute ( "data-tooltip" , `Last In Stock: ${ formatDisplayTime ( lastTime ) } ` ) ;
168
138
}
169
- if ( priceCell ) priceCell . textContent = "N/A" ;
139
+ if ( priceCell ) {
140
+ priceCell . textContent = "N/A" ;
170
141
priceCell . style . color = "#666" ;
171
- if ( linkCell ) linkCell . innerHTML = `<a href="#" style="color:#666;" rel="noopener noreferrer">N/A</a>` ;
172
- if ( skuSpan ) skuSpan . textContent = "SKU: N/A" ;
142
+ }
143
+ if ( linkCell ) {
144
+ linkCell . innerHTML = '<a href="#" style="color:#666;">N/A</a>' ;
145
+ }
146
+ if ( skuSpan ) {
147
+ skuSpan . textContent = "SKU: N/A" ;
148
+ }
149
+ return ;
150
+ }
151
+
152
+ // Update status cell with full styling
153
+ if ( statusCell ) {
154
+ const isInStock = item . inventory ?. listMap ?. some ( i => i . is_active === "true" ) ;
155
+ statusCell . textContent = isInStock ? "In Stock" : "Out of Stock" ;
156
+ statusCell . classList . add ( isInStock ? "in-stock" : "out-of-stock" ) ;
157
+
158
+ if ( isInStock ) {
159
+ updateLastInStockTime ( rowModelName , window . currentLocale ) ;
160
+ if ( isFavourited && previousStatus !== "In Stock" ) {
161
+ playNotificationSound ( ) ;
162
+ }
163
+ }
164
+
165
+ const lastTime = lastInStockTimes [ rowModelName ] ?. [ window . currentLocale ] ;
166
+ statusCell . setAttribute ( "data-tooltip" , `Last In Stock: ${ formatDisplayTime ( lastTime ) } ` ) ;
167
+ }
168
+
169
+ // Update price cell with full styling
170
+ if ( priceCell ) {
171
+ if ( item . productPrice ) {
172
+ const temp = document . createElement ( 'div' ) ;
173
+ temp . innerHTML = item . productPrice ;
174
+ priceCell . textContent = temp . textContent ;
175
+ priceCell . style . color = "" ;
176
+ } else {
177
+ priceCell . textContent = "N/A" ;
178
+ priceCell . style . color = "#666" ;
179
+ }
180
+ }
181
+
182
+ // Update link cell with full styling
183
+ if ( linkCell ) {
184
+ const url = item . inventory ?. listMap ?. [ 0 ] ?. product_url || item . internalLink ;
185
+ if ( url ) {
186
+ linkCell . innerHTML = `
187
+ <a href="${ url } " target="_blank" rel="noopener noreferrer">
188
+ View
189
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24">
190
+ <path fill="currentColor" d="M5 17.59L15.59 7H9V5h10v10h-2V8.41L6.41 19z"/>
191
+ </svg>
192
+ </a>` ;
193
+ linkCell . style . color = "" ;
194
+ } else {
195
+ linkCell . innerHTML = '<a href="#" style="color:#666;">N/A</a>' ;
196
+ }
197
+ }
198
+
199
+ // Update SKU
200
+ if ( skuSpan ) {
201
+ skuSpan . textContent = item . productSKU ? `SKU: ${ item . productSKU } ` : "SKU: N/A" ;
173
202
}
174
203
} ) ;
175
204
@@ -204,6 +233,7 @@ document.addEventListener("DOMContentLoaded", () => {
204
233
if ( localeDropdown ) {
205
234
localeDropdown . value = window . currentLocale ;
206
235
localeDropdown . addEventListener ( "change" , ( event ) => {
236
+ isLocaleChanging = true ;
207
237
window . currentLocale = event . target . value ;
208
238
localStorage . setItem ( "selectedLocale" , window . currentLocale ) ;
209
239
fetchStockData ( ) ;
0 commit comments