This commit is contained in:
2025-12-14 13:31:29 -06:00
parent eefd867ab5
commit a6672f08fa
19 changed files with 3252 additions and 1 deletions
+74
View File
@@ -0,0 +1,74 @@
const POCKETBASE_URL = 'https://pocketbase.ccllc.pro';
// Initialize PocketBase client
const pb = new PocketBase(POCKETBASE_URL);
// Load existing auth from cookies (PocketBase stores auth in cookies automatically)
try {
pb.authStore.loadFromCookie(document.cookie);
} catch (e) {
// If cookie loading fails, try localStorage
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);
}
}
}
// Check if user is already authenticated
function checkAuth() {
return pb.authStore.isValid;
}
// Handle login form submission
document.addEventListener('DOMContentLoaded', () => {
const loginForm = document.getElementById('loginForm');
const errorMessage = document.getElementById('errorMessage');
if (loginForm) {
loginForm.addEventListener('submit', async (e) => {
e.preventDefault();
const email = document.getElementById('email').value;
const password = document.getElementById('password').value;
const submitButton = document.getElementById('submitButton');
const originalButtonText = submitButton.textContent;
// Disable button and show loading state
submitButton.disabled = true;
submitButton.textContent = 'Logging in...';
errorMessage.textContent = '';
errorMessage.classList.add('hidden');
try {
// Authenticate with PocketBase (automatically stores in cookies)
const authData = await pb.collection('users').authWithPassword(email, password);
// Store token in localStorage as backup
if (authData && authData.token) {
localStorage.setItem('pb_auth_token', authData.token);
localStorage.setItem('pb_auth_model', JSON.stringify(authData.record));
}
// Redirect to dashboard
window.location.href = '/dashboard.html';
} catch (error) {
console.error('Login error:', error);
errorMessage.textContent = error.message || 'Invalid email or password';
errorMessage.classList.remove('hidden');
submitButton.disabled = false;
submitButton.textContent = originalButtonText;
}
});
}
// If already authenticated, redirect to dashboard
if (checkAuth() && (window.location.pathname === '/index.html' || window.location.pathname === '/')) {
window.location.href = '/dashboard.html';
}
});
+179
View File
@@ -0,0 +1,179 @@
const POCKETBASE_URL = 'https://pocketbase.ccllc.pro';
// Initialize PocketBase client
const pb = new PocketBase(POCKETBASE_URL);
// Check authentication and load user data
async function initDashboard() {
// Load auth from cookies first
try {
pb.authStore.loadFromCookie(document.cookie);
} catch (e) {
console.error('Error loading from cookie:', e);
}
// If not valid from cookie, try loading from localStorage
if (!pb.authStore.isValid) {
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 (e) {
console.error('Error loading from localStorage:', e);
}
}
}
// Check if user is authenticated
if (!pb.authStore.isValid) {
// Try to refresh the token if we have a token but it's expired
if (pb.authStore.token) {
try {
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, clear everything and redirect to login
pb.authStore.clear();
localStorage.removeItem('pb_auth_token');
localStorage.removeItem('pb_auth_model');
window.location.href = '/index.html';
return;
}
} else {
// No token at all, redirect to login
localStorage.removeItem('pb_auth_token');
localStorage.removeItem('pb_auth_model');
window.location.href = '/index.html';
return;
}
}
try {
// Display user information
const user = pb.authStore.model;
if (user) {
const userEmailElement = document.getElementById('userEmail');
const userNameElement = document.getElementById('userName');
if (userEmailElement) {
userEmailElement.textContent = user.email || 'User';
}
if (userNameElement) {
userNameElement.textContent = user.username || user.email || 'User';
}
} else {
// No user model, redirect to login
console.error('No user model found');
pb.authStore.clear();
localStorage.removeItem('pb_auth_token');
localStorage.removeItem('pb_auth_model');
window.location.href = '/index.html';
}
} catch (error) {
console.error('Dashboard init error:', error);
pb.authStore.clear();
localStorage.removeItem('pb_auth_token');
localStorage.removeItem('pb_auth_model');
window.location.href = '/index.html';
}
}
// Get auth token for API requests
function getAuthToken() {
return pb.authStore.token || localStorage.getItem('pb_auth_token') || '';
}
// Load dashboard statistics
async function loadStats() {
try {
const token = getAuthToken();
if (!token) {
return;
}
// Load employees stats
try {
const employeesResponse = await fetch('/api/employees?page=1&perPage=1000', {
headers: {
'Authorization': `Bearer ${token}`,
},
});
if (employeesResponse.ok) {
const employeesData = await employeesResponse.json();
const totalEmployeesElement = document.getElementById('totalEmployees');
if (totalEmployeesElement) {
totalEmployeesElement.innerHTML = employeesData.totalItems || 0;
}
// Count active employees (those with Type = "Employee")
const activeCount = employeesData.items?.filter(emp => emp.Type === 'Employee').length || 0;
const activeEmployeesElement = document.getElementById('activeEmployees');
if (activeEmployeesElement) {
activeEmployeesElement.innerHTML = activeCount;
}
}
} catch (error) {
console.error('Error loading employee stats:', error);
}
// Load reports stats
try {
const reportsResponse = await fetch('/api/reports?page=1&perPage=1', {
headers: {
'Authorization': `Bearer ${token}`,
},
});
if (reportsResponse.ok) {
const reportsData = await reportsResponse.json();
const totalReportsElement = document.getElementById('totalReports');
if (totalReportsElement) {
totalReportsElement.innerHTML = reportsData.totalItems || 0;
}
}
} catch (error) {
console.error('Error loading report stats:', error);
}
} catch (error) {
console.error('Error loading stats:', error);
}
}
// Handle logout
function handleLogout() {
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);
// Clear auth store anyway
pb.authStore.clear();
localStorage.removeItem('pb_auth_token');
localStorage.removeItem('pb_auth_model');
window.location.href = '/index.html';
}
});
}
}
// Initialize dashboard when page loads
document.addEventListener('DOMContentLoaded', async () => {
await initDashboard();
handleLogout();
loadStats();
});
+985
View File
@@ -0,0 +1,985 @@
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;
+632
View File
@@ -0,0 +1,632 @@
const POCKETBASE_URL = 'https://pocketbase.ccllc.pro';
const API_BASE = '/api/reports';
const EMPLOYEES_API = '/api/employees';
// Initialize PocketBase client
const pb = new PocketBase(POCKETBASE_URL);
// Load auth from localStorage/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);
}
}
}
// 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 '';
}
// Load employees for dropdown
let employeesList = [];
// Rich text editor instance
let reportEditor = null;
async function loadEmployees() {
try {
const token = getAuthToken();
if (!token) {
return;
}
const response = await fetch(`${EMPLOYEES_API}?page=1&perPage=1000`, {
headers: {
'Authorization': `Bearer ${token}`,
},
});
if (!response.ok) {
throw new Error('Failed to load employees');
}
const data = await response.json();
employeesList = data.items || [];
populateEmployeeDropdown();
} catch (error) {
console.error('Error loading employees:', error);
}
}
function populateEmployeeDropdown() {
const employeeSelect = document.getElementById('Employee');
if (!employeeSelect) return;
// Clear existing options except the first one
while (employeeSelect.options.length > 1) {
employeeSelect.remove(1);
}
employeesList.forEach(emp => {
const option = document.createElement('option');
option.value = emp.id;
const name = `${emp.First_Name || ''} ${emp.Last_Name || ''}`.trim() || emp.id;
option.textContent = `${name} (${emp.id})`;
employeeSelect.appendChild(option);
});
}
// 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 and display reports
let currentPage = 1;
let totalPages = 1;
async function loadReports(page = 1) {
const loadingIndicator = document.getElementById('loadingIndicator');
const tableContainer = document.getElementById('reportsTableContainer');
const emptyState = document.getElementById('emptyState');
const tableBody = document.getElementById('reportsTableBody');
loadingIndicator.classList.remove('hidden');
tableContainer.classList.add('hidden');
emptyState.classList.add('hidden');
try {
const token = getAuthToken();
if (!token) {
window.location.href = '/index.html';
return;
}
const response = await fetch(`${API_BASE}?page=${page}&perPage=50`, {
headers: {
'Authorization': `Bearer ${token}`,
},
});
if (response.status === 401) {
window.location.href = '/index.html';
return;
}
if (!response.ok) {
throw new Error('Failed to load reports');
}
const data = await response.json();
currentPage = data.page;
totalPages = data.totalPages;
loadingIndicator.classList.add('hidden');
if (data.items.length === 0) {
emptyState.classList.remove('hidden');
return;
}
tableContainer.classList.remove('hidden');
renderReports(data.items);
renderPagination();
} catch (error) {
console.error('Error loading reports:', error);
loadingIndicator.classList.add('hidden');
showMessage('Failed to load reports. Please try again.', 'error');
}
}
// Render reports table
function renderReports(reports) {
const tableBody = document.getElementById('reportsTableBody');
tableBody.innerHTML = '';
reports.forEach(report => {
const row = document.createElement('tr');
row.className = 'hover:bg-gray-50';
// Get employee name from expanded relation
let employeeName = '-';
if (report.expand && report.expand.Employee) {
const emp = report.expand.Employee;
employeeName = `${emp.First_Name || ''} ${emp.Last_Name || ''}`.trim() || emp.id || '-';
} else if (report.Employee) {
employeeName = report.Employee;
}
// Format report text (strip HTML tags and truncate if too long)
const reportHtml = report.Report || '-';
// Strip HTML tags for preview
const reportText = reportHtml.replace(/<[^>]*>/g, '').trim();
const reportPreview = reportText.length > 100 ? reportText.substring(0, 100) + '...' : reportText;
// 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'
})
: '-';
// Count and display links (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) });
const linksHtml = links.length > 0
? links.map(link => {
if (link.isVoxer) {
return `<a href="${escapeHtml(link.url)}" target="_blank" rel="noopener noreferrer" class="inline-flex items-center px-2 py-1 text-xs font-medium text-white bg-purple-600 hover:bg-purple-700 rounded">🎙️ Voxer ${link.num}</a>`;
} else {
return `<a href="${escapeHtml(link.url)}" target="_blank" rel="noopener noreferrer" class="inline-flex items-center px-2 py-1 text-xs font-medium text-indigo-600 hover:text-indigo-800 hover:bg-indigo-50 rounded border border-indigo-200">🔗 Link ${link.num}</a>`;
}
}).join('')
: '-';
row.innerHTML = `
<td class="px-3 sm:px-6 py-3 sm:py-4 whitespace-nowrap text-sm text-gray-900">${escapeHtml(employeeName)}</td>
<td class="px-3 sm:px-6 py-3 sm:py-4 text-sm text-gray-900">
<div class="max-w-xs truncate" title="${escapeHtml(reportText)}">
${reportPreview !== '-' ? escapeHtml(reportPreview) : '-'}
</div>
</td>
<td class="px-3 sm:px-6 py-3 sm:py-4 whitespace-nowrap text-sm text-gray-500">${escapeHtml(report.Centiment || '-')}</td>
<td class="px-3 sm:px-6 py-3 sm:py-4 whitespace-nowrap text-sm text-gray-500">${escapeHtml(reportDate)}</td>
<td class="px-3 sm:px-6 py-3 sm:py-4 text-sm text-gray-500">
<div class="flex flex-wrap gap-1">
${linksHtml === '-' ? '-' : linksHtml}
</div>
</td>
<td class="px-3 sm:px-6 py-3 sm:py-4 whitespace-nowrap text-right text-sm font-medium">
<button onclick="editReport('${report.id}')" class="text-indigo-600 hover:text-indigo-900 mr-2 sm:mr-4 py-1 px-2 sm:px-0">Edit</button>
<button onclick="deleteReport('${report.id}')" class="text-red-600 hover:text-red-900 py-1 px-2 sm:px-0">Delete</button>
</td>
`;
tableBody.appendChild(row);
});
}
// Render pagination
function renderPagination() {
const container = document.getElementById('paginationContainer');
if (totalPages <= 1) {
container.innerHTML = '';
return;
}
let html = '<div class="flex items-center justify-between">';
html += '<div class="flex-1 flex justify-between sm:hidden">';
if (currentPage > 1) {
html += `<button onclick="loadReports(${currentPage - 1})" class="relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50">Previous</button>`;
}
if (currentPage < totalPages) {
html += `<button onclick="loadReports(${currentPage + 1})" class="ml-3 relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50">Next</button>`;
}
html += '</div>';
html += '<div class="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">';
html += `<p class="text-sm text-gray-700">Page <span class="font-medium">${currentPage}</span> of <span class="font-medium">${totalPages}</span></p>`;
html += '<div>';
if (currentPage > 1) {
html += `<button onclick="loadReports(${currentPage - 1})" class="relative inline-flex items-center px-2 py-2 rounded-l-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50">Previous</button>`;
}
if (currentPage < totalPages) {
html += `<button onclick="loadReports(${currentPage + 1})" class="relative inline-flex items-center px-2 py-2 rounded-r-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50">Next</button>`;
}
html += '</div></div></div>';
container.innerHTML = html;
}
// Initialize rich text editor
function initRichTextEditor() {
if (!reportEditor) {
const editorContainer = document.getElementById('reportEditor');
if (editorContainer && typeof Quill !== 'undefined') {
try {
reportEditor = new Quill('#reportEditor', {
theme: 'snow',
modules: {
toolbar: [
[{ 'header': [1, 2, 3, false] }],
['bold', 'italic', 'underline', 'strike'],
[{ 'list': 'ordered'}, { 'list': 'bullet' }],
[{ 'color': [] }, { 'background': [] }],
['link'],
['clean']
]
}
});
// Sync editor content with hidden textarea for form submission
reportEditor.on('text-change', function() {
const htmlContent = reportEditor.root.innerHTML;
const textarea = document.getElementById('Report');
if (textarea) {
textarea.value = htmlContent;
}
});
} catch (error) {
console.error('Error initializing rich text editor:', error);
}
}
}
return reportEditor;
}
// Open modal for add/edit
async function openModal(reportId = null) {
const modal = document.getElementById('reportModal');
const form = document.getElementById('reportForm');
const modalTitle = document.getElementById('modalTitle');
const reportIdInput = document.getElementById('reportId');
// Show modal first so editor container is visible
modal.classList.remove('hidden');
// Initialize rich text editor (with a small delay to ensure DOM is ready)
setTimeout(() => {
initRichTextEditor();
}, 50);
// Ensure employees are loaded
if (employeesList.length === 0) {
await loadEmployees();
}
if (reportId) {
modalTitle.textContent = 'Edit Employee Report';
reportIdInput.value = reportId;
await loadReportForEdit(reportId);
} else {
modalTitle.textContent = 'Add Employee Report';
reportIdInput.value = '';
form.reset();
// Clear the rich text editor after it's initialized
setTimeout(() => {
if (reportEditor) {
reportEditor.setContents([]);
reportEditor.root.innerHTML = '<p><br></p>';
document.getElementById('Report').value = '';
}
}, 100);
}
}
// Load report data for editing
async function loadReportForEdit(id) {
try {
const token = getAuthToken();
const response = await fetch(`${API_BASE}/${id}`, {
headers: {
'Authorization': `Bearer ${token}`,
},
});
if (!response.ok) {
throw new Error('Failed to load report');
}
const report = await response.json();
document.getElementById('Employee').value = report.Employee || '';
// Set rich text editor content
const reportContent = report.Report || '';
if (reportEditor) {
// Set the HTML content in the editor
reportEditor.root.innerHTML = reportContent || '<p><br></p>';
document.getElementById('Report').value = reportContent;
} else {
// Editor not initialized yet, try again after a short delay
setTimeout(() => {
if (reportEditor) {
reportEditor.root.innerHTML = reportContent || '<p><br></p>';
document.getElementById('Report').value = reportContent;
} else {
document.getElementById('Report').value = reportContent;
}
}, 150);
}
document.getElementById('Centiment').value = report.Centiment || '';
// Format date and time for input field (YYYY-MM-DDTHH:mm) - convert from UTC to local
if (report.Report_Date) {
const date = new Date(report.Report_Date);
// Convert UTC to local time for display
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('Report_Date').value = `${year}-${month}-${day}T${hours}:${minutes}`;
} else {
document.getElementById('Report_Date').value = '';
}
document.getElementById('Link_01').value = report.Link_01 || '';
document.getElementById('Link_02').value = report.Link_02 || '';
document.getElementById('Link_03').value = report.Link_03 || '';
document.getElementById('Link_04').value = report.Link_04 || '';
document.getElementById('Link_05').value = report.Link_05 || '';
} catch (error) {
console.error('Error loading report:', error);
showMessage('Failed to load report data.', 'error');
}
}
// Edit report
window.editReport = function(id) {
openModal(id);
};
// Delete report
window.deleteReport = async function(id) {
if (!confirm('Are you sure you want to delete this report?')) {
return;
}
try {
const token = getAuthToken();
const response = await fetch(`${API_BASE}/${id}`, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${token}`,
},
});
if (!response.ok) {
throw new Error('Failed to delete report');
}
showMessage('Report deleted successfully.', 'success');
loadReports(currentPage);
} catch (error) {
console.error('Error deleting report:', error);
showMessage('Failed to delete report.', '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', async () => {
// Initialize page (auth check, user display, logout)
await initPage();
const form = document.getElementById('reportForm');
const modal = document.getElementById('reportModal');
const closeModal = document.getElementById('closeModal');
const cancelButton = document.getElementById('cancelButton');
const addReportButton = document.getElementById('addReportButton');
const addReportButtonEmpty = document.getElementById('addReportButtonEmpty');
// Load reports on page load
loadReports();
// Open modal handlers
if (addReportButton) {
addReportButton.addEventListener('click', () => openModal());
}
if (addReportButtonEmpty) {
addReportButtonEmpty.addEventListener('click', () => openModal());
}
// Close modal handlers
if (closeModal) {
closeModal.addEventListener('click', () => {
modal.classList.add('hidden');
});
}
if (cancelButton) {
cancelButton.addEventListener('click', () => {
modal.classList.add('hidden');
});
}
// Form submission
if (form) {
form.addEventListener('submit', async (e) => {
e.preventDefault();
const reportId = document.getElementById('reportId').value;
// Convert datetime-local to UTC ISO string
let reportDateUTC = undefined;
const reportDateValue = document.getElementById('Report_Date').value;
if (reportDateValue) {
const localDate = new Date(reportDateValue);
reportDateUTC = localDate.toISOString();
}
const formData = {
Employee: document.getElementById('Employee').value || undefined,
Report: document.getElementById('Report').value || undefined,
Centiment: document.getElementById('Centiment').value || undefined,
Report_Date: reportDateUTC,
Link_01: document.getElementById('Link_01').value || undefined,
Link_02: document.getElementById('Link_02').value || undefined,
Link_03: document.getElementById('Link_03').value || undefined,
Link_04: document.getElementById('Link_04').value || undefined,
Link_05: document.getElementById('Link_05').value || undefined,
};
try {
const token = getAuthToken();
const url = reportId ? `${API_BASE}/${reportId}` : API_BASE;
const method = reportId ? '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 ${reportId ? 'updated' : 'created'} successfully.`, 'success');
modal.classList.add('hidden');
loadReports(reportId ? currentPage : 1);
} catch (error) {
console.error('Error saving report:', error);
showMessage(error.message || 'Failed to save report.', 'error');
}
});
}
// Load employees when page loads
loadEmployees();
});
// Utility function to escape HTML
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}