|
|
| (9 intermediate revisions by the same user not shown) |
| Line 1: |
Line 1: |
| <includeonly><!-- Template:JourneyMaterials - Enhanced Interactive Version -->
| | {{#get_external_data:source = externaldb|from = data_journey_materials|data = Level=level, ItemLabel=item_label, TotalQty=total_qty, InternalName=internal_name|where = journey_id = {{{id|{{{1|}}}}}}|order by = level, item_label|cache = yes}} |
| {{#get_external_data: | | <div class="materials-enhanced-container"><div class="materials-progress-header materials-progress"><div class="progress-bar-container"><div class="progress-bar" data-progress="0"></div></div><div class="materials-progress-text"><span class="materials-progress-count">0/0</span> Materials Ready</div> |
| source = externaldb
| | </div> |
| |query = CALL sp_journey_components_bom({{{id|{{{1|}}}}}})
| | <div class="materials-grid"> |
| |data = Level = level,
| | <!-- LEVEL 1 – Direct Requirements --> |
| ItemLabel = item_label,
| | <div class="material-column level-1-column"> |
| TotalQty = total_qty
| | <h3 class="column-header">Direct Requirements</h3> |
| |cache seconds = 3600
| | <div class="material-items" data-level="1"> |
| }} | | {{#for_external_table:| |
| <div class="journey-materials-wrapper" data-journey-id="{{{id|{{{1|}}}}}}"> | | {{#ifeq:{{{Level}}}|1| |
| <!-- Level 1 Materials (Left Column) --> | | <div class="material-item" data-item="{{{InternalName}}}" data-level="1"><div class="material-checkbox-wrapper"><span class="material-checkbox" data-qty="{{{TotalQty}}}"></span></div><div class="material-content"><span class="material-name">{{{ItemLabel}}}</span><span class="material-qty">{{{TotalQty}}}</span></div><div class="material-children-indicator"></div></div> |
| <div class="materials-column level-1-column"> | | |}} |
| <div class="column-header">
| | }} |
| <span class="header-icon">📦</span>
| | </div> |
| Direct Materials
| |
| </div>
| |
| <div class="materials-list">
| |
| {{#for_external_table:|{{#ifeq:{{{Level}}}|1| | |
| <div class="material-item level-1" data-item="{{{ItemLabel}}}">
| |
| <input type="checkbox" class="material-checkbox" id="mat-{{{id|{{{1|}}}}}}-{{#replace:{{{ItemLabel}}}| |-}}" />
| |
| <label for="mat-{{{id|{{{1|}}}}}}-{{#replace:{{{ItemLabel}}}| |-}}">
| |
| <span class="material-icon">
| |
| {{#ifexist:File:{{#replace:{{{ItemLabel}}}| |_}}_-_Icon.png
| |
| |[[File:{{#replace:{{{ItemLabel}}}| |_}}_-_Icon.png|24px|link=]]
| |
| |<span class="icon-placeholder">●</span>
| |
| }}
| |
| </span>
| |
| <span class="material-name">{{{ItemLabel}}}</span>
| |
| <span class="material-qty">×{{{TotalQty}}}</span>
| |
| </label>
| |
| </div>
| |
| |}}}} | |
| </div>
| |
| </div> | | </div> |
|
| |
|
| <!-- Level 2+ Materials (Right Column) --> | | <!-- LEVEL 2 – Components --> |
| <div class="materials-column components-column"> | | <div class="material-column level-2-column"> |
| <div class="column-header">
| | <h3 class="column-header">Components</h3> |
| <span class="header-icon">🔧</span>
| | <div class="material-items" data-level="2"> |
| Components Needed
| | {{#for_external_table:| |
| </div>
| | {{#ifeq:{{{Level}}}|2| |
| <div class="materials-list">
| | <div class="material-item" data-item="{{{InternalName}}}" data-level="2"><div class="material-checkbox-wrapper"><span class="material-checkbox" data-qty="{{{TotalQty}}}"></span></div><div class="material-content"><span class="material-name">{{{ItemLabel}}}</span><span class="material-qty">{{{TotalQty}}}</span></div><div class="material-children-indicator"></div></div> |
| {{#vardefine:current_level|2}}
| | |}} |
| {{#for_external_table:|{{#ifexpr:{{{Level}}} >= 2| | | }} |
| {{#ifeq:{{{Level}}}|{{#var:current_level}}|| | | </div> |
| <div class="level-separator">Level {{{Level}}} Components</div>
| |
| {{#vardefine:current_level|{{{Level}}}}}
| |
| }}
| |
| <div class="material-item level-{{{Level}}}" data-level="{{{Level}}}" data-item="{{{ItemLabel}}}">
| |
| <input type="checkbox" class="material-checkbox component-checkbox"
| |
| id="comp-{{{id|{{{1|}}}}}}-{{#replace:{{{ItemLabel}}}| |-}}-{{{Level}}}" />
| |
| <label for="comp-{{{id|{{{1|}}}}}}-{{#replace:{{{ItemLabel}}}| |-}}-{{{Level}}}">
| |
| <span class="material-icon">
| |
| {{#ifexist:File:{{#replace:{{{ItemLabel}}}| |_}}_-_Icon.png
| |
| |[[File:{{#replace:{{{ItemLabel}}}| |_}}_-_Icon.png|20px|link=]]
| |
| |<span class="icon-placeholder level-{{{Level}}}">○</span>
| |
| }}
| |
| </span>
| |
| <span class="material-name">{{{ItemLabel}}}</span>
| |
| <span class="material-qty">×{{{TotalQty}}}</span>
| |
| </label>
| |
| </div>
| |
| |}}}} | |
| </div>
| |
| </div> | | </div> |
| </div>
| |
|
| |
|
| <!-- Add CSS --> | | <!-- LEVEL 3 – Base Resources --> |
| <style> | | <div class="material-column level-3-column"> |
| .journey-materials-wrapper {
| | <h3 class="column-header">Base Resources</h3> |
| display: flex;
| | <div class="material-items" data-level="3"> |
| gap: 20px;
| | {{#for_external_table:| |
| max-height: 500px;
| | {{#ifeq:{{{Level}}}|3| |
| background: rgba(0, 0, 0, 0.9);
| | <div class="material-item" data-item="{{{InternalName}}}" data-level="3"><div class="material-checkbox-wrapper"><span class="material-checkbox" data-qty="{{{TotalQty}}}"></span></div><div class="material-content"><span class="material-name">{{{ItemLabel}}}</span><span class="material-qty">{{{TotalQty}}}</span></div></div> |
| border: 1px solid #444;
| | |}} |
| border-radius: 8px;
| | }} |
| padding: 15px;
| | </div> |
| }
| | </div> |
| | | </div> |
| .materials-column {
| |
| flex: 1;
| |
| display: flex;
| |
| flex-direction: column;
| |
| min-width: 250px;
| |
| }
| |
| | |
| .level-1-column {
| |
| border-right: 1px solid #333;
| |
| padding-right: 20px;
| |
| }
| |
| | |
| .column-header {
| |
| display: flex;
| |
| align-items: center;
| |
| gap: 8px;
| |
| padding: 10px 0;
| |
| margin-bottom: 10px;
| |
| border-bottom: 2px solid #555;
| |
| font-weight: bold;
| |
| font-size: 16px;
| |
| color: #fff;
| |
| }
| |
| | |
| .header-icon {
| |
| font-size: 20px;
| |
| }
| |
| | |
| .materials-list {
| |
| overflow-y: auto;
| |
| overflow-x: hidden;
| |
| flex: 1;
| |
| padding-right: 5px;
| |
| }
| |
| | |
| .material-item {
| |
| display: flex;
| |
| align-items: center;
| |
| padding: 6px 8px;
| |
| margin-bottom: 4px;
| |
| border-radius: 4px;
| |
| transition: all 0.2s;
| |
| background: rgba(255, 255, 255, 0.02);
| |
| }
| |
| | |
| .material-item:hover {
| |
| background: rgba(255, 255, 255, 0.08);
| |
| }
| |
| | |
| .material-item.checked {
| |
| background: rgba(76, 175, 80, 0.2);
| |
| opacity: 0.8;
| |
| }
| |
| | |
| .material-checkbox {
| |
| width: 18px;
| |
| height: 18px;
| |
| margin-right: 10px;
| |
| cursor: pointer;
| |
| accent-color: #4CAF50;
| |
| }
| |
| | |
| .material-item label {
| |
| display: flex;
| |
| align-items: center;
| |
| gap: 8px;
| |
| flex: 1;
| |
| cursor: pointer;
| |
| }
| |
| | |
| .material-icon {
| |
| width: 24px;
| |
| height: 24px;
| |
| display: flex;
| |
| align-items: center;
| |
| justify-content: center;
| |
| }
| |
| | |
| .icon-placeholder {
| |
| font-size: 16px;
| |
| color: #888;
| |
| }
| |
| | |
| .material-name {
| |
| flex: 1;
| |
| color: #ddd;
| |
| font-size: 14px;
| |
| } | |
| | |
| .material-qty {
| |
| font-weight: bold;
| |
| color: #ffa500;
| |
| font-size: 14px;
| |
| margin-left: auto;
| |
| } | |
| | |
| .level-separator {
| |
| font-size: 12px;
| |
| color: #888;
| |
| margin: 10px 0 5px;
| |
| padding: 5px 0;
| |
| border-top: 1px dashed #444;
| |
| } | |
| | |
| /* Level-based styling */
| |
| .level-1 .material-name { color: #4CAF50; }
| |
| .level-2 .material-name { color: #2196F3; }
| |
| .level-3 .material-name { color: #FF9800; }
| |
| .level-4 .material-name { color: #9C27B0; }
| |
| | |
| /* Scrollbar styling */
| |
| .materials-list::-webkit-scrollbar {
| |
| width: 6px;
| |
| } | |
| | |
| .materials-list::-webkit-scrollbar-track {
| |
| background: #222;
| |
| } | |
| | |
| .materials-list::-webkit-scrollbar-thumb {
| |
| background: #555;
| |
| border-radius: 3px;
| |
| }
| |
| | |
| /* Responsive */
| |
| @media (max-width: 600px) {
| |
| .journey-materials-wrapper {
| |
| flex-direction: column;
| |
| max-height: 600px;
| |
| }
| |
|
| |
| .level-1-column {
| |
| border-right: none;
| |
| border-bottom: 1px solid #333;
| |
| padding-right: 0;
| |
| padding-bottom: 15px;
| |
| }
| |
| }
| |
| </style>
| |
| | |
| <!-- Add JavaScript for interactive checkboxes --> | |
| <script> | |
| (function() {
| |
| const wrapper = document.querySelector('.journey-materials-wrapper[data-journey-id="{{{id|{{{1|}}}}}}"]');
| |
| if (!wrapper) return;
| |
|
| |
| const checkboxes = wrapper.querySelectorAll('.material-checkbox');
| |
|
| |
| checkboxes.forEach(checkbox => {
| |
| checkbox.addEventListener('change', function() {
| |
| const item = this.closest('.material-item');
| |
| if (this.checked) {
| |
| item.classList.add('checked');
| |
| } else {
| |
| item.classList.remove('checked');
| |
| }
| |
|
| |
| // Save state to localStorage
| |
| const journeyId = wrapper.dataset.journeyId;
| |
| const itemId = this.id;
| |
| const checkedItems = JSON.parse(localStorage.getItem(`journey_${journeyId}_checked`) || '{}');
| |
| checkedItems[itemId] = this.checked;
| |
| localStorage.setItem(`journey_${journeyId}_checked`, JSON.stringify(checkedItems));
| |
|
| |
| // Check if all components are checked (for auto-checking direct materials)
| |
| checkDirectMaterialsCompletion();
| |
| });
| |
| });
| |
|
| |
| // Restore saved state
| |
| const journeyId = wrapper.dataset.journeyId;
| |
| const checkedItems = JSON.parse(localStorage.getItem(`journey_${journeyId}_checked`) || '{}');
| |
| Object.keys(checkedItems).forEach(itemId => {
| |
| const checkbox = document.getElementById(itemId);
| |
| if (checkbox && checkedItems[itemId]) {
| |
| checkbox.checked = true;
| |
| checkbox.closest('.material-item').classList.add('checked');
| |
| }
| |
| });
| |
|
| |
| // Function to check if all components for a material are complete
| |
| function checkDirectMaterialsCompletion() {
| |
| // This is a simplified version - you'd need to map which components
| |
| // belong to which direct materials based on your recipe data
| |
| const allComponentsChecked = wrapper.querySelectorAll('.component-checkbox:not(:checked)').length === 0;
| |
|
| |
| if (allComponentsChecked) {
| |
| // Visual indicator that all components are ready
| |
| wrapper.querySelector('.components-column').style.borderColor = '#4CAF50';
| |
| }
| |
| }
| |
|
| |
| // Add clear all button
| |
| const header = wrapper.querySelector('.column-header');
| |
| const clearBtn = document.createElement('button');
| |
| clearBtn.textContent = 'Clear All';
| |
| clearBtn.style.cssText = 'margin-left: auto; font-size: 12px; padding: 4px 8px; background: #666; border: none; border-radius: 4px; color: white; cursor: pointer;';
| |
| clearBtn.onclick = function() {
| |
| checkboxes.forEach(cb => {
| |
| cb.checked = false; | |
| cb.closest('.material-item').classList.remove('checked');
| |
| });
| |
| localStorage.removeItem(`journey_${journeyId}_checked`); | |
| };
| |
| header.appendChild(clearBtn);
| |
| })(); | |
| </script> | |
| </includeonly><noinclude> | |
| {{Documentation}}
| |
| </noinclude> | |
The query SELECT level,item_label,total_qty,internal_name FROM `data_journey_materials` WHERE journey_id = ORDER BY level, item_label is invalid (Error 1064: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'ORDER BY level, item_label' at line 1
Function: EDConnectorRdbms::fetch
Query: SELECT level,item_label,total_qty,internal_name FROM `data_journey_materials` WHERE journey_id = ORDER BY level, item_label
).