Initial commit
This commit is contained in:
386
static/js/assessment.js
Normal file
386
static/js/assessment.js
Normal file
@@ -0,0 +1,386 @@
|
||||
// Assessment-specific JavaScript functionality
|
||||
|
||||
// Enhanced drag and drop for assessment
|
||||
class AssessmentDragDrop {
|
||||
constructor() {
|
||||
this.draggedElement = null;
|
||||
this.initializeDragDrop();
|
||||
}
|
||||
|
||||
initializeDragDrop() {
|
||||
// Set up drag and drop for capability items
|
||||
document.addEventListener('dragstart', (e) => {
|
||||
if (e.target.classList.contains('capability-item')) {
|
||||
this.handleDragStart(e);
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('dragover', (e) => {
|
||||
if (e.target.closest('.drop-zone')) {
|
||||
this.handleDragOver(e);
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('dragleave', (e) => {
|
||||
if (e.target.closest('.drop-zone')) {
|
||||
this.handleDragLeave(e);
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('drop', (e) => {
|
||||
if (e.target.closest('.drop-zone')) {
|
||||
this.handleDrop(e);
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('dragend', (e) => {
|
||||
if (e.target.classList.contains('capability-item')) {
|
||||
this.handleDragEnd(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
handleDragStart(e) {
|
||||
this.draggedElement = e.target;
|
||||
e.target.classList.add('dragging');
|
||||
e.dataTransfer.effectAllowed = 'move';
|
||||
e.dataTransfer.setData('text/html', e.target.outerHTML);
|
||||
e.dataTransfer.setData('text/plain', e.target.dataset.capabilityId);
|
||||
}
|
||||
|
||||
handleDragOver(e) {
|
||||
e.preventDefault();
|
||||
e.dataTransfer.dropEffect = 'move';
|
||||
const dropZone = e.target.closest('.drop-zone');
|
||||
if (dropZone) {
|
||||
dropZone.classList.add('drag-over');
|
||||
}
|
||||
}
|
||||
|
||||
handleDragLeave(e) {
|
||||
const dropZone = e.target.closest('.drop-zone');
|
||||
if (dropZone && !dropZone.contains(e.relatedTarget)) {
|
||||
dropZone.classList.remove('drag-over');
|
||||
}
|
||||
}
|
||||
|
||||
handleDrop(e) {
|
||||
e.preventDefault();
|
||||
const dropZone = e.target.closest('.drop-zone');
|
||||
if (dropZone) {
|
||||
dropZone.classList.remove('drag-over');
|
||||
|
||||
if (this.draggedElement) {
|
||||
this.moveCapability(this.draggedElement, dropZone);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleDragEnd(e) {
|
||||
e.target.classList.remove('dragging');
|
||||
this.draggedElement = null;
|
||||
}
|
||||
|
||||
moveCapability(capabilityElement, targetDropZone) {
|
||||
// Remove from original position
|
||||
capabilityElement.remove();
|
||||
|
||||
// Create new element in target drop zone
|
||||
const newElement = this.createCapabilityElement(capabilityElement);
|
||||
|
||||
// Clear target drop zone and add new element
|
||||
targetDropZone.innerHTML = '';
|
||||
targetDropZone.appendChild(newElement);
|
||||
targetDropZone.classList.add('has-items');
|
||||
|
||||
// Update visual state
|
||||
this.updateDropZoneState(targetDropZone);
|
||||
}
|
||||
|
||||
createCapabilityElement(originalElement) {
|
||||
const newElement = document.createElement('div');
|
||||
newElement.className = 'capability-item';
|
||||
newElement.draggable = true;
|
||||
newElement.dataset.capabilityId = originalElement.dataset.capabilityId;
|
||||
newElement.dataset.capabilityName = originalElement.dataset.capabilityName;
|
||||
newElement.textContent = originalElement.dataset.capabilityName;
|
||||
|
||||
// Add click to remove functionality
|
||||
newElement.addEventListener('dblclick', () => {
|
||||
this.removeCapabilityFromLevel(newElement);
|
||||
});
|
||||
|
||||
return newElement;
|
||||
}
|
||||
|
||||
removeCapabilityFromLevel(capabilityElement) {
|
||||
const dropZone = capabilityElement.closest('.drop-zone');
|
||||
if (dropZone) {
|
||||
// Return to available capabilities
|
||||
this.returnToAvailable(capabilityElement);
|
||||
|
||||
// Update drop zone state
|
||||
this.updateDropZoneState(dropZone);
|
||||
}
|
||||
}
|
||||
|
||||
returnToAvailable(capabilityElement) {
|
||||
const availableContainer = document.getElementById('available-capabilities');
|
||||
if (availableContainer) {
|
||||
const newElement = this.createCapabilityElement(capabilityElement);
|
||||
availableContainer.appendChild(newElement);
|
||||
}
|
||||
}
|
||||
|
||||
updateDropZoneState(dropZone) {
|
||||
const hasItems = dropZone.querySelector('.capability-item');
|
||||
|
||||
if (hasItems) {
|
||||
dropZone.classList.add('has-items');
|
||||
dropZone.classList.remove('empty');
|
||||
} else {
|
||||
dropZone.classList.remove('has-items');
|
||||
dropZone.classList.add('empty');
|
||||
dropZone.innerHTML = '<div class="drop-placeholder">拖放能力到此處</div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Capability management
|
||||
class CapabilityManager {
|
||||
constructor() {
|
||||
this.capabilities = [];
|
||||
this.loadCapabilities();
|
||||
}
|
||||
|
||||
async loadCapabilities() {
|
||||
try {
|
||||
const response = await fetch('/api/capabilities');
|
||||
const data = await response.json();
|
||||
this.capabilities = data.capabilities;
|
||||
this.displayCapabilities();
|
||||
} catch (error) {
|
||||
console.error('Failed to load capabilities:', error);
|
||||
this.showError('載入能力清單失敗');
|
||||
}
|
||||
}
|
||||
|
||||
displayCapabilities() {
|
||||
const container = document.getElementById('available-capabilities');
|
||||
if (!container) return;
|
||||
|
||||
container.innerHTML = '';
|
||||
|
||||
this.capabilities.forEach(capability => {
|
||||
const capabilityElement = this.createCapabilityElement(capability);
|
||||
container.appendChild(capabilityElement);
|
||||
});
|
||||
}
|
||||
|
||||
createCapabilityElement(capability) {
|
||||
const element = document.createElement('div');
|
||||
element.className = 'capability-item';
|
||||
element.draggable = true;
|
||||
element.dataset.capabilityId = capability.id;
|
||||
element.dataset.capabilityName = capability.name;
|
||||
element.textContent = capability.name;
|
||||
|
||||
// Add tooltip with description
|
||||
element.title = this.getCapabilityDescription(capability);
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
getCapabilityDescription(capability) {
|
||||
const descriptions = [
|
||||
capability.l1_description,
|
||||
capability.l2_description,
|
||||
capability.l3_description,
|
||||
capability.l4_description,
|
||||
capability.l5_description
|
||||
].filter(desc => desc && desc.trim());
|
||||
|
||||
return descriptions.join('\n\n');
|
||||
}
|
||||
|
||||
showError(message) {
|
||||
// You can implement a more sophisticated error display here
|
||||
console.error(message);
|
||||
}
|
||||
}
|
||||
|
||||
// Assessment form validation and submission
|
||||
class AssessmentForm {
|
||||
constructor() {
|
||||
this.form = document.getElementById('assessment-form');
|
||||
this.initializeForm();
|
||||
}
|
||||
|
||||
initializeForm() {
|
||||
if (this.form) {
|
||||
this.form.addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
this.handleSubmit();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async handleSubmit() {
|
||||
if (!this.validateForm()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const assessmentData = this.collectAssessmentData();
|
||||
|
||||
try {
|
||||
this.showLoading(true);
|
||||
|
||||
const response = await fetch('/api/assessments', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(assessmentData)
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
this.showSuccess(result.message);
|
||||
this.clearForm();
|
||||
|
||||
} catch (error) {
|
||||
console.error('Assessment submission failed:', error);
|
||||
this.showError('評估提交失敗: ' + error.message);
|
||||
} finally {
|
||||
this.showLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
validateForm() {
|
||||
const department = document.getElementById('department').value.trim();
|
||||
const position = document.getElementById('position').value.trim();
|
||||
|
||||
if (!department) {
|
||||
this.showError('請填寫部門');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!position) {
|
||||
this.showError('請填寫職位');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if at least one capability is assigned
|
||||
const hasAssignedCapabilities = this.hasAssignedCapabilities();
|
||||
if (!hasAssignedCapabilities) {
|
||||
this.showError('請至少分配一個能力到某個等級');
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
hasAssignedCapabilities() {
|
||||
const levels = ['L1', 'L2', 'L3', 'L4', 'L5'];
|
||||
|
||||
for (let level of levels) {
|
||||
const dropZone = document.getElementById(`level-${level.toLowerCase()}`);
|
||||
if (dropZone && dropZone.querySelector('.capability-item')) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
collectAssessmentData() {
|
||||
const formData = new FormData(this.form);
|
||||
const assessmentData = {
|
||||
department: formData.get('department'),
|
||||
position: formData.get('position'),
|
||||
employee_name: formData.get('employee_name') || null,
|
||||
assessment_data: {}
|
||||
};
|
||||
|
||||
// Collect capability assignments
|
||||
const levels = ['L1', 'L2', 'L3', 'L4', 'L5'];
|
||||
|
||||
levels.forEach(level => {
|
||||
const dropZone = document.getElementById(`level-${level.toLowerCase()}`);
|
||||
const capabilityItems = dropZone.querySelectorAll('.capability-item');
|
||||
assessmentData.assessment_data[level] = Array.from(capabilityItems).map(item => item.dataset.capabilityName);
|
||||
});
|
||||
|
||||
return assessmentData;
|
||||
}
|
||||
|
||||
clearForm() {
|
||||
this.form.reset();
|
||||
|
||||
// Clear all drop zones
|
||||
const levels = ['L1', 'L2', 'L3', 'L4', 'L5'];
|
||||
levels.forEach(level => {
|
||||
const dropZone = document.getElementById(`level-${level.toLowerCase()}`);
|
||||
if (dropZone) {
|
||||
dropZone.innerHTML = '<div class="drop-placeholder">拖放能力到此處</div>';
|
||||
dropZone.classList.remove('has-items');
|
||||
}
|
||||
});
|
||||
|
||||
// Reload capabilities
|
||||
if (window.capabilityManager) {
|
||||
window.capabilityManager.loadCapabilities();
|
||||
}
|
||||
}
|
||||
|
||||
showLoading(show) {
|
||||
const submitButton = this.form.querySelector('button[type="submit"]');
|
||||
if (submitButton) {
|
||||
if (show) {
|
||||
submitButton.disabled = true;
|
||||
submitButton.innerHTML = '<span class="loading"></span> 儲存中...';
|
||||
} else {
|
||||
submitButton.disabled = false;
|
||||
submitButton.innerHTML = '<i class="bi bi-save me-1"></i>儲存評估';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
showSuccess(message) {
|
||||
// You can implement a more sophisticated success display here
|
||||
console.log('Success:', message);
|
||||
if (window.showSuccess) {
|
||||
window.showSuccess(message);
|
||||
}
|
||||
}
|
||||
|
||||
showError(message) {
|
||||
// You can implement a more sophisticated error display here
|
||||
console.error('Error:', message);
|
||||
if (window.showError) {
|
||||
window.showError(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize assessment functionality when DOM is loaded
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Initialize drag and drop
|
||||
window.assessmentDragDrop = new AssessmentDragDrop();
|
||||
|
||||
// Initialize capability manager
|
||||
window.capabilityManager = new CapabilityManager();
|
||||
|
||||
// Initialize assessment form
|
||||
window.assessmentForm = new AssessmentForm();
|
||||
});
|
||||
|
||||
// Global function for clearing assessment (called from HTML)
|
||||
function clearAssessment() {
|
||||
if (window.assessmentForm) {
|
||||
window.assessmentForm.clearForm();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user