alterzick commited on
Commit
2839df3
·
verified ·
1 Parent(s): 3c34d88

Add 2 files

Browse files
Files changed (2) hide show
  1. index.html +681 -72
  2. prompts.txt +2 -1
index.html CHANGED
@@ -3,7 +3,7 @@
3
  <head>
4
  <meta charset="UTF-8" />
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
6
- <title>Saving Operation Tracker</title>
7
  <script src="https://cdn.tailwindcss.com"></script>
8
  <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" rel="stylesheet"/>
9
  <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
@@ -56,6 +56,41 @@
56
  visibility: visible;
57
  opacity: 1;
58
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  </style>
60
  </head>
61
  <body class="bg-gray-50">
@@ -66,8 +101,8 @@
66
  <!-- Sidebar -->
67
  <div class="sidebar fixed top-0 left-0 h-full w-64 bg-gradient-to-b from-blue-800 to-blue-600 text-white shadow-lg z-40">
68
  <div class="p-5">
69
- <h1 class="text-2xl font-bold">SavingTrack</h1>
70
- <p class="text-sm opacity-90">Operation Efficiency Logger</p>
71
  </div>
72
  <nav class="mt-8">
73
  <a href="#" class="nav-link block py-3 px-6 bg-blue-700" data-page="dashboard">
@@ -99,7 +134,7 @@
99
  <!-- Main Content -->
100
  <div class="main-content relative min-h-screen ml-64 bg-gray-50 transition-all duration-300">
101
  <header class="bg-white shadow-sm p-4 flex justify-between items-center">
102
- <h2 class="text-xl font-semibold text-gray-700">Saving Operation Tracker</h2>
103
  <div class="text-sm text-gray-500">
104
  <i class="far fa-calendar-alt mr-1"></i> <span id="current-date"></span>
105
  </div>
@@ -118,11 +153,11 @@
118
  <p class="text-3xl font-bold text-blue-600 mt-2" id="total-time">0 hrs</p>
119
  </div>
120
  <div class="bg-white p-5 rounded-xl shadow-md border-l-4 border-purple-500 transform hover:scale-105 transition-transform">
121
- <h3 class="text-gray-500 text-sm font-medium">Material Saved</h3>
122
- <p class="text-3xl font-bold text-purple-600 mt-2" id="total-material">0 kg</p>
123
  </div>
124
  <div class="bg-white p-5 rounded-xl shadow-md border-l-4 border-orange-500 transform hover:scale-105 transition-transform">
125
- <h3 class="text-gray-500 text-sm font-medium">Processes Saved</h3>
126
  <p class="text-3xl font-bold text-orange-600 mt-2" id="total-processes">0</p>
127
  </div>
128
  </div>
@@ -140,7 +175,7 @@
140
  <canvas id="monthly-chart" height="250"></canvas>
141
  </div>
142
  <div class="bg-white p-6 rounded-xl shadow-md">
143
- <h3 class="text-lg font-semibold mb-4">Savings by Job Type</h3>
144
  <canvas id="jobtype-chart" height="250"></canvas>
145
  </div>
146
  </div>
@@ -153,17 +188,18 @@
153
  <form id="saving-form">
154
  <div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
155
  <div>
156
- <label class="block text-sm font-medium text-gray-600 mb-1">Job Type</label>
157
  <div class="flex">
158
  <select id="job-type" name="jobType" class="block w-full p-3 border border-gray-300 rounded-lg focus:ring focus:ring-blue-200">
159
- <option value="">Select a type</option>
160
- <option value="Welding">Welding</option>
161
- <option value="Cutting">Cutting</option>
162
- <option value="Machining">Machining</option>
163
- <option value="Assembly">Assembly</option>
164
- <option value="Quality Control">Quality Control</option>
165
- <option value="Maintenance">Maintenance</option>
166
- <option value="Logistics">Logistics</option>
 
167
  </select>
168
  <button type="button" id="add-job-btn" class="ml-2 bg-gray-200 px-3 rounded-lg hover:bg-gray-300">
169
  <i class="fas fa-plus"></i>
@@ -171,25 +207,82 @@
171
  </div>
172
  </div>
173
  <div>
174
- <label class="block text-sm font-medium text-gray-600 mb-1">Additional Job Type (if not listed)</label>
175
- <input type="text" id="custom-job-type" placeholder="Enter new job type" class="block w-full p-3 border border-gray-300 rounded-lg focus:ring focus:ring-blue-200"/>
176
  </div>
 
177
  <div>
178
- <label class="block text-sm font-medium text-gray-600 mb-1">Material Saved (kg)</label>
179
- <input type="number" name="materialSaved" step="0.1" required class="block w-full p-3 border border-gray-300 rounded-lg focus:ring focus:ring-blue-200"/>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
180
  </div>
 
181
  <div>
182
- <label class="block text-sm font-medium text-gray-600 mb-1">Time Saved (hours)</label>
183
- <input type="number" name="timeSaved" step="0.1" required class="block w-full p-3 border border-gray-300 rounded-lg focus:ring focus:ring-blue-200"/>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
184
  </div>
 
185
  <div>
186
- <label class="block text-sm font-medium text-gray-600 mb-1">Processes Saved</label>
187
- <input type="number" name="processesSaved" required class="block w-full p-3 border border-gray-300 rounded-lg focus:ring focus:ring-blue-200"/>
188
  </div>
189
  <div>
190
  <label class="block text-sm font-medium text-gray-600 mb-1">Cost Saved (Rp)</label>
191
  <input type="number" name="costSaved" required class="block w-full p-3 border border-gray-300 rounded-lg focus:ring focus:ring-blue-200"/>
192
  </div>
 
193
  <div>
194
  <label class="block text-sm font-medium text-gray-600 mb-1">Date</label>
195
  <input type="date" name="date" required class="block w-full p-3 border border-gray-300 rounded-lg focus:ring focus:ring-blue-200"/>
@@ -230,10 +323,10 @@
230
  <thead class="bg-gray-100 text-gray-600 uppercase text-sm leading-normal">
231
  <tr>
232
  <th class="py-3 px-6 text-left">No</th>
233
- <th class="py-3 px-6 text-left">Job Type</th>
234
- <th class="py-3 px-6 text-left">Material (kg)</th>
235
  <th class="py-3 px-6 text-left">Time (hrs)</th>
236
- <th class="py-3 px-6 text-left">Processes</th>
237
  <th class="py-3 px-6 text-left">Cost (Rp)</th>
238
  <th class="py-3 px-6 text-left">Date & Time</th>
239
  <th class="py-3 px-6 text-center">Actions</th>
@@ -251,11 +344,11 @@
251
  <section id="charts" class="page-content hidden">
252
  <div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
253
  <div class="bg-white p-6 rounded-xl shadow-md">
254
- <h3 class="text-lg font-semibold mb-4">Material vs Time Saved</h3>
255
  <canvas id="matrial-time-chart" height="250"></canvas>
256
  </div>
257
  <div class="bg-white p-6 rounded-xl shadow-md">
258
- <h3 class="text-lg font-semibold mb-4">Top 5 Job Types (by Cost)</h3>
259
  <canvas id="top-jobs-chart" height="250"></canvas>
260
  </div>
261
  </div>
@@ -274,7 +367,8 @@
274
  </button>
275
  </div>
276
  <div class="mt-6 text-sm text-gray-500">
277
- <p><strong>Required Columns:</strong> jobType, materialSaved, timeSaved, processesSaved, costSaved, date, time (ISO format), notes</p>
 
278
  </div>
279
  </div>
280
  </section>
@@ -300,6 +394,34 @@
300
  <option value="YYYY-MM-DD">YYYY-MM-DD</option>
301
  </select>
302
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
303
  <div class="flex space-x-4">
304
  <button id="reset-data-btn" class="px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 text-sm">
305
  <i class="fas fa-trash mr-2"></i> Reset All Data
@@ -314,8 +436,8 @@
314
  <!-- Add Job Type Modal -->
315
  <div id="add-job-modal" class="modal">
316
  <div class="bg-white p-6 rounded-lg shadow-lg max-w-md mx-auto mt-20">
317
- <h3 class="text-lg font-semibold mb-4">Add New Job Type</h3>
318
- <input type="text" id="new-job-input" placeholder="Enter job type name" class="w-full p-3 border border-gray-300 rounded-lg mb-4"/>
319
  <div class="flex justify-end space-x-3">
320
  <button id="cancel-job-btn" class="px-4 py-2 bg-gray-400 text-white rounded-lg">Cancel</button>
321
  <button id="save-job-btn" class="px-4 py-2 bg-blue-600 text-white rounded-lg">Save</button>
@@ -331,31 +453,63 @@
331
  <input type="hidden" id="edit-id"/>
