prespud-v3 / index.html
alterzick's picture
Add 3 files
8dadb9f verified
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pre-Spud Checklist - Workover & Well Intervention</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/file-saver@2.0.5/dist/FileSaver.min.js"></script>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
margin: 0;
padding: 20px;
background-color: #f8f9fa;
color: #212529;
}
header {
text-align: center;
margin-bottom: 30px;
padding: 20px;
background-color: #0d6efd;
color: white;
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.tabs {
display: flex;
gap: 10px;
margin-bottom: 20px;
flex-wrap: wrap;
border-bottom: 2px solid #dee2e6;
margin-bottom: 30px;
}
.tab-button {
padding: 10px 20px;
background: #e9ecef;
border: none;
border-radius: 8px 8px 0 0;
cursor: pointer;
font-weight: 500;
transition: all 0.2s ease;
}
.tab-button.active {
background: white;
font-weight: 600;
border-bottom: 2px solid transparent;
box-shadow: 0 -2px 0 #0d6efd inset;
}
.tab-content {
display: none;
background: white;
padding: 25px;
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
border: 1px solid #dee2e6;
}
.tab-content.active {
display: block;
}
form {
display: grid;
gap: 15px;
}
label {
font-weight: 600;
color: #495057;
}
input[type="text"],
input[type="date"],
select,
textarea {
width: 100%;
padding: 12px 15px;
border: 1px solid #ced4da;
border-radius: 4px;
box-sizing: border-box;
transition: border-color 0.2s ease;
}
input[type="text"]:focus,
input[type="date"]:focus,
select:focus,
textarea:focus {
outline: none;
border-color: #0d6efd;
box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25);
}
textarea {
resize: vertical;
min-height: 80px;
}
.checklist-container {
display: grid;
gap: 12px;
margin: 15px 0;
background-color: #f8f9fa;
padding: 15px;
border-radius: 8px;
}
.checklist-item {
display: flex;
align-items: center;
gap: 8px;
padding: 8px;
background: white;
border-radius: 4px;
border: 1px solid #dee2e6;
}
.checklist-item input[type="text"] {
flex: 1;
}
button {
background-color: #0d6efd;
color: white;
padding: 12px 20px;
border: none;
border-radius: 4px;
font-size: 16px;
cursor: pointer;
transition: background-color 0.2s ease;
font-weight: 500;
}
button:hover {
background-color: #0b5ed7;
}
button.edit-record, button.delete-record {
background-color: #ffc107;
padding: 6px 12px;
font-size: 14px;
margin-right: 5px;
}
button.edit-record:hover {
background-color: #e5a206;
}
button.delete-record {
background-color: #dc3545;
padding: 6px 12px;
font-size: 14px;
}
button.delete-record:hover {
background-color: #c52a3a;
}
button.remove {
background-color: #dc3545;
padding: 6px 12px;
font-size: 14px;
}
button.remove:hover {
background-color: #c52a3a;
}
.suggestion-list {
list-style: none;
padding: 0;
margin: 5px 0 0;
border: 1px solid #ced4da;
border-top: none;
max-height: 150px;
overflow-y: auto;
background: white;
position: absolute;
z-index: 1000;
width: calc(100% - 22px);
border-radius: 0 0 4px 4px;
}
.suggestion-list li {
padding: 8px 10px;
cursor: pointer;
border-bottom: 1px solid #eee;
transition: background-color 0.15s ease;
}
.suggestion-list li:hover {
background-color: #f8f9fa;
}
.action-buttons {
display: flex;
gap: 10px;
margin-top: 20px;
}
.history-group {
margin-bottom: 20px;
border: 1px solid #dee2e6;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
transition: transform 0.2s ease;
}
.history-group:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
.history-header {
background-color: #e9ecef;
padding: 12px 15px;
font-weight: 600;
font-size: 16px;
display: flex;
justify-content: space-between;
align-items: center;
}
.record-actions {
display: flex;
gap: 8px;
}
table {
width: 100%;
border-collapse: collapse;
}
th, td {
border: 1px solid #dee2e6;
padding: 12px;
text-align: left;
}
th {
background-color: #f8f9fa;
font-weight: 600;
color: #495057;
font-size: 15px;
}
tr:nth-child(even) {
background-color: #fbfcfc;
}
tr:hover {
background-color: #f2f5f7;
}
.filter-section {
margin-bottom: 20px;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 10px;
padding: 15px;
background-color: #f8f9fa;
border-radius: 8px;
border: 1px solid #dee2e6;
}
#no-records {
text-align: center;
padding: 40px 20px;
color: #6c757d;
font-style: italic;
border: 2px dashed #dee2e6;
border-radius: 8px;
margin-top: 20px;
}
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 2000;
justify-content: center;
align-items: center;
}
.modal-content {
background-color: white;
padding: 30px;
border-radius: 10px;
width: 90%;
max-width: 600px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
max-height: 90vh;
overflow-y: auto;
}
.close-modal {
position: absolute;
top: 10px;
right: 10px;
font-size: 24px;
font-weight: bold;
cursor: pointer;
color: #6c757d;
background: none;
border: none;
}
.close-modal:hover {
color: #000;
}
.input-group {
margin-bottom: 15px;
}
.export-import-buttons {
display: flex;
justify-content: space-between;
margin-bottom: 20px;
}
.edit-icon {
width: 14px;
height: 14px;
fill: white;
}
.delete-icon {
width: 14px;
height: 14px;
fill: white;
}
</style>
</head>
<body>
<header>
<h1 class="text-3xl font-bold mb-2">Pre-Spud Checklist</h1>
<p class="text-xl">Workover & Well Intervention Operations</p>
</header>
<div class="tabs">
<button class="tab-button active" data-tab="input">Input Rekaman Baru</button>
<button class="tab-button" data-tab="manage">Kelola Checklist</button>
<button class="tab-button" data-tab="history">Riwayat Rekaman</button>
<button class="tab-button" data-tab="export-import">Ekspor/Impor Data</button>
</div>
<div id="input" class="tab-content active">
<h2 class="text-2xl font-bold mb-4">Input Rekaman Baru</h2>
<form id="pre-spud-form">
<div class="input-group">
<label for="pre-spud-date">Tanggal Pre-Spud:</label>
<input type="date" id="pre-spud-date" required>
</div>
<div class="input-group">
<label for="operation-type">Jenis Operasi:</label>
<select id="operation-type" required>
<option value="">Pilih jenis operasi</option>
<option value="Workover">Workover</option>
<option value="Well Intervention">Well Intervention</option>
</select>
</div>
<div class="input-group">
<label for="program-number">Nomor Program:</label>
<div class="suggestion-wrapper relative">
<input type="text" id="program-number" required autocomplete="off">
<ul class="suggestion-list" id="program-suggestions"></ul>
</div>
</div>
<div class="input-group">
<label for="rig-name">Nama Rig:</label>
<div class="suggestion-wrapper relative">
<input type="text" id="rig-name" required autocomplete="off">
<ul class="suggestion-list" id="rig-suggestions"></ul>
</div>
</div>
<div class="input-group">
<label for="location-from">Lokasi Saat Ini:</label>
<div class="suggestion-wrapper relative">
<input type="text" id="location-from" required autocomplete="off">
<ul class="suggestion-list" id="from-suggestions"></ul>
</div>
</div>
<div class="input-group">
<label for="location-to">Lokasi Tujuan:</label>
<div class="suggestion-wrapper relative">
<input type="text" id="location-to" required autocomplete="off">
<ul class="suggestion-list" id="to-suggestions"></ul>
</div>
</div>
<div class="input-group">
<label>Checklist Persyaratan:</label>
<div id="dynamic-checklist" class="checklist-container"></div>
</div>
<div class="input-group">
<label for="notes">Catatan Tambahan (opsional):</label>
<textarea id="notes"></textarea>
</div>
<div class="action-buttons">
<button type="submit" id="submit-btn">Submit & Rekam Checklist</button>
</div>
</form>
</div>
<div id="manage" class="tab-content">
<h2 class="text-2xl font-bold mb-4">Kelola Item Checklist</h2>
<p class="text-gray-700 mb-4">Tambah, edit, atau hapus item checklist sesuai kebutuhan operasi.</p>
<div id="manage-checklist" class="checklist-container"></div>
<button id="add-new-item" class="mt-4">Tambah Item Baru</button>
</div>
<div id="history" class="tab-content">
<h2 class="text-2xl font-bold mb-4">Riwayat Rekaman Checklist</h2>
<div class="filter-section">
<input type="date" id="filter-date-from" placeholder="Dari Tanggal">
<input type="date" id="filter-date-to" placeholder="Sampai Tanggal">
<select id="filter-operation">
<option value="">Semua Jenis Operasi</option>
<option value="Workover">Workover</option>
<option value="Well Intervention">Well Intervention</option>
</select>
<input type="text" id="filter-location" placeholder="Cari Lokasi / Program / Rig">
<button id="apply-filter">Terapkan Filter</button>
<button id="clear-filter">Hapus Filter</button>
</div>
<div id="history-container"></div>
<div id="no-records">Belum ada rekaman.</div>
</div>
<div id="export-import" class="tab-content">
<h2 class="text-2xl font-bold mb-4">Ekspor/Impor Data</h2>
<p class="text-gray-700 mb-6">Gunakan fitur ini untuk menyimpan cadangan data atau memindahkan data antar perangkat.</p>
<div class="export-import-buttons mb-8">
<button id="export-data" class="bg-success hover:bg-green-500">Ekspor Data ke XLS</button>
<label for="import-file" class="bg-warning hover:bg-yellow-500 cursor-pointer inline-block py-2 px-4 rounded">
Impor Data dari File
</label>
<input type="file" id="import-file" accept=".xls,.xlsx,.json" class="hidden">
</div>
<div class="bg-blue-50 p-6 rounded-lg border border-blue-200">
<h3 class="text-lg font-semibold mb-3 text-blue-800">Petunjuk Impor/Ekspor</h3>
<ul class="list-disc pl-5 text-blue-700 space-y-2">
<li>Gunakan tombol "Ekspor Data ke XLS" untuk menyimpan cadangan semua data checklist</li>
<li>Gunakan tombol "Impor Data dari File" untuk memulihkan data dari file ekspor sebelumnya</li>
<li>File impor harus berformat XLS, XLSX, atau JSON</li>
<li>Impor data akan menggantikan semua data yang ada saat ini</li>
<li>Dianjurkan untuk membuat cadangan sebelum melakukan impor</li>
</ul>
</div>
</div>
<!-- Edit Record Modal -->
<div id="edit-modal" class="modal">
<div class="modal-content">
<button class="close-modal">&times;</button>
<h2 class="text-2xl font-bold mb-6">Edit Rekaman Checklist</h2>
<form id="edit-form">
<input type="hidden" id="edit-record-id">
<div class="input-group">
<label for="edit-pre-spud-date">Tanggal Pre-Spud:</label>
<input type="date" id="edit-pre-spud-date" required>
</div>
<div class="input-group">
<label for="edit-operation-type">Jenis Operasi:</label>
<select id="edit-operation-type" required>
<option value="">Pilih jenis operasi</option>
<option value="Workover">Workover</option>
<option value="Well Intervention">Well Intervention</option>
</select>
</div>
<div class="input-group">
<label for="edit-program-number">Nomor Program:</label>
<input type="text" id="edit-program-number" required>
</div>
<div class="input-group">
<label for="edit-rig-name">Nama Rig:</label>
<input type="text" id="edit-rig-name" required>
</div>
<div class="input-group">
<label for="edit-location-from">Lokasi Saat Ini:</label>
<input type="text" id="edit-location-from" required>
</div>
<div class="input-group">
<label for="edit-location-to">Lokasi Tujuan:</label>
<input type="text" id="edit-location-to" required>
</div>
<div class="input-group">
<label>Checklist Persyaratan:</label>
<div id="edit-dynamic-checklist" class="checklist-container"></div>
</div>
<div class="input-group">
<label for="edit-notes">Catatan Tambahan (opsional):</label>
<textarea id="edit-notes"></textarea>
</div>
<div class="action-buttons">
<button type="submit">Simpan Perubahan</button>
<button type="button" id="cancel-edit" class="bg-secondary">Batal</button>
</div>
</form>
</div>
</div>
<!-- Confirmation Modal -->
<div id="confirm-modal" class="modal">
<div class="modal-content" style="max-width: 400px;">
<button class="close-modal">&times;</button>
<h2 class="text-2xl font-bold mb-4">Konfirmasi</h2>
<p id="confirm-message" class="mb-6"></p>
<div class="action-buttons">
<button id="confirm-yes">Ya</button>
<button id="confirm-no" class="bg-secondary">Tidak</button>
</div>
</div>
</div>
<script>
// Tab functionality
const tabs = document.querySelectorAll('.tab-button');
const contents = document.querySelectorAll('.tab-content');
tabs.forEach(tab => {
tab.addEventListener('click', () => {
tabs.forEach(t => t.classList.remove('active'));
contents.forEach(c => c.classList.remove('active'));
tab.classList.add('active');
document.getElementById(tab.dataset.tab).classList.add('active');
if (tab.dataset.tab === 'history') loadRecords();
if (tab.dataset.tab === 'input' || tab.dataset.tab === 'manage') loadChecklistItems();
});
});
// Data storage keys
const checklistKey = 'checklistItems';
const recordsKey = 'preSpudRecords';
const suggestionsKey = 'fieldSuggestions'; // {programs: [], rigs: [], fromLocations: [], toLocations: []}
// Default checklist items
const defaultChecklist = [
"Inspeksi peralatan telah selesai",
"Protokol keselamatan telah diverifikasi",
"Pelatihan personel telah dikonfirmasi",
"Persetujuan regulasi telah diperoleh",
"Logistik dan transportasi telah diatur"
];
// Checklist management functions
function getChecklistItems() {
let items = JSON.parse(localStorage.getItem(checklistKey));
if (!items || items.length === 0) {
items = defaultChecklist;
localStorage.setItem(checklistKey, JSON.stringify(items));
}
return items;
}
function saveChecklistItems(items) {
localStorage.setItem(checklistKey, JSON.stringify(items));
}
function loadChecklistItems(forInput = true) {
const items = getChecklistItems();
const container = forInput ? document.getElementById('dynamic-checklist') : document.getElementById('manage-checklist');
container.innerHTML = '';
items.forEach((item, index) => {
const div = document.createElement('div');
div.className = 'checklist-item';
if (forInput) {
div.innerHTML = `<input type="checkbox" class="checklist-item-cb" id="cb-${index}"><label for="cb-${index}">${item}</label>`;
} else {
div.innerHTML = `<input type="text" value="${item}" data-index="${index}"><button class="remove" data-index="${index}">Hapus</button>`;
}
container.appendChild(div);
});
if (!forInput) {
// Add event listeners for manage tab
document.querySelectorAll('#manage-checklist .remove').forEach(btn => {
btn.addEventListener('click', e => {
const idx = parseInt(e.target.dataset.index);
const newItems = items.filter((_, i) => i !== idx);
saveChecklistItems(newItems);
loadChecklistItems(false);
});
});
document.querySelectorAll('#manage-checklist input[type="text"]').forEach(input => {
input.addEventListener('change', e => {
const idx = parseInt(e.target.dataset.index);
items[idx] = e.target.value.trim();
saveChecklistItems(items);
});
});
}
}
document.getElementById('add-new-item').addEventListener('click', () => {
const items = getChecklistItems();
items.push("Item checklist baru");
saveChecklistItems(items);
loadChecklistItems(false);
});
// Suggestions management
function getSuggestions() {
return JSON.parse(localStorage.getItem(suggestionsKey)) || {
programs: [],
rigs: [],
fromLocations: [],
toLocations: []
};
}
function saveSuggestions(suggestions) {
localStorage.setItem(suggestionsKey, JSON.stringify(suggestions));
}
function addSuggestion(field, value) {
if (!value.trim()) return;
const suggestions = getSuggestions();
const list = suggestions[field];
// Remove if exists
const index = list.indexOf(value.trim());
if (index > -1) list.splice(index, 1);
// Add to beginning
list.unshift(value.trim());
// Limit history to 20 items
if (list.length > 20) list.pop();
saveSuggestions(suggestions);
}
function setupSuggestions(inputId, suggestionListId, field) {
const input = document.getElementById(inputId);
const list = document.getElementById(suggestionListId);
input.addEventListener('input', () => {
const val = input.value.toLowerCase();
const suggestions = getSuggestions()[field];
list.innerHTML = '';
if (val) {
const filteredSuggestions = suggestions.filter(item =>
item.toLowerCase().includes(val)
).slice(0, 8);
if (filteredSuggestions.length > 0) {
filteredSuggestions.forEach(item => {
const li = document.createElement('li');
li.textContent = item;
li.addEventListener('click', () => {
input.value = item;
list.innerHTML = '';
});
list.appendChild(li);
});
}
}
});
input.addEventListener('blur', () => setTimeout(() => {
if (list && document.body.contains(list)) {
list.innerHTML = '';
}
}, 200));
}
// Setup suggestions for all fields
setupSuggestions('program-number', 'program-suggestions', 'programs');
setupSuggestions('rig-name', 'rig-suggestions', 'rigs');
setupSuggestions('location-from', 'from-suggestions', 'fromLocations');
setupSuggestions('location-to', 'to-suggestions', 'toLocations');
// Form submission
const form = document.getElementById('pre-spud-form');
form.addEventListener('submit', e => {
e.preventDefault();
const record = {
preSpudDate: document.getElementById('pre-spud-date').value,
recordDate: new Date().toLocaleString('id-ID'),
operationType: document.getElementById('operation-type').value,
programNumber: document.getElementById('program-number').value.trim(),
rigName: document.getElementById('rig-name').value.trim(),
fromLocation: document.getElementById('location-from').value.trim(),
toLocation: document.getElementById('location-to').value.trim(),
notes: document.getElementById('notes').value.trim(),
checklist: []
};
// Get checklist state
const checklistItems = getChecklistItems();
const checkboxes = document.querySelectorAll('.checklist-item-cb');
checklistItems.forEach((item, index) => {
record.checklist.push({
text: item,
checked: checkboxes[index]?.checked || false
});
});
// Save suggestions
addSuggestion('programs', record.programNumber);
addSuggestion('rigs', record.rigName);
addSuggestion('fromLocations', record.fromLocation);
addSuggestion('toLocations', record.toLocation);
// Save record
const records = JSON.parse(localStorage.getItem(recordsKey)) || [];
records.push(record);
localStorage.setItem(recordsKey, JSON.stringify(records));
form.reset();
alert('Checklist berhasil direkam!');
// Reload history if on history tab
if (document.querySelector('.tab-button.active').dataset.tab === 'history') {
loadRecords();
}
});
// Edit Modal Functionality
const editModal = document.getElementById('edit-modal');
const closeModalButtons = document.querySelectorAll('.close-modal');
const cancelEdit = document.getElementById('cancel-edit');
closeModalButtons.forEach(button => {
button.addEventListener('click', () => {
editModal.style.display = 'none';
confirmModal.style.display = 'none';
});
});
// Close modals when clicking outside of them
window.addEventListener('click', (e) => {
if (e.target === editModal) {
editModal.style.display = 'none';
}
if (e.target === confirmModal) {
confirmModal.style.display = 'none';
}
});
cancelEdit.addEventListener('click', () => {
editModal.style.display = 'none';
});
// Load checklist items for edit form
function loadEditChecklistItems(checklistStatus = null) {
const items = getChecklistItems();
const container = document.getElementById('edit-dynamic-checklist');
container.innerHTML = '';
items.forEach((item, index) => {
const div = document.createElement('div');
div.className = 'checklist-item';
// Check if we have status data, otherwise default to unchecked
const isChecked = checklistStatus && checklistStatus[index] ? checklistStatus[index].checked : false;
const checkedAttr = isChecked ? 'checked' : '';
div.innerHTML = `
<input type="checkbox" class="edit-checklist-item-cb" id="edit-cb-${index}" ${checkedAttr}>
<label for="edit-cb-${index}">${item}</label>
`;
container.appendChild(div);
});
}
// Edit form submission
document.getElementById('edit-form').addEventListener('submit', e => {
e.preventDefault();
const recordId = document.getElementById('edit-record-id').value;
const records = JSON.parse(localStorage.getItem(recordsKey)) || [];
const recordIndex = records.findIndex((_, index) => index.toString() === recordId);
if (recordIndex === -1) return;
// Update the record
records[recordIndex] = {
...records[recordIndex],
preSpudDate: document.getElementById('edit-pre-spud-date').value,
operationType: document.getElementById('edit-operation-type').value,
programNumber: document.getElementById('edit-program-number').value.trim(),
rigName: document.getElementById('edit-rig-name').value.trim(),
fromLocation: document.getElementById('edit-location-from').value.trim(),
toLocation: document.getElementById('edit-location-to').value.trim(),
notes: document.getElementById('edit-notes').value.trim(),
checklist: []
};
// Update checklist status
const checklistItems = getChecklistItems();
const checkboxes = document.querySelectorAll('.edit-checklist-item-cb');
checklistItems.forEach((item, index) => {
records[recordIndex].checklist.push({
text: item,
checked: checkboxes[index]?.checked || false
});
});
// Save updated records
localStorage.setItem(recordsKey, JSON.stringify(records));
editModal.style.display = 'none';
// Refresh history
if (document.querySelector('.tab-button.active').dataset.tab === 'history') {
loadRecords();
}
alert('Rekaman berhasil diperbarui!');
});
// History display and management
const confirmModal = document.getElementById('confirm-modal');
let pendingAction = null;
function loadRecords(filter = {}) {
const records = JSON.parse(localStorage.getItem(recordsKey)) || [];
const container = document.getElementById('history-container');
const noRecords = document.getElementById('no-records');
container.innerHTML = '';
let filtered = records;
// Apply filters
if (filter.dateFrom) filtered = filtered.filter(r => r.preSpudDate >= filter.dateFrom);
if (filter.dateTo) filtered = filtered.filter(r => r.preSpudDate <= filter.dateTo);
if (filter.operation) filtered = filtered.filter(r => r.operationType === filter.operation);
if (filter.search) {
const s = filter.search.toLowerCase();
filtered = filtered.filter(r =>
r.programNumber.toLowerCase().includes(s) ||
r.rigName.toLowerCase().includes(s) ||
r.fromLocation.toLowerCase().includes(s) ||
r.toLocation.toLowerCase().includes(s)
);
}
if (filtered.length === 0) {
noRecords.style.display = 'block';
return;
}
noRecords.style.display = 'none';
// Sort by preSpudDate descending
filtered.sort((a, b) => b.preSpudDate.localeCompare(a.preSpudDate));
filtered.forEach((record, index) => {
const group = document.createElement('div');
group.className = 'history-group';
// Calculate checklist status
const totalItems = record.checklist?.length || getChecklistItems().length;
const checkedItems = record.checklist?.filter(item => item.checked).length || 0;
const checklistStatus = `${checkedItems}/${totalItems}`;
const checklistPercent = totalItems > 0 ? Math.round((checkedItems / totalItems) * 100) : 0;
const checklistColor = checklistPercent === 100 ? 'text-green-600' : checklistPercent === 0 ? 'text-red-600' : 'text-yellow-600';
group.innerHTML = `
<div class="history-header">
<span>
<strong>[${record.preSpudDate}]</strong> ${record.operationType} -
Program: ${record.programNumber} -
Rig: ${record.rigName}
<br>
<small>(Dari: ${record.fromLocation} → Ke: ${record.toLocation})</small>
</span>
<div class="record-actions">
<button class="edit-record" data-id="${index}" title="Edit rekaman">
<svg class="edit-icon" viewBox="0 0 24 24">
<path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"/>
</svg>
</button>
<button class="delete-record" data-id="${index}" title="Hapus rekaman">
<svg class="delete-icon" viewBox="0 0 24 24">
<path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"/>
</svg>
</button>
</div>
</div>
<table>
<thead>
<tr>
<th>Waktu Rekam</th>
<th>Status Checklist</th>
<th>Catatan</th>
</tr>
</thead>
<tbody>
<tr>
<td>${record.recordDate}</td>
<td>
<div class="flex items-center">
<div class="w-full bg-gray-300 rounded-full h-2.5 mr-2">
<div class="bg-blue-600 h-2.5 rounded-full" style="width: ${checklistPercent}%"></div>
</div>
<span class="text-sm font-medium ${checklistColor}">${checklistStatus} selesai</span>
</div>
</td>
<td>${record.notes || '-'}</td>
</tr>
</tbody>
</table>
`;
container.appendChild(group);
});
// Add event listeners for edit and delete buttons
document.querySelectorAll('.edit-record').forEach(btn => {
btn.addEventListener('click', e => {
const recordId = e.target.closest('.edit-record').dataset.id;
openEditModal(recordId);
});
});
document.querySelectorAll('.delete-record').forEach(btn => {
btn.addEventListener('click', e => {
const recordId = e.target.closest('.delete-record').dataset.id;
confirmDelete(recordId);
});
});
}
function openEditModal(recordId) {
const records = JSON.parse(localStorage.getItem(recordsKey)) || [];
const record = records[recordId];
if (!record) return;
// Fill the form
document.getElementById('edit-record-id').value = recordId;
document.getElementById('edit-pre-spud-date').value = record.preSpudDate;
document.getElementById('edit-operation-type').value = record.operationType;
document.getElementById('edit-program-number').value = record.programNumber;
document.getElementById('edit-rig-name').value = record.rigName;
document.getElementById('edit-location-from').value = record.fromLocation;
document.getElementById('edit-location-to').value = record.toLocation;
document.getElementById('edit-notes').value = record.notes || '';
// Load checklist items with current status
loadEditChecklistItems(record.checklist);
// Show modal
editModal.style.display = 'flex';
}
function confirmDelete(recordId) {
pendingAction = {
type: 'delete',
recordId: recordId
};
document.getElementById('confirm-message').textContent = 'Apakah Anda yakin ingin menghapus rekaman ini? Tindakan ini tidak dapat dibatalkan.';
confirmModal.style.display = 'flex';
}
document.getElementById('confirm-yes').addEventListener('click', () => {
if (pendingAction.type === 'delete') {
const records = JSON.parse(localStorage.getItem(recordsKey)) || [];
records.splice(pendingAction.recordId, 1);
localStorage.setItem(recordsKey, JSON.stringify(records));
// Refresh history
loadRecords();
alert('Rekaman berhasil dihapus!');
}
confirmModal.style.display = 'none';
pendingAction = null;
});
document.getElementById('confirm-no').addEventListener('click', () => {
confirmModal.style.display = 'none';
pendingAction = null;
});
// Filter functionality
document.getElementById('apply-filter').addEventListener('click', () => {
const filter = {
dateFrom: document.getElementById('filter-date-from').value,
dateTo: document.getElementById('filter-date-to').value,
operation: document.getElementById('filter-operation').value,
search: document.getElementById('filter-location').value.trim()
};
loadRecords(filter);
});
document.getElementById('clear-filter').addEventListener('click', () => {
document.getElementById('filter-date-from').value = '';
document.getElementById('filter-date-to').value = '';
document.getElementById('filter-operation').value = '';
document.getElementById('filter-location').value = '';
loadRecords();
});
// Export/Import functionality
document.getElementById('export-data').addEventListener('click', () => {
// Create workbook
const wb = XLSX.utils.book_new();
// Create worksheet from records
const records = JSON.parse(localStorage.getItem(recordsKey)) || [];
if (records.length === 0) {
alert('Tidak ada data untuk diekspor.');
return;
}
// Transform records to flat data structure for spreadsheet
const exportData = records.map(record => {
const checklistStatus = record.checklist?.map(item =>
`${item.text}: ${item.checked ? 'Completed' : 'Pending'}`
).join('\n') || '';
return {
'Tanggal Pre-Spud': record.preSpudDate,
'Waktu Rekam': record.recordDate,
'Jenis Operasi': record.operationType,
'Nomor Program': record.programNumber,
'Nama Rig': record.rigName,
'Lokasi Saat Ini': record.fromLocation,
'Lokasi Tujuan': record.toLocation,
'Status Checklist': checklistStatus,
'Catatan': record.notes || ''
};
});
const ws = XLSX.utils.json_to_sheet(exportData);
// Add formatting
ws['!cols'] = [
{wch: 12}, // Tanggal Pre-Spud
{wch: 15}, // Waktu Rekam
{wch: 15}, // Jenis Operasi
{wch: 15}, // Nomor Program
{wch: 15}, // Nama Rig
{wch: 20}, // Lokasi Saat Ini
{wch: 20}, // Lokasi Tujuan
{wch: 40}, // Status Checklist
{wch: 30} // Catatan
];
// Add style to header
const headerRange = XLSX.utils.decode_range(ws['!ref']);
for (let C = headerRange.s.c; C <= headerRange.e.c; ++C) {
const headerCell = XLSX.utils.encode_cell({c: C, r: 0});
if (!ws[headerCell]) ws[headerCell] = {t: 's'};
ws[headerCell].s = {
font: {bold: true, color: {rgb: "FFFFFF"}},
fill: {fgColor: {rgb: "4F81BD"}},
alignment: {horizontal: "center"}
};
}
// Add worksheet to workbook
XLSX.utils.book_append_sheet(wb, ws, "PreSpudRecords");
// Create checklist items sheet
const checklistItems = getChecklistItems();
const checklistData = checklistItems.map(item => ({'Item Checklist': item}));
const checklistWs = XLSX.utils.json_to_sheet(checklistData);
// Add checklist worksheet to workbook
XLSX.utils.book_append_sheet(wb, checklistWs, "ChecklistItems");
// Export file
XLSX.writeFile(wb, `pre-spud-checklist-${new Date().toISOString().slice(0, 10)}.xlsx`);
});
document.getElementById('import-file').addEventListener('change', (e) => {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (e) => {
try {
const data = new Uint8Array(e.target.result);
const workbook = XLSX.read(data, {type: 'array'});
// Check if required sheets exist
if (!workbook.Sheets['PreSpudRecords']) {
alert('File tidak valid. Sheet "PreSpudRecords" tidak ditemukan.');
return;
}
// Parse the records sheet
const records = XLSX.utils.sheet_to_json(workbook.Sheets['PreSpudRecords']);
if (records.length === 0) {
alert('Tidak ada data ditemukan dalam file.');
return;
}
// Transform data back to our format
const importedRecords = records.map(record => {
// Parse checklist status
const checklistText = getChecklistItems();
const checklistStatus = record['Status Checklist'] || '';
const checklist = checklistText.map(itemText => {
// Check if the item is in the status text as completed
const isCompleted = checklistStatus.includes(`${itemText}: Completed`);
return {
text: itemText,
checked: isCompleted
};
});
return {
preSpudDate: record['Tanggal Pre-Spud'],
recordDate: record['Waktu Rekam'] || new Date().toLocaleString('id-ID'),
operationType: record['Jenis Operasi'],
programNumber: record['Nomor Program'],
rigName: record['Nama Rig'],
fromLocation: record['Lokasi Saat Ini'],
toLocation: record['Lokasi Tujuan'],
notes: record['Catatan'],
checklist: checklist
};
});
// If there's a ChecklistItems sheet, import that too
if (workbook.Sheets['ChecklistItems']) {
const checklistItemsSheet = XLSX.utils.sheet_to_json(workbook.Sheets['ChecklistItems']);
const importedChecklistItems = checklistItemsSheet.map(item => item['Item Checklist']);
if (importedChecklistItems.length > 0) {
saveChecklistItems(importedChecklistItems);
}
}
// Confirm with user before replacing data
if (confirm(`Impor data selesai. Apakah Anda ingin menggantikan semua data yang ada dengan ${importedRecords.length} rekaman baru?`)) {
localStorage.setItem(recordsKey, JSON.stringify(importedRecords));
// Refresh all views
loadChecklistItems(true);
loadChecklistItems(false);
if (document.querySelector('.tab-button.active').dataset.tab === 'history') {
loadRecords();
}
alert(`Data berhasil diimpor! ${importedRecords.length} rekaman telah dimuat.`);
}
// Reset file input
e.target.value = '';
} catch (error) {
console.error('Error importing data:', error);
alert('Terjadi kesalahan saat mengimpor file. Pastikan format file benar.');
e.target.value = '';
}
};
reader.readAsArrayBuffer(file);
});
// Initial loads
loadChecklistItems(true);
loadChecklistItems(false);
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-qwensite.hf.space/logo.svg" alt="qwensite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-qwensite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >QwenSite</a> - 🧬 <a href="https://enzostvs-qwensite.hf.space?remix=alterzick/prespud-v3" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>