- Added backend routes for DEV Portal dashboard and workflow editor - Created frontend templates for portal and editor using Jinja2 - Integrated draw.io for workflow diagram editing and saving - Developed API endpoints for features, ideas, and workflows management - Established database schema for features, ideas, and workflows - Documented DEV Portal functionality, API endpoints, and database structure
622 lines
24 KiB
HTML
622 lines
24 KiB
HTML
{% extends "shared/frontend/base.html" %}
|
|
|
|
{% block title %}DEV Portal - BMC Hub{% endblock %}
|
|
|
|
{% block extra_css %}
|
|
<style>
|
|
.nav-pills .nav-link {
|
|
color: var(--text-secondary);
|
|
border-radius: 8px;
|
|
padding: 0.75rem 1.5rem;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.nav-pills .nav-link.active {
|
|
background: var(--accent);
|
|
color: white;
|
|
}
|
|
|
|
.feature-card {
|
|
border-left: 4px solid;
|
|
transition: transform 0.2s, box-shadow 0.2s;
|
|
}
|
|
|
|
.feature-card:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 4px 20px rgba(0,0,0,0.1);
|
|
}
|
|
|
|
.status-planlagt { border-color: #6c757d; }
|
|
.status-i-gang { border-color: #0d6efd; }
|
|
.status-færdig { border-color: #198754; }
|
|
.status-sat-på-pause { border-color: #ffc107; }
|
|
|
|
.idea-card {
|
|
transition: transform 0.2s;
|
|
}
|
|
|
|
.idea-card:hover {
|
|
transform: scale(1.02);
|
|
}
|
|
|
|
.vote-button {
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.vote-button:hover {
|
|
transform: scale(1.1);
|
|
color: var(--accent) !important;
|
|
}
|
|
|
|
.workflow-thumbnail {
|
|
width: 100%;
|
|
height: 200px;
|
|
object-fit: cover;
|
|
border-radius: 8px;
|
|
background: #f8f9fa;
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="d-flex justify-content-between align-items-center mb-5">
|
|
<div>
|
|
<h2 class="fw-bold mb-1"><i class="bi bi-code-square me-2"></i>DEV Portal</h2>
|
|
<p class="text-muted mb-0">Roadmap, idéer og workflow dokumentation</p>
|
|
</div>
|
|
<div class="d-flex gap-2" id="actionButtons">
|
|
<!-- Dynamic buttons based on active tab -->
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Stats Cards -->
|
|
<div class="row g-4 mb-4" id="statsCards">
|
|
<div class="col-md-3">
|
|
<div class="card p-3">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<p class="text-muted mb-1">Features</p>
|
|
<h3 class="mb-0" id="featuresCount">-</h3>
|
|
</div>
|
|
<i class="bi bi-flag text-primary" style="font-size: 2rem;"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="card p-3">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<p class="text-muted mb-1">Idéer</p>
|
|
<h3 class="mb-0" id="ideasCount">-</h3>
|
|
</div>
|
|
<i class="bi bi-lightbulb text-warning" style="font-size: 2rem;"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="card p-3">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<p class="text-muted mb-1">Workflows</p>
|
|
<h3 class="mb-0" id="workflowsCount">-</h3>
|
|
</div>
|
|
<i class="bi bi-diagram-3 text-success" style="font-size: 2rem;"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="card p-3">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<p class="text-muted mb-1">I Gang</p>
|
|
<h3 class="mb-0" id="inProgressCount">-</h3>
|
|
</div>
|
|
<i class="bi bi-gear text-info" style="font-size: 2rem;"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Navigation Tabs -->
|
|
<ul class="nav nav-pills mb-4" role="tablist">
|
|
<li class="nav-item">
|
|
<a class="nav-link active" data-bs-toggle="pill" href="#roadmap">
|
|
<i class="bi bi-calendar3 me-2"></i>Roadmap
|
|
</a>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a class="nav-link" data-bs-toggle="pill" href="#ideas">
|
|
<i class="bi bi-lightbulb me-2"></i>Idéer & Brainstorm
|
|
</a>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a class="nav-link" data-bs-toggle="pill" href="#workflows">
|
|
<i class="bi bi-diagram-3 me-2"></i>Workflows
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
|
|
<!-- Tab Content -->
|
|
<div class="tab-content">
|
|
<!-- Roadmap Tab -->
|
|
<div class="tab-pane fade show active" id="roadmap">
|
|
<!-- Version Filter -->
|
|
<div class="mb-4">
|
|
<div class="btn-group" role="group">
|
|
<button type="button" class="btn btn-outline-primary active" onclick="filterByVersion(null)">Alle</button>
|
|
<button type="button" class="btn btn-outline-primary" onclick="filterByVersion('V1')">V1</button>
|
|
<button type="button" class="btn btn-outline-primary" onclick="filterByVersion('V2')">V2</button>
|
|
<button type="button" class="btn btn-outline-primary" onclick="filterByVersion('V3')">V3</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Kanban Board -->
|
|
<div class="row g-4">
|
|
<div class="col-md-3">
|
|
<div class="card p-3">
|
|
<h6 class="fw-bold mb-3 text-secondary">📋 Planlagt</h6>
|
|
<div id="planlagt-features" class="feature-column"></div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="card p-3">
|
|
<h6 class="fw-bold mb-3 text-primary">⚙️ I Gang</h6>
|
|
<div id="i-gang-features" class="feature-column"></div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="card p-3">
|
|
<h6 class="fw-bold mb-3 text-success">✅ Færdig</h6>
|
|
<div id="færdig-features" class="feature-column"></div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="card p-3">
|
|
<h6 class="fw-bold mb-3 text-warning">⏸️ På Pause</h6>
|
|
<div id="sat-på-pause-features" class="feature-column"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Ideas Tab -->
|
|
<div class="tab-pane fade" id="ideas">
|
|
<div class="row g-4" id="ideasGrid">
|
|
<!-- Dynamic ideas cards -->
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Workflows Tab -->
|
|
<div class="tab-pane fade" id="workflows">
|
|
<div class="row g-4" id="workflowsGrid">
|
|
<!-- Dynamic workflow cards -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Create Feature Modal -->
|
|
<div class="modal fade" id="createFeatureModal" tabindex="-1">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">Ny Feature</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form id="createFeatureForm">
|
|
<div class="mb-3">
|
|
<label class="form-label">Titel *</label>
|
|
<input type="text" class="form-control" id="featureTitle" required>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">Beskrivelse</label>
|
|
<textarea class="form-control" id="featureDescription" rows="3"></textarea>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-md-6 mb-3">
|
|
<label class="form-label">Version</label>
|
|
<select class="form-select" id="featureVersion">
|
|
<option value="">Vælg version</option>
|
|
<option value="V1">V1</option>
|
|
<option value="V2">V2</option>
|
|
<option value="V3">V3</option>
|
|
<option value="V4">V4</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-6 mb-3">
|
|
<label class="form-label">Status</label>
|
|
<select class="form-select" id="featureStatus">
|
|
<option value="planlagt">Planlagt</option>
|
|
<option value="i gang">I Gang</option>
|
|
<option value="færdig">Færdig</option>
|
|
<option value="sat på pause">På Pause</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-md-6 mb-3">
|
|
<label class="form-label">Prioritet (0-100)</label>
|
|
<input type="number" class="form-control" id="featurePriority" value="50" min="0" max="100">
|
|
</div>
|
|
<div class="col-md-6 mb-3">
|
|
<label class="form-label">Forventet Dato</label>
|
|
<input type="date" class="form-control" id="featureDate">
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annuller</button>
|
|
<button type="button" class="btn btn-primary" onclick="createFeature()">Opret Feature</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Create Idea Modal -->
|
|
<div class="modal fade" id="createIdeaModal" tabindex="-1">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">Ny Idé</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form id="createIdeaForm">
|
|
<div class="mb-3">
|
|
<label class="form-label">Titel *</label>
|
|
<input type="text" class="form-control" id="ideaTitle" required>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">Beskrivelse</label>
|
|
<textarea class="form-control" id="ideaDescription" rows="3"></textarea>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">Kategori</label>
|
|
<select class="form-select" id="ideaCategory">
|
|
<option value="feature">Feature</option>
|
|
<option value="improvement">Forbedring</option>
|
|
<option value="bugfix">Bugfix</option>
|
|
<option value="research">Research</option>
|
|
</select>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annuller</button>
|
|
<button type="button" class="btn btn-primary" onclick="createIdea()">Opret Idé</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{% endblock %}
|
|
|
|
{% block extra_js %}
|
|
<script>
|
|
let allFeatures = [];
|
|
let currentVersionFilter = null;
|
|
|
|
// Helper functions to open modals
|
|
function openCreateFeatureModal() {
|
|
const modal = new bootstrap.Modal(document.getElementById('createFeatureModal'));
|
|
modal.show();
|
|
}
|
|
|
|
function openCreateIdeaModal() {
|
|
const modal = new bootstrap.Modal(document.getElementById('createIdeaModal'));
|
|
modal.show();
|
|
}
|
|
|
|
async function loadStats() {
|
|
try {
|
|
const response = await fetch('/api/v1/devportal/stats');
|
|
if (!response.ok) throw new Error('Kunne ikke hente statistik');
|
|
const data = await response.json();
|
|
|
|
document.getElementById('featuresCount').textContent = data.features_count;
|
|
document.getElementById('ideasCount').textContent = data.ideas_count;
|
|
document.getElementById('workflowsCount').textContent = data.workflows_count;
|
|
|
|
const inProgress = data.features_by_status.find(s => s.status === 'i gang');
|
|
document.getElementById('inProgressCount').textContent = inProgress ? inProgress.count : 0;
|
|
} catch (error) {
|
|
console.error('Error loading stats:', error);
|
|
}
|
|
}
|
|
|
|
async function loadFeatures() {
|
|
try {
|
|
const response = await fetch('/api/v1/devportal/features');
|
|
if (!response.ok) throw new Error('Kunne ikke hente features');
|
|
allFeatures = await response.json();
|
|
console.log(`📊 Loaded ${allFeatures.length} features`);
|
|
displayFeatures();
|
|
} catch (error) {
|
|
console.error('Error loading features:', error);
|
|
alert('Fejl ved indlæsning af features: ' + error.message);
|
|
}
|
|
}
|
|
|
|
function displayFeatures() {
|
|
const features = currentVersionFilter
|
|
? allFeatures.filter(f => f.version === currentVersionFilter)
|
|
: allFeatures;
|
|
|
|
console.log(`🎯 Displaying ${features.length} features (filter: ${currentVersionFilter || 'none'})`);
|
|
|
|
// Clear columns
|
|
['planlagt', 'i gang', 'færdig', 'sat på pause'].forEach(status => {
|
|
const column = document.getElementById(`${status}-features`);
|
|
if (column) column.innerHTML = '';
|
|
});
|
|
|
|
features.forEach(feature => {
|
|
const column = document.getElementById(`${feature.status}-features`);
|
|
if (!column) return;
|
|
|
|
const card = document.createElement('div');
|
|
card.className = `card feature-card status-${feature.status} p-3 mb-2`;
|
|
card.innerHTML = `
|
|
<div class="d-flex justify-content-between align-items-start mb-2">
|
|
<h6 class="fw-bold mb-0">${feature.title}</h6>
|
|
<div>
|
|
${feature.version ? `<span class="badge bg-secondary me-1">${feature.version}</span>` : ''}
|
|
<button class="btn btn-sm btn-link text-danger p-0" onclick="deleteFeature(${feature.id})" title="Slet">
|
|
<i class="bi bi-trash"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
${feature.description ? `<p class="small text-muted mb-2">${feature.description}</p>` : ''}
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<small class="text-muted">Prioritet: ${feature.priority}</small>
|
|
${feature.expected_date ? `<small class="text-muted">${new Date(feature.expected_date).toLocaleDateString('da-DK')}</small>` : ''}
|
|
</div>
|
|
`;
|
|
column.appendChild(card);
|
|
});
|
|
}
|
|
|
|
function filterByVersion(version) {
|
|
currentVersionFilter = version;
|
|
|
|
// Update active button
|
|
document.querySelectorAll('.btn-group .btn').forEach(btn => btn.classList.remove('active'));
|
|
event.target.classList.add('active');
|
|
|
|
displayFeatures();
|
|
}
|
|
|
|
async function loadIdeas() {
|
|
try {
|
|
const response = await fetch('/api/v1/devportal/ideas');
|
|
if (!response.ok) throw new Error('Kunne ikke hente idéer');
|
|
const ideas = await response.json();
|
|
|
|
const grid = document.getElementById('ideasGrid');
|
|
grid.innerHTML = ideas.map(idea => `
|
|
<div class="col-md-4">
|
|
<div class="card idea-card p-3 h-100">
|
|
<div class="d-flex justify-content-between align-items-start mb-2">
|
|
<h6 class="fw-bold">${idea.title}</h6>
|
|
<span class="badge bg-primary">${idea.category || 'general'}</span>
|
|
</div>
|
|
${idea.description ? `<p class="text-muted small mb-3">${idea.description}</p>` : ''}
|
|
<div class="mt-auto d-flex justify-content-between align-items-center">
|
|
<div class="vote-button" onclick="voteIdea(${idea.id})">
|
|
<i class="bi bi-hand-thumbs-up me-1"></i>
|
|
<span>${idea.votes}</span>
|
|
</div>
|
|
<button class="btn btn-sm btn-outline-danger" onclick="deleteIdea(${idea.id})">
|
|
<i class="bi bi-trash"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`).join('');
|
|
} catch (error) {
|
|
console.error('Error loading ideas:', error);
|
|
alert('Fejl ved indlæsning af idéer: ' + error.message);
|
|
}
|
|
}
|
|
|
|
async function loadWorkflows() {
|
|
try {
|
|
const response = await fetch('/api/v1/devportal/workflows');
|
|
if (!response.ok) throw new Error('Kunne ikke hente workflows');
|
|
const workflows = await response.json();
|
|
|
|
const grid = document.getElementById('workflowsGrid');
|
|
grid.innerHTML = workflows.map(wf => `
|
|
<div class="col-md-4">
|
|
<div class="card p-3">
|
|
<div class="workflow-thumbnail mb-3 d-flex align-items-center justify-content-center">
|
|
<i class="bi bi-diagram-3" style="font-size: 3rem; opacity: 0.3;"></i>
|
|
</div>
|
|
<h6 class="fw-bold">${wf.title}</h6>
|
|
${wf.description ? `<p class="text-muted small mb-3">${wf.description}</p>` : ''}
|
|
<div class="d-flex gap-2">
|
|
<a href="/devportal/editor?id=${wf.id}" class="btn btn-sm btn-primary flex-grow-1">
|
|
<i class="bi bi-pencil me-1"></i>Rediger
|
|
</a>
|
|
<button class="btn btn-sm btn-outline-danger" onclick="deleteWorkflow(${wf.id})">
|
|
<i class="bi bi-trash"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`).join('');
|
|
} catch (error) {
|
|
console.error('Error loading workflows:', error);
|
|
alert('Fejl ved indlæsning af workflows: ' + error.message);
|
|
}
|
|
}
|
|
|
|
async function createFeature() {
|
|
const feature = {
|
|
title: document.getElementById('featureTitle').value,
|
|
description: document.getElementById('featureDescription').value,
|
|
version: document.getElementById('featureVersion').value,
|
|
status: document.getElementById('featureStatus').value,
|
|
priority: parseInt(document.getElementById('featurePriority').value),
|
|
expected_date: document.getElementById('featureDate').value || null
|
|
};
|
|
|
|
if (!feature.title) {
|
|
alert('Titel er påkrævet');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await fetch('/api/v1/devportal/features', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(feature)
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Kunne ikke oprette feature');
|
|
}
|
|
|
|
const modal = bootstrap.Modal.getInstance(document.getElementById('createFeatureModal'));
|
|
if (modal) modal.hide();
|
|
document.getElementById('createFeatureForm').reset();
|
|
await loadFeatures();
|
|
await loadStats();
|
|
console.log('✅ Feature created successfully');
|
|
} catch (error) {
|
|
console.error('Error creating feature:', error);
|
|
alert('Fejl ved oprettelse af feature: ' + error.message);
|
|
}
|
|
}
|
|
|
|
async function createIdea() {
|
|
const idea = {
|
|
title: document.getElementById('ideaTitle').value,
|
|
description: document.getElementById('ideaDescription').value,
|
|
category: document.getElementById('ideaCategory').value
|
|
};
|
|
|
|
if (!idea.title) {
|
|
alert('Titel er påkrævet');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await fetch('/api/v1/devportal/ideas', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(idea)
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Kunne ikke oprette idé');
|
|
}
|
|
|
|
const modal = bootstrap.Modal.getInstance(document.getElementById('createIdeaModal'));
|
|
if (modal) modal.hide();
|
|
document.getElementById('createIdeaForm').reset();
|
|
await loadIdeas();
|
|
await loadStats();
|
|
console.log('✅ Idea created successfully');
|
|
} catch (error) {
|
|
console.error('Error creating idea:', error);
|
|
alert('Fejl ved oprettelse af idé: ' + error.message);
|
|
}
|
|
}
|
|
|
|
async function voteIdea(id) {
|
|
try {
|
|
const response = await fetch(`/api/v1/devportal/ideas/${id}/vote`, { method: 'POST' });
|
|
if (!response.ok) {
|
|
throw new Error('Kunne ikke stemme');
|
|
}
|
|
await loadIdeas();
|
|
} catch (error) {
|
|
console.error('Error voting:', error);
|
|
alert('Fejl ved stemning: ' + error.message);
|
|
}
|
|
}
|
|
|
|
async function deleteIdea(id) {
|
|
if (!confirm('Er du sikker på at du vil slette denne idé?')) return;
|
|
try {
|
|
const response = await fetch(`/api/v1/devportal/ideas/${id}`, { method: 'DELETE' });
|
|
if (!response.ok) {
|
|
throw new Error('Kunne ikke slette idé');
|
|
}
|
|
await loadIdeas();
|
|
await loadStats();
|
|
} catch (error) {
|
|
console.error('Error deleting idea:', error);
|
|
alert('Fejl ved sletning: ' + error.message);
|
|
}
|
|
}
|
|
|
|
async function deleteFeature(id) {
|
|
if (!confirm('Er du sikker på at du vil slette denne feature?')) return;
|
|
try {
|
|
const response = await fetch(`/api/v1/devportal/features/${id}`, { method: 'DELETE' });
|
|
if (!response.ok) {
|
|
throw new Error('Kunne ikke slette feature');
|
|
}
|
|
await loadFeatures();
|
|
await loadStats();
|
|
} catch (error) {
|
|
console.error('Error deleting feature:', error);
|
|
alert('Fejl ved sletning: ' + error.message);
|
|
}
|
|
}
|
|
|
|
async function deleteWorkflow(id) {
|
|
if (!confirm('Er du sikker på at du vil slette denne workflow?')) return;
|
|
try {
|
|
const response = await fetch(`/api/v1/devportal/workflows/${id}`, { method: 'DELETE' });
|
|
if (!response.ok) {
|
|
throw new Error('Kunne ikke slette workflow');
|
|
}
|
|
await loadWorkflows();
|
|
await loadStats();
|
|
} catch (error) {
|
|
console.error('Error deleting workflow:', error);
|
|
alert('Fejl ved sletning: ' + error.message);
|
|
}
|
|
}
|
|
|
|
// Tab change handling
|
|
document.querySelectorAll('a[data-bs-toggle="pill"]').forEach(tab => {
|
|
tab.addEventListener('shown.bs.tab', (e) => {
|
|
const target = e.target.getAttribute('href');
|
|
const buttons = document.getElementById('actionButtons');
|
|
|
|
if (target === '#roadmap') {
|
|
buttons.innerHTML = '<button class="btn btn-primary" onclick="openCreateFeatureModal()"><i class="bi bi-plus-lg me-2"></i>Ny Feature</button>';
|
|
} else if (target === '#ideas') {
|
|
buttons.innerHTML = '<button class="btn btn-primary" onclick="openCreateIdeaModal()"><i class="bi bi-plus-lg me-2"></i>Ny Idé</button>';
|
|
} else if (target === '#workflows') {
|
|
buttons.innerHTML = '<a href="/devportal/editor" class="btn btn-primary"><i class="bi bi-plus-lg me-2"></i>Ny Workflow</a>';
|
|
}
|
|
});
|
|
});
|
|
|
|
// Initial load
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
console.log('DEV Portal loaded - functions available:', {
|
|
openCreateFeatureModal: typeof openCreateFeatureModal,
|
|
openCreateIdeaModal: typeof openCreateIdeaModal,
|
|
createFeature: typeof createFeature,
|
|
createIdea: typeof createIdea
|
|
});
|
|
|
|
loadStats();
|
|
loadFeatures();
|
|
loadIdeas();
|
|
loadWorkflows();
|
|
|
|
// Set initial button
|
|
document.getElementById('actionButtons').innerHTML = '<button class="btn btn-primary" onclick="openCreateFeatureModal()"><i class="bi bi-plus-lg me-2"></i>Ny Feature</button>';
|
|
});
|
|
</script>
|
|
{% endblock %}
|