332
  <div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
333
  <div>
334
- <label class="block text-sm font-medium text-gray-600 mb-1">Job Type</label>
335
  <select id="edit-job-type" class="block w-full p-3 border border-gray-300 rounded-lg focus:ring focus:ring-blue-200">
336
  <!-- Filled dynamically -->
337
  </select>
338
  </div>
 
339
  <div>
340
- <label class="block text-sm font-medium text-gray-600 mb-1">Material Saved (kg)</label>
341
- <input type="number" id="edit-material" step="0.1" class="block w-full p-3 border border-gray-300 rounded-lg"/>
 
 
 
 
 
 
 
 
 
 
 
 
 
342
  </div>
 
343
  <div>
344
- <label class="block text-sm font-medium text-gray-600 mb-1">Time Saved (hours)</label>
345
- <input type="number" id="edit-time" step="0.1" class="block w-full p-3 border border-gray-300 rounded-lg"/>
 
 
 
 
 
 
 
 
 
 
 
 
 
346
  </div>
 
347
  <div>
348
- <label class="block text-sm font-medium text-gray-600 mb-1">Processes Saved</label>
349
- <input type="number" id="edit-processes" class="block w-full p-3 border border-gray-300 rounded-lg"/>
350
  </div>
 
351
  <div>
352
  <label class="block text-sm font-medium text-gray-600 mb-1">Cost Saved</label>
353
  <input type="number" id="edit-cost" class="block w-full p-3 border border-gray-300 rounded-lg"/>
354
  </div>
 
355
  <div>
356
  <label class="block text-sm font-medium text-gray-600 mb-1">Date</label>
357
  <input type="date" id="edit-date" class="block w-full p-3 border border-gray-300 rounded-lg"/>
358
  </div>
 
359
  <div>
360
  <label class="block text-sm font-medium text-gray-600 mb-1">Time</label>
361
  <input type="time" id="edit-time-input" class="block w-full p-3 border border-gray-300 rounded-lg"/>
@@ -377,7 +531,21 @@
377
  // Initial state
378
  let savingsData = JSON.parse(localStorage.getItem('savingsData')) || [];
379
  let jobTypes = JSON.parse(localStorage.getItem('jobTypes')) || [
380
- "Welding", "Cutting", "Machining", "Assembly", "Quality Control", "Maintenance", "Logistics"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
381
  ];
382
 
383
  // Format currency
@@ -429,10 +597,11 @@
429
  function updateJobTypeDropdown() {
430
  const select = document.getElementById('job-type');
431
  const editSelect = document.getElementById('edit-job-type');
432
- select.innerHTML = '<option value="">Select a type</option>';
433
  jobTypes.forEach(type => {
434
  select.innerHTML += `<option value="${type}">${type}</option>`;
435
  });
 
436
  // Update edit modal dropdown
437
  if (editSelect) {
438
  editSelect.innerHTML = '';
@@ -442,6 +611,39 @@
442
  }
443
  }
444
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
445
  // Show add job modal
446
  document.getElementById('add-job-btn').addEventListener('click', () => {
447
  document.getElementById('add-job-modal').style.display = 'block';
@@ -458,12 +660,76 @@
458
  jobTypes.sort();
459
  localStorage.setItem('jobTypes', JSON.stringify(jobTypes));
460
  updateJobTypeDropdown();
461
- showToast('New job type added!');
462
  }
463
  document.getElementById('new-job-input').value = '';
464
  document.getElementById('add-job-modal').style.display = 'none';
465
  });
466
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
467
  // Form submission
468
  document.getElementById('saving-form').addEventListener('submit', (e) => {
469
  e.preventDefault();
@@ -482,6 +748,26 @@
482
  data.jobType = customJob;
483
  }
484
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
485
  data.timestamp = new Date().toISOString();
486
  data.id = Date.now(); // unique ID
487
  savingsData.push(data);
@@ -489,6 +775,10 @@
489
 
490
  e.target.reset();
491
  document.getElementById('custom-job-type').value = '';
 
 
 
 
492
  showToast("Data saved successfully!");
493
  updateDashboard();
494
  updateRecords();
@@ -499,15 +789,30 @@
499
  document.getElementById('cancel-input').addEventListener('click', () => {
500
  document.getElementById('saving-form').reset();
501
  document.getElementById('custom-job-type').value = '';
 
 
 
 
502
  });
503
 
504
  // Export to CSV
505
  document.getElementById('export-btn').addEventListener('click', () => {
 
 
 
 
 
506
  const csv = [
507
- ['Job Type', 'Material Saved (kg)', 'Time Saved (hrs)', 'Processes Saved', 'Cost Saved', 'Date', 'Time', 'Notes']
508
  ].concat(savingsData.map(item => [
509
- item.jobType, item.materialSaved, item.timeSaved, item.processesSaved,
510
- item.costSaved, item.date, item.time, item.notes || ''
 
 
 
 
 
 
511
  ])).map(row => row.join(',')).join('\n');
512
 
513
  const blob = new Blob([csv], { type: 'text/csv' });
@@ -533,8 +838,8 @@
533
  const lines = content.split('\n');
534
  const headers = lines[0].split(',').map(h => h.trim());
535
 
536
- const expectedHeaders = ['jobType', 'materialSaved', 'timeSaved', 'processesSaved', 'costSaved', 'date', 'time', 'notes'];
537
- if (!expectedHeaders.every(h => headers.includes(h))) {
538
  alert('CSV file must contain the required columns.');
539
  return;
540
  }
@@ -543,12 +848,41 @@
543
  if (lines[i].trim() === '') continue;
544
  const values = lines[i].split(',').map(v => v.trim());
545
  const obj = {};
 
 
546
  headers.forEach((h, idx) => {
547
- obj[h] = values[idx] || '';
548
  });
549
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
550
  obj.id = Date.now() + i;
551
  obj.timestamp = new Date().toISOString();
 
552
  savingsData.push(obj);
553
  }
554
 
@@ -568,9 +902,32 @@
568
 
569
  document.getElementById('edit-id').value = record.id;
570
  document.getElementById('edit-job-type').value = record.jobType;
571
- document.getElementById('edit-material').value = record.materialSaved;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
572
  document.getElementById('edit-time').value = record.timeSaved;
573
- document.getElementById('edit-processes').value = record.processesSaved;
574
  document.getElementById('edit-cost').value = record.costSaved;
575
  document.getElementById('edit-date').value = record.date;
576
  document.getElementById('edit-time-input').value = record.time;
@@ -579,6 +936,72 @@
579
  document.getElementById('edit-modal').style.display = 'block';
580
  }
581
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
582
  document.getElementById('cancel-edit').addEventListener('click', () => {
583
  document.getElementById('edit-modal').style.display = 'none';
584
  });
@@ -589,15 +1012,34 @@
589
  const record = savingsData.find(r => r.id == id);
590
  if (!record) return;
591
 
 
592
  record.jobType = document.getElementById('edit-job-type').value;
593
- record.materialSaved = document.getElementById('edit-material').value;
594
  record.timeSaved = document.getElementById('edit-time').value;
595
- record.processesSaved = document.getElementById('edit-processes').value;
596
  record.costSaved = document.getElementById('edit-cost').value;
597
  record.date = document.getElementById('edit-date').value;
598
  record.time = document.getElementById('edit-time-input').value;
599
  record.notes = document.getElementById('edit-notes').value;
600
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
601
  localStorage.setItem('savingsData', JSON.stringify(savingsData));
602
  document.getElementById('edit-modal').style.display = 'none';
603
  updateRecords();
@@ -635,7 +1077,9 @@
635
  const term = e.target.value.toLowerCase();
636
  const filtered = savingsData.filter(item =>
637
  item.jobType.toLowerCase().includes(term) ||
638
- item.notes?.toLowerCase().includes(term)
 
 
639
  );
640
  populateRecords(filtered);
641
  });
@@ -649,20 +1093,142 @@
649
  updateRecords();
650
  } else if (page === 'charts') {
651
  updateCharts();
 
 
652
  }
653
  }
654
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
655
  // Dashboard Stats
