Ultronprime commited on
Commit
d6049c9
·
verified ·
1 Parent(s): 6ccdb68

Update app.js

Browse files
Files changed (1) hide show
  1. app.js +164 -67
app.js CHANGED
@@ -8,22 +8,12 @@ document.addEventListener('DOMContentLoaded', () => {
8
  { name: "RG-58 Coaxial Cable", unit: "meters", maxStock: 200, currentStock: 200 },
9
  { name: "N-Type Connector", unit: "units", maxStock: 1000, currentStock: 1000 },
10
  { name: "Plastic Insulator", unit: "units", maxStock: 4000, currentStock: 4000 },
11
- ]
12
- };
13
-
14
- const productRecipes = {
15
- "Yagi-Uda Model 5": {
16
- "5mm Copper Wire": 10,
17
- "RG-58 Coaxial Cable": 5,
18
- "N-Type Connector": 1,
19
- "Plastic Insulator": 8,
20
  },
21
- "Basic Dipole Antenna": {
22
- "5mm Copper Wire": 5,
23
- "RG-58 Coaxial Cable": 2,
24
- "N-Type Connector": 1,
25
- "Plastic Insulator": 2,
26
- }
27
  };
28
 
29
  let appState;
@@ -36,41 +26,114 @@ document.addEventListener('DOMContentLoaded', () => {
36
  const savedState = localStorage.getItem('antennaTrackerState');
37
  if (savedState) {
38
  appState = JSON.parse(savedState);
 
 
 
39
  } else {
40
- // Use a deep copy to avoid modifying the initialState object
41
  appState = JSON.parse(JSON.stringify(initialState));
42
  }
43
  }
44
 
45
  // --- UI RENDERING ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
 
 
 
 
 
 
 
 
 
 
 
 
47
  function renderInventory() {
 
 
 
48
  appState.materials.forEach(material => {
49
- const card = document.querySelector(`.material-card[data-material-name="${material.name}"]`);
50
- if (!card) return;
51
-
52
- const currentStockEl = card.querySelector('.current-stock');
53
- const progressBarEl = card.querySelector('.progress-bar');
54
-
55
- currentStockEl.textContent = material.currentStock;
56
-
57
  const stockPercentage = (material.currentStock / material.maxStock) * 100;
58
- progressBarEl.style.width = `${stockPercentage}%`;
59
-
60
- // Update status colors
61
- card.classList.remove('status-ok', 'status-warning', 'status-critical');
62
- progressBarEl.classList.remove('progress-ok', 'progress-warning', 'progress-critical');
63
-
64
- if (stockPercentage > 50) {
65
- card.classList.add('status-ok');
66
- progressBarEl.classList.add('progress-ok');
67
- } else if (stockPercentage > 20) {
68
- card.classList.add('status-warning');
69
- progressBarEl.classList.add('progress-warning');
70
- } else {
71
- card.classList.add('status-critical');
72
- progressBarEl.classList.add('progress-critical');
73
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  });
75
  }
76
 
@@ -79,13 +142,12 @@ document.addEventListener('DOMContentLoaded', () => {
79
  function handleUpdateStock(productName, quantity) {
80
  if (quantity <= 0) return;
81
 
82
- const recipe = productRecipes[productName];
83
  if (!recipe) {
84
- alert(`Error: No recipe found for ${productName}.`);
85
  return;
86
  }
87
 
88
- // 1. Check if there are enough materials
89
  let canProduce = true;
90
  let missingMaterials = [];
91
  for (const materialName in recipe) {
@@ -93,42 +155,82 @@ document.addEventListener('DOMContentLoaded', () => {
93
  const material = appState.materials.find(m => m.name === materialName);
94
  if (!material || material.currentStock < required) {
95
  canProduce = false;
96
- missingMaterials.push(`${required} ${material.unit} of ${materialName} (have ${material.currentStock})`);
97
  }
98
  }
99
 
100
- // 2. If not enough, show an alert
101
  if (!canProduce) {
102
- alert(`Insufficient materials to produce ${quantity} of ${productName}.\n\nNeeded:\n- ${missingMaterials.join('\n- ')}`);
103
  return;
104
  }
105
 
106
- // 3. If enough, deduct materials
107
  for (const materialName in recipe) {
108
  const required = recipe[materialName] * quantity;
109
  const material = appState.materials.find(m => m.name === materialName);
110
  material.currentStock -= required;
111
  }
 
 
 
 
 
 
 
112
 
113
- // 4. Save state and re-render
114
  saveState();
115
  renderInventory();
 
 
116
 
117
- // 5. Clear the input field for this product
118
  const productCard = document.querySelector(`.product-card[data-product-name="${productName}"]`);
119
- if(productCard) {
120
- productCard.querySelector('input').value = '0';
121
- }
122
  }
123
-
124
- function handleResetData() {
125
- if (confirm('Are you sure you want to reset all inventory data? This action cannot be undone.')) {
126
- localStorage.removeItem('antennaTrackerState');
127
- init(); // Re-initialize the application
128
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
129
  }
130
-
131
- // Modal logic
132
  const resetModal = document.getElementById('reset-modal');
133
  const showResetModalBtn = document.getElementById('show-reset-modal-btn');
134
  const cancelResetBtn = document.getElementById('cancel-reset-btn');
@@ -140,25 +242,20 @@ document.addEventListener('DOMContentLoaded', () => {
140
  localStorage.removeItem('antennaTrackerState');
141
  init();
142
  resetModal.classList.add('hidden');
 
143
  });
144
 
145
-
146
  // --- INITIALIZATION ---
147
-
148
  function init() {
149
  loadState();
150
  renderInventory();
 
151
 
152
- // Attach event listeners to all "Update Stock" buttons
153
  document.querySelectorAll('.product-card').forEach(card => {
154
  const updateBtn = card.querySelector('.update-btn');
155
  const input = card.querySelector('input');
156
  const productName = card.dataset.productName;
157
-
158
- updateBtn.addEventListener('click', () => {
159
- const quantity = parseInt(input.value, 10);
160
- handleUpdateStock(productName, quantity);
161
- });
162
  });
163
  }
164
 
 
8
  { name: "RG-58 Coaxial Cable", unit: "meters", maxStock: 200, currentStock: 200 },
9
  { name: "N-Type Connector", unit: "units", maxStock: 1000, currentStock: 1000 },
10
  { name: "Plastic Insulator", unit: "units", maxStock: 4000, currentStock: 4000 },
11
+ ],
12
+ productRecipes: {
13
+ "Yagi-Uda Model 5": { "5mm Copper Wire": 10, "RG-58 Coaxial Cable": 5, "N-Type Connector": 1, "Plastic Insulator": 8 },
14
+ "Basic Dipole Antenna": { "5mm Copper Wire": 5, "RG-58 Coaxial Cable": 2, "N-Type Connector": 1, "Plastic Insulator": 2 }
 
 
 
 
 
15
  },
16
+ productionLog: [] // NEW: To store history of production
 
 
 
 
 
17
  };
18
 
19
  let appState;
 
26
  const savedState = localStorage.getItem('antennaTrackerState');
27
  if (savedState) {
28
  appState = JSON.parse(savedState);
29
+ // Ensure new properties exist if loading old state
30
+ if (!appState.productionLog) appState.productionLog = [];
31
+ if (!appState.productRecipes) appState.productRecipes = initialState.productRecipes;
32
  } else {
 
33
  appState = JSON.parse(JSON.stringify(initialState));
34
  }
35
  }
36
 
37
  // --- UI RENDERING ---
38
+
39
+ /**
40
+ * NEW: Toast notification system to replace alerts.
41
+ * @param {string} message The message to display.
42
+ * @param {string} type 'success', 'error', or 'info'.
43
+ */
44
+ function showToast(message, type = 'info') {
45
+ const container = document.getElementById('toast-container');
46
+ const toast = document.createElement('div');
47
+ const icon = type === 'success' ? 'fa-check-circle' : 'fa-times-circle';
48
+
49
+ toast.className = `toast toast-${type}`;
50
+ toast.innerHTML = `<i class="fas ${icon}"></i><span>${message}</span>`;
51
+
52
+ container.appendChild(toast);
53
+
54
+ // Trigger the animation
55
+ setTimeout(() => toast.classList.add('show'), 10);
56
 
57
+ // Remove the toast after 3 seconds
58
+ setTimeout(() => {
59
+ toast.classList.remove('show');
60
+ // Remove the element from DOM after transition ends
61
+ toast.addEventListener('transitionend', () => toast.remove());
62
+ }, 3000);
63
+ }
64
+
65
+ /**
66
+ * REFACTORED: Now generates the material cards from scratch based on state.
67
+ */
68
  function renderInventory() {
69
+ const container = document.getElementById('material-cards');
70
+ container.innerHTML = ''; // Clear existing cards
71
+
72
  appState.materials.forEach(material => {
 
 
 
 
 
 
 
 
73
  const stockPercentage = (material.currentStock / material.maxStock) * 100;
74
+ let statusClass = 'status-ok';
75
+ let progressClass = 'progress-ok';
76
+
77
+ if (stockPercentage <= 50 && stockPercentage > 20) {
78
+ statusClass = 'status-warning';
79
+ progressClass = 'progress-warning';
80
+ } else if (stockPercentage <= 20) {
81
+ statusClass = 'status-critical';
82
+ progressClass = 'progress-critical';
 
 
 
 
 
 
83
  }
84
+
85
+ const cardHTML = `
86
+ <div class="material-card bg-white rounded-lg shadow p-4 border-l-4 ${statusClass}" data-material-name="${material.name}">
87
+ <div class="flex justify-between items-start mb-1">
88
+ <h3 class="font-medium text-gray-700">${material.name}</h3>
89
+ <div class="text-xs text-gray-500 flex items-center gap-2">
90
+ <span>MAX:</span>
91
+ <span class="font-semibold max-stock-value">${material.maxStock}</span>
92
+ <i class="fas fa-pencil-alt text-gray-400 hover:text-blue-500 cursor-pointer edit-max-stock"></i>
93
+ </div>
94
+ </div>
95
+ <div class="flex justify-between items-baseline mb-2">
96
+ <div class="text-2xl font-bold current-stock">${material.currentStock}</div>
97
+ <span class="text-sm text-gray-500 unit">${material.unit}</span>
98
+ </div>
99
+ <div class="w-full bg-gray-200 rounded-full h-2.5">
100
+ <div class="h-2.5 rounded-full ${progressClass} progress-bar" style="width: ${stockPercentage}%"></div>
101
+ </div>
102
+ </div>
103
+ `;
104
+ container.insertAdjacentHTML('beforeend', cardHTML);
105
+ });
106
+
107
+ // Re-attach event listeners for editing max stock after re-rendering
108
+ attachMaxStockEditListeners();
109
+ }
110
+
111
+ /**
112
+ * NEW: Renders the production log list.
113
+ */
114
+ function renderProductionLog() {
115
+ const list = document.getElementById('production-log-list');
116
+ list.innerHTML = ''; // Clear the list
117
+
118
+ if (appState.productionLog.length === 0) {
119
+ list.innerHTML = `<li class="text-gray-500 text-center pt-4">No production recorded yet.</li>`;
120
+ return;
121
+ }
122
+
123
+ // Show most recent entries first
124
+ [...appState.productionLog].reverse().forEach(entry => {
125
+ const date = new Date(entry.date);
126
+ const formattedDate = `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`;
127
+ const logHTML = `
128
+ <li class="p-2 border-b border-gray-100 flex justify-between items-center text-sm">
129
+ <div>
130
+ <span class="font-semibold text-blue-600">${entry.quantity}x</span>
131
+ <span class="text-gray-800">${entry.productName}</span>
132
+ </div>
133
+ <span class="text-xs text-gray-400">${formattedDate}</span>
134
+ </li>
135
+ `;
136
+ list.insertAdjacentHTML('beforeend', logHTML);
137
  });
138
  }
139
 
 
142
  function handleUpdateStock(productName, quantity) {
143
  if (quantity <= 0) return;
144
 
145
+ const recipe = appState.productRecipes[productName];
146
  if (!recipe) {
147
+ showToast(`Error: No recipe found for ${productName}.`, 'error');
148
  return;
149
  }
150
 
 
151
  let canProduce = true;
152
  let missingMaterials = [];
153
  for (const materialName in recipe) {
 
155
  const material = appState.materials.find(m => m.name === materialName);
156
  if (!material || material.currentStock < required) {
157
  canProduce = false;
158
+ missingMaterials.push(`${materialName}: need ${required}, have ${material.currentStock}`);
159
  }
160
  }
161
 
 
162
  if (!canProduce) {
163
+ showToast(`Insufficient materials for ${quantity}x ${productName}.`, 'error');
164
  return;
165
  }
166
 
 
167
  for (const materialName in recipe) {
168
  const required = recipe[materialName] * quantity;
169
  const material = appState.materials.find(m => m.name === materialName);
170
  material.currentStock -= required;
171
  }
172
+
173
+ // NEW: Log the production
174
+ appState.productionLog.push({
175
+ productName: productName,
176
+ quantity: quantity,
177
+ date: new Date().toISOString()
178
+ });
179
 
 
180
  saveState();
181
  renderInventory();
182
+ renderProductionLog();
183
+ showToast(`Produced ${quantity}x ${productName}.`, 'success');
184
 
 
185
  const productCard = document.querySelector(`.product-card[data-product-name="${productName}"]`);
186
+ if(productCard) productCard.querySelector('input').value = '0';
 
 
187
  }
188
+
189
+ /**
190
+ * NEW: Logic for inline editing of max stock values.
191
+ */
192
+ function attachMaxStockEditListeners() {
193
+ document.querySelectorAll('.edit-max-stock').forEach(icon => {
194
+ icon.addEventListener('click', (e) => {
195
+ const valueSpan = e.target.previousElementSibling;
196
+ const materialName = e.target.closest('.material-card').dataset.materialName;
197
+ const material = appState.materials.find(m => m.name === materialName);
198
+
199
+ // Create an input field
200
+ const input = document.createElement('input');
201
+ input.type = 'number';
202
+ input.value = material.maxStock;
203
+ input.className = 'w-20 text-right font-semibold border rounded px-1';
204
+
205
+ // Replace span with input
206
+ valueSpan.replaceWith(input);
207
+ input.focus();
208
+ input.select();
209
+
210
+ // Save on blur (clicking away)
211
+ input.addEventListener('blur', () => {
212
+ const newValue = parseInt(input.value, 10);
213
+ if (!isNaN(newValue) && newValue >= material.currentStock) {
214
+ material.maxStock = newValue;
215
+ saveState();
216
+ renderInventory(); // Re-render to update everything
217
+ } else {
218
+ showToast('Max stock must be a number and greater than current stock.', 'error');
219
+ input.replaceWith(valueSpan); // Revert on invalid input
220
+ }
221
+ });
222
+
223
+ // Save on Enter key
224
+ input.addEventListener('keydown', (event) => {
225
+ if (event.key === 'Enter') {
226
+ input.blur();
227
+ }
228
+ });
229
+ });
230
+ });
231
  }
232
+
233
+ // --- MODAL & RESET LOGIC ---
234
  const resetModal = document.getElementById('reset-modal');
235
  const showResetModalBtn = document.getElementById('show-reset-modal-btn');
236
  const cancelResetBtn = document.getElementById('cancel-reset-btn');
 
242
  localStorage.removeItem('antennaTrackerState');
243
  init();
244
  resetModal.classList.add('hidden');
245
+ showToast('Application data has been reset.', 'info');
246
  });
247
 
 
248
  // --- INITIALIZATION ---
 
249
  function init() {
250
  loadState();
251
  renderInventory();
252
+ renderProductionLog();
253
 
 
254
  document.querySelectorAll('.product-card').forEach(card => {
255
  const updateBtn = card.querySelector('.update-btn');
256
  const input = card.querySelector('input');
257
  const productName = card.dataset.productName;
258
+ updateBtn.addEventListener('click', () => handleUpdateStock(productName, parseInt(input.value, 10)));
 
 
 
 
259
  });
260
  }
261