Initial commit
This commit is contained in:
695
static/js/modules-page.js
Normal file
695
static/js/modules-page.js
Normal file
@ -0,0 +1,695 @@
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
console.log('Modules page initializing...');
|
||||
|
||||
// Reference to container and filters
|
||||
const modulesContainer = document.getElementById('modules-container');
|
||||
const moduleSearch = document.getElementById('module-search');
|
||||
const stageFilter = document.getElementById('stage-filter');
|
||||
const componentFilter = document.getElementById('component-filter');
|
||||
const templateFilter = document.getElementById('template-filter');
|
||||
|
||||
// Data structures
|
||||
let allModules = [];
|
||||
let stageNames = {};
|
||||
let componentNames = {};
|
||||
let templateUsage = {};
|
||||
let processedTemplates = [];
|
||||
|
||||
// Functions
|
||||
function initializeData() {
|
||||
console.log('Initializing data...');
|
||||
|
||||
// Check if data is available
|
||||
if (typeof moduleData === 'undefined') {
|
||||
console.error('Error: moduleData is undefined');
|
||||
modulesContainer.innerHTML = '<div class="error">Error: Module data not available</div>';
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof rawProtocolTemplates === 'undefined' || typeof templateMapper === 'undefined') {
|
||||
console.error('Error: Template data or mapper not available');
|
||||
modulesContainer.innerHTML = '<div class="error">Error: Template data not available</div>';
|
||||
return false;
|
||||
}
|
||||
|
||||
// Log what we're working with
|
||||
console.log('Module categories:', Object.keys(moduleData));
|
||||
console.log('Templates count:', rawProtocolTemplates.length);
|
||||
|
||||
try {
|
||||
// Process templates
|
||||
rawProtocolTemplates.forEach(template => {
|
||||
const processedTemplate = templateMapper.convertTemplateToModules(template, moduleData);
|
||||
processedTemplates.push(processedTemplate);
|
||||
});
|
||||
|
||||
// Collect all modules into a flat array
|
||||
for (const category in moduleData) {
|
||||
moduleData[category].forEach(module => {
|
||||
// Add category to module
|
||||
module.category = category;
|
||||
|
||||
// Initialize template usage
|
||||
module.usedInTemplates = [];
|
||||
|
||||
// Ensure all modules have a componentId - no uncategorized modules allowed
|
||||
if (!module.componentId) {
|
||||
// Try to infer from category first
|
||||
if (category !== 'uncategorized') {
|
||||
console.log(`Module ${module.id} missing componentId, using category: ${category}`);
|
||||
module.componentId = category;
|
||||
}
|
||||
// If that doesn't work, infer from title or content
|
||||
else {
|
||||
// First, try to find clues in the title
|
||||
if (module.title) {
|
||||
const titleLower = module.title.toLowerCase();
|
||||
// Check for key component names in the title
|
||||
if (titleLower.includes('principle')) module.componentId = 'principles';
|
||||
else if (titleLower.includes('process')) module.componentId = 'process';
|
||||
else if (titleLower.includes('assessment')) module.componentId = 'assessment';
|
||||
else if (titleLower.includes('intake')) module.componentId = 'intake';
|
||||
else if (titleLower.includes('appeal')) module.componentId = 'appeal';
|
||||
else if (titleLower.includes('deliberat')) module.componentId = 'deliberation';
|
||||
else if (titleLower.includes('resolut')) module.componentId = 'resolution';
|
||||
else if (titleLower.includes('facilit')) module.componentId = 'facilitation';
|
||||
else if (titleLower.includes('particip')) module.componentId = 'participants';
|
||||
else if (titleLower.includes('file') || titleLower.includes('submit')) module.componentId = 'filing';
|
||||
else if (titleLower.includes('notif') || titleLower.includes('inform')) module.componentId = 'notification';
|
||||
else if (titleLower.includes('delegat')) module.componentId = 'delegation';
|
||||
else module.componentId = 'process'; // Default to process
|
||||
}
|
||||
// If no title clues, check content
|
||||
else if (module.content) {
|
||||
const contentLower = module.content.toLowerCase();
|
||||
// Same checks in content
|
||||
if (contentLower.includes('principle')) module.componentId = 'principles';
|
||||
else if (contentLower.includes('process')) module.componentId = 'process';
|
||||
else if (contentLower.includes('assessment')) module.componentId = 'assessment';
|
||||
else if (contentLower.includes('intake')) module.componentId = 'intake';
|
||||
else if (contentLower.includes('appeal')) module.componentId = 'appeal';
|
||||
else if (contentLower.includes('deliberat')) module.componentId = 'deliberation';
|
||||
else if (contentLower.includes('resolut')) module.componentId = 'resolution';
|
||||
else if (contentLower.includes('facilit')) module.componentId = 'facilitation';
|
||||
else if (contentLower.includes('particip')) module.componentId = 'participants';
|
||||
else if (contentLower.includes('file') || contentLower.includes('submit')) module.componentId = 'filing';
|
||||
else if (contentLower.includes('notif') || contentLower.includes('inform')) module.componentId = 'notification';
|
||||
else if (contentLower.includes('delegat')) module.componentId = 'delegation';
|
||||
else module.componentId = 'process'; // Default to process
|
||||
}
|
||||
// Last resort default
|
||||
else {
|
||||
module.componentId = 'process';
|
||||
}
|
||||
|
||||
console.log(`Module ${module.id} had no componentId, assigned to: ${module.componentId}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Add to all modules array
|
||||
allModules.push(module);
|
||||
});
|
||||
}
|
||||
|
||||
console.log('Total modules collected:', allModules.length);
|
||||
|
||||
// Track which templates use each module
|
||||
processedTemplates.forEach(template => {
|
||||
for (const stageId in template.moduleRefs) {
|
||||
if (!stageNames[stageId]) {
|
||||
// Create a readable stage name
|
||||
stageNames[stageId] = stageId.charAt(0).toUpperCase() + stageId.slice(1).replace(/_/g, ' ');
|
||||
}
|
||||
|
||||
for (const componentId in template.moduleRefs[stageId]) {
|
||||
if (!componentNames[componentId]) {
|
||||
// Create a readable component name
|
||||
componentNames[componentId] = componentId.charAt(0).toUpperCase() + componentId.slice(1).replace(/_/g, ' ');
|
||||
}
|
||||
|
||||
for (const fieldId in template.moduleRefs[stageId][componentId]) {
|
||||
const moduleId = template.moduleRefs[stageId][componentId][fieldId];
|
||||
|
||||
// Find the module with this id
|
||||
const matchingModule = allModules.find(m => m.id === moduleId);
|
||||
if (matchingModule) {
|
||||
// Add template to module's usage
|
||||
if (!matchingModule.usedInTemplates.includes(template.title)) {
|
||||
matchingModule.usedInTemplates.push(template.title);
|
||||
}
|
||||
|
||||
// Set stage and component for the module if not already set
|
||||
if (!matchingModule.stageId) {
|
||||
matchingModule.stageId = stageId;
|
||||
}
|
||||
|
||||
// Track template usage
|
||||
if (!templateUsage[template.title]) {
|
||||
templateUsage[template.title] = [];
|
||||
}
|
||||
if (!templateUsage[template.title].includes(moduleId)) {
|
||||
templateUsage[template.title].push(moduleId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Define official stages from YAML file
|
||||
const officialStages = {
|
||||
'intake': 'Intake',
|
||||
'process': 'Process',
|
||||
'assessment': 'Assessment',
|
||||
'deliberation': 'Deliberation',
|
||||
'resolution': 'Resolution',
|
||||
'appeal': 'Appeal',
|
||||
'delegation': 'Delegation'
|
||||
};
|
||||
|
||||
// Define official components with standardized display names
|
||||
const officialComponents = {
|
||||
// Process stage components
|
||||
'principles': 'Principles',
|
||||
'community_values': 'Values',
|
||||
'participants': 'Participants',
|
||||
'facilitation': 'Facilitation',
|
||||
'ground_rules': 'Ground Rules',
|
||||
'skills': 'Skills',
|
||||
|
||||
// Intake stage components
|
||||
'process_start': 'Process Start',
|
||||
'filing': 'Filing',
|
||||
'notification': 'Notification',
|
||||
'rules_access': 'Rules Access',
|
||||
'information_access': 'Information Access',
|
||||
'participant_inclusion': 'Participant Inclusion',
|
||||
|
||||
// Assessment stage components
|
||||
'dispute_assessment': 'Assessment',
|
||||
'values_adherence': 'Values Adherence',
|
||||
'jurisdiction': 'Jurisdiction',
|
||||
|
||||
// Deliberation stage components
|
||||
'deliberation_process': 'Deliberation Process',
|
||||
'additional_voices': 'Additional Voices',
|
||||
'deliberation_conclusion': 'Deliberation Conclusion',
|
||||
|
||||
// Resolution stage components
|
||||
'resolution_process': 'Resolution Process',
|
||||
'resolution_failure': 'Resolution Failure',
|
||||
|
||||
// Appeal stage components
|
||||
'appeal_criteria': 'Appeal Criteria',
|
||||
'appeal_process': 'Appeal Process',
|
||||
|
||||
// Delegation stage components
|
||||
'delegation_options': 'Delegation Options'
|
||||
};
|
||||
|
||||
// Map all component IDs (including custom ones) to official stages
|
||||
const componentToStageMap = {
|
||||
// Process stage components
|
||||
'principles': 'process',
|
||||
'community_values': 'process',
|
||||
'participants': 'process',
|
||||
'facilitation': 'process',
|
||||
'ground_rules': 'process',
|
||||
'skills': 'process',
|
||||
'values': 'process',
|
||||
'agreements': 'process',
|
||||
'participation_commitments': 'process',
|
||||
|
||||
// Intake stage components
|
||||
'process_start': 'intake',
|
||||
'filing': 'intake',
|
||||
'notification': 'intake',
|
||||
'rules_access': 'intake',
|
||||
'information_access': 'intake',
|
||||
'participant_inclusion': 'intake',
|
||||
'reporting': 'intake',
|
||||
|
||||
// Assessment stage components
|
||||
'dispute_assessment': 'assessment',
|
||||
'values_adherence': 'assessment',
|
||||
'jurisdiction': 'assessment',
|
||||
'non_participation': 'assessment',
|
||||
|
||||
// Deliberation stage components
|
||||
'deliberation_process': 'deliberation',
|
||||
'additional_voices': 'deliberation',
|
||||
'deliberation_conclusion': 'deliberation',
|
||||
'decision_making': 'deliberation',
|
||||
'discussion': 'deliberation',
|
||||
|
||||
// Resolution stage components
|
||||
'resolution_process': 'resolution',
|
||||
'resolution_failure': 'resolution',
|
||||
|
||||
// Appeal stage components
|
||||
'appeal_criteria': 'appeal',
|
||||
'appeal_process': 'appeal',
|
||||
'appeal_deliberation': 'appeal',
|
||||
'appeal_resolution': 'appeal',
|
||||
|
||||
// Delegation stage components
|
||||
'delegation_options': 'delegation'
|
||||
};
|
||||
|
||||
// Map non-standard components to official ones
|
||||
const componentMapping = {
|
||||
'direct_conversation': 'deliberation_process',
|
||||
'conflict_awareness': 'dispute_assessment',
|
||||
'accessing_help': 'process_start',
|
||||
'preparation': 'principles',
|
||||
'selection': 'participant_inclusion',
|
||||
'criteria': 'appeal_criteria',
|
||||
'agreement': 'resolution_process',
|
||||
'process': 'principles',
|
||||
'process_change': 'resolution_failure',
|
||||
'assessment': 'dispute_assessment',
|
||||
'situation': 'dispute_assessment',
|
||||
'reflection': 'deliberation_conclusion',
|
||||
'monitoring': 'dispute_assessment',
|
||||
'participation_requirement': 'participant_inclusion'
|
||||
};
|
||||
|
||||
allModules.forEach(module => {
|
||||
// If module has no stageId, try to infer from category
|
||||
if (!module.stageId && module.category) {
|
||||
// Check if category matches a known stage
|
||||
if (stageNames[module.category]) {
|
||||
module.stageId = module.category;
|
||||
} else {
|
||||
// Use category as stageId if not already recognized
|
||||
module.stageId = module.category;
|
||||
// Create a readable stage name from category
|
||||
stageNames[module.category] = module.category.charAt(0).toUpperCase() +
|
||||
module.category.slice(1).replace(/_/g, ' ');
|
||||
}
|
||||
}
|
||||
|
||||
// If still no stageId, try to infer from componentId using the mapping
|
||||
if (!module.stageId && module.componentId) {
|
||||
const mappedStage = componentToStageMap[module.componentId];
|
||||
if (mappedStage) {
|
||||
module.stageId = mappedStage;
|
||||
console.log(`Module ${module.id} missing stageId, inferred from component: ${mappedStage}`);
|
||||
} else {
|
||||
// Default stage if no mapping exists
|
||||
module.stageId = 'process';
|
||||
console.log(`Module ${module.id} missing stageId, defaulting to: process`);
|
||||
}
|
||||
}
|
||||
|
||||
// If STILL no stageId (somehow), assign to process
|
||||
if (!module.stageId) {
|
||||
module.stageId = 'process';
|
||||
console.log(`Module ${module.id} had no stageId, assigned to: process`);
|
||||
}
|
||||
|
||||
// Force module to use only official stages
|
||||
if (officialStages[module.stageId]) {
|
||||
// Use official stage name
|
||||
module.stageName = officialStages[module.stageId];
|
||||
} else {
|
||||
// Map to closest official stage
|
||||
const mappedStage = componentToStageMap[module.componentId];
|
||||
if (mappedStage && officialStages[mappedStage]) {
|
||||
module.stageId = mappedStage;
|
||||
module.stageName = officialStages[mappedStage];
|
||||
console.log(`Module ${module.id} had invalid stage "${module.stageId}", remapped to: ${mappedStage}`);
|
||||
} else {
|
||||
// Default to Process stage if can't map anywhere else
|
||||
module.stageId = 'process';
|
||||
module.stageName = officialStages['process'];
|
||||
console.log(`Module ${module.id} had invalid stage "${module.stageId}", defaulting to: Process`);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle component mapping for non-standard components
|
||||
if (componentMapping[module.componentId]) {
|
||||
const originalComponentId = module.componentId;
|
||||
module.componentId = componentMapping[originalComponentId];
|
||||
console.log(`Module ${module.id} had non-standard component "${originalComponentId}", mapped to: ${module.componentId}`);
|
||||
}
|
||||
|
||||
// Set a readable component name using official list
|
||||
if (officialComponents[module.componentId]) {
|
||||
module.componentName = officialComponents[module.componentId];
|
||||
} else {
|
||||
// Generate a readable name for custom components
|
||||
module.componentName = module.componentId.charAt(0).toUpperCase() +
|
||||
module.componentId.slice(1).replace(/_/g, ' ');
|
||||
|
||||
// Log any component not in the official list
|
||||
console.log(`Module ${module.id} uses custom component: ${module.componentId}`);
|
||||
}
|
||||
});
|
||||
|
||||
// Log the distribution of modules by stage
|
||||
const stageDistribution = {};
|
||||
allModules.forEach(module => {
|
||||
stageDistribution[module.stageName] = (stageDistribution[module.stageName] || 0) + 1;
|
||||
});
|
||||
console.log('Module distribution by stage:', stageDistribution);
|
||||
|
||||
console.log('Data initialization complete');
|
||||
console.log('Stages:', Object.keys(stageNames));
|
||||
console.log('Components:', Object.keys(componentNames));
|
||||
console.log('Templates:', Object.keys(templateUsage));
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Error initializing data:', error);
|
||||
modulesContainer.innerHTML = `<div class="error">Error initializing data: ${error.message}</div>`;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function populateFilters() {
|
||||
console.log('Populating filters...');
|
||||
|
||||
// Clear existing options
|
||||
stageFilter.innerHTML = '<option value="">All Stages</option>';
|
||||
componentFilter.innerHTML = '<option value="">All Components</option>';
|
||||
templateFilter.innerHTML = '<option value="">All Templates</option>';
|
||||
|
||||
// Get unique stages and components
|
||||
const stages = [...new Set(allModules.map(m => m.stageName))].sort();
|
||||
const components = [...new Set(allModules.map(m => m.componentName))].sort();
|
||||
const templates = Object.keys(templateUsage).sort();
|
||||
|
||||
console.log('Filter options - Stages:', stages);
|
||||
console.log('Filter options - Components:', components);
|
||||
console.log('Filter options - Templates:', templates);
|
||||
|
||||
// Add options to filters
|
||||
stages.forEach(stage => {
|
||||
const option = document.createElement('option');
|
||||
option.value = stage;
|
||||
option.textContent = stage;
|
||||
stageFilter.appendChild(option);
|
||||
});
|
||||
|
||||
components.forEach(component => {
|
||||
const option = document.createElement('option');
|
||||
option.value = component;
|
||||
option.textContent = component;
|
||||
componentFilter.appendChild(option);
|
||||
});
|
||||
|
||||
templates.forEach(template => {
|
||||
const option = document.createElement('option');
|
||||
option.value = template;
|
||||
option.textContent = template;
|
||||
templateFilter.appendChild(option);
|
||||
});
|
||||
}
|
||||
|
||||
function renderModules() {
|
||||
console.log('Rendering modules...');
|
||||
|
||||
// Clear container
|
||||
modulesContainer.innerHTML = '';
|
||||
|
||||
// Get filter values
|
||||
const searchText = moduleSearch.value.toLowerCase();
|
||||
const stageValue = stageFilter.value;
|
||||
const componentValue = componentFilter.value;
|
||||
const templateValue = templateFilter.value;
|
||||
|
||||
console.log('Filter values:', {
|
||||
search: searchText,
|
||||
stage: stageValue,
|
||||
component: componentValue,
|
||||
template: templateValue
|
||||
});
|
||||
|
||||
// Create a stage-based organization of modules
|
||||
const modulesByStage = {};
|
||||
|
||||
// Filter modules based on search and filter criteria
|
||||
allModules.forEach(module => {
|
||||
// Check if module matches search text
|
||||
const matchesSearch = searchText === '' ||
|
||||
module.title.toLowerCase().includes(searchText) ||
|
||||
(module.content && module.content.toLowerCase().includes(searchText));
|
||||
|
||||
// Check if module matches stage filter
|
||||
const matchesStage = stageValue === '' || module.stageName === stageValue;
|
||||
|
||||
// Check if module matches component filter
|
||||
const matchesComponent = componentValue === '' || module.componentName === componentValue;
|
||||
|
||||
// Check if module matches template filter
|
||||
const matchesTemplate = templateValue === '' ||
|
||||
module.usedInTemplates.includes(templateValue);
|
||||
|
||||
// Include module if it matches all criteria
|
||||
if (matchesSearch && matchesStage && matchesComponent && matchesTemplate) {
|
||||
// Initialize stage object if not exists
|
||||
if (!modulesByStage[module.stageName]) {
|
||||
modulesByStage[module.stageName] = [];
|
||||
}
|
||||
|
||||
// Add module to its stage
|
||||
modulesByStage[module.stageName].push(module);
|
||||
}
|
||||
});
|
||||
|
||||
// Sort stages according to the official order from the YAML file
|
||||
const stageOrder = [
|
||||
'Intake', 'Process', 'Assessment',
|
||||
'Deliberation', 'Resolution', 'Appeal', 'Delegation'
|
||||
];
|
||||
|
||||
// Get all stages from the data
|
||||
const availableStages = Object.keys(modulesByStage);
|
||||
|
||||
// Sort stages according to the defined order, with any others at the end
|
||||
const sortedStages = [];
|
||||
|
||||
// First add stages in the predefined order (if they exist in the data)
|
||||
stageOrder.forEach(stage => {
|
||||
if (availableStages.includes(stage)) {
|
||||
sortedStages.push(stage);
|
||||
}
|
||||
});
|
||||
|
||||
// Then add any other stages not in the predefined order (sorted alphabetically)
|
||||
availableStages
|
||||
.filter(stage => !stageOrder.includes(stage))
|
||||
.sort()
|
||||
.forEach(stage => sortedStages.push(stage));
|
||||
|
||||
// If no modules match, show message
|
||||
if (sortedStages.length === 0) {
|
||||
modulesContainer.innerHTML = '<div class="no-results">No modules match your search criteria</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
// Create HTML for each stage and its modules
|
||||
sortedStages.forEach(stageName => {
|
||||
const stageModules = modulesByStage[stageName];
|
||||
|
||||
// Create stage section using similar structure to builder
|
||||
const stageSection = document.createElement('div');
|
||||
stageSection.className = 'stage-section';
|
||||
|
||||
// Create stage header
|
||||
const stageHeader = document.createElement('div');
|
||||
stageHeader.className = 'stage-header';
|
||||
|
||||
const stageHeaderContent = document.createElement('div');
|
||||
stageHeaderContent.className = 'stage-header-content';
|
||||
stageHeaderContent.innerHTML = `
|
||||
<h2>${stageName}</h2>
|
||||
<div class="stage-brief">
|
||||
Contains ${stageModules.length} module${stageModules.length !== 1 ? 's' : ''}
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Create toggle button
|
||||
const toggleBtn = document.createElement('button');
|
||||
toggleBtn.className = 'toggle-btn';
|
||||
toggleBtn.innerHTML = '+';
|
||||
toggleBtn.setAttribute('aria-label', 'Toggle stage content');
|
||||
|
||||
stageHeader.appendChild(stageHeaderContent);
|
||||
stageHeader.appendChild(toggleBtn);
|
||||
stageSection.appendChild(stageHeader);
|
||||
|
||||
// Create stage body
|
||||
const stageBody = document.createElement('div');
|
||||
stageBody.className = 'stage-body';
|
||||
stageBody.style.display = 'none';
|
||||
|
||||
// Group modules by component
|
||||
const modulesByComponent = {};
|
||||
stageModules.forEach(module => {
|
||||
// Use just the component name without duplicating stage info
|
||||
const cleanComponentName = module.componentName.replace(module.stageName, '').trim();
|
||||
const displayComponentName = cleanComponentName || module.componentName;
|
||||
|
||||
if (!modulesByComponent[displayComponentName]) {
|
||||
modulesByComponent[displayComponentName] = [];
|
||||
}
|
||||
modulesByComponent[displayComponentName].push(module);
|
||||
});
|
||||
|
||||
// Sort components
|
||||
const sortedComponents = Object.keys(modulesByComponent).sort();
|
||||
|
||||
// Create components container
|
||||
const componentsContainer = document.createElement('div');
|
||||
componentsContainer.className = 'components';
|
||||
|
||||
// Create component sections
|
||||
sortedComponents.forEach(componentName => {
|
||||
const componentModules = modulesByComponent[componentName];
|
||||
|
||||
// Create a component group heading with count
|
||||
const componentHeading = document.createElement('h3');
|
||||
componentHeading.className = 'component-group-heading';
|
||||
|
||||
// Create main text
|
||||
const headingText = document.createTextNode(componentName);
|
||||
componentHeading.appendChild(headingText);
|
||||
|
||||
// Add count in parentheses
|
||||
const moduleCount = componentModules.length;
|
||||
const countSpan = document.createElement('span');
|
||||
countSpan.className = 'component-module-count';
|
||||
countSpan.textContent = ` (${moduleCount} module${moduleCount !== 1 ? 's' : ''})`;
|
||||
componentHeading.appendChild(countSpan);
|
||||
|
||||
componentsContainer.appendChild(componentHeading);
|
||||
|
||||
// Add modules to component section
|
||||
componentModules.forEach(module => {
|
||||
const moduleCard = createModuleCard(module);
|
||||
componentsContainer.appendChild(moduleCard);
|
||||
});
|
||||
});
|
||||
|
||||
stageBody.appendChild(componentsContainer);
|
||||
stageSection.appendChild(stageBody);
|
||||
|
||||
// Add toggle functionality
|
||||
stageHeaderContent.addEventListener('click', function() {
|
||||
if (stageBody.style.display === 'none') {
|
||||
stageBody.style.display = 'block';
|
||||
toggleBtn.innerHTML = '−';
|
||||
} else {
|
||||
stageBody.style.display = 'none';
|
||||
toggleBtn.innerHTML = '+';
|
||||
}
|
||||
});
|
||||
|
||||
toggleBtn.addEventListener('click', function(e) {
|
||||
e.stopPropagation();
|
||||
if (stageBody.style.display === 'none') {
|
||||
stageBody.style.display = 'block';
|
||||
toggleBtn.innerHTML = '−';
|
||||
} else {
|
||||
stageBody.style.display = 'none';
|
||||
toggleBtn.innerHTML = '+';
|
||||
}
|
||||
});
|
||||
|
||||
modulesContainer.appendChild(stageSection);
|
||||
});
|
||||
|
||||
console.log('Modules rendering complete');
|
||||
}
|
||||
|
||||
function createModuleCard(module) {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'component-card';
|
||||
card.dataset.id = module.id;
|
||||
|
||||
// Format templates list
|
||||
const templatesList = module.usedInTemplates.length > 0
|
||||
? module.usedInTemplates.join(', ')
|
||||
: 'Not used in any template';
|
||||
|
||||
// Create card content with structure similar to builder component
|
||||
card.innerHTML = `
|
||||
<div class="component-header">
|
||||
<h3>${module.title}</h3>
|
||||
</div>
|
||||
<div class="component-body">
|
||||
<div class="module-meta">
|
||||
<span class="tag stage-tag">${module.stageName}</span>
|
||||
<span class="tag component-tag">${module.componentName}</span>
|
||||
</div>
|
||||
<div class="module-content">
|
||||
<textarea readonly>${module.content}</textarea>
|
||||
</div>
|
||||
<div class="module-templates">
|
||||
<strong>Used in templates:</strong> ${templatesList}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Make the header clickable to toggle content visibility
|
||||
const header = card.querySelector('.component-header');
|
||||
const body = card.querySelector('.component-body');
|
||||
|
||||
// Add toggle button to the header (already positioned by CSS flexbox)
|
||||
const toggleBtn = document.createElement('button');
|
||||
toggleBtn.className = 'toggle-btn';
|
||||
toggleBtn.innerHTML = '+';
|
||||
toggleBtn.setAttribute('aria-label', 'Toggle module content');
|
||||
header.appendChild(toggleBtn);
|
||||
|
||||
// Start with content hidden
|
||||
body.style.display = 'none';
|
||||
|
||||
// Toggle functionality
|
||||
function toggleContent() {
|
||||
if (body.style.display === 'none') {
|
||||
body.style.display = 'block';
|
||||
toggleBtn.innerHTML = '−';
|
||||
card.classList.add('expanded');
|
||||
} else {
|
||||
body.style.display = 'none';
|
||||
toggleBtn.innerHTML = '+';
|
||||
card.classList.remove('expanded');
|
||||
}
|
||||
}
|
||||
|
||||
header.addEventListener('click', toggleContent);
|
||||
|
||||
return card;
|
||||
}
|
||||
|
||||
// Initialize page
|
||||
function init() {
|
||||
console.log('Initializing modules page...');
|
||||
|
||||
// Load data
|
||||
if (!initializeData()) {
|
||||
console.error('Failed to initialize data');
|
||||
return;
|
||||
}
|
||||
|
||||
// Populate filters
|
||||
populateFilters();
|
||||
|
||||
// Render modules
|
||||
renderModules();
|
||||
|
||||
// Add event listeners to filters
|
||||
moduleSearch.addEventListener('input', renderModules);
|
||||
stageFilter.addEventListener('change', renderModules);
|
||||
componentFilter.addEventListener('change', renderModules);
|
||||
templateFilter.addEventListener('change', renderModules);
|
||||
|
||||
console.log('Modules page initialized with', allModules.length, 'modules');
|
||||
}
|
||||
|
||||
// Start initialization
|
||||
init();
|
||||
});
|
Reference in New Issue
Block a user