656
  function updateDashboard() {
657
  const totalCost = savingsData.reduce((sum, item) => sum + parseFloat(item.costSaved || 0), 0);
658
  const totalTime = savingsData.reduce((sum, item) => sum + parseFloat(item.timeSaved || 0), 0);
659
- const totalMaterial = savingsData.reduce((sum, item) => sum + parseFloat(item.materialSaved || 0), 0);
660
- const totalProcesses = savingsData.reduce((sum, item) => sum + parseInt(item.processesSaved || 0), 0);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
661
 
662
  document.getElementById('total-cost').textContent = formatCurrency(totalCost);
663
  document.getElementById('total-time').textContent = `${totalTime.toFixed(1)} hrs`;
664
- document.getElementById('total-material').textContent = `${totalMaterial.toFixed(1)} kg`;
665
- document.getElementById('total-processes').textContent = totalProcesses;
666
 
667
  // Recent activities
668
  const recent = savingsData
@@ -671,16 +1237,23 @@
671
  const recentContainer = document.getElementById('recent-activities');
672
  recentContainer.innerHTML = '';
673
  recent.forEach(item => {
 
 
 
 
 
 
674
  const div = document.createElement('div');
675
  div.className = 'flex justify-between items-center p-3 bg-gray-50 rounded-lg';
676
  div.innerHTML = `
677
  <div>
678
  <p class="font-medium">${item.jobType}</p>
679
  <p class="text-sm text-gray-500">${formatDate(item.date)} at ${item.time}</p>
 
680
  </div>
681
  <div class="text-right">
682
  <p class="font-medium text-green-600">${formatCurrency(item.costSaved)}</p>
683
- <p class="text-sm text-gray-500">${item.materialSaved} kg saved</p>
684
  </div>
685
  `;
686
  recentContainer.appendChild(div);
@@ -701,14 +1274,30 @@
701
  }
702
 
703
  data.slice().reverse().forEach((item, index) => {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
704
  const tr = document.createElement('tr');
705
  tr.className = 'border-t border-gray-200 hover:bg-gray-50';
706
  tr.innerHTML = `
707
  <td class="py-3 px-6">${data.length - index}</td>
708
  <td class="py-3 px-6">${item.jobType}</td>
709
- <td class="py-3 px-6">${parseFloat(item.materialSaved).toFixed(1)}</td>
710
  <td class="py-3 px-6">${parseFloat(item.timeSaved).toFixed(1)}</td>
711
- <td class="py-3 px-6">${item.processesSaved}</td>
712
  <td class="py-3 px-6">${formatCurrency(item.costSaved)}</td>
713
  <td class="py-3 px-6">${formatDate(item.date)} ${item.time}</td>
714
  <td class="py-3 px-6 text-center">
@@ -776,7 +1365,7 @@
776
  datasets: [{
777
  data: Object.values(jobCounts),
778
  backgroundColor: [
779
- '#FF6384', '#36A2EB', '#FFCE56', '#4BC0C0', '#9966FF', '#FF9F40', '#C9CBCF'
780
  ]
781
  }]
782
  },
@@ -786,32 +1375,49 @@
786
  });
787
 
788
  // Material vs Time Chart
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
789
  const materialTimeCtx = document.getElementById('matrial-time-chart').getContext('2d');
790
  if (materialTimeChart) materialTimeChart.destroy();
791
  materialTimeChart = new Chart(materialTimeCtx, {
792
  type: 'scatter',
793
  data: {
794
  datasets: [{
795
- label: 'Material vs Time Saved',
796
- data: savingsData.map(item => ({
797
- x: parseFloat(item.materialSaved),
798
- y: parseFloat(item.timeSaved),
799
- r: Math.sqrt(parseFloat(item.costSaved)) / 10,
800
- jobType: item.jobType
801
- })),
802
  backgroundColor: 'rgba(75, 192, 192, 0.6)'
803
  }]
804
  },
805
  options: {
806
  responsive: true,
807
  scales: {
808
- x: { title: { display: true, text: 'Material Saved (kg)' } },
809
- y: { title: { display: true, text: 'Time Saved (hours)' } }
 
 
 
 
 
 
810
  }
811
  }
812
  });
813
 
814
- // Top 5 Jobs by Cost
815
  const sortedJobs = Object.entries(jobCounts)
816
  .sort((a,b) => b[1] - a[1])
817
  .slice(0, 5);
@@ -846,6 +1452,9 @@
846
 
847
  // Initialize
848
  updateJobTypeDropdown();
 
 
 
849
  updateDashboard();
850
  updateRecords();
851
  updateCharts();
 
3
  <head>
4
  <meta charset="UTF-8" />
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
6
+ <title>Drilling & Workover Saving Tracker</title>
7
  <script src="https://cdn.tailwindcss.com"></script>
8
  <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" rel="stylesheet"/>
9
  <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
 
56
  visibility: visible;
57
  opacity: 1;
58
  }
59
+ .custom-input {
60
+ border: 1px solid #d1d5db;
61
+ border-radius: 0.5rem;
62
+ }
63
+ .tag {
64
+ display: inline-block;
65
+ padding: 0.25rem 0.75rem;
66
+ border-radius: 9999px;
67
+ font-size: 0.875rem;
68
+ margin: 0.25rem;
69
+ background-color: #e5e7eb;
70
+ position: relative;
71
+ }
72
+ .tag .remove {
73
+ margin-left: 0.5rem;
74
+ cursor: pointer;
75
+ color: #9ca3af;
76
+ }
77
+ .tag .remove:hover {
78
+ color: #ef4444;
79
+ }
80
+ .process-tag {
81
+ background-color: #dbeafe;
82
+ color: #1e40af;
83
+ }
84
+ .material-tag {
85
+ background-color: #ecfccb;
86
+ color: #65a30d;
87
+ }
88
+ .process-tag .remove, .material-tag .remove {
89
+ color: #3b82f6;
90
+ }
91
+ .process-tag .remove:hover, .material-tag .remove:hover {
92
+ color: #ef4444;
93
+ }
94
  </style>
95
  </head>
96
  <body class="bg-gray-50">
 
101
  <!-- Sidebar -->
102
  <div class="sidebar fixed top-0 left-0 h-full w-64 bg-gradient-to-b from-blue-800 to-blue-600 text-white shadow-lg z-40">
103
  <div class="p-5">
104
+ <h1 class="text-2xl font-bold">WellSave</h1>
105
+ <p class="text-sm opacity-90">Drilling & Workover Tracker</p>
106
  </div>
107
  <nav class="mt-8">
108
  <a href="#" class="nav-link block py-3 px-6 bg-blue-700" data-page="dashboard">
 
134
  <!-- Main Content -->
135
  <div class="main-content relative min-h-screen ml-64 bg-gray-50 transition-all duration-300">
136
  <header class="bg-white shadow-sm p-4 flex justify-between items-center">
137
+ <h2 class="text-xl font-semibold text-gray-700">Drilling & Workover Saving Tracker</h2>
138
  <div class="text-sm text-gray-500">
139
  <i class="far fa-calendar-alt mr-1"></i> <span id="current-date"></span>
140
  </div>
 
153
  <p class="text-3xl font-bold text-blue-600 mt-2" id="total-time">0 hrs</p>
154
  </div>
155
  <div class="bg-white p-5 rounded-xl shadow-md border-l-4 border-purple-500 transform hover:scale-105 transition-transform">
156
+ <h3 class="text-gray-500 text-sm font-medium">Items Saved</h3>
157
+ <p class="text-3xl font-bold text-purple-600 mt-2" id="total-material">0 items</p>
158
  </div>
159
  <div class="bg-white p-5 rounded-xl shadow-md border-l-4 border-orange-500 transform hover:scale-105 transition-transform">
160
+ <h3 class="text-gray-500 text-sm font-medium">Operations Saved</h3>
161
  <p class="text-3xl font-bold text-orange-600 mt-2" id="total-processes">0</p>
162
  </div>
163
  </div>
 
175
  <canvas id="monthly-chart" height="250"></canvas>
176
  </div>
177
  <div class="bg-white p-6 rounded-xl shadow-md">
178
+ <h3 class="text-lg font-semibold mb-4">Savings by Operation Type</h3>
179
  <canvas id="jobtype-chart" height="250"></canvas>
180
  </div>
181
  </div>
 
188
  <form id="saving-form">
189
  <div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
190
  <div>
191
+ <label class="block text-sm font-medium text-gray-600 mb-1">Operation Type</label>
192
  <div class="flex">
193
  <select id="job-type" name="jobType" class="block w-full p-3 border border-gray-300 rounded-lg focus:ring focus:ring-blue-200">
194
+ <option value="">Select operation type</option>
195
+ <option value="Drilling - Tripping">Drilling - Tripping</option>
196
+ <option value="Drilling - BHA Runs">Drilling - BHA Runs</option>
197
+ <option value="Drilling - Bit Runs">Drilling - Bit Runs</option>
198
+ <option value="Drilling - Cementing">Drilling - Cementing</option>
199
+ <option value="Workover - Tubing Change">Workover - Tubing Change</option>
200
+ <option value="Workover - Pump Repair">Workover - Pump Repair</option>
201
+ <option value="Workover - Zone Isolation">Workover - Zone Isolation</option>
202
+ <option value="Workover - Stimulations">Workover - Stimulations</option>
203
  </select>
204
  <button type="button" id="add-job-btn" class="ml-2 bg-gray-200 px-3 rounded-lg hover:bg-gray-300">
205
  <i class="fas fa-plus"></i>
 
207
  </div>
208
  </div>
209
  <div>
210
+ <label class="block text-sm font-medium text-gray-600 mb-1">Additional Operation Type (if not listed)</label>
211
+ <input type="text" id="custom-job-type" placeholder="Enter new operation type" class="block w-full p-3 border border-gray-300 rounded-lg focus:ring focus:ring-blue-200"/>
212
  </div>
213
+
214
  <div>
215
+ <label class="block text-sm font-medium text-gray-600 mb-1">Materials Saved (items)</label>
216
+ <div class="border border-gray-300 rounded-lg p-3">
217
+ <div id="material-tags" class="flex flex-wrap mb-2">
218
+ <!-- Material tags will be added here -->
219
+ </div>
220
+ <div class="flex">
221
+ <select id="material-select" class="p-2 border border-gray-300 rounded-l-lg flex-grow">
222
+ <option value="">Select material</option>
223
+ <option value="Drill Bits">Drill Bits</option>
224
+ <option value="Motor Assemblies">Motor Assemblies</option>
225
+ <option value="Mud Motors">Mud Motors</option>
226
+ <option value="Measurement While Drilling (MWD)">MWD Tools</option>
227
+ <option value="Logging While Drilling (LWD)">LWD Tools</option>
228
+ <option value="Casing/Line Pipe">Casing/Line Pipe</option>
229
+ <option value="Tubing">Tubing</option>
230
+ <option value="Packers"> Packers</option>
231
+ <option value="Safety Valves">Safety Valves</option>
232
+ <option value="Pump Jacks">Pump Jacks</option>
233
+ <option value="Gas Lift Valves">Gas Lift Valves</option>
234
+ <option value="Chokes">Chokes</option>
235
+ </select>
236
+ <input type="number" id="material-quantity" placeholder="Qty" min="1" value="1" class="p-2 border border-gray-300 w-24"/>
237
+ <button type="button" id="add-material-btn" class="bg-blue-600 text-white px-4 rounded-r-lg hover:bg-blue-700">
238
+ <i class="fas fa-plus"></i>
239
+ </button>
240
+ </div>
241
+ </div>
242
+ <small class="text-gray-500">Add materials saved (select item and quantity)</small>
243
  </div>
244
+
245
  <div>
246
+ <label class="block text-sm font-medium text-gray-600 mb-1">Operations/Processes Saved</label>
247
+ <div class="border border-gray-300 rounded-lg p-3">
248
+ <div id="process-tags" class="flex flex-wrap mb-2">
249
+ <!-- Process tags will be added here -->
250
+ </div>
251
+ <div class="flex">
252
+ <select id="process-select" class="p-2 border border-gray-300 rounded-l-lg flex-grow">
253
+ <option value="">Select process</option>
254
+ <option value="Connection Time">Connection Time</option>
255
+ <option value="Tripping Time">Tripping Time</option>
256
+ <option value="Circulation Time">Circulation Time</option>
257
+ <option value="Rig Move">Rig Move</option>
258
+ <option value="Blowout Preventer (BOP) Testing">BOP Testing</option>
259
+ <option value="Casing Running">Casing Running</option>
260
+ <option value="Cement Evaluation">Cement Evaluation</option>
261
+ <option value="Well Control">Well Control</option>
262
+ <option value="Tubing Running">Tubing Running</option>
263
+ <option value="Perforation">Perforation</option>
264
+ <option value="Coiled Tubing">Coiled Tubing</option>
265
+ <option value="Fishing">Fishing</option>
266
+ <option value="Plug and Abandon">Plug and Abandon</option>
267
+ </select>
268
+ <input type="number" id="process-quantity" placeholder="Qty" min="1" value="1" class="p-2 border border-gray-300 w-24"/>
269
+ <button type="button" id="add-process-btn" class="bg-blue-600 text-white px-4 rounded-r-lg hover:bg-blue-700">
270
+ <i class="fas fa-plus"></i>
271
+ </button>
272
+ </div>
273
+ </div>
274
+ <small class="text-gray-500">Add operations saved (select process and quantity)</small>
275
  </div>
276
+
277
  <div>
278
+ <label class="block text-sm font-medium text-gray-600 mb-1">Time Saved (hours)</label>
279
+ <input type="number" name="timeSaved" step="0.1" required class="block w-full p-3 border border-gray-300 rounded-lg focus:ring focus:ring-blue-200"/>
280
  </div>
281
  <div>
282
  <label class="block text-sm font-medium text-gray-600 mb-1">Cost Saved (Rp)</label>
283
  <input type="number" name="costSaved" required class="block w-full p-3 border border-gray-300 rounded-lg focus:ring focus:ring-blue-200"/>
284
  </div>
285
+
286
  <div>
287
  <label class="block text-sm font-medium text-gray-600 mb-1">Date</label>
288
  <input type="date" name="date" required class="block w-full p-3 border border-gray-300 rounded-lg focus:ring focus:ring-blue-200"/>
 
323
  <thead class="bg-gray-100 text-gray-600 uppercase text-sm leading-normal">
324
  <tr>
325
  <th class="py-3 px-6 text-left">No</th>
326
+ <th class="py-3 px-6 text-left">Operation Type</th>
327
+ <th class="py-3 px-6 text-left">Materials Saved</th>
328
  <th class="py-3 px-6 text-left">Time (hrs)</th>
329
+ <th class="py-3 px-6 text-left">Operations</th>
330
  <th class="py-3 px-6 text-left">Cost (Rp)</th>
331
  <th class="py-3 px-6 text-left">Date & Time</th>
332
  <th class="py-3 px-6 text-center">Actions</th>
 
344
  <section id="charts" class="page-content hidden">
345
  <div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
346
  <div class="bg-white p-6 rounded-xl shadow-md">
347
+ <h3 class="text-lg font-semibold mb-4">Material Items vs Time Saved</h3>
348
  <canvas id="matrial-time-chart" height="250"></canvas>
349
  </div>
350
  <div class="bg-white p-6 rounded-xl shadow-md">
351
+ <h3 class="text-lg font-semibold mb-4">Top 5 Operations (by Cost)</h3>
352
  <canvas id="top-jobs-chart" height="250"></canvas>
353
  </div>
354
  </div>
 
367
  </button>
368
  </div>
369
  <div class="mt-6 text-sm text-gray-500">
370
+ <p><strong>Required Columns:</strong> jobType, materialsSaved, timeSaved, processesSaved, costSaved, date, time (ISO format), notes</p>
371
+ <p><strong>Materials/Processes Format:</strong> Use semicolon to separate items, e.g., "Drill Bits:2;MWD Tools:1"</p>
372
  </div>
373
  </div>
374
  </section>
 
394
  <option value="YYYY-MM-DD">YYYY-MM-DD</option>
395
  </select>
396
  </div>
397
+ <div>
398
+ <label class="block text-sm font-medium text-gray-600 mb-2">Custom Materials</label>
399
+ <div class="border border-gray-300 rounded-lg p-3 mb-2 max-h-40 overflow-y-auto">
400
+ <div id="custom-materials-list">
401
+ <!-- Materials will be listed here -->
402
+ </div>
403
+ <div class="flex mt-2">
404
+ <input type="text" id="new-material-name" placeholder="New material name" class="p-2 border border-gray-300 rounded-l-lg flex-grow"/>
405
+ <button id="add-material-setting" class="bg-blue-600 text-white px-3 rounded-r-lg hover:bg-blue-700">
406
+ <i class="fas fa-plus"></i>
407
+ </button>
408
+ </div>
409
+ </div>
410
+ </div>
411
+ <div>
412
+ <label class="block text-sm font-medium text-gray-600 mb-2">Custom Processes</label>
413
+ <div class="border border-gray-300 rounded-lg p-3 mb-2 max-h-40 overflow-y-auto">
414
+ <div id="custom-processes-list">
415
+ <!-- Processes will be listed here -->
416
+ </div>
417
+ <div class="flex mt-2">
418
+ <input type="text" id="new-process-name" placeholder="New process name" class="p-2 border border-gray-300 rounded-l-lg flex-grow"/>
419
+ <button id="add-process-setting" class="bg-blue-600 text-white px-3 rounded-r-lg hover:bg-blue-700">
420
+ <i class="fas fa-plus"></i>
421
+ </button>
422
+ </div>
423
+ </div>
424
+ </div>
425
  <div class="flex space-x-4">
426
  <button id="reset-data-btn" class="px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 text-sm">
427
  <i class="fas fa-trash mr-2"></i> Reset All Data
 
436
  <!-- Add Job Type Modal -->
437
  <div id="add-job-modal" class="modal">
438
  <div class="bg-white p-6 rounded-lg shadow-lg max-w-md mx-auto mt-20">
439
+ <h3 class="text-lg font-semibold mb-4">Add New Operation Type</h3>
440
+ <input type="text" id="new-job-input" placeholder="Enter operation type name" class="w-full p-3 border border-gray-300 rounded-lg mb-4"/>
441
  <div class="flex justify-end space-x-3">
442
  <button id="cancel-job-btn" class="px-4 py-2 bg-gray-400 text-white rounded-lg">Cancel</button>
443
  <button id="save-job-btn" class="px-4 py-2 bg-blue-600 text-white rounded-lg">Save</button>
 
453
  <input type="hidden" id="edit-id"/>
454
  <div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
455
  <div>
456
+ <label class="block text-sm font-medium text-gray-600 mb-1">Operation Type</label>
457
  <select id="edit-job-type" class="block w-full p-3 border border-gray-300 rounded-lg focus:ring focus:ring-blue-200">
458
  <!-- Filled dynamically -->
459
  </select>
460
  </div>
461
+
462
  <div>
463
+ <label class="block text-sm font-medium text-gray-600 mb-1">Materials Saved</label>
464
+ <div class="border border-gray-300 rounded-lg p-3">
465
+ <div id="edit-material-tags" class="flex flex-wrap mb-2">
466
+ <!-- Material tags will be added here -->
467
+ </div>
468
+ <div class="flex">
469
+ <select id="edit-material-select" class="p-2 border border-gray-300 rounded-l-lg flex-grow">
470
+ <!-- Filled dynamically -->
471
+ </select>
472
+ <input type="number" id="edit-material-quantity" placeholder="Qty" min="1" value="1" class="p-2 border border-gray-300 w-24"/>
473
+ <button type="button" id="edit-add-material-btn" class="bg-blue-600 text-white px-4 rounded-r-lg hover:bg-blue-700">
474
+ <i class="fas fa-plus"></i>
475
+ </button>
476
+ </div>
477
+ </div>
478
  </div>
479
+
480
  <div>
481
+ <label class="block text-sm font-medium text-gray-600 mb-1">Operations/Processes Saved</label>
482
+ <div class="border border-gray-300 rounded-lg p-3">
483
+ <div id="edit-process-tags" class="flex flex-wrap mb-2">
484
+ <!-- Process tags will be added here -->
485
+ </div>
486
+ <div class="flex">
487
+ <select id="edit-process-select" class="p-2 border border-gray-300 rounded-l-lg flex-grow">
488
+ <!-- Filled dynamically -->
489
+ </select>
490
+ <input type="number" id="edit-process-quantity" placeholder="Qty" min="1" value="1" class="p-2 border border-gray-300 w-24"/>
491
+ <button type="button" id="edit-add-process-btn" class="bg-blue-600 text-white px-4 rounded-r-lg hover:bg-blue-700">
492
+ <i class="fas fa-plus"></i>
493
+ </button>
494
+ </div>
495
+ </div>
496
  </div>
497
+
498
  <div>
499
+ <label class="block text-sm font-medium text-gray-600 mb-1">Time Saved (hours)</label>
500
+ <input type="number" id="edit-time" step="0.1" class="block w-full p-3 border border-gray-300 rounded-lg"/>
501
  </div>
502
+
503
  <div>
504
  <label class="block text-sm font-medium text-gray-600 mb-1">Cost Saved</label>
505
  <input type="number" id="edit-cost" class="block w-full p-3 border border-gray-300 rounded-lg"/>
506
  </div>
507
+
508
  <div>
509
  <label class="block text-sm font-medium text-gray-600 mb-1">Date</label>
510
  <input type="date" id="edit-date" class="block w-full p-3 border border-gray-300 rounded-lg"/>
511
  </div>
512
+
513
  <div>
514
  <label class="block text-sm font-medium text-gray-600 mb-1">Time</label>
515
  <input type="time" id="edit-time-input" class="block w-full p-3 border border-gray-300 rounded-lg"/>
 
531
  // Initial state
532
  let savingsData = JSON.parse(localStorage.getItem('savingsData')) || [];
533
  let jobTypes = JSON.parse(localStorage.getItem('jobTypes')) || [
534
+ "Drilling - Tripping", "Drilling - BHA Runs", "Drilling - Bit Runs", "Drilling - Cementing",
535
+ "Workover - Tubing Change", "Workover - Pump Repair", "Workover - Zone Isolation", "Workover - Stimulations"
536
+ ];
537
+
538
+ // Materials and processes - loaded from localStorage or defaults
539
+ let materialsList = JSON.parse(localStorage.getItem('materialsList')) || [
540
+ "Drill Bits", "Motor Assemblies", "Mud Motors", "Measurement While Drilling (MWD)",
541
+ "Logging While Drilling (LWD)", "Casing/Line Pipe", "Tubing", "Packers",
542
+ "Safety Valves", "Pump Jacks", "Gas Lift Valves", "Chokes"
543
+ ];
544
+
545
+ let processesList = JSON.parse(localStorage.getItem('processesList')) || [
546
+ "Connection Time", "Tripping Time", "Circulation Time", "Rig Move",
547
+ "Blowout Preventer (BOP) Testing", "Casing Running", "Cement Evaluation",
548
+ "Well Control", "Tubing Running", "Perforation", "Coiled Tubing", "Fishing", "Plug and Abandon"
549
  ];
550
 
551
  // Format currency
 
597
  function updateJobTypeDropdown() {
598
  const select = document.getElementById('job-type');
599
  const editSelect = document.getElementById('edit-job-type');
600
+ select.innerHTML = '<option value="">Select operation type</option>';
601
  jobTypes.forEach(type => {
602
  select.innerHTML += `<option value="${type}">${type}</option>`;
603
  });
604
+
605
  // Update edit modal dropdown
606
  if (editSelect) {
607
  editSelect.innerHTML = '';
 
611
  }
612
  }
613
 
614
+ // Update Materials and Processes dropdowns
615
+ function updateMaterialsDropdown() {
616
+ const select = document.getElementById('material-select');
617
+ const editSelect = document.getElementById('edit-material-select');
618
+ select.innerHTML = '<option value="">Select material</option>';
619
+ materialsList.forEach(material => {
620
+ select.innerHTML += `<option value="${material}">${material}</option>`;
621
+ });
622
+
623
+ if (editSelect) {
624
+ editSelect.innerHTML = '<option value="">Select material</option>';
625
+ materialsList.forEach(material => {
626
+ editSelect.innerHTML += `<option value="${material}">${material}</option>`;
627
+ });
628
+ }
629
+ }
630
+
631
+ function updateProcessesDropdown() {
632
+ const select = document.getElementById('process-select');
633
+ const editSelect = document.getElementById('edit-process-select');
634
+ select.innerHTML = '<option value="">Select process</option>';
635
+ processesList.forEach(process => {
636
+ select.innerHTML += `<option value="${process}">${process}</option>`;
637
+ });
638
+
639
+ if (editSelect) {
640
+ editSelect.innerHTML = '<option value="">Select process</option>';
641
+ processesList.forEach(process => {
642
+ editSelect.innerHTML += `<option value="${process}">${process}</option>`;
643
+ });
644
+ }
645
+ }
646
+
647
  // Show add job modal
648
  document.getElementById('add-job-btn').addEventListener('click', () => {
649
  document.getElementById('add-job-modal').style.display = 'block';
 
660
  jobTypes.sort();
661
  localStorage.setItem('jobTypes', JSON.stringify(jobTypes));
662
  updateJobTypeDropdown();
663
+ showToast('New operation type added!');
664
  }
665
  document.getElementById('new-job-input').value = '';
666
  document.getElementById('add-job-modal').style.display = 'none';
667
  });
668
 
669
+ // Add material tag
670
+ function addMaterialTag(material, qty) {
671
+ const container = document.getElementById('material-tags');
672
+ const tag = document.createElement('span');
673
+ tag.className = 'tag material-tag';
674
+ tag.innerHTML = `${material}: ${qty} <span class="remove material-remove" data-material="${material}">×</span>`;
675
+ container.appendChild(tag);
676
+ }
677
+
678
+ // Add process tag
679
+ function addProcessTag(process, qty) {
680
+ const container = document.getElementById('process-tags');
681
+ const tag = document.createElement('span');
682
+ tag.className = 'tag process-tag';
683
+ tag.innerHTML = `${process}: ${qty} <span class="remove process-remove" data-process="${process}">×</span>`;
684
+ container.appendChild(tag);
685
+ }
686
+
687
+ // Add material button
688
+ document.getElementById('add-material-btn').addEventListener('click', () => {
689
+ const material = document.getElementById('material-select').value;
690
+ const qty = document.getElementById('material-quantity').value;
691
+
692
+ if (material && qty > 0) {
693
+ // Check if material tag already exists and remove it
694
+ const existing = document.querySelector(`.tag[data-material="${material}"]`);
695
+ if (existing) existing.remove();
696
+
697
+ addMaterialTag(material, qty);
698
+ document.getElementById('material-select').value = '';
699
+ document.getElementById('material-quantity').value = '1';
700
+ }
701
+ });
702
+
703
+ // Add process button
704
+ document.getElementById('add-process-btn').addEventListener('click', () => {
705
+ const process = document.getElementById('process-select').value;
706
+ const qty = document.getElementById('process-quantity').value;
707
+
708
+ if (process && qty > 0) {
709
+ // Check if process tag already exists and remove it
710
+ const existing = document.querySelector(`.tag[data-process="${process}"]`);
711
+ if (existing) existing.remove();
712
+
713
+ addProcessTag(process, qty);
714
+ document.getElementById('process-select').value = '';
715
+ document.getElementById('process-quantity').value = '1';
716
+ }
717
+ });
718
+
719
+ // Remove material tag
720
+ document.getElementById('material-tags').addEventListener('click', (e) => {
721
+ if (e.target.classList.contains('material-remove')) {
722
+ e.target.parentElement.remove();
723
+ }
724
+ });
725
+
726
+ // Remove process tag
727
+ document.getElementById('process-tags').addEventListener('click', (e) => {
728
+ if (e.target.classList.contains('process-remove')) {
729
+ e.target.parentElement.remove();
730
+ }
731
+ });
732
+
733
  // Form submission
734
  document.getElementById('saving-form').addEventListener('submit', (e) => {
735
  e.preventDefault();
 
748
  data.jobType = customJob;
749
  }
750
 
751
+ // Get materials
752
+ const materialTags = document.querySelectorAll('#material-tags .tag');
753
+ data.materialsSaved = [];
754
+ materialTags.forEach(tag => {
755
+ const material = tag.querySelector('.material-remove').getAttribute('data-material');
756
+ const qtyText = tag.textContent.trim().split(': ')[1];
757
+ const qty = qtyText.split(' ')[0];
758
+ data.materialsSaved.push({ name: material, quantity: qty });
759
+ });
760
+
761
+ // Get processes
762
+ const processTags = document.querySelectorAll('#process-tags .tag');
763
+ data.processesSaved = [];
764
+ processTags.forEach(tag => {
765
+ const process = tag.querySelector('.process-remove').getAttribute('data-process');
766
+ const qtyText = tag.textContent.trim().split(': ')[1];
767
+ const qty = qtyText.split(' ')[0];
768
+ data.processesSaved.push({ name: process, quantity: qty });
769
+ });
770
+
771
  data.timestamp = new Date().toISOString();
772
  data.id = Date.now(); // unique ID
773
  savingsData.push(data);
 
775
 
776
  e.target.reset();
777
  document.getElementById('custom-job-type').value = '';
778
+ document.getElementById('material-tags').innerHTML = '';
779
+ document.getElementById('process-tags').innerHTML = '';
780
+ document.getElementById('material-quantity').value = '1';
781
+ document.getElementById('process-quantity').value = '1';
782
  showToast("Data saved successfully!");
783
  updateDashboard();
784
  updateRecords();
 
789
  document.getElementById('cancel-input').addEventListener('click', () => {
790
  document.getElementById('saving-form').reset();
791
  document.getElementById('custom-job-type').value = '';
792
+ document.getElementById('material-tags').innerHTML = '';
793
+ document.getElementById('process-tags').innerHTML = '';
794
+ document.getElementById('material-quantity').value = '1';
795
+ document.getElementById('process-quantity').value = '1';
796
  });
797
 
798
  // Export to CSV
799
  document.getElementById('export-btn').addEventListener('click', () => {
800
+ // Format materials and processes as strings
801
+ const formatItems = (items) => {
802
+ return items.map(item => `${item.name}:${item.quantity}`).join(';');
803
+ };
804
+
805
  const csv = [
806
+ ['Job Type', 'Materials Saved', 'Time Saved (hrs)', 'Operations Saved', 'Cost Saved', 'Date', 'Time', 'Notes']
807
  ].concat(savingsData.map(item => [
808
+ item.jobType,
809
+ formatItems(item.materialsSaved || []),
810
+ item.timeSaved,
811
+ formatItems(item.processesSaved || []),
812
+ item.costSaved,
813
+ item.date,
814
+ item.time,
815
+ item.notes || ''
816
  ])).map(row => row.join(',')).join('\n');
817
 
818
  const blob = new Blob([csv], { type: 'text/csv' });
 
838
  const lines = content.split('\n');
839
  const headers = lines[0].split(',').map(h => h.trim());
840
 
841
+ const requiredHeaders = ['Job Type', 'Materials Saved', 'Time Saved (hrs)', 'Operations Saved', 'Cost Saved', 'Date', 'Time'];
842
+ if (!requiredHeaders.every(h => headers.includes(h))) {
843
  alert('CSV file must contain the required columns.');
844
  return;
845
  }
 
848
  if (lines[i].trim() === '') continue;
849
  const values = lines[i].split(',').map(v => v.trim());
850
  const obj = {};
851
+
852
+ // Map headers to object properties
853
  headers.forEach((h, idx) => {
854
+ obj[h.toLowerCase().replace(/ \([^)]*\)/g, '').replace(/ /g, '')] = values[idx] || '';
855
  });
856
 
857
+ // Parse materials saved
858
+ obj.materialsaved = [];
859
+ if (obj.materialssaved && obj.materialssaved.includes(':')) {
860
+ obj.materialssaved.split(';').forEach(item => {
861
+ const [name, qty] = item.split(':');
862
+ obj.materialsaved.push({ name: name.trim(), quantity: qty.trim() });
863
+ });
864
+ }
865
+
866
+ // Parse processes saved
867
+ obj.processessaved = [];
868
+ if (obj.operationssaved && obj.operationssaved.includes(':')) {
869
+ obj.operationssaved.split(';').forEach(item => {
870
+ const [name, qty] = item.split(':');
871
+ obj.processessaved.push({ name: name.trim(), quantity: qty.trim() });
872
+ });
873
+ }
874
+
875
+ // Clean up the object
876
+ delete obj.materialssaved;
877
+ delete obj.operationssaved;
878
+
879
+ // Set the job type
880
+ obj.jobtype = obj.jobtype;
881
+
882
+ // Set unique ID and timestamp
883
  obj.id = Date.now() + i;
884
  obj.timestamp = new Date().toISOString();
885
+
886
  savingsData.push(obj);
887
  }
888
 
 
902
 
903
  document.getElementById('edit-id').value = record.id;
904
  document.getElementById('edit-job-type').value = record.jobType;
905
+
906
+ // Clear existing tags
907
+ document.getElementById('edit-material-tags').innerHTML = '';
908
+ document.getElementById('edit-process-tags').innerHTML = '';
909
+
910
+ // Add material tags
911
+ if (record.materialsaved && record.materialsaved.length > 0) {
912
+ record.materialsaved.forEach(item => {
913
+ const tag = document.createElement('span');
914
+ tag.className = 'tag material-tag';
915
+ tag.innerHTML = `${item.name}: ${item.quantity} <span class="remove edit-material-remove" data-material="${item.name}">×</span>`;
916
+ document.getElementById('edit-material-tags').appendChild(tag);
917
+ });
918
+ }
919
+
920
+ // Add process tags
921
+ if (record.processessaved && record.processessaved.length > 0) {
922
+ record.processessaved.forEach(item => {
923
+ const tag = document.createElement('span');
924
+ tag.className = 'tag process-tag';
925
+ tag.innerHTML = `${item.name}: ${item.quantity} <span class="remove edit-process-remove" data-process="${item.name}">×</span>`;
926
+ document.getElementById('edit-process-tags').appendChild(tag);
927
+ });
928
+ }
929
+
930
  document.getElementById('edit-time').value = record.timeSaved;
 
931
  document.getElementById('edit-cost').value = record.costSaved;
932
  document.getElementById('edit-date').value = record.date;
933
  document.getElementById('edit-time-input').value = record.time;
 
936
  document.getElementById('edit-modal').style.display = 'block';
937
  }
938
 
939
+ // Add material tag in edit mode
940
+ function addEditMaterialTag(material, qty) {
941
+ const container = document.getElementById('edit-material-tags');
942
+ const tag = document.createElement('span');
943
+ tag.className = 'tag material-tag';
944
+ tag.innerHTML = `${material}: ${qty} <span class="remove edit-material-remove" data-material="${material}">×</span>`;
945
+ container.appendChild(tag);
946
+ }
947
+
948
+ // Add process tag in edit mode
949
+ function addEditProcessTag(process, qty) {
950
+ const container = document.getElementById('edit-process-tags');
951
+ const tag = document.createElement('span');
952
+ tag.className = 'tag process-tag';
953
+ tag.innerHTML = `${process}: ${qty} <span class="remove edit-process-remove" data-process="${process}">×</span>`;
954
+ container.appendChild(tag);
955
+ }
956
+
957
+ // Add material button in edit mode
958
+ document.getElementById('edit-add-material-btn').addEventListener('click', () => {
959
+ const material = document.getElementById('edit-material-select').value;
960
+ const qty = document.getElementById('edit-material-quantity').value;
961
+
962
+ if (material && qty > 0) {
963
+ // Check if material tag already exists and remove it
964
+ const existing = Array.from(document.querySelectorAll('.edit-material-remove'))
965
+ .find(el => el.getAttribute('data-material') === material);
966
+ if (existing) existing.parentElement.remove();
967
+
968
+ addEditMaterialTag(material, qty);
969
+ document.getElementById('edit-material-select').value = '';
970
+ document.getElementById('edit-material-quantity').value = '1';
971
+ }
972
+ });
973
+
974
+ // Add process button in edit mode
975
+ document.getElementById('edit-add-process-btn').addEventListener('click', () => {
976
+ const process = document.getElementById('edit-process-select').value;
977
+ const qty = document.getElementById('edit-process-quantity').value;
978
+
979
+ if (process && qty > 0) {
980
+ // Check if process tag already exists and remove it
981
+ const existing = Array.from(document.querySelectorAll('.edit-process-remove'))
982
+ .find(el => el.getAttribute('data-process') === process);
983
+ if (existing) existing.parentElement.remove();
984
+
985
+ addEditProcessTag(process, qty);
986
+ document.getElementById('edit-process-select').value = '';
987
+ document.getElementById('edit-process-quantity').value = '1';
988
+ }
989
+ });
990
+
991
+ // Remove material tag in edit mode
992
+ document.getElementById('edit-material-tags').addEventListener('click', (e) => {
993
+ if (e.target.classList.contains('edit-material-remove')) {
994
+ e.target.parentElement.remove();
995
+ }
996
+ });
997
+
998
+ // Remove process tag in edit mode
999
+ document.getElementById('edit-process-tags').addEventListener('click', (e) => {
1000
+ if (e.target.classList.contains('edit-process-remove')) {
1001
+ e.target.parentElement.remove();
1002
+ }
1003
+ });
1004
+
1005
  document.getElementById('cancel-edit').addEventListener('click', () => {
1006
  document.getElementById('edit-modal').style.display = 'none';
1007
  });
 
1012
  const record = savingsData.find(r => r.id == id);
1013
  if (!record) return;
1014
 
1015
+ // Update fields
1016
  record.jobType = document.getElementById('edit-job-type').value;
 
1017
  record.timeSaved = document.getElementById('edit-time').value;
 
1018
  record.costSaved = document.getElementById('edit-cost').value;
1019
  record.date = document.getElementById('edit-date').value;
1020
  record.time = document.getElementById('edit-time-input').value;
1021
  record.notes = document.getElementById('edit-notes').value;
1022
 
1023
+ // Update materials
1024
+ record.materialsaved = [];
1025
+ const materialTags = document.querySelectorAll('#edit-material-tags .tag');
1026
+ materialTags.forEach(tag => {
1027
+ const material = tag.querySelector('.edit-material-remove').getAttribute('data-material');
1028
+ const qtyText = tag.textContent.trim().split(': ')[1];
1029
+ const qty = qtyText.split(' ')[0];
1030
+ record.materialsaved.push({ name: material, quantity: qty });
1031
+ });
1032
+
1033
+ // Update processes
1034
+ record.processessaved = [];
1035
+ const processTags = document.querySelectorAll('#edit-process-tags .tag');
1036
+ processTags.forEach(tag => {
1037
+ const process = tag.querySelector('.edit-process-remove').getAttribute('data-process');
1038
+ const qtyText = tag.textContent.trim().split(': ')[1];
1039
+ const qty = qtyText.split(' ')[0];
1040
+ record.processessaved.push({ name: process, quantity: qty });
1041
+ });
1042
+
1043
  localStorage.setItem('savingsData', JSON.stringify(savingsData));
1044
  document.getElementById('edit-modal').style.display = 'none';
1045
  updateRecords();
 
1077
  const term = e.target.value.toLowerCase();
1078
  const filtered = savingsData.filter(item =>
1079
  item.jobType.toLowerCase().includes(term) ||
1080
+ item.notes?.toLowerCase().includes(term) ||
1081
+ (item.materialsaved && item.materialsaved.some(m => m.name.toLowerCase().includes(term))) ||
1082
+ (item.processessaved && item.processessaved.some(p => p.name.toLowerCase().includes(term)))
1083
  );
1084
  populateRecords(filtered);
1085
  });
 
1093
  updateRecords();
1094
  } else if (page === 'charts') {
1095
  updateCharts();
1096
+ } else if (page === 'settings') {
1097
+ updateSettings();
1098
  }
1099
  }
1100
 
1101
+ // Settings page updates
1102
+ function updateSettings() {
1103
+ // Update currency select
1104
+ const currentCurrency = localStorage.getItem('currency') || 'IDR';
1105
+ document.getElementById('currency-select').value = currentCurrency;
1106
+
1107
+ // Update date format select
1108
+ const dateFormat = localStorage.getItem('dateFormat') || 'DD/MM/YYYY';
1109
+ document.getElementById('date-format-select').value = dateFormat;
1110
+
1111
+ // Update material list
1112
+ const materialsListEl = document.getElementById('custom-materials-list');
1113
+ materialsListEl.innerHTML = '';
1114
+ materialsList.forEach(material => {
1115
+ const div = document.createElement('div');
1116
+ div.className = 'flex justify-between items-center py-1 border-b border-gray-100';
1117
+ div.innerHTML = `
1118
+ <span>${material}</span>
1119
+ <button class="text-red-500 text-sm remove-material-btn" data-material="${material}">Remove</button>
1120
+ `;
1121
+ materialsListEl.appendChild(div);
1122
+ });
1123
+
1124
+ // Add event listeners for removing materials
1125
+ document.querySelectorAll('.remove-material-btn').forEach(btn => {
1126
+ btn.addEventListener('click', (e) => {
1127
+ const material = e.target.getAttribute('data-material');
1128
+ materialsList = materialsList.filter(m => m !== material);
1129
+ localStorage.setItem('materialsList', JSON.stringify(materialsList));
1130
+ updateMaterialsDropdown();
1131
+ updateSettings();
1132
+ showToast('Material removed!');
1133
+ });
1134
+ });
1135
+
1136
+ // Update processes list
1137
+ const processesListEl = document.getElementById('custom-processes-list');
1138
+ processesListEl.innerHTML = '';
1139
+ processesList.forEach(process => {
1140
+ const div = document.createElement('div');
1141
+ div.className = 'flex justify-between items-center py-1 border-b border-gray-100';
1142
+ div.innerHTML = `
1143
+ <span>${process}</span>
1144
+ <button class="text-red-500 text-sm remove-process-btn" data-process="${process}">Remove</button>
1145
+ `;
1146
+ processesListEl.appendChild(div);
1147
+ });
1148
+
1149
+ // Add event listeners for removing processes
1150
+ document.querySelectorAll('.remove-process-btn').forEach(btn => {
1151
+ btn.addEventListener('click', (e) => {
1152
+ const process = e.target.getAttribute('data-process');
1153
+ processesList = processesList.filter(p => p !== process);
1154
+ localStorage.setItem('processesList', JSON.stringify(processesList));
1155
+ updateProcessesDropdown();
1156
+ updateSettings();
1157
+ showToast('Process removed!');
1158
+ });
1159
+ });
1160
+ }
1161
+
1162
+ // Add material from settings
1163
+ document.getElementById('add-material-setting').addEventListener('click', () => {
1164
+ const newMaterial = document.getElementById('new-material-name').value.trim();
1165
+ if (newMaterial && !materialsList.includes(newMaterial)) {
1166
+ materialsList.push(newMaterial);
1167
+ materialsList.sort();
1168
+ localStorage.setItem('materialsList', JSON.stringify(materialsList));
1169
+ updateMaterialsDropdown();
1170
+ updateSettings();
1171
+ showToast('New material added!');
1172
+ }
1173
+ document.getElementById('new-material-name').value = '';
1174
+ });
1175
+
1176
+ // Add process from settings
1177
+ document.getElementById('add-process-setting').addEventListener('click', () => {
1178
+ const newProcess = document.getElementById('new-process-name').value.trim();
1179
+ if (newProcess && !processesList.includes(newProcess)) {
1180
+ processesList.push(newProcess);
1181
+ processesList.sort();
1182
+ localStorage.setItem('processesList', JSON.stringify(processesList));
1183
+ updateProcessesDropdown();
1184
+ updateSettings();
1185
+ showToast('New process added!');
1186
+ }
1187
+ document.getElementById('new-process-name').value = '';
1188
+ });
1189
+
1190
+ // Currency and date format changes
1191
+ document.getElementById('currency-select').addEventListener('change', (e) => {
1192
+ localStorage.setItem('currency', e.target.value);
1193
+ updateDashboard();
1194
+ updateCharts();
1195
+ });
1196
+
1197
+ document.getElementById('date-format-select').addEventListener('change', (e) => {
1198
+ localStorage.setItem('dateFormat', e.target.value);
1199
+ updateRecords();
1200
+ updateDashboard();
1201
+ });
1202
+
1203
  // Dashboard Stats
1204
  function updateDashboard() {
1205
  const totalCost = savingsData.reduce((sum, item) => sum + parseFloat(item.costSaved || 0), 0);
1206
  const totalTime = savingsData.reduce((sum, item) => sum + parseFloat(item.timeSaved || 0), 0);
1207
+
1208
+ // Count total items saved
1209
+ let totalItems = 0;
1210
+ savingsData.forEach(item => {
1211
+ if (item.materialsaved) {
1212
+ item.materialsaved.forEach(material => {
1213
+ totalItems += parseInt(material.quantity) || 0;
1214
+ });
1215
+ }
1216
+ });
1217
+
1218
+ // Count total operations
1219
+ let totalOperations = 0;
1220
+ savingsData.forEach(item => {
1221
+ if (item.processessaved) {
1222
+ item.processessaved.forEach(process => {
1223
+ totalOperations += parseInt(process.quantity) || 0;
1224
+ });
1225
+ }
1226
+ });
1227
 
1228
  document.getElementById('total-cost').textContent = formatCurrency(totalCost);
1229
  document.getElementById('total-time').textContent = `${totalTime.toFixed(1)} hrs`;
1230
+ document.getElementById('total-material').textContent = `${totalItems} items`;
1231
+ document.getElementById('total-processes').textContent = totalOperations;
1232
 
1233
  // Recent activities
1234
  const recent = savingsData
 
1237
  const recentContainer = document.getElementById('recent-activities');
1238
  recentContainer.innerHTML = '';
1239
  recent.forEach(item => {
1240
+ // Format materials string
1241
+ let materialsText = '';
1242
+ if (item.materialsaved && item.materialsaved.length > 0) {
1243
+ materialsText = item.materialsaved.map(m => `${m.quantity} ${m.name}`).join(', ');
1244
+ }
1245
+
1246
  const div = document.createElement('div');
1247
  div.className = 'flex justify-between items-center p-3 bg-gray-50 rounded-lg';
1248
  div.innerHTML = `
1249
  <div>
1250
  <p class="font-medium">${item.jobType}</p>
1251
  <p class="text-sm text-gray-500">${formatDate(item.date)} at ${item.time}</p>
1252
+ ${materialsText ? `<p class="text-sm text-gray-500">${materialsText}</p>` : ''}
1253
  </div>
1254
  <div class="text-right">
1255
  <p class="font-medium text-green-600">${formatCurrency(item.costSaved)}</p>
1256
+ <p class="text-sm text-gray-500">${totalItems} items saved</p>
1257
  </div>
1258
  `;
1259
  recentContainer.appendChild(div);
 
1274
  }
1275
 
1276
  data.slice().reverse().forEach((item, index) => {
1277
+ // Format materials text
1278
+ let materialsText = '';
1279
+ if (item.materialsaved && item.materialsaved.length > 0) {
1280
+ materialsText = item.materialsaved.map(m =>
1281
+ `<span class="inline-block bg-green-100 text-green-800 text-xs px-2 py-1 rounded-full mr-1 mb-1">${m.quantity}×${m.name}</span>`
1282
+ ).join('');
1283
+ }
1284
+
1285
+ // Format processes text
1286
+ let processesText = '';
1287
+ if (item.processessaved && item.processessaved.length > 0) {
1288
+ processesText = item.processessaved.map(p =>
1289
+ `<span class="inline-block bg-blue-100 text-blue-800 text-xs px-2 py-1 rounded-full mr-1 mb-1">${p.quantity}×${p.name}</span>`
1290
+ ).join('');
1291
+ }
1292
+
1293
  const tr = document.createElement('tr');
1294
  tr.className = 'border-t border-gray-200 hover:bg-gray-50';
1295
  tr.innerHTML = `
1296
  <td class="py-3 px-6">${data.length - index}</td>
1297
  <td class="py-3 px-6">${item.jobType}</td>
1298
+ <td class="py-3 px-6">${materialsText}</td>
1299
  <td class="py-3 px-6">${parseFloat(item.timeSaved).toFixed(1)}</td>
1300
+ <td class="py-3 px-6">${processesText}</td>
1301
  <td class="py-3 px-6">${formatCurrency(item.costSaved)}</td>
1302
  <td class="py-3 px-6">${formatDate(item.date)} ${item.time}</td>
1303
  <td class="py-3 px-6 text-center">
 
1365
  datasets: [{
1366
  data: Object.values(jobCounts),
1367
  backgroundColor: [
1368
+ '#FF6384', '#36A2EB', '#FFCE56', '#4BC0C0', '#9966FF', '#FF9F40', '#C9CBCF', '#46BFBD'
1369
  ]
1370
  }]
1371
  },
 
1375
  });
1376
 
1377
  // Material vs Time Chart
1378
+ // We need to calculate total items per record
1379
+ const chartData = savingsData.map(item => {
1380
+ let totalItems = 0;
1381
+ if (item.materialsaved) {
1382
+ item.materialsaved.forEach(material => {
1383
+ totalItems += parseInt(material.quantity) || 0;
1384
+ });
1385
+ }
1386
+ return {
1387
+ x: totalItems,
1388
+ y: parseFloat(item.timeSaved),
1389
+ r: Math.sqrt(parseFloat(item.costSaved)) / 10,
1390
+ jobType: item.jobType
1391
+ };
1392
+ });
1393
+
1394
  const materialTimeCtx = document.getElementById('matrial-time-chart').getContext('2d');
1395
  if (materialTimeChart) materialTimeChart.destroy();
1396
  materialTimeChart = new Chart(materialTimeCtx, {
1397
  type: 'scatter',
1398
  data: {
1399
  datasets: [{
1400
+ label: 'Items vs Time Saved',
1401
+ data: chartData,
 
 
 
 
 
1402
  backgroundColor: 'rgba(75, 192, 192, 0.6)'
1403
  }]
1404
  },
1405
  options: {
1406
  responsive: true,
1407
  scales: {
1408
+ x: {
1409
+ title: { display: true, text: 'Items Saved' },
1410
+ beginAtZero: true
1411
+ },
1412
+ y: {
1413
+ title: { display: true, text: 'Time Saved (hours)' },
1414
+ beginAtZero: true
1415
+ }
1416
  }
1417
  }
1418
  });
1419
 
1420
+ // Top 5 Operations by Cost
1421
  const sortedJobs = Object.entries(jobCounts)
1422
  .sort((a,b) => b[1] - a[1])
1423
  .slice(0, 5);
 
1452
 
1453
  // Initialize
1454
  updateJobTypeDropdown();
1455
+ updateMaterialsDropdown();
1456
+ updateProcessesDropdown();
1457
+ updateSettings();
1458
  updateDashboard();
1459
  updateRecords();
1460
  updateCharts();
prompts.txt CHANGED
@@ -1 +1,2 @@
1
- buatkan aplikasi untuk menghitung saving operation untuk jenis pekerjaan, material yang di hemat , waktu pekerjaan yang di hemat, proses yang di hemat , tanggal, waktu dan nilai cost. pastikan ada fitur inputing , record, historical dan import, edit dan berikan fitur tambahan jika ada jenis pekerjaan yang belum di masukkan
 
 
1
+ buatkan aplikasi untuk menghitung saving operation untuk jenis pekerjaan, material yang di hemat , waktu pekerjaan yang di hemat, proses yang di hemat , tanggal, waktu dan nilai cost. pastikan ada fitur inputing , record, historical dan import, edit dan berikan fitur tambahan jika ada jenis pekerjaan yang belum di masukkan
2
+ tolong di adjust untuk record operation drilling dan workover . untuk material saved satuannya per item bukan kg dan dapat di tambahkan listnya. dan proses juga bisa di tambah jika bentuk list