Files
2025-12-02 16:27:21 +00:00

434 lines
14 KiB
Python

#!/usr/bin/env python3
import json
# Read flows.json
with open('/home/mdares/.node-red/flows.json', 'r') as f:
flows = json.load(f)
# Find Home template
home_template_node = None
for node in flows:
if node.get('id') == '1821c4842945ecd8':
home_template_node = node
break
if not home_template_node:
print("❌ ERROR: Could not find Home template")
exit(1)
template_code = home_template_node.get('format', '')
# ============================================================================
# PART 1: Add message handler for stop-prompt
# ============================================================================
# Find where to insert the handler - after scrapPrompt handler
insert_point = template_code.find("if (msg.topic === 'scrapPrompt')")
if insert_point < 0:
print("❌ ERROR: Could not find scrapPrompt handler")
exit(1)
# Find the end of the scrapPrompt handler block (find the next "if (msg" after it)
next_handler_start = template_code.find("\n if (msg", insert_point + 100)
if next_handler_start < 0:
# Try to find end of watch block
next_handler_start = template_code.find("\n });", insert_point + 100)
stop_prompt_handler = '''
// Stop Reason Prompt Handler
if (!scope.stopPrompt) {
scope.stopPrompt = {
show: false,
selectedCategory: null,
selectedReason: null,
notes: ''
};
}
if (msg._mode === 'stop-prompt') {
scope.stopPrompt.show = true;
scope.stopPrompt.selectedCategory = null;
scope.stopPrompt.selectedReason = null;
scope.stopPrompt.notes = '';
scope.$applyAsync();
return;
}
'''
# Insert the handler
template_code = template_code[:next_handler_start] + stop_prompt_handler + template_code[next_handler_start:]
print("✅ Added stop prompt message handler")
# ============================================================================
# PART 2: Add stop prompt modal HTML
# ============================================================================
# Find where scrap modal is defined (should be near the end, before </div> closing tags)
# Look for closing div tags at the end
scrap_modal_end = template_code.rfind('</div>\n</div>\n\n<script')
if scrap_modal_end < 0:
print("❌ ERROR: Could not find insertion point for modal HTML")
exit(1)
stop_modal_html = '''
<!-- Stop Reason Modal -->
<div class="modal" ng-show="stopPrompt.show" ng-click="stopPrompt.show = false">
<div class="modal-overlay"></div>
<div class="modal-card modal-card-stop" ng-click="$event.stopPropagation()">
<div class="modal-header-stop">
<div style="display: flex; align-items: center; gap: 0.5rem;">
<span style="font-size: 1.5rem;">⚠️</span>
<span>Production Stopped</span>
</div>
</div>
<div class="modal-body">
<p class="stop-question">Why are you stopping production?</p>
<!-- Planned Stops Section -->
<div class="stop-category-section">
<div class="stop-category-header planned-header">
<span>📋 Planned Stops</span>
<span class="stop-category-note">(Won't affect availability)</span>
</div>
<div class="stop-reasons-grid">
<button class="stop-reason-option planned"
ng-class="{selected: stopPrompt.selectedCategory === 'planned' && stopPrompt.selectedReason === 'Lunch break'}"
ng-click="selectStopReason('planned', 'Lunch break')">
<span class="reason-icon">🍽️</span>
<span class="reason-text">Lunch break</span>
</button>
<button class="stop-reason-option planned"
ng-class="{selected: stopPrompt.selectedCategory === 'planned' && stopPrompt.selectedReason === 'Scheduled break'}"
ng-click="selectStopReason('planned', 'Scheduled break')">
<span class="reason-icon">☕</span>
<span class="reason-text">Scheduled break</span>
</button>
<button class="stop-reason-option planned"
ng-class="{selected: stopPrompt.selectedCategory === 'planned' && stopPrompt.selectedReason === 'Shift change'}"
ng-click="selectStopReason('planned', 'Shift change')">
<span class="reason-icon">🔄</span>
<span class="reason-text">Shift change</span>
</button>
<button class="stop-reason-option planned"
ng-class="{selected: stopPrompt.selectedCategory === 'planned' && stopPrompt.selectedReason === 'Planned maintenance'}"
ng-click="selectStopReason('planned', 'Planned maintenance')">
<span class="reason-icon">🔧</span>
<span class="reason-text">Planned maintenance</span>
</button>
</div>
</div>
<!-- Unplanned Stops Section -->
<div class="stop-category-section">
<div class="stop-category-header unplanned-header">
<span>🚨 Unplanned Stops</span>
<span class="stop-category-note">(Will affect availability)</span>
</div>
<div class="stop-reasons-grid">
<button class="stop-reason-option unplanned"
ng-class="{selected: stopPrompt.selectedCategory === 'unplanned' && stopPrompt.selectedReason === 'Machine malfunction'}"
ng-click="selectStopReason('unplanned', 'Machine malfunction')">
<span class="reason-icon">⚙️</span>
<span class="reason-text">Machine malfunction</span>
</button>
<button class="stop-reason-option unplanned"
ng-class="{selected: stopPrompt.selectedCategory === 'unplanned' && stopPrompt.selectedReason === 'Material shortage'}"
ng-click="selectStopReason('unplanned', 'Material shortage')">
<span class="reason-icon">📦</span>
<span class="reason-text">Material shortage</span>
</button>
<button class="stop-reason-option unplanned"
ng-class="{selected: stopPrompt.selectedCategory === 'unplanned' && stopPrompt.selectedReason === 'Quality issue'}"
ng-click="selectStopReason('unplanned', 'Quality issue')">
<span class="reason-icon">❌</span>
<span class="reason-text">Quality issue</span>
</button>
<button class="stop-reason-option unplanned"
ng-class="{selected: stopPrompt.selectedCategory === 'unplanned' && stopPrompt.selectedReason === 'Operator error'}"
ng-click="selectStopReason('unplanned', 'Operator error')">
<span class="reason-icon">👤</span>
<span class="reason-text">Operator error</span>
</button>
<button class="stop-reason-option unplanned"
ng-class="{selected: stopPrompt.selectedCategory === 'unplanned' && stopPrompt.selectedReason === 'Other'}"
ng-click="selectStopReason('unplanned', 'Other')">
<span class="reason-icon">❓</span>
<span class="reason-text">Other</span>
</button>
</div>
</div>
<!-- Notes Section -->
<div class="stop-notes-section">
<label class="stop-notes-label">Additional notes (optional):</label>
<textarea class="stop-notes-input"
ng-model="stopPrompt.notes"
placeholder="Enter any additional details about the stop..."></textarea>
</div>
<!-- Action Buttons -->
<div class="modal-actions">
<button class="btn-cancel" ng-click="stopPrompt.show = false">Cancel</button>
<button class="btn-primary"
ng-disabled="!stopPrompt.selectedReason"
ng-click="submitStopReason()">
Submit Stop Reason
</button>
</div>
</div>
</div>
</div>
'''
# Insert the modal HTML
template_code = template_code[:scrap_modal_end] + stop_modal_html + template_code[scrap_modal_end:]
print("✅ Added stop prompt modal HTML")
# ============================================================================
# PART 3: Add CSS for stop modal (matching scrap modal theme)
# ============================================================================
# Find where CSS is defined (look for <style> tag)
css_end = template_code.find('</style>')
if css_end < 0:
print("❌ ERROR: Could not find CSS section")
exit(1)
stop_modal_css = '''
/* Stop Reason Modal Styling */
.modal-card-stop {
width: min(90vw, 36rem);
max-height: 85vh;
overflow-y: auto;
}
.modal-header-stop {
background: linear-gradient(135deg, #d32f2f 0%, #f44336 100%);
color: white;
padding: 1rem;
font-size: var(--fs-section-title);
font-weight: 600;
border-radius: 0.5rem 0.5rem 0 0;
}
.stop-question {
font-size: var(--fs-label-lg);
text-align: center;
margin-bottom: 1rem;
color: var(--text);
font-weight: 500;
}
.stop-category-section {
margin-bottom: 1.5rem;
}
.stop-category-header {
padding: 0.5rem 0.75rem;
border-radius: 0.25rem;
margin-bottom: 0.5rem;
font-weight: 600;
display: flex;
justify-content: space-between;
align-items: center;
}
.stop-category-header.planned-header {
background: #e8f5e9;
color: #2e7d32;
}
.stop-category-header.unplanned-header {
background: #ffebee;
color: #c62828;
}
.stop-category-note {
font-size: 0.75rem;
font-weight: 400;
opacity: 0.8;
}
.stop-reasons-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
gap: 0.5rem;
}
.stop-reason-option {
padding: 0.75rem;
border: 2px solid var(--border);
background: white;
border-radius: 0.5rem;
cursor: pointer;
transition: all 0.2s;
display: flex;
flex-direction: column;
align-items: center;
gap: 0.25rem;
text-align: center;
}
.stop-reason-option:hover {
border-color: var(--accent);
background: var(--surface);
transform: translateY(-2px);
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.stop-reason-option.selected {
border-color: var(--accent);
background: var(--accent);
color: white;
font-weight: 600;
}
.stop-reason-option.planned {
border-left: 4px solid #4caf50;
}
.stop-reason-option.unplanned {
border-left: 4px solid #f44336;
}
.stop-reason-option.planned.selected {
border-color: #4caf50;
background: #4caf50;
}
.stop-reason-option.unplanned.selected {
border-color: #f44336;
background: #f44336;
}
.reason-icon {
font-size: 1.5rem;
}
.reason-text {
font-size: 0.85rem;
}
.stop-notes-section {
margin-bottom: 1rem;
}
.stop-notes-label {
display: block;
margin-bottom: 0.5rem;
font-weight: 500;
color: var(--text);
}
.stop-notes-input {
width: 100%;
padding: 0.75rem;
border: 1px solid var(--border);
border-radius: 0.375rem;
font-family: inherit;
font-size: var(--fs-body);
resize: vertical;
min-height: 4rem;
background: white;
}
.stop-notes-input:focus {
outline: none;
border-color: var(--accent);
box-shadow: 0 0 0 3px rgba(33, 150, 243, 0.1);
}
'''
template_code = template_code[:css_end] + stop_modal_css + template_code[css_end:]
print("✅ Added stop prompt CSS")
# ============================================================================
# PART 4: Add JavaScript functions for stop modal
# ============================================================================
# Find where JavaScript functions are (after <script> tag)
script_start = template_code.find('<script>')
if script_start < 0:
print("❌ ERROR: Could not find script section")
exit(1)
# Find a good insertion point - after scope definition
scope_start = template_code.find('(function(scope) {', script_start)
if scope_start < 0:
print("❌ ERROR: Could not find scope function")
exit(1)
insertion_point = scope_start + len('(function(scope) {') + 1
stop_modal_js = '''
// ========================================================================
// Stop Reason Modal Functions
// ========================================================================
scope.selectStopReason = function(category, reason) {
scope.stopPrompt.selectedCategory = category;
scope.stopPrompt.selectedReason = reason;
};
scope.submitStopReason = function() {
if (!scope.stopPrompt.selectedCategory || !scope.stopPrompt.selectedReason) {
return;
}
// Send stop reason to Node-RED
scope.send({
action: 'stop-reason',
payload: {
category: scope.stopPrompt.selectedCategory,
reason: scope.stopPrompt.selectedReason,
notes: scope.stopPrompt.notes || ''
}
});
// Close the modal
scope.stopPrompt.show = false;
};
'''
template_code = template_code[:insertion_point] + stop_modal_js + template_code[insertion_point:]
print("✅ Added stop prompt JavaScript functions")
# Update the node
home_template_node['format'] = template_code
# Write updated flows
with open('/home/mdares/.node-red/flows.json', 'w') as f:
json.dump(flows, f, indent=4)
print("\n" + "="*60)
print("✅ STOP PROMPT ADDED TO HOME TEMPLATE")
print("="*60)
print("\n📋 What was added:")
print(" 1. Message handler for _mode='stop-prompt'")
print(" 2. Stop reason modal HTML (matches scrap modal style)")
print(" 3. CSS styling for stop modal")
print(" 4. JavaScript functions: selectStopReason, submitStopReason")
print("\n✨ Features:")
print(" - Planned stops: Lunch, Break, Shift change, Maintenance")
print(" - Unplanned stops: Malfunction, Material, Quality, Operator, Other")
print(" - Optional notes field")
print(" - Visual categorization (green=planned, red=unplanned)")
print(" - Sends action='stop-reason' back to Work Order buttons")
print("\n🧪 To test:")
print(" 1. Restart Node-RED")
print(" 2. Start a work order")
print(" 3. Click START to begin production")
print(" 4. Click STOP - modal should appear immediately")
print(" 5. Select a reason and submit")
print(" 6. Verify tracking is disabled and reason is logged")