rig-ops-procedure-v2 / index.html
alterzick's picture
Add 2 files
543dd2f verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Rig Operation Procedure Builder - LEGO Style</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/docx/7.2.0/docx.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Poppins', sans-serif;
}
.procedure-block {
@apply bg-white border-2 border-dashed border-gray-300 rounded-lg shadow-sm p-4 mb-4 cursor-move transition-all hover:shadow-md;
}
.procedure-block.dragging {
@apply opacity-50 border-blue-400;
}
.upload-area {
@apply border-2 border-dashed border-gray-300 rounded-lg p-6 text-center cursor-pointer hover:bg-gray-50 transition-all relative overflow-hidden;
}
.upload-area input[type="file"] {
@apply absolute inset-0 w-full h-full opacity-0 cursor-pointer;
}
.drag-over {
@apply bg-blue-50 border-blue-400;
}
.step-number {
@apply bg-blue-600 text-white w-8 h-8 rounded-full flex items-center justify-center text-sm font-semibold;
}
.flex-between-center {
@apply flex items-center justify-between;
}
</style>
</head>
<body class="bg-gradient-to-br from-slate-50 to-slate-100 min-h-screen">
<div class="container mx-auto px-4 py-8 max-w-6xl">
<!-- Header -->
<header class="text-center mb-8">
<h1 class="text-4xl font-bold text-gray-800 mb-2">🧱 Rig Operation Procedure Builder</h1>
<p class="text-lg text-gray-600">Build your procedures like LEGO blocks. Drag, edit, and publish!</p>
</header>
<!-- Main Toolbar -->
<div class="bg-white rounded-lg shadow-lg p-6 mb-6">
<div class="flex-between-center mb-4">
<h2 class="text-xl font-semibold text-gray-700">🛠️ Procedure Toolbox</h2>
<div class="flex space-x-3">
<button id="saveBtn" class="bg-green-600 hover:bg-green-700 text-white px-5 py-2 rounded-lg text-sm font-medium flex items-center gap-1 transition">
<i class="fas fa-save"></i> Save
</button>
<button id="printBtn" class="bg-blue-600 hover:bg-blue-700 text-white px-5 py-2 rounded-lg text-sm font-medium flex items-center gap-1 transition">
<i class="fas fa-print"></i> Print
</button>
<button id="downloadWordBtn" class="bg-purple-600 hover:bg-purple-700 text-white px-5 py-2 rounded-lg text-sm font-medium flex items-center gap-1 transition">
<i class="fas fa-file-word"></i> Download Word
</button>
<button id="downloadRTFBtn" class="bg-teal-600 hover:bg-teal-700 text-white px-5 py-2 rounded-lg text-sm font-medium flex items-center gap-1 transition">
<i class="fas fa-file-alt"></i> Download RTF
</button>
<button id="approvalBtn" class="bg-orange-500 hover:bg-orange-600 text-white px-5 py-2 rounded-lg text-sm font-medium flex items-center gap-1 transition">
<i class="fas fa-check-circle"></i> Submit for Approval
</button>
</div>
</div>
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-4">
<div class="toolbox-item p-4 bg-blue-50 border border-blue-200 rounded-lg cursor-pointer hover:bg-blue-100 transition text-center" data-type="image">
<i class="fas fa-image text-blue-600 text-2xl mb-2"></i>
<p class="text-sm font-medium text-blue-800">Upload Image</p>
</div>
<div class="toolbox-item p-4 bg-green-50 border border-green-200 rounded-lg cursor-pointer hover:bg-green-100 transition text-center" data-type="time">
<i class="fas fa-clock text-green-600 text-2xl mb-2"></i>
<p class="text-sm font-medium text-green-800">Time Estimate</p>
</div>
<div class="toolbox-item p-4 bg-red-50 border border-red-200 rounded-lg cursor-pointer hover:bg-red-100 transition text-center" data-type="material">
<i class="fas fa-tools text-red-600 text-2xl mb-2"></i>
<p class="text-sm font-medium text-red-800">Materials</p>
</div>
<div class="toolbox-item p-4 bg-yellow-50 border border-yellow-200 rounded-lg cursor-pointer hover:bg-yellow-100 transition text-center" data-type="safety">
<i class="fas fa-shield-alt text-yellow-600 text-2xl mb-2"></i>
<p class="text-sm font-medium text-yellow-800">Safety Concerns</p>
</div>
<div class="toolbox-item p-4 bg-indigo-50 border border-indigo-200 rounded-lg cursor-pointer hover:bg-indigo-100 transition text-center" data-type="personnel">
<i class="fas fa-users text-indigo-600 text-2xl mb-2"></i>
<p class="text-sm font-medium text-indigo-800">Personnel</p>
</div>
<div class="toolbox-item p-4 bg-gray-50 border border-gray-200 rounded-lg cursor-pointer hover:bg-gray-100 transition text-center" data-type="note">
<i class="fas fa-sticky-note text-gray-600 text-2xl mb-2"></i>
<p class="text-sm font-medium text-gray-800">Notes</p>
</div>
<div class="toolbox-item p-4 bg-pink-50 border border-pink-200 rounded-lg cursor-pointer hover:bg-pink-100 transition text-center" data-type="warning">
<i class="fas fa-exclamation-triangle text-pink-600 text-2xl mb-2"></i>
<p class="text-sm font-medium text-pink-800">Warning</p>
</div>
<div class="toolbox-item p-4 bg-teal-50 border border-teal-200 rounded-lg cursor-pointer hover:bg-teal-100 transition text-center" data-type="checklist">
<i class="fas fa-check-square text-teal-600 text-2xl mb-2"></i>
<p class="text-sm font-medium text-teal-800">Checklist</p>
</div>
<div class="toolbox-item p-4 bg-purple-50 border border-purple-200 rounded-lg cursor-pointer hover:bg-purple-100 transition text-center" data-type="reference">
<i class="fas fa-book text-purple-600 text-2xl mb-2"></i>
<p class="text-sm font-medium text-purple-800">Reference Doc</p>
</div>
<div class="toolbox-item p-4 bg-gray-50 border border-gray-300 rounded-lg cursor-pointer hover:bg-gray-100 transition text-center" data-type="step">
<i class="fas fa-list-ol text-gray-600 text-2xl mb-2"></i>
<p class="text-sm font-medium text-gray-800">Text Step</p>
</div>
</div>
</div>
<!-- Procedure Builder Area -->
<div id="procedureBuilder" class="bg-white rounded-lg shadow-lg p-6 min-h-96 mb-6">
<div class="text-center text-gray-500 mb-4" id="placeholder">
<i class="fas fa-plus-circle text-3xl mb-2 text-gray-400"></i>
<p>Drag item dari toolbox atau klik untuk menambahkan blok prosedur</p>
</div>
</div>
<!-- Database & Approval Panel -->
<div class="bg-white rounded-lg shadow-lg p-6">
<h2 class="text-xl font-semibold text-gray-700 mb-4">📁 Database & Approval Status</h2>
<div class="overflow-x-auto">
<table class="min-w-full text-sm">
<thead>
<tr class="border-b">
<th class="text-left py-2">Procedure ID</th>
<th class="text-left py-2">Title</th>
<th class="text-left py-2">Last Edited</th>
<th class="text-left py-2">Status</th>
<th class="text-left py-2">Actions</th>
</tr>
</thead>
<tbody id="procedureTableBody">
<!-- Placeholder row -->
<tr class="border-b">
<td class="py-2">PRC-001</td>
<td>Main Drilling Sequence</td>
<td>2023-10-15 14:30</td>
<td><span class="bg-yellow-100 text-yellow-800 px-2 py-1 rounded text-xs">Pending</span></td>
<td>
<button class="text-blue-600 text-sm hover:underline">Edit</button> |
<button class="text-gray-600 text-sm hover:underline">View</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- Template Blocks (Hidden) -->
<div class="hidden">
<!-- Image Block -->
<div id="template-image" class="procedure-block">
<div class="flex-between-center mb-3">
<div class="step-number">?</div>
<div class="text-sm text-gray-500">Image Upload</div>
<div class="flex gap-1">
<button class="edit-block text-blue-600 hover:text-blue-800 text-xs"><i class="fas fa-edit"></i></button>
<button class="remove-block text-red-600 hover:text-red-800 text-xs"><i class="fas fa-trash"></i></button>
</div>
</div>
<div class="upload-area" data-type="image">
<i class="fas fa-cloud-upload-alt text-gray-400 text-3xl mb-2"></i>
<p class="text-gray-500 text-sm">Klik atau seret gambar ke sini</p>
<input type="file" accept="image/*" class="upload-input">
<img class="hidden mt-2 max-h-48 rounded border" />
</div>
</div>
<!-- Time Block -->
<div id="template-time" class="procedure-block">
<div class="flex-between-center mb-3">
<div class="step-number">?</div>
<div class="text-sm text-gray-500">Time Estimate</div>
<div class="flex gap-1">
<button class="edit-block text-blue-600 hover:text-blue-800 text-xs"><i class="fas fa-edit"></i></button>
<button class="remove-block text-red-600 hover:text-red-800 text-xs"><i class="fas fa-trash"></i></button>
</div>
</div>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-xs font-semibold text-gray-700 mb-1">Start Time:</label>
<input type="time" class="w-full border border-gray-300 rounded px-2 py-1 text-sm" value="08:00">
</div>
<div>
<label class="block text-xs font-semibold text-gray-700 mb-1">End Time:</label>
<input type="time" class="w-full border border-gray-300 rounded px-2 py-1 text-sm" value="10:30">
</div>
</div>
<div class="mt-2">
<label class="block text-xs font-semibold text-gray-700 mb-1">Total Duration:</label>
<input type="text" class="w-full border border-gray-300 rounded px-2 py-1 text-sm bg-gray-100" value="2h 30m" readonly>
</div>
</div>
<!-- Material Block -->
<div id="template-material" class="procedure-block">
<div class="flex-between-center mb-3">
<div class="step-number">?</div>
<div class="text-sm text-gray-500">Required Materials</div>
<div class="flex gap-1">
<button class="edit-block text-blue-600 hover:text-blue-800 text-xs"><i class="fas fa-edit"></i></button>
<button class="remove-block text-red-600 hover:text-red-800 text-xs"><i class="fas fa-trash"></i></button>
</div>
</div>
<div class="material-list space-y-2 mb-2"></div>
<button class="add-material bg-gray-200 hover:bg-gray-300 text-gray-700 px-3 py-1 rounded text-xs flex items-center gap-1">
<i class="fas fa-plus text-xs"></i> Add Material
</button>
</div>
<!-- Safety Block -->
<div id="template-safety" class="procedure-block">
<div class="flex-between-center mb-3">
<div class="step-number">?</div>
<div class="text-sm text-gray-500">Safety Concerns</div>
<div class="flex gap-1">
<button class="edit-block text-blue-600 hover:text-blue-800 text-xs"><i class="fas fa-edit"></i></button>
<button class="remove-block text-red-600 hover:text-red-800 text-xs"><i class="fas fa-trash"></i></button>
</div>
</div>
<textarea class="w-full border border-red-200 rounded px-3 py-2 text-sm bg-red-50 resize-none focus:ring-2 focus:ring-red-300"
placeholder="Describe safety concerns, PPE requirements, emergency procedures..."></textarea>
</div>
<!-- Personnel Block -->
<div id="template-personnel" class="procedure-block">
<div class="flex-between-center mb-3">
<div class="step-number">?</div>
<div class="text-sm text-gray-500">Required Personnel</div>
<div class="flex gap-1">
<button class="edit-block text-blue-600 hover:text-blue-800 text-xs"><i class="fas fa-edit"></i></button>
<button class="remove-block text-red-600 hover:text-red-800 text-xs"><i class="fas fa-trash"></i></button>
</div>
</div>
<div class="personnel-list space-y-2 mb-2"></div>
<button class="add-personnel bg-gray-200 hover:bg-gray-300 text-gray-700 px-3 py-1 rounded text-xs flex items-center gap-1">
<i class="fas fa-plus text-xs"></i> Add Personnel
</button>
</div>
<!-- Note Block -->
<div id="template-note" class="procedure-block">
<div class="flex-between-center mb-3">
<div class="step-number">?</div>
<div class="text-sm text-gray-500">Additional Notes</div>
<div class="flex gap-1">
<button class="edit-block text-blue-600 hover:text-blue-800 text-xs"><i class="fas fa-edit"></i></button>
<button class="remove-block text-red-600 hover:text-red-800 text-xs"><i class="fas fa-trash"></i></button>
</div>
</div>
<textarea class="w-full border border-gray-200 rounded px-3 py-2 text-sm resize-none focus:ring-2 focus:ring-gray-300"
placeholder="Enter additional information or instructions..."></textarea>
</div>
<!-- Warning Block -->
<div id="template-warning" class="procedure-block border-l-4 border-l-orange-500">
<div class="flex-between-center mb-3">
<div class="step-number">?</div>
<div class="text-sm text-gray-500">⚠️ Critical Warning</div>
<div class="flex gap-1">
<button class="edit-block text-blue-600 hover:text-blue-800 text-xs"><i class="fas fa-edit"></i></button>
<button class="remove-block text-red-600 hover:text-red-800 text-xs"><i class="fas fa-trash"></i></button>
</div>
</div>
<textarea class="w-full border border-orange-200 rounded px-3 py-2 text-sm bg-orange-50 resize-none focus:ring-2 focus:ring-orange-300 font-medium"
placeholder="Enter critical warning message..."></textarea>
</div>
<!-- Checklist Block -->
<div id="template-checklist" class="procedure-block">
<div class="flex-between-center mb-3">
<div class="step-number">?</div>
<div class="text-sm text-gray-500">Checklist</div>
<div class="flex gap-1">
<button class="edit-block text-blue-600 hover:text-blue-800 text-xs"><i class="fas fa-edit"></i></button>
<button class="remove-block text-red-600 hover:text-red-800 text-xs"><i class="fas fa-trash"></i></button>
</div>
</div>
<div class="checklist-items space-y-2 mb-2"></div>
<button class="add-checklist bg-gray-200 hover:bg-gray-300 text-gray-700 px-3 py-1 rounded text-xs flex items-center gap-1">
<i class="fas fa-plus text-xs"></i> Add Item
</button>
</div>
<!-- Reference Block -->
<div id="template-reference" class="procedure-block">
<div class="flex-between-center mb-3">
<div class="step-number">?</div>
<div class="text-sm text-gray-500">Reference Document</div>
<div class="flex gap-1">
<button class="edit-block text-blue-600 hover:text-blue-800 text-xs"><i class="fas fa-edit"></i></button>
<button class="remove-block text-red-600 hover:text-red-800 text-xs"><i class="fas fa-trash"></i></button>
</div>
</div>
<div class="upload-area" data-type="reference">
<i class="fas fa-file-pdf text-gray-400 text-3xl mb-2"></i>
<p class="text-gray-500 text-sm">Klik atau seret dokumen referensi</p>
<input type="file" class="upload-input">
</div>
</div>
<!-- Text Step Block -->
<div id="template-step" class="procedure-block">
<div class="flex-between-center mb-3">
<div class="step-number">?</div>
<div class="text-sm text-gray-500">Procedure Step</div>
<div class="flex gap-1">
<button class="edit-block text-blue-600 hover:text-blue-800 text-xs"><i class="fas fa-edit"></i></button>
<button class="remove-block text-red-600 hover:text-red-800 text-xs"><i class="fas fa-trash"></i></button>
</div>
</div>
<textarea class="w-full border border-gray-200 rounded px-3 py-2 text-sm focus:ring-2 focus:ring-blue-300"
placeholder="Describe the operation procedure step by step..."></textarea>
</div>
</div>
<script>
// Global state
let procedureCounter = 1;
let blocks = [];
let selectedProcedure = null;
// Elements
const builder = document.getElementById('procedureBuilder');
const placeholder = document.getElementById('placeholder');
const toolboxItems = document.querySelectorAll('.toolbox-item');
const saveBtn = document.getElementById('saveBtn');
const printBtn = document.getElementById('printBtn');
const downloadWordBtn = document.getElementById('downloadWordBtn');
const downloadRTFBtn = document.getElementById('downloadRTFBtn');
const approvalBtn = document.getElementById('approvalBtn');
// Initialize
function init() {
setupToolbox();
setupBuilder();
setupEvents();
loadProcedures();
}
// Setup toolbox drag and click
function setupToolbox() {
toolboxItems.forEach(item => {
const type = item.dataset.type;
// Click to add
item.addEventListener('click', () => {
addBlock(type);
});
// Drag start
item.addEventListener('dragstart', (e) => {
e.dataTransfer.setData('text/plain', type);
item.style.opacity = '0.5';
});
item.addEventListener('dragend', () => {
item.style.opacity = '1';
});
});
}
// Setup builder area
function setupBuilder() {
// Allow drop
builder.addEventListener('dragover', (e) => {
e.preventDefault();
const uploadAreas = builder.querySelectorAll('.upload-area');
uploadAreas.forEach(area => {
if (area.contains(e.target) || area === e.target) {
area.classList.add('drag-over');
}
});
});
builder.addEventListener('dragleave', (e) => {
const uploadAreas = builder.querySelectorAll('.upload-area');
uploadAreas.forEach(area => {
if (area.contains(e.target) || area === e.target) {
area.classList.remove('drag-over');
}
});
});
builder.addEventListener('drop', (e) => {
e.preventDefault();
const uploadAreas = builder.querySelectorAll('.upload-area');
uploadAreas.forEach(area => {
area.classList.remove('drag-over');
});
const type = e.dataTransfer.getData('text/plain');
if (type) {
addBlock(type);
}
});
// Click on builder to add
builder.addEventListener('click', (e) => {
if (e.target === builder || e.target === placeholder) {
if (blocks.length === 0) {
addBlock('step');
}
}
});
}
// Add block to builder
function addBlock(type) {
const template = document.getElementById(`template-${type}`).cloneNode(true);
const id = `block-${Date.now()}`;
template.id = id;
template.style.display = 'block';
const stepNumber = template.querySelector('.step-number');
stepNumber.textContent = procedureCounter++;
if (type === 'material') {
setupMaterialBlock(template);
} else if (type === 'personnel') {
setupPersonnelBlock(template);
} else if (type === 'checklist') {
setupChecklistBlock(template);
} else if (type === 'image' || type === 'reference') {
setupUploadArea(template);
}
template.querySelector('.remove-block').addEventListener('click', () => {
if (confirm('Are you sure you want to remove this block?')) {
template.remove();
updateStepNumbers();
}
});
template.querySelector('.edit-block').addEventListener('click', () => {
alert('Edit mode. Modify content directly.');
});
template.setAttribute('draggable', true);
template.addEventListener('dragstart', (e) => {
e.dataTransfer.setData('text/plain', 'move');
e.dataTransfer.setDragImage(template, 0, 0);
template.classList.add('dragging');
});
template.addEventListener('dragend', () => {
template.classList.remove('dragging');
});
builder.appendChild(template);
placeholder.style.display = 'none';
blocks.push({ id, type, element: template });
}
function setupMaterialBlock(block) {
const list = block.querySelector('.material-list');
const addButton = block.querySelector('.add-material');
function addMaterialItem() {
const item = document.createElement('div');
item.className = 'flex gap-2 items-center';
item.innerHTML = `
<input type="text" class="flex-1 border border-gray-300 rounded px-2 py-1 text-sm" placeholder="Material name">
<input type="number" class="w-20 border border-gray-300 rounded px-2 py-1 text-sm" placeholder="Qty" value="1">
<select class="border border-gray-300 rounded px-2 py-1 text-sm">
<option>pcs</option>
<option>kg</option>
<option>liter</option>
<option>unit</option>
</select>
<button class="remove-material text-red-600 hover:text-red-800 text-xs"><i class="fas fa-times"></i></button>
`;
list.appendChild(item);
item.querySelector('.remove-material').addEventListener('click', () => {
item.remove();
});
}
addButton.addEventListener('click', addMaterialItem);
addMaterialItem();
}
function setupPersonnelBlock(block) {
const list = block.querySelector('.personnel-list');
const addButton = block.querySelector('.add-personnel');
function addPersonnelItem() {
const item = document.createElement('div');
item.className = 'flex gap-2 items-center';
item.innerHTML = `
<input type="text" class="flex-1 border border-gray-300 rounded px-2 py-1 text-sm" placeholder="Name">
<select class="w-32 border border-gray-300 rounded px-2 py-1 text-sm">
<option>Driller</option>
<option>Engineer</option>
<option>Supervisor</option>
<option>Assistant</option>
<option>Inspector</option>
</select>
<button class="remove-personnel text-red-600 hover:text-red-800 text-xs"><i class="fas fa-times"></i></button>
`;
list.appendChild(item);
item.querySelector('.remove-personnel').addEventListener('click', () => {
item.remove();
});
}
addButton.addEventListener('click', addPersonnelItem);
addPersonnelItem();
}
function setupChecklistBlock(block) {
const list = block.querySelector('.checklist-items');
const addButton = block.querySelector('.add-checklist');
function addChecklistItem() {
const item = document.createElement('div');
item.className = 'flex items-center gap-2';
item.innerHTML = `
<input type="checkbox" class="w-4 h-4">
<input type="text" class="flex-1 border border-gray-300 rounded px-2 py-1 text-sm" placeholder="Task to check">
<button class="remove-checklist text-red-600 hover:text-red-800 text-xs"><i class="fas fa-times"></i></button>
`;
list.appendChild(item);
item.querySelector('.remove-checklist').addEventListener('click', () => {
item.remove();
});
}
addButton.addEventListener('click', addChecklistItem);
addChecklistItem();
}
function setupUploadArea(block) {
const area = block.querySelector('.upload-area');
const input = area.querySelector('input[type="file"]');
const img = area.querySelector('img');
area.addEventListener('click', () => {
input.click();
});
input.addEventListener('change', (e) => {
const file = e.target.files[0];
if (file && img) {
const reader = new FileReader();
reader.onload = (e) => {
img.src = e.target.result;
img.classList.remove('hidden');
};
reader.readAsDataURL(file);
}
});
area.addEventListener('drop', (e) => {
e.preventDefault();
const file = e.dataTransfer.files[0];
if (file) {
input.files = e.dataTransfer.files;
const reader = new FileReader();
reader.onload = (e) => {
if (img) {
img.src = e.target.result;
img.classList.remove('hidden');
}
};
reader.readAsDataURL(file);
}
});
}
function updateStepNumbers() {
procedureCounter = 1;
document.querySelectorAll('.procedure-block').forEach(block => {
const num = block.querySelector('.step-number');
if (num) {
num.textContent = procedureCounter++;
}
});
}
function setupEvents() {
builder.addEventListener('dragover', (e) => {
e.preventDefault();
const afterElement = getDragAfterElement(builder, e.clientY);
const draggable = document.querySelector('.dragging');
if (afterElement == null) {
builder.appendChild(draggable);
} else {
builder.insertBefore(draggable, afterElement);
}
});
saveBtn.addEventListener('click', () => {
const title = prompt('Enter procedure title:', 'New Procedure') || 'Untitled';
const id = 'PRC-' + Date.now().toString().substr(-6).toUpperCase();
const now = new Date().toISOString().replace('T', ' ').substr(0, 16);
const procedure = {
id,
title,
lastEdited: now,
status: 'Draft',
blocks: Array.from(document.querySelectorAll('.procedure-block')).map(block => {
return {
id: block.id,
type: block.querySelector('.toolbox-item')?.dataset.type || 'unknown',
html: block.outerHTML
};
})
};
let procedures = JSON.parse(localStorage.getItem('rigProcedures') || '[]');
procedures.push(procedure);
localStorage.setItem('rigProcedures', JSON.stringify(procedures));
alert(`Procedure "${title}" saved successfully!`);
loadProcedures();
});
printBtn.addEventListener('click', () => {
window.print();
});
// Download as DOCX
downloadWordBtn.addEventListener('click', () => {
const { Document, Paragraph, TextRun, ImageRun, Packer } = docx;
const blocks = document.querySelectorAll('.procedure-block');
const elements = [];
blocks.forEach(block => {
const type = block.querySelector('.text-sm').textContent.trim();
// Extract text content
let content = '';
const textarea = block.querySelector('textarea');
const inputs = block.querySelectorAll('input[type="text"], input[type="number"], select');
const timeInputs = block.querySelectorAll('input[type="time"]');
const checklist = block.querySelectorAll('.checklist-items input[type="checkbox"]');
const img = block.querySelector('img');
// Add step title
elements.push(new Paragraph({
children: [
new TextRun({
text: `Step ${block.querySelector('.step-number').textContent}: ${type}`,
bold: true,
size: 28
})
],
spacing: { after: 100 }
}));
if (textarea && textarea.value) {
elements.push(new Paragraph({
children: [new TextRun(textarea.value)],
spacing: { after: 100 }
}));
}
if (inputs.length > 0) {
const tableRows = Array.from(inputs).reduce((rows, input, i, arr) => {
if (i % 3 === 0) rows.push([]);
const value = input.value || input.options?.[input.selectedIndex]?.text || '';
rows[rows.length - 1].push(value);
return rows;
}, []).map(row => {
return new docx.TableRow({
children: row.map(cell => {
return new docx.TableCell({
children: [new Paragraph(cell)],
width: { size: 33, type: docx.WidthType.PERCENTAGE }
});
})
});
});
if (tableRows.length > 0) {
elements.push(new docx.Table({
rows: tableRows,
width: { size: 100, type: docx.WidthType.PERCENTAGE }
}));
}
}
if (timeInputs.length > 1) {
const start = timeInputs[0].value;
const end = timeInputs[1].value;
elements.push(new Paragraph({
children: [new TextRun(`Duration: ${start} - ${end}`)],
spacing: { after: 100 }
}));
}
if (checklist.length > 0) {
Array.from(block.querySelectorAll('.checklist-items > div')).forEach(itemDiv => {
const checkbox = itemDiv.querySelector('input[type="checkbox"]');
const label = itemDiv.querySelector('input[type="text"]');
elements.push(new Paragraph({
children: [
new TextRun({
text: `[${checkbox.checked ? 'x' : ' '}] ${label.value}`,
size: 24
})
]
}));
});
}
if (img && !img.classList.contains('hidden')) {
const imgData = img.src;
elements.push(new Paragraph({
children: [
new ImageRun({
data: atob(imgData.split(',')[1]),
transformation: {
width: 300,
height: 200
}
})
],
spacing: { after: 100 }
}));
}
elements.push(new Paragraph({ spacing: { after: 100 } })); // spacer
});
const doc = new Document({
sections: [{
properties: {},
children: elements
}]
});
Packer.toBlob(doc).then(blob => {
saveAs(blob, 'Rig_Operation_Procedure.docx');
});
});
// Download as RTF (simplified RTF string)
downloadRTFBtn.addEventListener('click', () => {
let rtfContent = '{\\rtf1\\ansi\\deff0{\\fonttbl{\\f0 Times New Roman;}}\n';
rtfContent += '\\viewkind4\\uc1\\pard\\f0\\fs24\\b Rig Operation Procedure\\b0\\par\n';
rtfContent += '\\par\n';
document.querySelectorAll('.procedure-block').forEach(block => {
const stepNum = block.querySelector('.step-number').textContent;
const type = block.querySelector('.text-sm').textContent.trim();
rtfContent += `\\b Step ${stepNum}: ${type}\\b0\\par\n`;
const textarea = block.querySelector('textarea');
if (textarea && textarea.value) {
rtfContent += textarea.value.replace(/\n/g, '\\line ') + '\\par\n';
}
const timeInputs = block.querySelectorAll('input[type="time"]');
if (timeInputs.length === 2) {
const start = timeInputs[0].value;
const end = timeInputs[1].value;
rtfContent += `Duration: ${start} - ${end}\\par\n`;
}
block.querySelectorAll('.material-list > div').forEach(item => {
const name = item.querySelector('input[type="text"]')?.value || '';
const qty = item.querySelector('input[type="number"]')?.value || '';
const unit = item.querySelector('select')?.options?.[item.querySelector('select')?.selectedIndex]?.text || '';
if (name) rtfContent += `\\bullet\\t${qty} ${unit} of ${name}\\par\n`;
});
block.querySelectorAll('.personnel-list > div').forEach(item => {
const name = item.querySelector('input[type="text"]')?.value || '';
const role = item.querySelector('select')?.options?.[item.querySelector('select')?.selectedIndex]?.text || '';
if (name) rtfContent += `\\bullet\\t${name} (${role})\\par\n`;
});
block.querySelectorAll('.checklist-items > div').forEach(item => {
const label = item.querySelector('input[type="text"]')?.value || '';
const checked = item.querySelector('input[type="checkbox"]')?.checked ? 'x' : ' ';
rtfContent += `\\bullet\\t[${checked}] ${label}\\par\n`;
});
rtfContent += '\\par\n';
});
rtfContent += '}';
const blob = new Blob([rtfContent], { type: 'application/rtf' });
saveAs(blob, 'Rig_Operation_Procedure.rtf');
});
approvalBtn.addEventListener('click', () => {
if (blocks.length === 0) {
alert('Please add at least one block before submitting.');
return;
}
if (confirm('Submit this procedure for approval? It will be reviewed by the safety team.')) {
const procedures = JSON.parse(localStorage.getItem('rigProcedures') || '[]');
if (procedures.length > 0) {
procedures[procedures.length - 1].status = 'Pending';
localStorage.setItem('rigProcedures', JSON.stringify(procedures));
alert('Procedure submitted for approval!');
loadProcedures();
}
}
});
}
function getDragAfterElement(container, y) {
const draggableElements = [...container.querySelectorAll('.procedure-block:not(.dragging)')];
return draggableElements.reduce((closest, child) => {
const box = child.getBoundingClientRect();
const offset = y - box.top - box.height / 2;
if (offset < 0 && offset > closest.offset) {
return { offset: offset, element: child };
} else {
return closest;
}
}, { offset: Number.NEGATIVE_INFINITY }).element;
}
function loadProcedures() {
const tableBody = document.getElementById('procedureTableBody');
tableBody.innerHTML = '';
const procedures = JSON.parse(localStorage.getItem('rigProcedures') || '[]');
procedures.forEach(proc => {
const tr = document.createElement('tr');
tr.className = 'border-b';
tr.innerHTML = `
<td class="py-2">${proc.id}</td>
<td>${proc.title}</td>
<td>${proc.lastEdited}</td>
<td><span class="px-2 py-1 rounded text-xs ${
proc.status === 'Approved' ? 'bg-green-100 text-green-800' :
proc.status === 'Rejected' ? 'bg-red-100 text-red-800' :
'bg-yellow-100 text-yellow-800'
}">${proc.status}</span></td>
<td>
<button class="text-blue-600 text-sm hover:underline edit-proc" data-id="${proc.id}">Edit</button> |
<button class="text-gray-600 text-sm hover:underline view-proc" data-id="${proc.id}">View</button>
</td>
`;
tableBody.appendChild(tr);
});
document.querySelectorAll('.edit-proc').forEach(btn => {
btn.addEventListener('click', (e) => {
const id = e.target.dataset.id;
loadProcedure(id);
});
});
document.querySelectorAll('.view-proc').forEach(btn => {
btn.addEventListener('click', (e) => {
const id = e.target.dataset.id;
loadProcedure(id, true);
});
});
}
function loadProcedure(id, readOnly = false) {
const procedures = JSON.parse(localStorage.getItem('rigProcedures') || '[]');
const procedure = procedures.find(p => p.id === id);
if (!procedure) return;
builder.innerHTML = '';
blocks = [];
procedureCounter = 1;
procedure.blocks.forEach(blockObj => {
const div = document.createElement('div');
div.innerHTML = blockObj.html;
const block = div.firstElementChild;
block.style.display = 'block';
if (readOnly) {
block.querySelectorAll('input, textarea, button').forEach(el => {
if (el.type !== 'checkbox') {
el.disabled = true;
}
if (el.classList.contains('remove-block') || el.classList.contains('edit-block')) {
el.style.display = 'none';
}
});
block.removeAttribute('draggable');
} else {
block.querySelector('.remove-block').addEventListener('click', () => {
if (confirm('Remove this block?')) {
block.remove();
updateStepNumbers();
}
});
block.querySelector('.edit-block').addEventListener('click', () => {
alert('Edit mode. Modify content directly.');
});
}
builder.appendChild(block);
blocks.push({ id: block.id, type: blockObj.type, element: block });
});
placeholder.style.display = blocks.length === 0 ? 'block' : 'none';
updateStepNumbers();
}
window.addEventListener('DOMContentLoaded', init);
</script>
<style media="print">
.toolbox, .toolbox-item, #placeholder, .edit-block, .remove-block {
display: none !important;
}
.procedure-block {
page-break-inside: avoid;
border: 1px dashed #ccc;
}
body {
background: white;
font-size: 11pt;
}
.upload-area img {
max-height: 200px;
}
</style>
<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/rig-ops-procedure-v2" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>