progression 50% pour evolution des joueurs
CI - Security, Lint & Tests / validate (push) Failing after 1m13s
CI - Security, Lint & Tests / validate (push) Failing after 1m13s
This commit is contained in:
@@ -68,6 +68,12 @@
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li>
|
||||
<a href="{{ url_for('objectives.list_objectives') }}" class="{% if request.endpoint and 'objectives' in request.endpoint %}active{% endif %}">
|
||||
<i class="fas fa-bullseye"></i>
|
||||
<span>{{ _('Objectives') }}</span>
|
||||
</a>
|
||||
</li>
|
||||
{% if current_user.role == 'player' %}
|
||||
<li>
|
||||
<a href="{{ url_for('teams.my_teams') }}" class="{% if request.endpoint == 'teams.my_teams' %}active{% endif %}">
|
||||
|
||||
@@ -0,0 +1,348 @@
|
||||
{% extends "layouts/base.html" %}
|
||||
{% block title %}{% if edit_mode %}{{ _('Edit Objective') }}{% else %}{{ _('New Objective') }}{% endif %} - UdeS team manager{% endblock %}
|
||||
{% block page_title %}{% if edit_mode %}{{ _('Edit Objective') }}{% else %}{{ _('New Objective') }}{% endif %}{% endblock %}
|
||||
{% block breadcrumb %}<span class="breadcrumb">{{ _('Home') }} / <a href="{{ url_for('objectives.list_objectives') }}">{{ _('Objectives') }}</a> / {% if edit_mode %}{{ _('Edit') }}{% else %}{{ _('New') }}{% endif %}</span>{% endblock %}
|
||||
{% block content %}
|
||||
<div class="objective-editor">
|
||||
<div class="objective-editor-form">
|
||||
<div class="card">
|
||||
<div class="card-header"><h3><i class="fas fa-edit"></i> {% if edit_mode %}{{ _('Edit Objective') }}{% else %}{{ _('Create Objective') }}{% endif %}</h3></div>
|
||||
<div class="card-body">
|
||||
<form method="POST" action="{% if edit_mode %}{{ url_for('objectives.update_objective', objective_id=objective.id) }}{% else %}{{ url_for('objectives.create_objective') }}{% endif %}" class="form" id="objForm">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<div class="form-section">
|
||||
<h4><i class="fas fa-info-circle"></i> {{ _('1. General Information') }}</h4>
|
||||
<div class="form-row">
|
||||
<div class="form-group col-6">
|
||||
<label for="obj_type">{{ _('Type') }}</label>
|
||||
<select name="type" id="obj_type" class="form-select" onchange="updatePreview();toggleAssignment()">
|
||||
{% for val, label in objective_types %}<option value="{{ val }}"{% if objective and objective.type == val %} selected{% endif %}>{{ label }}</option>{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group col-6">
|
||||
<label for="obj_status">{{ _('Status') }}</label>
|
||||
<select name="status" id="obj_status" class="form-select" onchange="updatePreview()">
|
||||
{% for val, label in objective_statuses %}<option value="{{ val }}"{% if objective and objective.status == val %} selected{% endif %}>{{ label }}</option>{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="obj_title">{{ _('Title') }} <span class="required">*</span></label>
|
||||
<input type="text" name="title" id="obj_title" class="form-input" required value="{{ objective.title if objective else '' }}" placeholder="{{ _('e.g. Improve headshot accuracy') }}" oninput="updatePreview()">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="obj_description">{{ _('Description') }}</label>
|
||||
<textarea name="description" id="obj_description" rows="3" class="form-textarea" placeholder="{{ _('Describe the objective...') }}" oninput="updatePreview()">{{ objective.description if objective else '' }}</textarea>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group col-6" id="playerField">
|
||||
<label for="obj_player_id">{{ _('Assigned to (Player)') }}</label>
|
||||
<select name="player_id" id="obj_player_id" class="form-select" onchange="updatePreview()">
|
||||
<option value="">{{ _('-- Select a player --') }}</option>
|
||||
{% for p in players %}<option value="{{ p.id }}"{% if objective and objective.player_id == p.id %} selected{% endif %}>{{ p.username }}</option>{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group col-6" id="teamField">
|
||||
<label for="obj_team_id">{{ _('Team') }}</label>
|
||||
<select name="team_id" id="obj_team_id" class="form-select" onchange="updatePreview()">
|
||||
<option value="">{{ _('-- Select a team --') }}</option>
|
||||
{% for t in teams %}<option value="{{ t.id }}"{% if objective and objective.team_id == t.id %} selected{% endif %}>{{ t.name }}</option>{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="checkbox-label"><input type="checkbox" name="is_template" id="obj_is_template" value="1"{% if objective and objective.is_template %} checked{% endif %} onchange="updatePreview();toggleAssignment()"> {{ _('This is a template (reusable pattern)') }}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-section">
|
||||
<h4><i class="fas fa-chart-line"></i> {{ _('2. KPI & Target') }}</h4>
|
||||
<div class="form-row">
|
||||
<div class="form-group col-4">
|
||||
<label for="obj_kpi_metric">{{ _('KPI metric') }}</label>
|
||||
<input type="text" name="kpi_metric" id="obj_kpi_metric" class="form-input" value="{{ objective.kpi_metric if objective else '' }}" placeholder="{{ _('e.g. HS rate, KDA, CS/min') }}" oninput="updatePreview()">
|
||||
</div>
|
||||
<div class="form-group col-4">
|
||||
<label for="obj_kpi_target">{{ _('Target value') }}</label>
|
||||
<input type="number" step="0.01" name="kpi_target" id="obj_kpi_target" class="form-input" value="{{ objective.kpi_target if objective else '' }}" placeholder="{{ _('e.g. 40') }}" oninput="updatePreview()">
|
||||
</div>
|
||||
<div class="form-group col-4">
|
||||
<label for="obj_kpi_unit">{{ _('Unit') }}</label>
|
||||
<input type="text" name="kpi_unit" id="obj_kpi_unit" class="form-input" value="{{ objective.kpi_unit if objective else '' }}" placeholder="{{ _('e.g. points, percentage') }}" oninput="updatePreview()">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-section">
|
||||
<h4><i class="fas fa-calendar-alt"></i> {{ _('3. Deadline & Priority') }}</h4>
|
||||
<div class="form-row">
|
||||
<div class="form-group col-4">
|
||||
<label for="obj_start_date">{{ _('Start date') }}</label>
|
||||
<input type="date" name="start_date" id="obj_start_date" class="form-input" value="{{ objective.start_date.strftime('%Y-%m-%d') if objective and objective.start_date else '' }}" onchange="updatePreview()">
|
||||
</div>
|
||||
<div class="form-group col-4">
|
||||
<label for="obj_due_date">{{ _('Deadline') }} <span class="required">*</span></label>
|
||||
<input type="date" name="due_date" id="obj_due_date" class="form-input" required value="{{ objective.due_date.strftime('%Y-%m-%d') if objective else '' }}" onchange="updatePreview()">
|
||||
</div>
|
||||
<div class="form-group col-4">
|
||||
<label for="obj_priority">{{ _('Priority') }}</label>
|
||||
<select name="priority" id="obj_priority" class="form-select" onchange="updatePreview()">
|
||||
{% for val, label in objective_priorities %}<option value="{{ val }}"{% if objective and objective.priority == val %} selected{% endif %}>{{ label }}</option>{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-section">
|
||||
<h4><i class="fas fa-bell"></i> {{ _('4. Tracking & Alerts') }}</h4>
|
||||
<div class="form-row">
|
||||
<div class="form-group col-6">
|
||||
<label for="obj_check_frequency">{{ _('Check frequency') }}</label>
|
||||
<select name="check_frequency" id="obj_check_frequency" class="form-select">
|
||||
<option value="">{{ _('-- None --') }}</option>
|
||||
{% for val, label in objective_frequencies %}<option value="{{ val }}"{% if objective and objective.check_frequency == val %} selected{% endif %}>{{ label }}</option>{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group col-6">
|
||||
<label for="obj_progress">{{ _('Progress') }} (<span id="progressVal">{{ objective.progress if objective else 0 }}</span>%)</label>
|
||||
<input type="range" id="obj_progress" name="progress" min="0" max="100" value="{{ objective.progress if objective else 0 }}" class="form-range" oninput="document.getElementById('progressVal').textContent=this.value;updatePreview()">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-section">
|
||||
<h4><i class="fas fa-cog"></i> {{ _('5. Advanced Settings') }}</h4>
|
||||
<div class="form-group">
|
||||
<label for="obj_category">{{ _('Category') }}</label>
|
||||
<input type="text" name="category" id="obj_category" class="form-input" value="{{ objective.category if objective else '' }}" placeholder="{{ _('e.g. Mechanics, Teamplay, Communication') }}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="obj_tags">{{ _('Tags') }}</label>
|
||||
<input type="text" name="tags" id="obj_tags" class="form-input" value="{{ objective.tags if objective else '' }}" placeholder="{{ _('Separate with commas') }}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<a href="{{ url_for('objectives.list_objectives') }}" class="btn btn-secondary">{{ _('Cancel') }}</a>
|
||||
<button type="submit" class="btn btn-primary"><i class="fas fa-save"></i> {% if edit_mode %}{{ _('Update') }}{% else %}{{ _('Create') }}{% endif %}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="objective-editor-preview">
|
||||
<div class="card mb-4" id="previewCard">
|
||||
<div class="card-header"><h3><i class="fas fa-eye"></i> {{ _('Player Preview') }}</h3></div>
|
||||
<div class="card-body">
|
||||
<div id="previewContent">
|
||||
<h4 id="previewTitle">{{ objective.title if objective else _('Objective title') }}</h4>
|
||||
<p id="previewDesc" class="text-muted small">{{ objective.description if objective else '' }}</p>
|
||||
<div class="objective-tags" id="previewTags">{% if objective and objective.tags %}{% for tag in objective.tags.split(',') %}<span class="badge badge-light">{{ tag.strip() }}</span>{% endfor %}{% endif %}</div>
|
||||
<div class="preview-stats">
|
||||
<div class="preview-stat"><span class="preview-label">{{ _('Assigned to') }}</span><span id="previewAssignee" class="preview-value">{% if objective and objective.player %}{{ objective.player.username }}{% else %}-{% endif %}</span></div>
|
||||
<div class="preview-stat"><span class="preview-label">{{ _('KPI') }}</span><span id="previewKpi" class="preview-value">{% if objective and objective.kpi_metric %}{{ objective.kpi_metric }}: {{ objective.kpi_target }}{{ objective.kpi_unit }}{% else %}-{% endif %}</span></div>
|
||||
<div class="preview-stat"><span class="preview-label">{{ _('Deadline') }}</span><span id="previewDeadline" class="preview-value">{% if objective and objective.due_date %}{{ objective.due_date.strftime('%Y-%m-%d') }}{% endif %}</span></div>
|
||||
</div>
|
||||
<div class="progress-section">
|
||||
<span class="preview-label">{{ _('Progress') }}</span>
|
||||
<div class="progress-bar-container" style="width:100%"><div class="progress-bar" id="previewBar" style="width:{{ objective.progress if objective else 0 }}%"></div></div>
|
||||
<strong><span id="previewProgress">{{ objective.progress if objective else 0 }}</span>%</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3">
|
||||
<span id="previewStatusBadge" class="badge">{% if objective %}{% for val, label in objective_statuses %}{% if val == objective.status %}{{ label }}{% endif %}{% endfor %}{% else %}{{ _('Active') }}{% endif %}</span>
|
||||
<span id="previewPriorityBadge" class="badge">{% if objective %}{{ {'high':_('High'),'medium':_('Medium'),'low':_('Low')}.get(objective.priority,objective.priority) }}{% else %}{{ _('Medium') }}{% endif %}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card mb-4">
|
||||
<div class="card-header"><h3><i class="fas fa-chart-line"></i> {{ _('KPI Progression') }}</h3></div>
|
||||
<div class="card-body">
|
||||
<svg id="progressChart" viewBox="0 0 400 200" style="width:100%;height:auto;" xmlns="http://www.w3.org/2000/svg"></svg>
|
||||
{% if edit_mode and objective.kpi_target %}
|
||||
<div class="mt-2 text-center" id="predictionInfo"></div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% if edit_mode %}
|
||||
<div class="card mb-4">
|
||||
<div class="card-header"><h3><i class="fas fa-database"></i> {{ _('KPI Measurements') }}</h3></div>
|
||||
<div class="card-body">
|
||||
<form method="POST" action="{{ url_for('objectives.add_entry', objective_id=objective.id) }}" class="form-inline">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<div class="form-group" style="flex:1;">
|
||||
<input type="number" step="0.01" name="value" class="form-input" placeholder="{{ _('Value') }}" required>
|
||||
</div>
|
||||
<div class="form-group" style="flex:2;">
|
||||
<input type="text" name="note" class="form-input" placeholder="{{ _('Note (optional)') }}">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-sm btn-primary"><i class="fas fa-plus"></i> {{ _('Record') }}</button>
|
||||
</form>
|
||||
{% if entries %}
|
||||
<table class="table mt-2">
|
||||
<thead><tr><th>{{ _('Date') }}</th><th>{{ _('Value') }}</th><th>{{ _('Note') }}</th></tr></thead>
|
||||
<tbody>
|
||||
{% for e in entries %}
|
||||
<tr><td>{{ e.recorded_at.strftime('%Y-%m-%d %H:%M') }}</td><td><strong>{{ e.value }}</strong></td><td class="text-muted small">{{ e.note or '' }}</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="card mb-4">
|
||||
<div class="card-header"><h3><i class="fas fa-comments"></i> {{ _('Comments') }}</h3></div>
|
||||
<div class="card-body">
|
||||
<form method="POST" action="{{ url_for('objectives.add_comment', objective_id=objective.id) }}" class="form">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<div class="form-group">
|
||||
<textarea name="content" rows="2" class="form-textarea" placeholder="{{ _('Add a comment...') }}" required></textarea>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-sm btn-primary"><i class="fas fa-paper-plane"></i> {{ _('Comment') }}</button>
|
||||
</form>
|
||||
{% if comments %}
|
||||
<div class="comments-list mt-2">
|
||||
{% for c in comments %}
|
||||
<div class="comment-item">
|
||||
<strong>{{ c.coach.username }}</strong> <span class="text-muted small">{{ c.created_at.strftime('%Y-%m-%d %H:%M') }}</span>
|
||||
<p>{{ c.content }}</p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% if personal_notes or team_notes %}
|
||||
<div class="card mb-4">
|
||||
<div class="card-header"><h3><i class="fas fa-sticky-note"></i> {{ _('Related Notes') }}</h3></div>
|
||||
<div class="card-body">
|
||||
{% if personal_notes %}
|
||||
<h5>{{ _('Player Notes') }}</h5>
|
||||
{% for n in personal_notes %}
|
||||
<div class="comment-item">
|
||||
<strong>{{ n.coach.username }}</strong> <span class="text-muted small">{{ n.created_at.strftime('%Y-%m-%d') }}</span>
|
||||
<p>{{ n.content[:200] }}</p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if team_notes %}
|
||||
<h5 class="mt-2">{{ _('Team Notes') }}</h5>
|
||||
{% for n in team_notes %}
|
||||
<div class="comment-item">
|
||||
<strong>{{ n.coach.username }}</strong> <span class="text-muted small">{{ n.created_at.strftime('%Y-%m-%d') }}</span>
|
||||
<p>{{ n.content[:200] }}</p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if templates %}
|
||||
<div class="card">
|
||||
<div class="card-header"><h3><i class="fas fa-copy"></i> {{ _('Templates') }}</h3></div>
|
||||
<div class="card-body" id="templatesList">
|
||||
{% for t in templates %}
|
||||
<div class="template-item">
|
||||
<h5>{{ t.title }}</h5>
|
||||
<p class="text-muted small">{{ t.description[:120] if t.description else '' }}</p>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary use-template-btn" onclick="applyTemplate({{ t.id }})"><i class="fas fa-arrow-right"></i> {{ _('Use this template') }}</button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block scripts %}
|
||||
<script>
|
||||
var templateDataUrl = '{{ url_for("objectives.objective_data", objective_id=0) }}';
|
||||
var templates = {};
|
||||
{% for t in templates %}
|
||||
(function(id) {
|
||||
fetch(templateDataUrl.replace('/0/', '/' + id + '/')).then(function(r) { return r.json(); }).then(function(d) { templates[id] = d; });
|
||||
})({{ t.id }});
|
||||
{% endfor %}
|
||||
|
||||
function toggleAssignment() {
|
||||
var t = document.getElementById('obj_type').value;
|
||||
var isTmpl = document.getElementById('obj_is_template').checked;
|
||||
var pf = document.getElementById('playerField');
|
||||
var tf = document.getElementById('teamField');
|
||||
if (isTmpl) { pf.style.display = 'none'; tf.style.display = 'none'; return; }
|
||||
pf.style.display = t === 'team' ? 'none' : '';
|
||||
tf.style.display = t === 'individual' ? 'none' : '';
|
||||
}
|
||||
function applyTemplate(id) {
|
||||
var t = templates[id]; if (!t) return;
|
||||
document.getElementById('obj_title').value = t.title || '';
|
||||
document.getElementById('obj_description').value = t.description || '';
|
||||
document.getElementById('obj_type').value = t.type || 'individual';
|
||||
document.getElementById('obj_status').value = t.status || 'active';
|
||||
document.getElementById('obj_priority').value = t.priority || 'medium';
|
||||
document.getElementById('obj_kpi_metric').value = t.kpi_metric || '';
|
||||
document.getElementById('obj_kpi_target').value = t.kpi_target || '';
|
||||
document.getElementById('obj_kpi_unit').value = t.kpi_unit || '';
|
||||
document.getElementById('obj_start_date').value = t.start_date || '';
|
||||
document.getElementById('obj_due_date').value = '';
|
||||
document.getElementById('obj_check_frequency').value = t.check_frequency || '';
|
||||
document.getElementById('obj_category').value = t.category || '';
|
||||
document.getElementById('obj_tags').value = t.tags || '';
|
||||
document.getElementById('obj_progress').value = 0;
|
||||
document.getElementById('progressVal').textContent = '0';
|
||||
toggleAssignment(); updatePreview();
|
||||
}
|
||||
function updatePreview() {
|
||||
var title = document.getElementById('obj_title').value || '';
|
||||
var desc = document.getElementById('obj_description').value || '';
|
||||
var status = document.getElementById('obj_status').value;
|
||||
var priority = document.getElementById('obj_priority').value;
|
||||
var kpiMetric = document.getElementById('obj_kpi_metric').value;
|
||||
var kpiTarget = document.getElementById('obj_kpi_target').value;
|
||||
var kpiUnit = document.getElementById('obj_kpi_unit').value;
|
||||
var deadline = document.getElementById('obj_due_date').value;
|
||||
var progress = document.getElementById('obj_progress').value;
|
||||
var sel = document.getElementById('obj_player_id');
|
||||
var playerName = sel.options[sel.selectedIndex] ? sel.options[sel.selectedIndex].text : '-';
|
||||
document.getElementById('previewTitle').textContent = title || '{{ _('Objective title') | e }}';
|
||||
document.getElementById('previewDesc').textContent = desc;
|
||||
document.getElementById('previewAssignee').textContent = playerName;
|
||||
document.getElementById('previewKpi').textContent = kpiMetric ? kpiMetric + ': ' + kpiTarget + (kpiUnit || '') : '-';
|
||||
document.getElementById('previewDeadline').textContent = deadline;
|
||||
document.getElementById('previewBar').style.width = progress + '%';
|
||||
document.getElementById('previewProgress').textContent = progress;
|
||||
var sl = {}, sc = {'active':'badge-success','pending':'badge-warning','completed':'badge-info','overdue':'badge-danger'};
|
||||
var pl = {}, pc = {'high':'badge-danger','medium':'badge-warning','low':'badge-secondary'};
|
||||
{% for val, label in objective_statuses %}sl['{{ val }}'] = '{{ label | e }}';{% endfor %}
|
||||
{% for val, label in objective_priorities %}pl['{{ val }}'] = '{{ label | e }}';{% endfor %}
|
||||
document.getElementById('previewStatusBadge').textContent = sl[status] || status;
|
||||
document.getElementById('previewStatusBadge').className = 'badge ' + (sc[status] || 'badge-secondary');
|
||||
document.getElementById('previewPriorityBadge').textContent = pl[priority] || priority;
|
||||
document.getElementById('previewPriorityBadge').className = 'badge ' + (pc[priority] || 'badge-secondary');
|
||||
drawChart(progress, kpiTarget);
|
||||
}
|
||||
function drawChart(progress, target) {
|
||||
var svg = document.getElementById('progressChart');
|
||||
var w = 400, h = 200, pt = 20, pr = 20, pb = 30, pl = 40;
|
||||
var cw = w - pl - pr, ch = h - pt - pb;
|
||||
var prog = Math.min(parseInt(progress) || 0, 100);
|
||||
var py = pt + ch * (1 - prog / 100);
|
||||
var tgt = parseFloat(target) || 100;
|
||||
var tgtScale = Math.max(tgt, prog);
|
||||
var tgtNorm = tgt / (tgtScale);
|
||||
var ty = pt + ch * (1 - tgtNorm);
|
||||
var html = '<rect x="0" y="0" width="' + w + '" height="' + h + '" fill="transparent"/>';
|
||||
html += '<line x1="' + pl + '" y1="' + (pt + ch) + '" x2="' + (pl + cw) + '" y2="' + (pt + ch) + '" stroke="#ddd" stroke-width="1"/>';
|
||||
html += '<line x1="' + pl + '" y1="' + pt + '" x2="' + pl + '" y2="' + (pt + ch) + '" stroke="#ddd" stroke-width="1"/>';
|
||||
html += '<text x="' + (pl - 30) + '" y="' + (pt + 5) + '" fill="#999" font-size="10">' + tgtScale + '</text>';
|
||||
html += '<text x="' + (pl - 30) + '" y="' + (pt + ch / 2 + 4) + '" fill="#999" font-size="10">' + (tgtScale / 2).toFixed(1) + '</text>';
|
||||
html += '<text x="' + (pl - 30) + '" y="' + (pt + ch + 4) + '" fill="#999" font-size="10">0</text>';
|
||||
html += '<text x="' + pl + '" y="' + (pt + ch + 18) + '" fill="#999" font-size="10">{{ _('Now') | e }}</text>';
|
||||
html += '<text x="' + (pl + cw - 50) + '" y="' + (pt + ch + 18) + '" fill="#999" font-size="10">{{ _('Deadline') | e }}</text>';
|
||||
html += '<line x1="' + pl + '" y1="' + ty + '" x2="' + (pl + cw) + '" y2="' + ty + '" stroke="#4CAF50" stroke-width="1.5" stroke-dasharray="4,3"/>';
|
||||
html += '<text x="' + (pl + cw + 4) + '" y="' + (ty + 4) + '" fill="#4CAF50" font-size="9">{{ _('Target') | e }}</text>';
|
||||
html += '<polygon points="' + pl + ',' + (pt + ch) + ' ' + pl + ',' + py + ' ' + (pl + (prog / 100) * cw) + ',' + py + ' ' + (pl + (prog / 100) * cw) + ',' + (pt + ch) + '" fill="rgba(59,130,246,0.15)"/>';
|
||||
html += '<line x1="' + pl + '" y1="' + py + '" x2="' + (pl + (prog / 100) * cw) + '" y2="' + py + '" stroke="#3B82F6" stroke-width="2"/>';
|
||||
html += '<circle cx="' + (pl + (prog / 100) * cw) + '" cy="' + py + '" r="4" fill="#3B82F6"/>';
|
||||
html += '<text x="' + (pl + (prog / 100) * cw - 12) + '" y="' + (py - 8) + '" fill="#3B82F6" font-size="10" font-weight="bold">' + prog + '%</text>';
|
||||
svg.innerHTML = html;
|
||||
}
|
||||
document.addEventListener('DOMContentLoaded', function() { toggleAssignment(); updatePreview(); });
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,125 @@
|
||||
{% extends "layouts/base.html" %}
|
||||
{% block title %}{{ objective.title }} - UdeS team manager{% endblock %}
|
||||
{% block page_title %}{{ objective.title }}{% endblock %}
|
||||
{% block breadcrumb %}<span class="breadcrumb">{{ _('Home') }} / <a href="{{ url_for('objectives.list_objectives') }}">{{ _('Objectives') }}</a> / {{ objective.title }}</span>{% endblock %}
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col-8">
|
||||
<div class="card mb-4">
|
||||
<div class="card-header"><h3><i class="fas fa-bullseye"></i> {{ objective.title }}</h3></div>
|
||||
<div class="card-body">
|
||||
<p>{{ objective.description or '' }}</p>
|
||||
<div class="preview-stats">
|
||||
<div class="preview-stat"><span class="preview-label">{{ _('Type') }}</span><span class="preview-value">{% for val, label in objective_types %}{% if val == objective.type %}{{ label }}{% endif %}{% endfor %}</span></div>
|
||||
<div class="preview-stat"><span class="preview-label">{{ _('Assigned to') }}</span><span class="preview-value">{% if objective.player %}{{ objective.player.username }}{% elif objective.team %}{{ objective.team.name }}{% else %}-{% endif %}</span></div>
|
||||
<div class="preview-stat"><span class="preview-label">{{ _('Category') }}</span><span class="preview-value">{{ objective.category or '-' }}</span></div>
|
||||
</div>
|
||||
<div class="mt-3">
|
||||
<span id="statusBadge" class="badge">{% for val, label in objective_statuses %}{% if val == objective.status %}{{ label }}{% endif %}{% endfor %}</span>
|
||||
<span id="priorityBadge" class="badge">{% set pl = {'high':_('High'),'medium':_('Medium'),'low':_('Low')} %}{{ pl.get(objective.priority,objective.priority) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card mb-4">
|
||||
<div class="card-header"><h3><i class="fas fa-chart-line"></i> {{ _('KPI Progression') }}</h3></div>
|
||||
<div class="card-body">
|
||||
<svg id="progressChart" viewBox="0 0 400 200" style="width:100%;height:auto;" xmlns="http://www.w3.org/2000/svg"></svg>
|
||||
{% if objective.kpi_target %}<div class="mt-2 text-center" id="predictionInfo"></div>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="card mb-4">
|
||||
<div class="card-header"><h3><i class="fas fa-database"></i> {{ _('KPI Measurements') }}</h3></div>
|
||||
<div class="card-body">
|
||||
<form method="POST" action="{{ url_for('objectives.add_entry', objective_id=objective.id) }}" class="form-inline">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<div class="form-group" style="flex:1;"><input type="number" step="0.01" name="value" class="form-input" placeholder="{{ _('Value (optional)') }}"></div>
|
||||
<div class="form-group" style="flex:2;"><input type="text" name="note" class="form-input" placeholder="{{ _('Note or comment') }}"></div>
|
||||
<button type="submit" class="btn btn-sm btn-primary"><i class="fas fa-plus"></i> {{ _('Record') }}</button>
|
||||
</form>
|
||||
{% if entries %}<table class="table mt-2"><thead><tr><th>{{ _('Date') }}</th><th>{{ _('Value') }}</th><th>{{ _('Note') }}</th></tr></thead><tbody>
|
||||
{% for e in entries %}<tr><td>{{ e.recorded_at.strftime('%Y-%m-%d %H:%M') }}</td><td><strong>{% if e.value is not none %}{{ e.value }}{% else %}-{% endif %}</strong></td><td class="text-muted small">{{ e.note or '' }}</td></tr>{% endfor %}</tbody></table>{% else %}<p class="text-muted mt-2">{{ _('No measurements recorded yet.') }}</p>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="card mb-4">
|
||||
<div class="card-header"><h3><i class="fas fa-comments"></i> {{ _('Comments') }}</h3></div>
|
||||
<div class="card-body">{% if comments %}<div class="comments-list">{% for c in comments %}<div class="comment-item"><strong>{{ c.coach.username }}</strong> <span class="text-muted small">{{ c.created_at.strftime('%Y-%m-%d %H:%M') }}</span><p>{{ c.content }}</p></div>{% endfor %}</div>{% else %}<p class="text-muted">{{ _('No comments yet.') }}</p>{% endif %}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<div class="card mb-4">
|
||||
<div class="card-header"><h3><i class="fas fa-info-circle"></i> {{ _('Details') }}</h3></div>
|
||||
<div class="card-body">
|
||||
<div class="preview-stats">
|
||||
<div class="preview-stat"><span class="preview-label">{{ _('Priority') }}</span><span class="preview-value">{% set pl = {'high':_('High'),'medium':_('Medium'),'low':_('Low')} %}{{ pl.get(objective.priority,objective.priority) }}</span></div>
|
||||
<div class="preview-stat"><span class="preview-label">{{ _('Status') }}</span><span class="preview-value">{% for val, label in objective_statuses %}{% if val == objective.status %}{{ label }}{% endif %}{% endfor %}</span></div>
|
||||
<div class="preview-stat"><span class="preview-label">{{ _('Start date') }}</span><span class="preview-value">{{ objective.start_date.strftime('%Y-%m-%d') if objective.start_date else '-' }}</span></div>
|
||||
<div class="preview-stat"><span class="preview-label">{{ _('Deadline') }}</span><span class="preview-value">{{ objective.due_date.strftime('%Y-%m-%d') if objective.due_date else '-' }}</span></div>
|
||||
<div class="preview-stat"><span class="preview-label">{{ _('Progress') }}</span><span class="preview-value">{{ objective.progress }}%</span></div>
|
||||
</div>
|
||||
{% if objective.kpi_target %}<div class="progress-section mt-2"><span class="preview-label">{{ _('KPI Target') }}</span><strong>{{ objective.kpi_target }}{% if objective.kpi_unit %}{{ objective.kpi_unit }}{% endif %}</strong></div>{% endif %}
|
||||
{% if can_edit %}<a href="{{ url_for('objectives.edit_objective', objective_id=objective.id) }}" class="btn btn-sm btn-primary mt-3"><i class="fas fa-edit"></i> {{ _('Edit') }}</a>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% if personal_notes or team_notes %}<div class="card mb-4"><div class="card-header"><h3><i class="fas fa-sticky-note"></i> {{ _('Related Notes') }}</h3></div><div class="card-body">{% if personal_notes %}<h5>{{ _('Personal Notes') }}</h5>{% for n in personal_notes %}<div class="comment-item"><strong>{{ n.coach.username }}</strong> <span class="text-muted small">{{ n.created_at.strftime('%Y-%m-%d') }}</span><p>{{ n.content[:200] }}</p></div>{% endfor %}{% endif %}{% if team_notes %}<h5 class="mt-2">{{ _('Team Notes') }}</h5>{% for n in team_notes %}<div class="comment-item"><strong>{{ n.coach.username }}</strong> <span class="text-muted small">{{ n.created_at.strftime('%Y-%m-%d') }}</span><p>{{ n.content[:200] }}</p></div>{% endfor %}{% endif %}</div></div>{% endif %}
|
||||
</div>
|
||||
</div>{% endblock %}
|
||||
{% block scripts %}
|
||||
<script>
|
||||
function drawChart(progress, target) {
|
||||
var svg = document.getElementById('progressChart');
|
||||
if (!svg) return;
|
||||
var w = 400, h = 200, pt = 20, pr = 20, pb = 30, pl = 40;
|
||||
var cw = w - pl - pr, ch = h - pt - pb;
|
||||
var prog = Math.min(parseInt(progress) || 0, 100);
|
||||
var py = pt + ch * (1 - prog / 100);
|
||||
var tgt = parseFloat(target) || 100;
|
||||
var tgtScale = Math.max(tgt, prog);
|
||||
var ty = pt + ch * (1 - tgt / tgtScale);
|
||||
var html = '<rect x="0" y="0" width="' + w + '" height="' + h + '" fill="transparent"/>';
|
||||
html += '<line x1="' + pl + '" y1="' + (pt + ch) + '" x2="' + (pl + cw) + '" y2="' + (pt + ch) + '" stroke="#ddd" stroke-width="1"/>';
|
||||
html += '<line x1="' + pl + '" y1="' + pt + '" x2="' + pl + '" y2="' + (pt + ch) + '" stroke="#ddd" stroke-width="1"/>';
|
||||
html += '<text x="' + (pl - 30) + '" y="' + (pt + 5) + '" fill="#999" font-size="10">' + tgtScale + '</text>';
|
||||
html += '<text x="' + (pl - 30) + '" y="' + (pt + ch / 2 + 4) + '" fill="#999" font-size="10">' + (tgtScale / 2).toFixed(1) + '</text>';
|
||||
html += '<text x="' + (pl - 30) + '" y="' + (pt + ch + 4) + '" fill="#999" font-size="10">0</text>';
|
||||
html += '<line x1="' + pl + '" y1="' + ty + '" x2="' + (pl + cw) + '" y2="' + ty + '" stroke="#4CAF50" stroke-width="1.5" stroke-dasharray="4,3"/>';
|
||||
html += '<text x="' + (pl + cw + 4) + '" y="' + (ty + 4) + '" fill="#4CAF50" font-size="9">{{ _('Target') | e }}</text>';
|
||||
html += '<polygon points="' + pl + ',' + (pt + ch) + ' ' + pl + ',' + py + ' ' + (pl + (prog / 100) * cw) + ',' + py + ' ' + (pl + (prog / 100) * cw) + ',' + (pt + ch) + '" fill="rgba(59,130,246,0.15)"/>';
|
||||
html += '<line x1="' + pl + '" y1="' + py + '" x2="' + (pl + (prog / 100) * cw) + '" y2="' + py + '" stroke="#3B82F6" stroke-width="2"/>';
|
||||
html += '<circle cx="' + (pl + (prog / 100) * cw) + '" cy="' + py + '" r="4" fill="#3B82F6"/>';
|
||||
svg.innerHTML = html;
|
||||
fetchTrendAndEntries();
|
||||
}
|
||||
function fetchTrendAndEntries() {
|
||||
var trendUrl = '{{ url_for("objectives.trend_data", objective_id=objective.id) }}';
|
||||
var entriesUrl = '{{ url_for("objectives.list_entries", objective_id=objective.id) }}';
|
||||
Promise.all([fetch(trendUrl).then(function(r){return r.json()}), fetch(entriesUrl).then(function(r){return r.json()})]).then(function(results) {
|
||||
var trend = results[0], entries = results[1];
|
||||
var infoEl = document.getElementById('predictionInfo');
|
||||
if (trend.predicted_achievement_date) {
|
||||
infoEl.innerHTML = '<strong>{{ _("Predicted achievement") }}:</strong> ' + new Date(trend.predicted_achievement_date).toLocaleDateString() + ' - <span class=\"' + (trend.confidence > 0.6 ? 'confidence-high' : trend.confidence > 0.3 ? 'confidence-medium' : 'confidence-low') + '\">{{ _("Confidence") }}: ' + (trend.confidence * 100).toFixed(0) + '%</span>';
|
||||
}
|
||||
if (entries && entries.length > 1) {
|
||||
var svg = document.getElementById('progressChart');
|
||||
var html = svg.innerHTML;
|
||||
var w = 400, pl = 40, pt = 20, cw = 320, ch = 150;
|
||||
var entryValues = entries.map(function(e){return parseFloat(e.value)||0;});
|
||||
var maxVal = Math.max.apply(null, entryValues);
|
||||
var range = maxVal || 1;
|
||||
var stepX = cw / (entries.length - 1);
|
||||
entryValues.forEach(function(v, i) {
|
||||
var ex = pl + i * stepX;
|
||||
var ey = pt + ch * (1 - v / range);
|
||||
html += '<circle cx="' + ex + '" cy="' + ey + '" r="3" fill="#8B5CF6"/>';
|
||||
if (i > 0) {
|
||||
var pex = pl + (i - 1) * stepX;
|
||||
var pey = pt + ch * (1 - entryValues[i-1] / range);
|
||||
html += '<line x1="' + pex + '" y1="' + pey + '" x2="' + ex + '" y2="' + ey + '" stroke="#8B5CF6" stroke-width="1.5"/>';
|
||||
}
|
||||
});
|
||||
svg.innerHTML = html;
|
||||
}
|
||||
}).catch(function(){});
|
||||
}
|
||||
document.addEventListener('DOMContentLoaded', function() { drawChart({{ objective.progress }}, '{{ objective.kpi_target if objective.kpi_target else '' }}'); });
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,64 @@
|
||||
{% extends "layouts/base.html" %}
|
||||
{% import 'layouts/_pagination.html' as pager %}
|
||||
{% block title %}{{ _('Objectives') }} - UdeS team manager{% endblock %}
|
||||
{% block page_title %}{{ _('Objective Management') }}{% endblock %}
|
||||
{% block breadcrumb %}<span class="breadcrumb">{{ _('Home') }} / {{ _('Objectives') }}</span>{% endblock %}
|
||||
{% block content %}
|
||||
<div class="stats-grid mb-4">
|
||||
<div class="stat-card"><div class="stat-icon bg-primary"><i class="fas fa-flag-checkered"></i></div><div class="stat-info"><h3>{{ count_total }}</h3><p>{{ _('Total') }}</p></div></div>
|
||||
<div class="stat-card"><div class="stat-icon bg-info"><i class="fas fa-chart-line"></i></div><div class="stat-info"><h3>{{ count_kpi }}</h3><p>{{ _('Custom KPIs') }}</p></div></div>
|
||||
<div class="stat-card"><div class="stat-icon bg-secondary"><i class="fas fa-copy"></i></div><div class="stat-info"><h3>{{ count_templates }}</h3><p>{{ _('Templates') }}</p></div></div>
|
||||
<div class="stat-card"><div class="stat-icon bg-success"><i class="fas fa-percentage"></i></div><div class="stat-info"><h3>{{ success_rate }}%</h3><p>{{ _('Success rate') }}</p></div></div>
|
||||
</div>
|
||||
<div class="card mb-4"><div class="card-body"><div class="objectives-toolbar">
|
||||
<nav class="objectives-tabs" role="tablist">
|
||||
<a href="{{ url_for('objectives.list_objectives', tab='all') }}" class="tab-link {% if tab == 'all' %}active{% endif %}">{{ _('All') }} ({{ count_total }})</a>
|
||||
<a href="{{ url_for('objectives.list_objectives', tab='kpi') }}" class="tab-link {% if tab == 'kpi' %}active{% endif %}">{{ _('Custom KPIs') }} ({{ count_kpi }})</a>
|
||||
<a href="{{ url_for('objectives.list_objectives', tab='modeles') }}" class="tab-link {% if tab in ('modeles','templates') %}active{% endif %}">{{ _('Templates') }} ({{ count_templates }})</a>
|
||||
</nav>
|
||||
<form method="GET" action="{{ url_for('objectives.list_objectives') }}" class="objectives-search-bar">
|
||||
<input type="hidden" name="tab" value="{{ tab }}">
|
||||
<div class="form-inline">
|
||||
<div class="form-group" style="flex:1;min-width:200px;"><input type="text" name="search" class="form-input" placeholder="{{ _('Search an objective...') }}"></div>
|
||||
<div class="form-group"><select name="status" class="form-select"><option value="">{{ _('Status') }}</option>{% for val, label in objective_statuses %}<option value="{{ val }}"{% if f_status == val %} selected{% endif %}>{{ label }}</option>{% endfor %}</select></div>
|
||||
<div class="form-group"><select name="priority" class="form-select"><option value="">{{ _('Priority') }}</option>{% for val, label in objective_priorities %}<option value="{{ val }}"{% if f_priority == val %} selected{% endif %}>{{ label }}</option>{% endfor %}</select></div>
|
||||
<div class="form-group"><select name="type" class="form-select"><option value="">{{ _('Type') }}</option>{% for val, label in objective_types %}<option value="{{ val }}"{% if f_type == val %} selected{% endif %}>{{ label }}</option>{% endfor %}</select></div>
|
||||
<div class="form-group"><select name="team" class="form-select"><option value="">{{ _('Team') }}</option>{% for t in teams %}<option value="{{ t.id }}"{% if f_team == t.id %} selected{% endif %}>{{ t.name }}</option>{% endfor %}</select></div>
|
||||
<button type="submit" class="btn btn-sm btn-primary"><i class="fas fa-filter"></i> {{ _('Filters') }}</button>
|
||||
</div></form>
|
||||
</div></div></div>
|
||||
<div class="card"><div class="card-header"><h3><i class="fas fa-bullseye"></i> {{ _('Objectives') }}</h3>{% if can_edit %}<a href="{{ url_for('objectives.new_objective') }}" class="btn btn-sm btn-primary"><i class="fas fa-plus"></i> {{ _('New Objective') }}</a>{% endif %}</div>
|
||||
<div class="card-body"><div class="table-container"><table class="table objectives-table"><thead><tr>
|
||||
<th>{{ _('Objective') }}</th><th>{{ _('Assigned to') }}</th><th>{{ _('Team') }}</th><th>{{ _('Priority') }}</th><th>{{ _('Status') }}</th><th>{{ _('Target KPI') }}</th><th>{{ _('Progress') }}</th><th>{{ _('Deadline') }}</th>{% if can_edit %}<th>{{ _('Actions') }}</th>{% endif %}
|
||||
</tr></thead><tbody>
|
||||
{% for obj in objectives.items %}<tr data-objective-id="{{ obj.id }}">
|
||||
<td><strong>{{ obj.title }}</strong>{% if obj.tags %}<div class="objective-tags">{% for tag in obj.tags.split(',') %}<span class="badge badge-light">{{ tag.strip() }}</span>{% endfor %}</div>{% endif %}{% if obj.kpi_metric %}<small class="text-muted d-block">{{ obj.kpi_metric }}</small>{% endif %}</td>
|
||||
<td>{% if obj.player %}<span class="user-avatar avatar-sm">{{ obj.player.username[:2]|upper }}</span> {{ obj.player.username }}{% else %}<span class="text-muted">-</span>{% endif %}</td>
|
||||
<td>{% if obj.team %}{{ obj.team.name }}{% else %}<span class="text-muted">-</span>{% endif %}</td>
|
||||
<td>{% set pc = {'high':'badge-danger','medium':'badge-warning','low':'badge-secondary'} %}<span class="badge {{ pc.get(obj.priority,'badge-secondary') }}">{{ {'high':_('High'),'medium':_('Medium'),'low':_('Low')}.get(obj.priority,obj.priority) }}</span></td>
|
||||
<td>{% set sc = {'active':'badge-success','pending':'badge-warning','completed':'badge-info','overdue':'badge-danger'} %}<span class="badge {{ sc.get(obj.status,'badge-secondary') }}">{% for val, label in objective_statuses %}{% if val == obj.status %}{{ label }}{% endif %}{% endfor %}</span></td>
|
||||
<td>{% if obj.kpi_target is not none %}{{ obj.kpi_target }}{% if obj.kpi_unit %}{{ obj.kpi_unit }}{% endif %}{% else %}<span class="text-muted">-</span>{% endif %}</td>
|
||||
<td><div class="progress-bar-container"><div class="progress-bar" style="width:{{ obj.progress }}%"></div></div><small>{{ obj.progress }}%</small></td>
|
||||
<td>{% if obj.is_template %}-{% elif obj.due_date %}{{ obj.due_date.strftime('%Y-%m-%d') }}{% endif %}</td>
|
||||
{% if can_edit %}<td class="actions-cell"><a href="{{ url_for('objectives.edit_objective', objective_id=obj.id) }}" class="btn btn-sm btn-outline-primary" title="{{ _('Edit') }}"><i class="fas fa-edit"></i></a> <a href="{{ url_for('objectives.view_objective', objective_id=obj.id) }}" class="btn btn-sm btn-outline-info" title="{{ _('View') }}"><i class="fas fa-eye"></i></a> <button class="btn btn-sm btn-outline-danger delete-obj-btn" data-obj-id="{{ obj.id }}" data-obj-title="{{ obj.title }}" title="{{ _('Delete') }}"><i class="fas fa-trash"></i></button></td>{% elif current_user.role == 'player' %}<td><a href="{{ url_for('objectives.view_objective', objective_id=obj.id) }}" class="btn btn-sm btn-outline-info"><i class="fas fa-eye"></i> {{ _('View') }}</a></td>{% endif %}
|
||||
</tr>{% else %}
|
||||
<tr><td colspan="{{ 9 if can_edit else 8 }}" class="text-center"><div class="empty-state"><i class="fas fa-bullseye"></i><h3>{{ _('No objectives yet') }}</h3><p>{{ _('Create your first objective to start tracking player evolution.') }}</p></div></td></tr>{% endfor %}
|
||||
</tbody></table></div>{{ pager.controls(pagination) }}</div></div>
|
||||
<div id="deleteModal" class="modal hidden"><div class="modal-backdrop" onclick="hideDeleteModal();"></div><div class="modal-content"><div class="modal-header"><h3><i class="fas fa-trash-alt"></i> {{ _('Delete Objective') }}</h3><button class="modal-close" onclick="hideDeleteModal();">×</button></div><div class="modal-body"><p>{{ _('Are you sure you want to delete this objective?') }}</p><p><strong id="deleteObjTitle"></strong></p><form id="deleteForm" method="POST" action=""><input type="hidden" name="csrf_token" value="{{ csrf_token() }}"><div class="form-actions"><button type="button" class="btn btn-secondary" onclick="hideDeleteModal();">{{ _('Cancel') }}</button><button type="submit" class="btn btn-danger"><i class="fas fa-trash"></i> {{ _('Delete') }}</button></div></form></div></div></div>
|
||||
{% endblock %}
|
||||
{% block scripts %}<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
document.querySelectorAll('.delete-obj-btn').forEach(function(btn) {
|
||||
btn.addEventListener('click', function() {
|
||||
var id = this.getAttribute('data-obj-id');
|
||||
var title = this.getAttribute('data-obj-title');
|
||||
document.getElementById('deleteObjTitle').textContent = title;
|
||||
document.getElementById('deleteForm').action = '{{ url_for("objectives.delete_objective", objective_id=0) }}'.replace('/0/', '/' + id + '/');
|
||||
document.getElementById('deleteModal').classList.remove('hidden');
|
||||
});
|
||||
});
|
||||
document.getElementById('deleteModal').querySelector('.modal-backdrop').addEventListener('click', hideDeleteModal);
|
||||
});
|
||||
function hideDeleteModal(){document.getElementById('deleteModal').classList.add('hidden');}
|
||||
document.addEventListener('keydown',function(e){if(e.key==='Escape'){hideDeleteModal();}});
|
||||
</script>{% endblock %}
|
||||
Reference in New Issue
Block a user