mirror of
https://github.com/DRYTRIX/TimeTracker.git
synced 2026-02-09 05:38:55 -06:00
feat(ui): refresh templates and dashboards; improve admin and error pages
- Update global layout and styles: `app/templates/base.html`, `app/static/base.css` - Modernize analytics dashboards (web + mobile) - Revamp auth pages: login, profile, edit profile - Refresh error pages: 400/403/404/500 and generic - Polish main dashboard and search - Enhance tasks views: create/edit/view, kanban, my/overdue - Update clients, projects, invoices, and reports pages - Refine timer pages (timer/edit/manual_entry) - Tweak admin routes and templates - Update license server util and integration docs - Refresh README and help/about content Notes: - UI-focused changes; no database migrations included.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Timer - {{ app_name }}{% endblock %}
|
||||
{% block title %}{{ _('Timer') }} - {{ app_name }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid">
|
||||
@@ -10,13 +10,13 @@
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h1 class="h2 mb-1">
|
||||
<i class="fas fa-clock text-primary me-2"></i>Timer
|
||||
<i class="fas fa-clock text-primary me-2"></i>{{ _('Timer') }}
|
||||
</h1>
|
||||
<p class="text-muted mb-0">Track your time with precision</p>
|
||||
<p class="text-muted mb-0">{{ _('Track your time with precision') }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<button type="button" id="openStartTimerBtn" class="btn-header btn-primary" data-bs-toggle="modal" data-bs-target="#startTimerModal">
|
||||
<i class="fas fa-play"></i>Start Timer
|
||||
<i class="fas fa-play"></i>{{ _('Start Timer') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -29,7 +29,7 @@
|
||||
<div class="card border-success">
|
||||
<div class="card-header bg-success text-white">
|
||||
<h5 class="mb-0">
|
||||
<i class="fas fa-play-circle me-2"></i>Active Timer
|
||||
<i class="fas fa-play-circle me-2"></i>{{ _('Active Timer') }}
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
@@ -49,17 +49,17 @@
|
||||
<div class="d-flex align-items-center">
|
||||
<i class="fas fa-clock text-muted me-2"></i>
|
||||
<small class="text-muted">
|
||||
Started: <span id="activeTimerStart" class="fw-semibold"></span>
|
||||
{{ _('Started:') }} <span id="activeTimerStart" class="fw-semibold"></span>
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3 text-center">
|
||||
<div class="timer-display mb-2" id="activeTimerDisplay">00:00:00</div>
|
||||
<small class="text-muted fw-semibold">Duration</small>
|
||||
<small class="text-muted fw-semibold">{{ _('Duration') }}</small>
|
||||
</div>
|
||||
<div class="col-md-3 text-center text-md-end">
|
||||
<button type="button" class="btn btn-danger px-4" id="stopTimerBtn">
|
||||
<i class="fas fa-stop me-2"></i>Stop Timer
|
||||
<i class="fas fa-stop me-2"></i>{{ _('Stop Timer') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -76,10 +76,10 @@
|
||||
<div class="mb-4">
|
||||
<i class="fas fa-clock fa-3x text-muted opacity-50"></i>
|
||||
</div>
|
||||
<h3 class="text-muted mb-3">No Active Timer</h3>
|
||||
<p class="text-muted mb-4">Start a timer to begin tracking your time effectively.</p>
|
||||
<h3 class="text-muted mb-3">{{ _('No Active Timer') }}</h3>
|
||||
<p class="text-muted mb-4">{{ _('Start a timer to begin tracking your time effectively.') }}</p>
|
||||
<button type="button" id="openStartTimerBtnEmpty" class="btn btn-primary px-4" data-bs-toggle="modal" data-bs-target="#startTimerModal">
|
||||
<i class="fas fa-play me-2"></i>Start Timer
|
||||
<i class="fas fa-play me-2"></i>{{ _('Start Timer') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -92,7 +92,7 @@
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">
|
||||
<i class="fas fa-history me-2 text-primary"></i>Recent Time Entries
|
||||
<i class="fas fa-history me-2 text-primary"></i>{{ _('Recent Time Entries') }}
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
@@ -111,7 +111,7 @@
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">
|
||||
<i class="fas fa-play me-2 text-success"></i>Start Timer
|
||||
<i class="fas fa-play me-2 text-success"></i>{{ _('Start Timer') }}
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
@@ -119,34 +119,34 @@
|
||||
<div class="modal-body">
|
||||
<div class="mb-4">
|
||||
<label for="projectSelect" class="form-label fw-semibold">
|
||||
<i class="fas fa-project-diagram me-1"></i>Project *
|
||||
<i class="fas fa-project-diagram me-1"></i>{{ _('Project') }} *
|
||||
</label>
|
||||
<select class="form-select form-select-lg" id="projectSelect" name="project_id" required>
|
||||
<option value="">Select a project...</option>
|
||||
<option value="">{{ _('Select a project...') }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label for="timerNotes" class="form-label fw-semibold">
|
||||
<i class="fas fa-sticky-note me-1"></i>Notes
|
||||
<i class="fas fa-sticky-note me-1"></i>{{ _('Notes') }}
|
||||
</label>
|
||||
<textarea class="form-control" id="timerNotes" name="notes" rows="3"
|
||||
placeholder="What are you working on?"></textarea>
|
||||
placeholder="{{ _('What are you working on?') }}"></textarea>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="timerTags" class="form-label fw-semibold">
|
||||
<i class="fas fa-tags me-1"></i>Tags
|
||||
<i class="fas fa-tags me-1"></i>{{ _('Tags') }}
|
||||
</label>
|
||||
<input type="text" class="form-control" id="timerTags" name="tags"
|
||||
placeholder="tag1, tag2, tag3">
|
||||
<div class="form-text">Separate tags with commas</div>
|
||||
placeholder="{{ _('tag1, tag2, tag3') }}">
|
||||
<div class="form-text">{{ _('Separate tags with commas') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||
<i class="fas fa-times me-1"></i>Cancel
|
||||
<i class="fas fa-times me-1"></i>{{ _('Cancel') }}
|
||||
</button>
|
||||
<button type="submit" class="btn btn-success">
|
||||
<i class="fas fa-play me-2"></i>Start Timer
|
||||
<i class="fas fa-play me-2"></i>{{ _('Start Timer') }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -160,7 +160,7 @@
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">
|
||||
<i class="fas fa-edit me-2 text-primary"></i>Edit Time Entry
|
||||
<i class="fas fa-edit me-2 text-primary"></i>{{ _('Edit Time Entry') }}
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
@@ -171,17 +171,17 @@
|
||||
<div class="col-md-6">
|
||||
<div class="mb-4">
|
||||
<label for="editProjectSelect" class="form-label fw-semibold">
|
||||
<i class="fas fa-project-diagram me-1"></i>Project *
|
||||
<i class="fas fa-project-diagram me-1"></i>{{ _('Project') }} *
|
||||
</label>
|
||||
<select class="form-select" id="editProjectSelect" name="project_id" required>
|
||||
<option value="">Select a project...</option>
|
||||
<option value="">{{ _('Select a project...') }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="mb-4">
|
||||
<label class="form-label fw-semibold">
|
||||
<i class="fas fa-clock me-1"></i>Duration
|
||||
<i class="fas fa-clock me-1"></i>{{ _('Duration') }}
|
||||
</label>
|
||||
<div class="form-control-plaintext" id="editDurationDisplay">--:--:--</div>
|
||||
</div>
|
||||
@@ -191,7 +191,7 @@
|
||||
<div class="col-md-6">
|
||||
<div class="mb-4">
|
||||
<label for="editStartTime" class="form-label fw-semibold">
|
||||
<i class="fas fa-play me-1"></i>Start Time *
|
||||
<i class="fas fa-play me-1"></i>{{ _('Start Time') }} *
|
||||
</label>
|
||||
<input type="datetime-local" class="form-control" id="editStartTime" name="start_time" required>
|
||||
</div>
|
||||
@@ -199,7 +199,7 @@
|
||||
<div class="col-md-6">
|
||||
<div class="mb-4">
|
||||
<label for="editEndTime" class="form-label fw-semibold">
|
||||
<i class="fas fa-stop me-1"></i>End Time *
|
||||
<i class="fas fa-stop me-1"></i>{{ _('End Time') }} *
|
||||
</label>
|
||||
<input type="datetime-local" class="form-control" id="editEndTime" name="end_time" required>
|
||||
</div>
|
||||
@@ -207,7 +207,7 @@
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label for="editNotes" class="form-label fw-semibold">
|
||||
<i class="fas fa-sticky-note me-1"></i>Notes
|
||||
<i class="fas fa-sticky-note me-1"></i>{{ _('Notes') }}
|
||||
</label>
|
||||
<textarea class="form-control" id="editNotes" name="notes" rows="3"></textarea>
|
||||
</div>
|
||||
@@ -215,11 +215,11 @@
|
||||
<div class="col-md-8">
|
||||
<div class="mb-4">
|
||||
<label for="editTags" class="form-label fw-semibold">
|
||||
<i class="fas fa-tags me-1"></i>Tags
|
||||
<i class="fas fa-tags me-1"></i>{{ _('Tags') }}
|
||||
</label>
|
||||
<input type="text" class="form-control" id="editTags" name="tags"
|
||||
placeholder="tag1, tag2, tag3">
|
||||
<div class="form-text">Separate tags with commas</div>
|
||||
placeholder="{{ _('tag1, tag2, tag3') }}">
|
||||
<div class="form-text">{{ _('Separate tags with commas') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
@@ -227,7 +227,7 @@
|
||||
<div class="form-check form-switch mt-4">
|
||||
<input class="form-check-input" type="checkbox" id="editBillable" name="billable">
|
||||
<label class="form-check-label fw-semibold" for="editBillable">
|
||||
<i class="fas fa-dollar-sign me-1"></i>Billable
|
||||
<i class="fas fa-dollar-sign me-1"></i>{{ _('Billable') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -236,13 +236,13 @@
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-danger me-auto" id="deleteTimerBtn">
|
||||
<i class="fas fa-trash me-1"></i>Delete
|
||||
<i class="fas fa-trash me-1"></i>{{ _('Delete') }}
|
||||
</button>
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||
<i class="fas fa-times me-1"></i>Cancel
|
||||
<i class="fas fa-times me-1"></i>{{ _('Cancel') }}
|
||||
</button>
|
||||
<button type="submit" class="btn btn-primary" id="editSaveBtn">
|
||||
<i class="fas fa-save me-2"></i>Save Changes
|
||||
<i class="fas fa-save me-2"></i>{{ _('Save Changes') }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -256,24 +256,24 @@
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">
|
||||
<i class="fas fa-trash me-2 text-danger"></i>Delete Time Entry
|
||||
<i class="fas fa-trash me-2 text-danger"></i>{{ _('Delete Time Entry') }}
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="{{ _('Close') }}"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="alert alert-warning">
|
||||
<i class="fas fa-exclamation-triangle me-2"></i>
|
||||
<strong>Warning:</strong> This action cannot be undone.
|
||||
<strong>{{ _('Warning:') }}</strong> {{ _('This action cannot be undone.') }}
|
||||
</div>
|
||||
<p>Are you sure you want to delete this time entry?</p>
|
||||
<p class="text-muted mb-0">This will permanently remove the entry and cannot be recovered.</p>
|
||||
<p>{{ _('Are you sure you want to delete this time entry?') }}</p>
|
||||
<p class="text-muted mb-0">{{ _('This will permanently remove the entry and cannot be recovered.') }}</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||
<i class="fas fa-times me-1"></i>Cancel
|
||||
<i class="fas fa-times me-1"></i>{{ _('Cancel') }}
|
||||
</button>
|
||||
<button type="button" class="btn btn-danger" id="confirmDeleteBtn">
|
||||
<i class="fas fa-trash me-2"></i>Delete Entry
|
||||
<i class="fas fa-trash me-2"></i>{{ _('Delete Entry') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -297,8 +297,8 @@ function loadProjects() {
|
||||
if (!projectSelect || !editProjectSelect) return;
|
||||
|
||||
// Clear existing options
|
||||
projectSelect.innerHTML = '<option value="">Select a project...</option>';
|
||||
editProjectSelect.innerHTML = '<option value="">Select a project...</option>';
|
||||
projectSelect.innerHTML = '<option value="">{{ _('Select a project...') }}</option>';
|
||||
editProjectSelect.innerHTML = '<option value="">{{ _('Select a project...') }}</option>';
|
||||
|
||||
data.projects.forEach(project => {
|
||||
if (project.status === 'active') {
|
||||
@@ -313,7 +313,7 @@ function loadProjects() {
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error loading projects:', error);
|
||||
showToast('Error loading projects', 'error');
|
||||
showToast('{{ _('Error loading projects') }}', 'error');
|
||||
});
|
||||
window.projectsLoadedPromise = promise;
|
||||
return promise;
|
||||
@@ -342,10 +342,10 @@ function showActiveTimer() {
|
||||
document.getElementById('noActiveTimerSection').style.display = 'none';
|
||||
document.getElementById('activeTimerSection').style.display = 'block';
|
||||
const openBtns = [document.getElementById('openStartTimerBtn'), document.getElementById('openStartTimerBtnEmpty')];
|
||||
openBtns.forEach(btn => { if (btn) { btn.disabled = true; btn.classList.add('disabled'); btn.setAttribute('title', 'Stop the current timer first'); }});
|
||||
openBtns.forEach(btn => { if (btn) { btn.disabled = true; btn.classList.add('disabled'); btn.setAttribute('title', '{{ _('Stop the current timer first') }}'); }});
|
||||
|
||||
document.getElementById('activeProjectName').textContent = activeTimer.project_name;
|
||||
document.getElementById('activeTimerNotes').textContent = activeTimer.notes || 'No notes';
|
||||
document.getElementById('activeTimerNotes').textContent = activeTimer.notes || '{{ _('No notes') }}';
|
||||
document.getElementById('activeTimerStart').textContent = new Date(activeTimer.start_time).toLocaleString();
|
||||
}
|
||||
|
||||
@@ -400,10 +400,10 @@ function loadRecentEntries() {
|
||||
<div class="mb-4">
|
||||
<i class="fas fa-clock fa-4x text-muted opacity-50"></i>
|
||||
</div>
|
||||
<h5 class="text-muted mb-3">No time entries yet</h5>
|
||||
<p class="text-muted mb-4">Start tracking your time to see entries here</p>
|
||||
<h5 class="text-muted mb-3">{{ _('No time entries yet') }}</h5>
|
||||
<p class="text-muted mb-4">{{ _('Start tracking your time to see entries here') }}</p>
|
||||
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#startTimerModal">
|
||||
<i class="fas fa-play me-2"></i>Start Your First Timer
|
||||
<i class="fas fa-play me-2"></i>{{ _('Start Your First Timer') }}
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
@@ -426,7 +426,7 @@ function loadRecentEntries() {
|
||||
${new Date(entry.start_time).toLocaleDateString()}
|
||||
<i class="fas fa-clock ms-2 me-1"></i>
|
||||
${new Date(entry.start_time).toLocaleTimeString()} -
|
||||
${entry.end_time ? new Date(entry.end_time).toLocaleTimeString() : 'Running'}
|
||||
${entry.end_time ? new Date(entry.end_time).toLocaleTimeString() : '{{ _('Running') }}'}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
@@ -434,10 +434,10 @@ function loadRecentEntries() {
|
||||
<div class="badge bg-primary fs-6 mb-2">${entry.duration_formatted}</div>
|
||||
<br>
|
||||
<div class="btn-group" role="group">
|
||||
<button class="btn btn-sm btn-action btn-action--edit" onclick="editEntry(${entry.id})" title="Edit entry">
|
||||
<button class="btn btn-sm btn-action btn-action--edit" onclick="editEntry(${entry.id})" title="{{ _('Edit entry') }}">
|
||||
<i class="fas fa-edit"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-action btn-action--danger" onclick="deleteEntry(${entry.id})" title="Delete entry">
|
||||
<button class="btn btn-sm btn-action btn-action--danger" onclick="deleteEntry(${entry.id})" title="{{ _('Delete entry') }}">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
@@ -463,7 +463,7 @@ document.getElementById('startTimerForm').addEventListener('submit', function(e)
|
||||
|
||||
// Show loading state
|
||||
const submitBtn = this.querySelector('button[type="submit"]');
|
||||
submitBtn.innerHTML = '<div class="loading-spinner me-2"></div>Starting...';
|
||||
submitBtn.innerHTML = '<div class="loading-spinner me-2"></div>{{ _('Starting...') }}';
|
||||
submitBtn.disabled = true;
|
||||
|
||||
fetch('/api/timer/start', {
|
||||
@@ -477,28 +477,28 @@ document.getElementById('startTimerForm').addEventListener('submit', function(e)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
showToast('Timer started successfully', 'success');
|
||||
showToast('{{ _('Timer started successfully') }}', 'success');
|
||||
bootstrap.Modal.getInstance(document.getElementById('startTimerModal')).hide();
|
||||
this.reset();
|
||||
checkTimerStatus();
|
||||
loadRecentEntries();
|
||||
} else {
|
||||
showToast(data.error || data.message || 'Error starting timer', 'error');
|
||||
showToast(data.error || data.message || '{{ _('Error starting timer') }}', 'error');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error starting timer:', error);
|
||||
showToast('Error starting timer', 'error');
|
||||
showToast('{{ _('Error starting timer') }}', 'error');
|
||||
})
|
||||
.finally(() => {
|
||||
submitBtn.innerHTML = '<i class="fas fa-play me-2"></i>Start Timer';
|
||||
submitBtn.innerHTML = '<i class="fas fa-play me-2"></i>{{ _('Start Timer') }}';
|
||||
submitBtn.disabled = false;
|
||||
});
|
||||
});
|
||||
|
||||
// Stop timer
|
||||
document.getElementById('stopTimerBtn').addEventListener('click', function() {
|
||||
this.innerHTML = '<div class="loading-spinner me-2"></div>Stopping...';
|
||||
this.innerHTML = '<div class="loading-spinner me-2"></div>{{ _('Stopping...') }}';
|
||||
this.disabled = true;
|
||||
|
||||
fetch('/api/timer/stop', {
|
||||
@@ -508,19 +508,19 @@ document.getElementById('stopTimerBtn').addEventListener('click', function() {
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
showToast('Timer stopped successfully', 'success');
|
||||
showToast('{{ _('Timer stopped successfully') }}', 'success');
|
||||
hideActiveTimer();
|
||||
loadRecentEntries();
|
||||
} else {
|
||||
showToast(data.message || 'Error stopping timer', 'error');
|
||||
showToast(data.message || '{{ _('Error stopping timer') }}', 'error');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error stopping timer:', error);
|
||||
showToast('Error stopping timer', 'error');
|
||||
showToast('{{ _('Error stopping timer') }}', 'error');
|
||||
})
|
||||
.finally(() => {
|
||||
this.innerHTML = '<i class="fas fa-stop me-2"></i>Stop Timer';
|
||||
this.innerHTML = '<i class="fas fa-stop me-2"></i>{{ _('Stop Timer') }}';
|
||||
this.disabled = false;
|
||||
});
|
||||
});
|
||||
@@ -537,7 +537,7 @@ function editEntry(entryId) {
|
||||
const editProjectSelect = document.getElementById('editProjectSelect');
|
||||
// Ensure the project option exists; if not, append it
|
||||
if (!Array.from(editProjectSelect.options).some(o => o.value == data.project_id)) {
|
||||
const opt = new Option(data.project_name || `Project ${data.project_id}`, data.project_id);
|
||||
const opt = new Option(data.project_name || `{{ _('Project') }} ${data.project_id}`, data.project_id);
|
||||
editProjectSelect.add(opt);
|
||||
}
|
||||
editProjectSelect.value = data.project_id;
|
||||
@@ -563,7 +563,7 @@ function editEntry(entryId) {
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error loading entry:', error);
|
||||
showToast('Error loading entry', 'error');
|
||||
showToast('{{ _('Error loading entry') }}', 'error');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -589,13 +589,13 @@ function performEditSave() {
|
||||
const startVal = data.start_time ? new Date(data.start_time) : null;
|
||||
const endVal = data.end_time ? new Date(data.end_time) : null;
|
||||
if (startVal && endVal && endVal <= startVal) {
|
||||
showToast('End time must be after start time', 'error');
|
||||
showToast('{{ _('End time must be after start time') }}', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const submitBtn = form.querySelector('button[type="submit"]');
|
||||
if (submitBtn) {
|
||||
submitBtn.innerHTML = '<div class="loading-spinner me-2"></div>Saving...';
|
||||
submitBtn.innerHTML = '<div class="loading-spinner me-2"></div>{{ _('Saving...') }}';
|
||||
submitBtn.disabled = true;
|
||||
}
|
||||
|
||||
@@ -609,21 +609,21 @@ function performEditSave() {
|
||||
.then(payload => {
|
||||
console.debug('PUT response', payload);
|
||||
if (payload.success) {
|
||||
showToast('Entry updated successfully', 'success');
|
||||
showToast('{{ _('Entry updated successfully') }}', 'success');
|
||||
const modal = bootstrap.Modal.getInstance(document.getElementById('editTimerModal'));
|
||||
if (modal) modal.hide();
|
||||
loadRecentEntries();
|
||||
} else {
|
||||
showToast(payload.message || 'Error updating entry', 'error');
|
||||
showToast(payload.message || '{{ _('Error updating entry') }}', 'error');
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Error updating entry:', err);
|
||||
showToast('Error updating entry', 'error');
|
||||
showToast('{{ _('Error updating entry') }}', 'error');
|
||||
})
|
||||
.finally(() => {
|
||||
if (submitBtn) {
|
||||
submitBtn.innerHTML = '<i class="fas fa-save me-2"></i>Save Changes';
|
||||
submitBtn.innerHTML = '<i class="fas fa-save me-2"></i>{{ _('Save Changes') }}';
|
||||
submitBtn.disabled = false;
|
||||
}
|
||||
});
|
||||
@@ -706,7 +706,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
// Show loading state if button exists
|
||||
if (button) {
|
||||
button.innerHTML = '<div class="loading-spinner me-2"></div>Deleting...';
|
||||
button.innerHTML = '<div class="loading-spinner me-2"></div>{{ _('Deleting...') }}';
|
||||
button.disabled = true;
|
||||
}
|
||||
|
||||
@@ -717,7 +717,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
showToast('Entry deleted successfully', 'success');
|
||||
showToast('{{ _('Entry deleted successfully') }}', 'success');
|
||||
|
||||
// Hide modals
|
||||
bootstrap.Modal.getInstance(document.getElementById('deleteEntryModal')).hide();
|
||||
@@ -731,17 +731,17 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
checkTimerStatus();
|
||||
}
|
||||
} else {
|
||||
showToast(data.message || 'Error deleting entry', 'error');
|
||||
showToast(data.message || '{{ _('Error deleting entry') }}', 'error');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error deleting entry:', error);
|
||||
showToast('Error deleting entry', 'error');
|
||||
showToast('{{ _('Error deleting entry') }}', 'error');
|
||||
})
|
||||
.finally(() => {
|
||||
// Reset button state if it exists
|
||||
if (button) {
|
||||
button.innerHTML = '<i class="fas fa-trash me-1"></i>Delete';
|
||||
button.innerHTML = '<i class="fas fa-trash me-1"></i>{{ _('Delete') }}';
|
||||
button.disabled = false;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user