unit history added
This commit is contained in:
@@ -178,6 +178,14 @@
|
||||
<p id="viewNote" class="mt-1 text-gray-900 dark:text-white whitespace-pre-wrap">--</p>
|
||||
</div>
|
||||
|
||||
<!-- Unit History Timeline -->
|
||||
<div class="border-t border-gray-200 dark:border-gray-700 pt-6">
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">Timeline</h3>
|
||||
<div id="historyTimeline" class="space-y-3">
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">Loading history...</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Photos -->
|
||||
<div class="border-t border-gray-200 dark:border-gray-700 pt-6">
|
||||
<div class="flex justify-between items-start mb-4">
|
||||
@@ -762,9 +770,130 @@ async function uploadPhoto(file) {
|
||||
}
|
||||
}
|
||||
|
||||
// Load and display unit history timeline
|
||||
async function loadUnitHistory() {
|
||||
try {
|
||||
const response = await fetch(`/api/roster/history/${unitId}`);
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to load history');
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
const timeline = document.getElementById('historyTimeline');
|
||||
|
||||
if (data.history && data.history.length > 0) {
|
||||
timeline.innerHTML = '';
|
||||
data.history.forEach(entry => {
|
||||
const timelineEntry = createTimelineEntry(entry);
|
||||
timeline.appendChild(timelineEntry);
|
||||
});
|
||||
} else {
|
||||
timeline.innerHTML = '<p class="text-sm text-gray-500 dark:text-gray-400">No history yet. Changes will appear here.</p>';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading history:', error);
|
||||
document.getElementById('historyTimeline').innerHTML = '<p class="text-sm text-red-500">Failed to load history</p>';
|
||||
}
|
||||
}
|
||||
|
||||
// Create a timeline entry element
|
||||
function createTimelineEntry(entry) {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'flex gap-3 p-3 rounded-lg bg-gray-50 dark:bg-slate-700/50';
|
||||
|
||||
// Icon based on change type
|
||||
const icons = {
|
||||
'note_change': `<svg class="w-5 h-5 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path>
|
||||
</svg>`,
|
||||
'deployed_change': `<svg class="w-5 h-5 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
</svg>`,
|
||||
'retired_change': `<svg class="w-5 h-5 text-orange-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
</svg>`
|
||||
};
|
||||
|
||||
const icon = icons[entry.change_type] || `<svg class="w-5 h-5 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
</svg>`;
|
||||
|
||||
// Format change description
|
||||
let description = '';
|
||||
if (entry.change_type === 'note_change') {
|
||||
description = `<strong>Note changed</strong>`;
|
||||
if (entry.old_value) {
|
||||
description += `<br><span class="text-xs text-gray-500 dark:text-gray-400">From: "${entry.old_value}"</span>`;
|
||||
}
|
||||
if (entry.new_value) {
|
||||
description += `<br><span class="text-xs text-gray-600 dark:text-gray-300">To: "${entry.new_value}"</span>`;
|
||||
}
|
||||
} else if (entry.change_type === 'deployed_change') {
|
||||
description = `<strong>Status changed to ${entry.new_value}</strong>`;
|
||||
} else if (entry.change_type === 'retired_change') {
|
||||
description = `<strong>Marked as ${entry.new_value}</strong>`;
|
||||
} else {
|
||||
description = `<strong>${entry.field_name} changed</strong>`;
|
||||
if (entry.old_value && entry.new_value) {
|
||||
description += `<br><span class="text-xs text-gray-500 dark:text-gray-400">${entry.old_value} → ${entry.new_value}</span>`;
|
||||
}
|
||||
}
|
||||
|
||||
// Format timestamp
|
||||
const timestamp = new Date(entry.changed_at).toLocaleString();
|
||||
|
||||
div.innerHTML = `
|
||||
<div class="flex-shrink-0">
|
||||
${icon}
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="text-sm text-gray-900 dark:text-white">
|
||||
${description}
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||
${timestamp}
|
||||
${entry.source !== 'manual' ? `<span class="ml-2 px-2 py-0.5 bg-gray-200 dark:bg-gray-600 rounded text-xs">${entry.source}</span>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-shrink-0">
|
||||
<button onclick="deleteHistoryEntry(${entry.id})" class="text-gray-400 hover:text-red-500 transition-colors" title="Delete this history entry">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
return div;
|
||||
}
|
||||
|
||||
// Delete a history entry
|
||||
async function deleteHistoryEntry(historyId) {
|
||||
if (!confirm('Are you sure you want to delete this history entry?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/roster/history/${historyId}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
// Reload history
|
||||
await loadUnitHistory();
|
||||
} else {
|
||||
const result = await response.json();
|
||||
alert(`Error: ${result.detail || 'Unknown error'}`);
|
||||
}
|
||||
} catch (error) {
|
||||
alert(`Error: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Load data when page loads
|
||||
loadUnitData().then(() => {
|
||||
loadPhotos();
|
||||
loadUnitHistory();
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user