Initial commit, 90% there
This commit is contained in:
@@ -0,0 +1,433 @@
|
||||
#!/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")
|
||||
Reference in New Issue
Block a user