986 lines
34 KiB
JavaScript
986 lines
34 KiB
JavaScript
const POCKETBASE_URL = 'https://pocketbase.ccllc.pro';
|
|
const pb = new PocketBase(POCKETBASE_URL);
|
|
|
|
// Load auth from localStorage or cookies
|
|
try {
|
|
pb.authStore.loadFromCookie(document.cookie);
|
|
} catch (e) {
|
|
const storedToken = localStorage.getItem('pb_auth_token');
|
|
const storedModel = localStorage.getItem('pb_auth_model');
|
|
if (storedToken && storedModel) {
|
|
try {
|
|
pb.authStore.save(storedToken, JSON.parse(storedModel));
|
|
} catch (e2) {
|
|
console.error('Error loading auth:', e2);
|
|
}
|
|
}
|
|
}
|
|
|
|
let currentPage = 1;
|
|
const perPage = 50;
|
|
let allEmployees = [];
|
|
let currentFilter = 'all';
|
|
let currentSearch = '';
|
|
let currentEditingEmployeeId = null;
|
|
let quickReportEditor = null;
|
|
|
|
// Get auth token for API requests
|
|
function getAuthToken() {
|
|
// Try to get from PocketBase auth store first
|
|
if (pb.authStore.token) {
|
|
return pb.authStore.token;
|
|
}
|
|
|
|
// Fallback to localStorage
|
|
const storedToken = localStorage.getItem('pb_auth_token');
|
|
if (storedToken) {
|
|
// Also update the auth store with the token
|
|
const storedModel = localStorage.getItem('pb_auth_model');
|
|
if (storedModel) {
|
|
try {
|
|
pb.authStore.save(storedToken, JSON.parse(storedModel));
|
|
} catch (e) {
|
|
console.error('Error loading auth from localStorage:', e);
|
|
}
|
|
}
|
|
return storedToken;
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
// Show message
|
|
function showMessage(message, type = 'success') {
|
|
const container = document.getElementById('messageContainer');
|
|
const bgColor = type === 'success' ? 'bg-green-50 border-green-200 text-green-700' : 'bg-red-50 border-red-200 text-red-700';
|
|
|
|
container.innerHTML = `
|
|
<div class="${bgColor} border px-4 py-3 rounded-lg">
|
|
${message}
|
|
</div>
|
|
`;
|
|
|
|
setTimeout(() => {
|
|
container.innerHTML = '';
|
|
}, 5000);
|
|
}
|
|
|
|
// Load employees
|
|
async function loadEmployees(page = 1) {
|
|
try {
|
|
const token = getAuthToken();
|
|
console.log('Loading employees, token present:', !!token);
|
|
|
|
if (!token) {
|
|
console.error('No auth token found');
|
|
window.location.href = '/index.html';
|
|
return;
|
|
}
|
|
|
|
const url = `/api/employees?page=${page}&perPage=${perPage}&sort=Last_Name`;
|
|
console.log('Fetching from:', url);
|
|
|
|
const response = await fetch(url, {
|
|
headers: {
|
|
'Authorization': `Bearer ${token}`,
|
|
},
|
|
});
|
|
|
|
console.log('Response status:', response.status);
|
|
|
|
if (response.status === 401) {
|
|
console.error('Unauthorized - redirecting to login');
|
|
window.location.href = '/index.html';
|
|
return;
|
|
}
|
|
|
|
if (!response.ok) {
|
|
const error = await response.json();
|
|
console.error('Error response:', error);
|
|
throw new Error(error.error || 'Failed to load employees');
|
|
}
|
|
|
|
const data = await response.json();
|
|
console.log('Successfully loaded employees:', data);
|
|
allEmployees = data.items;
|
|
applyFiltersAndSearch();
|
|
displayPagination(data);
|
|
currentPage = page;
|
|
} catch (error) {
|
|
console.error('Error loading employees:', error);
|
|
showMessage(error.message || 'Failed to load employees', 'error');
|
|
document.getElementById('employeesTableBody').innerHTML = `
|
|
<tr>
|
|
<td colspan="5" class="px-6 py-4 text-center text-red-500">Error loading employees: ${error.message}</td>
|
|
</tr>
|
|
`;
|
|
}
|
|
}
|
|
|
|
// Filter and search employees
|
|
function applyFiltersAndSearch() {
|
|
let filtered = [...allEmployees];
|
|
|
|
// Apply type filter
|
|
if (currentFilter === 'employee') {
|
|
filtered = filtered.filter(emp => emp.Type === 'Employee');
|
|
} else if (currentFilter === 'sub-contractor') {
|
|
filtered = filtered.filter(emp => emp.Type === 'Sub-Contractor');
|
|
}
|
|
|
|
// Apply search
|
|
if (currentSearch.trim()) {
|
|
const searchLower = currentSearch.toLowerCase().trim();
|
|
filtered = filtered.filter(emp => {
|
|
const firstName = (emp.First_Name || '').toLowerCase();
|
|
const lastName = (emp.Last_Name || '').toLowerCase();
|
|
const id = (emp.id || '').toLowerCase();
|
|
const fullName = `${firstName} ${lastName}`;
|
|
return firstName.includes(searchLower) ||
|
|
lastName.includes(searchLower) ||
|
|
fullName.includes(searchLower) ||
|
|
id.includes(searchLower);
|
|
});
|
|
}
|
|
|
|
// Sort by last name A to Z
|
|
filtered.sort((a, b) => {
|
|
const lastNameA = (a.Last_Name || '').toLowerCase();
|
|
const lastNameB = (b.Last_Name || '').toLowerCase();
|
|
if (lastNameA < lastNameB) return -1;
|
|
if (lastNameA > lastNameB) return 1;
|
|
// If last names are the same, sort by first name
|
|
const firstNameA = (a.First_Name || '').toLowerCase();
|
|
const firstNameB = (b.First_Name || '').toLowerCase();
|
|
if (firstNameA < firstNameB) return -1;
|
|
if (firstNameA > firstNameB) return 1;
|
|
return 0;
|
|
});
|
|
|
|
displayEmployees(filtered);
|
|
}
|
|
|
|
// Display employees in table
|
|
function displayEmployees(employees) {
|
|
const tbody = document.getElementById('employeesTableBody');
|
|
|
|
if (employees.length === 0) {
|
|
tbody.innerHTML = `
|
|
<tr>
|
|
<td colspan="5" class="px-6 py-4 text-center text-gray-500">No employees found</td>
|
|
</tr>
|
|
`;
|
|
return;
|
|
}
|
|
|
|
tbody.innerHTML = employees.map(emp => `
|
|
<tr class="hover:bg-gray-50">
|
|
<td class="px-3 sm:px-6 py-3 sm:py-4 whitespace-nowrap text-sm text-gray-900">${emp.id || '-'}</td>
|
|
<td class="px-3 sm:px-6 py-3 sm:py-4 whitespace-nowrap text-sm text-gray-900">${emp.First_Name || '-'}</td>
|
|
<td class="px-3 sm:px-6 py-3 sm:py-4 whitespace-nowrap text-sm text-gray-900">${emp.Last_Name || '-'}</td>
|
|
<td class="px-3 sm:px-6 py-3 sm:py-4 whitespace-nowrap text-sm text-gray-500">${emp.Type || '-'}</td>
|
|
<td class="px-3 sm:px-6 py-3 sm:py-4 whitespace-nowrap text-right text-sm font-medium">
|
|
<button onclick="editEmployee('${emp.id}')" class="text-indigo-600 hover:text-indigo-900 mr-2 sm:mr-3 py-1 px-2 sm:px-0">Edit</button>
|
|
<button onclick="deleteEmployee('${emp.id}')" class="text-red-600 hover:text-red-900 py-1 px-2 sm:px-0">Delete</button>
|
|
</td>
|
|
</tr>
|
|
`).join('');
|
|
}
|
|
|
|
// Update filter button styles
|
|
function updateFilterButtons() {
|
|
const allBtn = document.getElementById('filterAll');
|
|
const employeeBtn = document.getElementById('filterEmployee');
|
|
const subContractorBtn = document.getElementById('filterSubContractor');
|
|
|
|
// Reset all buttons
|
|
[allBtn, employeeBtn, subContractorBtn].forEach(btn => {
|
|
btn.classList.remove('bg-indigo-600', 'text-white', 'hover:bg-indigo-700');
|
|
btn.classList.add('bg-gray-200', 'text-gray-700', 'hover:bg-gray-300');
|
|
});
|
|
|
|
// Set active button
|
|
let activeBtn;
|
|
if (currentFilter === 'all') {
|
|
activeBtn = allBtn;
|
|
} else if (currentFilter === 'employee') {
|
|
activeBtn = employeeBtn;
|
|
} else if (currentFilter === 'sub-contractor') {
|
|
activeBtn = subContractorBtn;
|
|
}
|
|
|
|
if (activeBtn) {
|
|
activeBtn.classList.remove('bg-gray-200', 'text-gray-700', 'hover:bg-gray-300');
|
|
activeBtn.classList.add('bg-indigo-600', 'text-white', 'hover:bg-indigo-700');
|
|
}
|
|
}
|
|
|
|
// Display pagination
|
|
function displayPagination(data) {
|
|
const pagination = document.getElementById('pagination');
|
|
|
|
if (data.totalPages <= 1) {
|
|
pagination.innerHTML = `<div class="text-sm text-gray-500">Total: ${data.totalItems} employees</div>`;
|
|
return;
|
|
}
|
|
|
|
pagination.innerHTML = `
|
|
<div class="text-sm text-gray-500">
|
|
Showing ${(data.page - 1) * data.perPage + 1} to ${Math.min(data.page * data.perPage, data.totalItems)} of ${data.totalItems} employees
|
|
</div>
|
|
<div class="flex space-x-2">
|
|
<button
|
|
onclick="loadEmployees(${data.page - 1})"
|
|
${data.page === 1 ? 'disabled' : ''}
|
|
class="px-3 py-1 border rounded-lg ${data.page === 1 ? 'bg-gray-100 text-gray-400 cursor-not-allowed' : 'bg-white text-gray-700 hover:bg-gray-50'}"
|
|
>
|
|
Previous
|
|
</button>
|
|
<span class="px-3 py-1 text-gray-700">Page ${data.page} of ${data.totalPages}</span>
|
|
<button
|
|
onclick="loadEmployees(${data.page + 1})"
|
|
${data.page === data.totalPages ? 'disabled' : ''}
|
|
class="px-3 py-1 border rounded-lg ${data.page === data.totalPages ? 'bg-gray-100 text-gray-400 cursor-not-allowed' : 'bg-white text-gray-700 hover:bg-gray-50'}"
|
|
>
|
|
Next
|
|
</button>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
// Open modal for adding employee
|
|
function openAddModal() {
|
|
currentEditingEmployeeId = null;
|
|
document.getElementById('modalTitle').textContent = 'Add Employee';
|
|
document.getElementById('employeeForm').reset();
|
|
document.getElementById('employeeId').value = '';
|
|
|
|
// Hide Reports tab for new employees
|
|
document.getElementById('employeeReportsTab').style.display = 'none';
|
|
|
|
// Show Employee Info tab
|
|
switchToEmployeeInfoTab();
|
|
|
|
document.getElementById('employeeModal').classList.remove('hidden');
|
|
}
|
|
|
|
// Switch to Employee Info tab
|
|
function switchToEmployeeInfoTab() {
|
|
document.getElementById('employeeInfoTab').classList.add('border-indigo-500', 'text-indigo-600');
|
|
document.getElementById('employeeInfoTab').classList.remove('border-transparent', 'text-gray-500');
|
|
document.getElementById('employeeReportsTab').classList.remove('border-indigo-500', 'text-indigo-600');
|
|
document.getElementById('employeeReportsTab').classList.add('border-transparent', 'text-gray-500');
|
|
document.getElementById('employeeInfoContent').classList.remove('hidden');
|
|
document.getElementById('employeeReportsContent').classList.add('hidden');
|
|
}
|
|
|
|
// Switch to Reports tab
|
|
function switchToReportsTab() {
|
|
document.getElementById('employeeReportsTab').classList.add('border-indigo-500', 'text-indigo-600');
|
|
document.getElementById('employeeReportsTab').classList.remove('border-transparent', 'text-gray-500');
|
|
document.getElementById('employeeInfoTab').classList.remove('border-indigo-500', 'text-indigo-600');
|
|
document.getElementById('employeeInfoTab').classList.add('border-transparent', 'text-gray-500');
|
|
document.getElementById('employeeInfoContent').classList.add('hidden');
|
|
document.getElementById('employeeReportsContent').classList.remove('hidden');
|
|
}
|
|
|
|
// Initialize quick report editor
|
|
function initQuickReportEditor() {
|
|
if (!quickReportEditor) {
|
|
const editorContainer = document.getElementById('quickReportEditor');
|
|
if (editorContainer && typeof Quill !== 'undefined') {
|
|
try {
|
|
quickReportEditor = new Quill('#quickReportEditor', {
|
|
theme: 'snow',
|
|
modules: {
|
|
toolbar: [
|
|
[{ 'header': [1, 2, 3, false] }],
|
|
['bold', 'italic', 'underline'],
|
|
[{ 'list': 'ordered'}, { 'list': 'bullet' }],
|
|
['link'],
|
|
['clean']
|
|
]
|
|
}
|
|
});
|
|
|
|
// Force text color to be dark
|
|
const style = document.createElement('style');
|
|
style.textContent = `
|
|
#quickReportEditor .ql-editor {
|
|
color: #1f2937 !important;
|
|
background-color: #ffffff !important;
|
|
}
|
|
#quickReportEditor .ql-editor.ql-blank::before {
|
|
color: #9ca3af !important;
|
|
}
|
|
#quickReportEditor .ql-editor p,
|
|
#quickReportEditor .ql-editor h1,
|
|
#quickReportEditor .ql-editor h2,
|
|
#quickReportEditor .ql-editor h3 {
|
|
color: #1f2937 !important;
|
|
}
|
|
`;
|
|
document.head.appendChild(style);
|
|
|
|
quickReportEditor.on('text-change', function() {
|
|
const htmlContent = quickReportEditor.root.innerHTML;
|
|
document.getElementById('quickReport').value = htmlContent;
|
|
});
|
|
} catch (error) {
|
|
console.error('Error initializing quick report editor:', error);
|
|
}
|
|
}
|
|
}
|
|
return quickReportEditor;
|
|
}
|
|
|
|
// Load employee reports
|
|
async function loadEmployeeReports(employeeId) {
|
|
try {
|
|
const token = getAuthToken();
|
|
const response = await fetch(`/api/reports?employee=${employeeId}&perPage=1000&sort=-Report_Date`, {
|
|
headers: {
|
|
'Authorization': `Bearer ${token}`,
|
|
},
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Failed to load reports');
|
|
}
|
|
|
|
const data = await response.json();
|
|
displayEmployeeReports(data.items || []);
|
|
} catch (error) {
|
|
console.error('Error loading employee reports:', error);
|
|
document.getElementById('employeeReportsList').innerHTML = '<div class="text-center text-red-500 py-4">Failed to load reports</div>';
|
|
}
|
|
}
|
|
|
|
// Display employee reports in post board style
|
|
function displayEmployeeReports(reports) {
|
|
const container = document.getElementById('employeeReportsList');
|
|
|
|
if (reports.length === 0) {
|
|
container.innerHTML = '<div class="text-center text-gray-500 py-8">No reports yet. Add one to get started!</div>';
|
|
return;
|
|
}
|
|
|
|
const reportsHtml = reports.map(report => {
|
|
// Format date and time (convert from UTC to local for display)
|
|
const reportDate = report.Report_Date
|
|
? new Date(report.Report_Date).toLocaleString(undefined, {
|
|
year: 'numeric',
|
|
month: 'short',
|
|
day: 'numeric',
|
|
hour: '2-digit',
|
|
minute: '2-digit',
|
|
timeZoneName: 'short'
|
|
})
|
|
: 'No date';
|
|
|
|
const sentimentColor = {
|
|
'Positive': 'bg-green-100 text-green-800',
|
|
'Negative': 'bg-red-100 text-red-800',
|
|
'Neutral': 'bg-gray-100 text-gray-800'
|
|
}[report.Centiment] || 'bg-gray-100 text-gray-800';
|
|
|
|
const reportText = report.Report || '';
|
|
|
|
// Collect all links and detect Voxer links
|
|
const links = [];
|
|
const isVoxerLink = (url) => {
|
|
if (!url) return false;
|
|
const lowerUrl = url.toLowerCase();
|
|
return lowerUrl.includes('voxer.com') || lowerUrl.includes('voxer');
|
|
};
|
|
|
|
if (report.Link_01) links.push({ num: 1, url: report.Link_01, isVoxer: isVoxerLink(report.Link_01) });
|
|
if (report.Link_02) links.push({ num: 2, url: report.Link_02, isVoxer: isVoxerLink(report.Link_02) });
|
|
if (report.Link_03) links.push({ num: 3, url: report.Link_03, isVoxer: isVoxerLink(report.Link_03) });
|
|
if (report.Link_04) links.push({ num: 4, url: report.Link_04, isVoxer: isVoxerLink(report.Link_04) });
|
|
if (report.Link_05) links.push({ num: 5, url: report.Link_05, isVoxer: isVoxerLink(report.Link_05) });
|
|
|
|
return `
|
|
<div class="bg-white border border-gray-200 rounded-lg p-3 sm:p-4 shadow-sm hover:shadow-md transition-shadow">
|
|
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-2 sm:gap-0 mb-3">
|
|
<div class="flex flex-wrap items-center gap-2">
|
|
<span class="text-xs sm:text-sm font-medium text-gray-900">📅 ${reportDate}</span>
|
|
${report.Centiment ? `<span class="px-2 py-1 text-xs font-medium rounded ${sentimentColor}">${escapeHtml(report.Centiment)}</span>` : ''}
|
|
</div>
|
|
<div class="flex gap-2">
|
|
<button onclick="editEmployeeReport('${report.id}')" class="text-indigo-600 hover:text-indigo-800 text-xs sm:text-sm py-1 px-2">
|
|
Edit
|
|
</button>
|
|
<button onclick="deleteEmployeeReport('${report.id}')" class="text-red-600 hover:text-red-800 text-xs sm:text-sm py-1 px-2 -mr-2 sm:mr-0 sm:px-0">
|
|
Delete
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="prose prose-sm max-w-none text-gray-700 mb-3">
|
|
${report.Report || '<em>No content</em>'}
|
|
</div>
|
|
${links.length > 0 ? `
|
|
<div class="mt-3 pt-3 border-t border-gray-200">
|
|
<div class="text-xs font-medium text-gray-500 mb-2">Links:</div>
|
|
<div class="flex flex-wrap gap-2">
|
|
${links.map(link => {
|
|
if (link.isVoxer) {
|
|
return `
|
|
<a href="${escapeHtml(link.url)}" target="_blank" rel="noopener noreferrer"
|
|
class="inline-flex items-center px-3 py-2 text-sm font-medium text-white bg-purple-600 hover:bg-purple-700 rounded-lg border border-purple-700 transition-colors shadow-sm">
|
|
🎙️ Voxer ${link.num}
|
|
</a>
|
|
`;
|
|
} else {
|
|
return `
|
|
<a href="${escapeHtml(link.url)}" target="_blank" rel="noopener noreferrer"
|
|
class="inline-flex items-center px-3 py-2 text-sm font-medium text-indigo-600 hover:text-indigo-800 hover:bg-indigo-50 rounded-lg border border-indigo-200 transition-colors">
|
|
🔗 Link ${link.num}
|
|
</a>
|
|
`;
|
|
}
|
|
}).join('')}
|
|
</div>
|
|
</div>
|
|
` : ''}
|
|
</div>
|
|
`;
|
|
}).join('');
|
|
|
|
container.innerHTML = reportsHtml;
|
|
}
|
|
|
|
// Edit employee report
|
|
let currentEditingReportId = null;
|
|
|
|
window.editEmployeeReport = async function(reportId) {
|
|
try {
|
|
currentEditingReportId = reportId;
|
|
const token = getAuthToken();
|
|
const response = await fetch(`/api/reports/${reportId}`, {
|
|
headers: {
|
|
'Authorization': `Bearer ${token}`,
|
|
},
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Failed to load report');
|
|
}
|
|
|
|
const report = await response.json();
|
|
|
|
// Populate the quick report form
|
|
initQuickReportEditor();
|
|
|
|
// Set rich text editor content
|
|
const reportContent = report.Report || '';
|
|
setTimeout(() => {
|
|
if (quickReportEditor) {
|
|
quickReportEditor.root.innerHTML = reportContent || '<p><br></p>';
|
|
document.getElementById('quickReport').value = reportContent;
|
|
}
|
|
}, 100);
|
|
|
|
document.getElementById('quickSentiment').value = report.Centiment || '';
|
|
|
|
// Format date and time for input field (convert from UTC to local)
|
|
if (report.Report_Date) {
|
|
const date = new Date(report.Report_Date);
|
|
const year = date.getFullYear();
|
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
const day = String(date.getDate()).padStart(2, '0');
|
|
const hours = String(date.getHours()).padStart(2, '0');
|
|
const minutes = String(date.getMinutes()).padStart(2, '0');
|
|
document.getElementById('quickReportDate').value = `${year}-${month}-${day}T${hours}:${minutes}`;
|
|
} else {
|
|
document.getElementById('quickReportDate').value = '';
|
|
}
|
|
|
|
// Set links
|
|
document.getElementById('quickLink01').value = report.Link_01 || '';
|
|
document.getElementById('quickLink02').value = report.Link_02 || '';
|
|
document.getElementById('quickLink03').value = report.Link_03 || '';
|
|
document.getElementById('quickLink04').value = report.Link_04 || '';
|
|
document.getElementById('quickLink05').value = report.Link_05 || '';
|
|
|
|
// Update form title
|
|
const formTitle = document.querySelector('#addReportForm h4');
|
|
if (formTitle) {
|
|
formTitle.textContent = 'Edit Report';
|
|
}
|
|
|
|
// Show the form
|
|
document.getElementById('addReportForm').classList.remove('hidden');
|
|
|
|
// Scroll to form
|
|
document.getElementById('addReportForm').scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
} catch (error) {
|
|
console.error('Error loading report:', error);
|
|
showMessage('Failed to load report', 'error');
|
|
}
|
|
};
|
|
|
|
// Delete employee report
|
|
window.deleteEmployeeReport = async function(reportId) {
|
|
if (!confirm('Are you sure you want to delete this report?')) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const token = getAuthToken();
|
|
const response = await fetch(`/api/reports/${reportId}`, {
|
|
method: 'DELETE',
|
|
headers: {
|
|
'Authorization': `Bearer ${token}`,
|
|
},
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Failed to delete report');
|
|
}
|
|
|
|
// Reload reports
|
|
if (currentEditingEmployeeId) {
|
|
await loadEmployeeReports(currentEditingEmployeeId);
|
|
}
|
|
showMessage('Report deleted successfully', 'success');
|
|
} catch (error) {
|
|
console.error('Error deleting report:', error);
|
|
showMessage('Failed to delete report', 'error');
|
|
}
|
|
};
|
|
|
|
// Edit employee
|
|
async function editEmployee(id) {
|
|
try {
|
|
currentEditingEmployeeId = id;
|
|
const token = getAuthToken();
|
|
const response = await fetch(`/api/employees/${id}`, {
|
|
headers: {
|
|
'Authorization': `Bearer ${token}`,
|
|
},
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Failed to load employee');
|
|
}
|
|
|
|
const employee = await response.json();
|
|
|
|
document.getElementById('modalTitle').textContent = 'Edit Employee';
|
|
document.getElementById('employeeId').value = employee.id || '';
|
|
document.getElementById('firstName').value = employee.First_Name || '';
|
|
document.getElementById('lastName').value = employee.Last_Name || '';
|
|
document.getElementById('email').value = employee.Email || '';
|
|
document.getElementById('phoneNumber').value = employee.Phone_Number || '';
|
|
document.getElementById('voxerId').value = employee.Voxer_ID || '';
|
|
document.getElementById('type').value = employee.Type || '';
|
|
|
|
// Show Reports tab button
|
|
document.getElementById('employeeReportsTab').style.display = 'block';
|
|
|
|
// Show Employee Info tab by default
|
|
switchToEmployeeInfoTab();
|
|
|
|
// Load employee reports
|
|
await loadEmployeeReports(id);
|
|
|
|
document.getElementById('employeeModal').classList.remove('hidden');
|
|
} catch (error) {
|
|
console.error('Error loading employee:', error);
|
|
showMessage('Failed to load employee', 'error');
|
|
}
|
|
}
|
|
|
|
// Delete employee
|
|
async function deleteEmployee(id) {
|
|
if (!confirm('Are you sure you want to delete this employee?')) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const token = getAuthToken();
|
|
const response = await fetch(`/api/employees/${id}`, {
|
|
method: 'DELETE',
|
|
headers: {
|
|
'Authorization': `Bearer ${token}`,
|
|
},
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const error = await response.json();
|
|
throw new Error(error.error || 'Failed to delete employee');
|
|
}
|
|
|
|
showMessage('Employee deleted successfully');
|
|
// Reset search and filter when reloading
|
|
currentSearch = '';
|
|
currentFilter = 'all';
|
|
const searchInput = document.getElementById('searchInput');
|
|
if (searchInput) {
|
|
searchInput.value = '';
|
|
}
|
|
updateFilterButtons();
|
|
loadEmployees(currentPage);
|
|
} catch (error) {
|
|
console.error('Error deleting employee:', error);
|
|
showMessage(error.message || 'Failed to delete employee', 'error');
|
|
}
|
|
}
|
|
|
|
// Initialize page - check auth and display user info
|
|
async function initPage() {
|
|
// First, ensure we have the token loaded from localStorage if not in auth store
|
|
const token = getAuthToken();
|
|
|
|
if (!token) {
|
|
// No token at all, redirect to login
|
|
console.log('No auth token found, redirecting to login');
|
|
window.location.href = '/index.html';
|
|
return;
|
|
}
|
|
|
|
// Check if auth store is valid, if not try to refresh
|
|
if (!pb.authStore.isValid) {
|
|
try {
|
|
// Try to refresh the token
|
|
await pb.collection('users').authRefresh();
|
|
// Update localStorage after refresh
|
|
if (pb.authStore.token && pb.authStore.model) {
|
|
localStorage.setItem('pb_auth_token', pb.authStore.token);
|
|
localStorage.setItem('pb_auth_model', JSON.stringify(pb.authStore.model));
|
|
}
|
|
} catch (e) {
|
|
console.error('Token refresh failed:', e);
|
|
// If refresh fails but we have a token, still try to continue
|
|
// The API will validate it
|
|
if (!pb.authStore.token) {
|
|
pb.authStore.clear();
|
|
localStorage.removeItem('pb_auth_token');
|
|
localStorage.removeItem('pb_auth_model');
|
|
window.location.href = '/index.html';
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Display user information
|
|
const user = pb.authStore.model;
|
|
if (user) {
|
|
const userEmailElement = document.getElementById('userEmail');
|
|
if (userEmailElement) {
|
|
userEmailElement.textContent = user.email || 'User';
|
|
}
|
|
} else {
|
|
// Try to get user from localStorage
|
|
const storedModel = localStorage.getItem('pb_auth_model');
|
|
if (storedModel) {
|
|
try {
|
|
const userData = JSON.parse(storedModel);
|
|
const userEmailElement = document.getElementById('userEmail');
|
|
if (userEmailElement) {
|
|
userEmailElement.textContent = userData.email || 'User';
|
|
}
|
|
} catch (e) {
|
|
console.error('Error parsing stored user model:', e);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Handle logout button
|
|
const logoutButton = document.getElementById('logoutButton');
|
|
if (logoutButton) {
|
|
logoutButton.addEventListener('click', async () => {
|
|
try {
|
|
await pb.authStore.clear();
|
|
localStorage.removeItem('pb_auth_token');
|
|
localStorage.removeItem('pb_auth_model');
|
|
window.location.href = '/index.html';
|
|
} catch (error) {
|
|
console.error('Logout error:', error);
|
|
pb.authStore.clear();
|
|
localStorage.removeItem('pb_auth_token');
|
|
localStorage.removeItem('pb_auth_model');
|
|
window.location.href = '/index.html';
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// Handle form submission
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
// Initialize page (auth check, user display, logout)
|
|
initPage();
|
|
|
|
// Load employees on page load
|
|
loadEmployees();
|
|
|
|
// Add employee button
|
|
document.getElementById('addEmployeeBtn').addEventListener('click', openAddModal);
|
|
|
|
// Cancel button
|
|
document.getElementById('cancelBtn').addEventListener('click', () => {
|
|
document.getElementById('employeeModal').classList.add('hidden');
|
|
document.getElementById('addReportForm').classList.add('hidden');
|
|
if (quickReportEditor) {
|
|
quickReportEditor.setContents([]);
|
|
quickReportEditor.root.innerHTML = '<p><br></p>';
|
|
}
|
|
});
|
|
|
|
// Close modal button
|
|
const closeModalBtn = document.getElementById('closeEmployeeModal');
|
|
if (closeModalBtn) {
|
|
closeModalBtn.addEventListener('click', () => {
|
|
document.getElementById('employeeModal').classList.add('hidden');
|
|
document.getElementById('addReportForm').classList.add('hidden');
|
|
if (quickReportEditor) {
|
|
quickReportEditor.setContents([]);
|
|
quickReportEditor.root.innerHTML = '<p><br></p>';
|
|
}
|
|
});
|
|
}
|
|
|
|
// Tab switching
|
|
document.getElementById('employeeInfoTab').addEventListener('click', switchToEmployeeInfoTab);
|
|
document.getElementById('employeeReportsTab').addEventListener('click', switchToReportsTab);
|
|
|
|
// Add Report button
|
|
const addReportBtn = document.getElementById('addReportBtn');
|
|
if (addReportBtn) {
|
|
addReportBtn.addEventListener('click', () => {
|
|
currentEditingReportId = null;
|
|
initQuickReportEditor();
|
|
document.getElementById('addReportForm').classList.remove('hidden');
|
|
// Set current date/time as default
|
|
const now = new Date();
|
|
const year = now.getFullYear();
|
|
const month = String(now.getMonth() + 1).padStart(2, '0');
|
|
const day = String(now.getDate()).padStart(2, '0');
|
|
const hours = String(now.getHours()).padStart(2, '0');
|
|
const minutes = String(now.getMinutes()).padStart(2, '0');
|
|
document.getElementById('quickReportDate').value = `${year}-${month}-${day}T${hours}:${minutes}`;
|
|
// Reset form title
|
|
const formTitle = document.querySelector('#addReportForm h4');
|
|
if (formTitle) {
|
|
formTitle.textContent = 'Add New Report';
|
|
}
|
|
});
|
|
}
|
|
|
|
// Cancel quick report
|
|
const cancelQuickReport = document.getElementById('cancelQuickReport');
|
|
if (cancelQuickReport) {
|
|
cancelQuickReport.addEventListener('click', () => {
|
|
document.getElementById('addReportForm').classList.add('hidden');
|
|
currentEditingReportId = null;
|
|
if (quickReportEditor) {
|
|
quickReportEditor.setContents([]);
|
|
quickReportEditor.root.innerHTML = '<p><br></p>';
|
|
}
|
|
document.getElementById('quickReportForm').reset();
|
|
// Clear all link fields
|
|
document.getElementById('quickLink01').value = '';
|
|
document.getElementById('quickLink02').value = '';
|
|
document.getElementById('quickLink03').value = '';
|
|
document.getElementById('quickLink04').value = '';
|
|
document.getElementById('quickLink05').value = '';
|
|
// Reset form title
|
|
const formTitle = document.querySelector('#addReportForm h4');
|
|
if (formTitle) {
|
|
formTitle.textContent = 'Add New Report';
|
|
}
|
|
});
|
|
}
|
|
|
|
// Quick report form submission
|
|
const quickReportForm = document.getElementById('quickReportForm');
|
|
if (quickReportForm) {
|
|
quickReportForm.addEventListener('submit', async (e) => {
|
|
e.preventDefault();
|
|
|
|
if (!currentEditingEmployeeId) {
|
|
showMessage('Please save the employee first', 'error');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const token = getAuthToken();
|
|
|
|
// Convert datetime-local to UTC ISO string
|
|
let reportDateUTC = undefined;
|
|
const reportDateValue = document.getElementById('quickReportDate').value;
|
|
if (reportDateValue) {
|
|
const localDate = new Date(reportDateValue);
|
|
reportDateUTC = localDate.toISOString();
|
|
}
|
|
|
|
const formData = {
|
|
Employee: currentEditingEmployeeId,
|
|
Report: document.getElementById('quickReport').value || undefined,
|
|
Centiment: document.getElementById('quickSentiment').value || undefined,
|
|
Report_Date: reportDateUTC,
|
|
Link_01: document.getElementById('quickLink01').value || undefined,
|
|
Link_02: document.getElementById('quickLink02').value || undefined,
|
|
Link_03: document.getElementById('quickLink03').value || undefined,
|
|
Link_04: document.getElementById('quickLink04').value || undefined,
|
|
Link_05: document.getElementById('quickLink05').value || undefined,
|
|
};
|
|
|
|
const url = currentEditingReportId ? `/api/reports/${currentEditingReportId}` : '/api/reports';
|
|
const method = currentEditingReportId ? 'PATCH' : 'POST';
|
|
|
|
const response = await fetch(url, {
|
|
method,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${token}`,
|
|
},
|
|
body: JSON.stringify(formData),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const error = await response.json();
|
|
throw new Error(error.error || 'Failed to save report');
|
|
}
|
|
|
|
showMessage(`Report ${currentEditingReportId ? 'updated' : 'added'} successfully`, 'success');
|
|
document.getElementById('addReportForm').classList.add('hidden');
|
|
quickReportForm.reset();
|
|
currentEditingReportId = null;
|
|
if (quickReportEditor) {
|
|
quickReportEditor.setContents([]);
|
|
quickReportEditor.root.innerHTML = '<p><br></p>';
|
|
}
|
|
// Clear all link fields
|
|
document.getElementById('quickLink01').value = '';
|
|
document.getElementById('quickLink02').value = '';
|
|
document.getElementById('quickLink03').value = '';
|
|
document.getElementById('quickLink04').value = '';
|
|
document.getElementById('quickLink05').value = '';
|
|
|
|
// Update form title back to "Add New Report"
|
|
const formTitle = document.querySelector('#addReportForm h4');
|
|
if (formTitle) {
|
|
formTitle.textContent = 'Add New Report';
|
|
}
|
|
|
|
// Reload reports
|
|
await loadEmployeeReports(currentEditingEmployeeId);
|
|
} catch (error) {
|
|
console.error('Error saving report:', error);
|
|
showMessage(error.message || 'Failed to save report', 'error');
|
|
}
|
|
});
|
|
}
|
|
|
|
// Search input
|
|
const searchInput = document.getElementById('searchInput');
|
|
if (searchInput) {
|
|
searchInput.addEventListener('input', (e) => {
|
|
currentSearch = e.target.value;
|
|
applyFiltersAndSearch();
|
|
});
|
|
}
|
|
|
|
// Filter buttons
|
|
const filterAll = document.getElementById('filterAll');
|
|
const filterEmployee = document.getElementById('filterEmployee');
|
|
const filterSubContractor = document.getElementById('filterSubContractor');
|
|
|
|
if (filterAll) {
|
|
filterAll.addEventListener('click', () => {
|
|
currentFilter = 'all';
|
|
updateFilterButtons();
|
|
applyFiltersAndSearch();
|
|
});
|
|
}
|
|
|
|
if (filterEmployee) {
|
|
filterEmployee.addEventListener('click', () => {
|
|
currentFilter = 'employee';
|
|
updateFilterButtons();
|
|
applyFiltersAndSearch();
|
|
});
|
|
}
|
|
|
|
if (filterSubContractor) {
|
|
filterSubContractor.addEventListener('click', () => {
|
|
currentFilter = 'sub-contractor';
|
|
updateFilterButtons();
|
|
applyFiltersAndSearch();
|
|
});
|
|
}
|
|
|
|
// Initialize filter button styles
|
|
updateFilterButtons();
|
|
|
|
// Form submission
|
|
document.getElementById('employeeForm').addEventListener('submit', async (e) => {
|
|
e.preventDefault();
|
|
|
|
const formData = new FormData(e.target);
|
|
const employeeId = formData.get('id');
|
|
const data = {
|
|
First_Name: formData.get('First_Name') || '',
|
|
Last_Name: formData.get('Last_Name') || '',
|
|
Email: formData.get('Email') || '',
|
|
Phone_Number: formData.get('Phone_Number') || '',
|
|
Voxer_ID: formData.get('Voxer_ID') || '',
|
|
Type: formData.get('Type') || '',
|
|
};
|
|
|
|
// Note: ID is only needed in URL for updates, not in body
|
|
// For creates, ID is optional and auto-generated if not provided
|
|
|
|
try {
|
|
const token = getAuthToken();
|
|
const url = employeeId ? `/api/employees/${employeeId}` : '/api/employees';
|
|
const method = employeeId ? 'PATCH' : 'POST';
|
|
|
|
const response = await fetch(url, {
|
|
method: method,
|
|
headers: {
|
|
'Authorization': `Bearer ${token}`,
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify(data),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const error = await response.json();
|
|
throw new Error(error.error || 'Failed to save employee');
|
|
}
|
|
|
|
showMessage(`Employee ${employeeId ? 'updated' : 'created'} successfully`);
|
|
document.getElementById('employeeModal').classList.add('hidden');
|
|
// Reset search and filter when reloading
|
|
currentSearch = '';
|
|
currentFilter = 'all';
|
|
const searchInput = document.getElementById('searchInput');
|
|
if (searchInput) {
|
|
searchInput.value = '';
|
|
}
|
|
updateFilterButtons();
|
|
loadEmployees(currentPage);
|
|
} catch (error) {
|
|
console.error('Error saving employee:', error);
|
|
showMessage(error.message || 'Failed to save employee', 'error');
|
|
}
|
|
});
|
|
});
|
|
|
|
// Utility function to escape HTML
|
|
function escapeHtml(text) {
|
|
if (!text) return '';
|
|
const div = document.createElement('div');
|
|
div.textContent = text;
|
|
return div.innerHTML;
|
|
}
|
|
|
|
// Make functions globally available
|
|
window.editEmployee = editEmployee;
|
|
window.deleteEmployee = deleteEmployee;
|
|
window.loadEmployees = loadEmployees;
|
|
|