@@ -449,11 +522,15 @@
// Initialize PocketBase
const pb = new PocketBase('https://pocketbase.ccllc.pro');
+ // API key for admin operations (fetched from 'api' collection)
+ let API_KEY = null;
+
// State management
let allJobs = [];
let filteredJobs = [];
let currentPage = 1;
const itemsPerPage = 10;
+ let collectionSchema = null;
// Initialize page
async function initJobsPage() {
@@ -470,7 +547,19 @@
const user = pb.authStore.model;
menuBar.updateUserName(user.name || user.email);
- // Load jobs
+ // Debug: Check authentication and user details
+ console.log('Current user:', user);
+ console.log('Auth store:', pb.authStore);
+ console.log('Is authenticated:', pb.authStore.isValid);
+
+ // Debug: Check what collections are available
+ await debugCollections();
+
+ // Fetch API key from 'api' collection for admin operations
+ await loadApiKey();
+
+ // Load collection schema first, then jobs
+ await loadCollectionSchema();
await loadJobs();
} catch (error) {
@@ -482,24 +571,276 @@
}
}
+ /**
+ * Fetch API key from the 'api' collection
+ * The key is stored in a field called 'API_KEY'
+ */
+ async function loadApiKey() {
+ // Try different collection name variations
+ const collectionNames = ['api', 'API', 'Api'];
+
+ for (const collectionName of collectionNames) {
+ try {
+ console.log(`Trying to load API key from collection: ${collectionName}`);
+
+ // Fetch the API key from the collection
+ const records = await pb.collection(collectionName).getFullList({
+ sort: '-created'
+ });
+
+ console.log(`Found ${records?.length || 0} record(s) in ${collectionName} collection`);
+
+ if (records && records.length > 0) {
+ const firstRecord = records[0];
+ console.log(`First record fields:`, Object.keys(firstRecord));
+ console.log(`First record data:`, firstRecord);
+
+ // Try different field name variations
+ // Priority: API_KEY (as shown in PocketBase admin), then other variations
+ API_KEY = firstRecord.API_KEY ||
+ firstRecord.Key ||
+ firstRecord.key ||
+ firstRecord.KEY ||
+ firstRecord.api_key ||
+ firstRecord.apiKey ||
+ firstRecord.ApiKey ||
+ null;
+
+ if (API_KEY) {
+ console.log(`✅ API key loaded successfully from ${collectionName} collection`);
+ return; // Success, exit function
+ } else {
+ console.warn(`API key field not found in ${collectionName} collection record. Available fields:`, Object.keys(firstRecord));
+ }
+ } else {
+ console.warn(`No records found in ${collectionName} collection`);
+ }
+ } catch (error) {
+ console.warn(`Could not load from ${collectionName} collection:`, error.message);
+ // Try next collection name
+ }
+ }
+
+ // If we get here, no API key was found
+ console.warn('⚠️ API key not found in any api collection. Operations will use user authentication.');
+ }
+
+ async function addressToCoordinates(address) {
+ // Check if input is already coordinates (format: "lat,lng" or "lat, lng")
+ const coordPattern = /^(-?\d+\.?\d*),\s*(-?\d+\.?\d*)$/;
+ const match = address.trim().match(coordPattern);
+ if (match) {
+ // Already coordinates, parse and return as object
+ const lat = parseFloat(match[1]);
+ const lon = parseFloat(match[2]);
+ return { lat, lon };
+ }
+
+ // Try to geocode the address using a geocoding service
+ // Using OpenStreetMap Nominatim (free, no API key required)
+ try {
+ const encodedAddress = encodeURIComponent(address);
+ const response = await fetch(`https://nominatim.openstreetmap.org/search?format=json&q=${encodedAddress}&limit=1`);
+ const data = await response.json();
+
+ if (data && data.length > 0) {
+ const lat = parseFloat(data[0].lat);
+ const lon = parseFloat(data[0].lon);
+ return { lat, lon };
+ } else {
+ console.warn('Could not geocode address:', address);
+ // Return null if geocoding fails (don't include Job_Address)
+ return null;
+ }
+ } catch (error) {
+ console.error('Geocoding error:', error);
+ // Return null if geocoding fails (don't include Job_Address)
+ return null;
+ }
+ }
+
+ async function getNextJobNumber() {
+ try {
+ // Use getFirstListItem to get only the highest job number (most efficient)
+ // Sort by Job_Number descending to get the highest first
+ const highestJob = await pb.collection('Jobs').getFirstListItem('', {
+ sort: '-Job_Number'
+ });
+
+ const highestJobNumber = highestJob.Job_Number || 0;
+ const nextJobNumber = highestJobNumber + 1;
+
+ // Ensure it's within 1000-9999 range
+ if (nextJobNumber < 1000) {
+ return 1000;
+ } else if (nextJobNumber > 9999) {
+ throw new Error('Maximum job number (9999) reached');
+ }
+
+ return nextJobNumber;
+
+ } catch (error) {
+ // If no records exist or any other error, start with 1000
+ if (error.status === 404 || error.message?.includes('not found')) {
+ // No jobs exist, start with 1000
+ return 1000;
+ }
+ console.error('Error getting next job number:', error);
+ // Fallback: start with 1000 if we can't determine the next number
+ return 1000;
+ }
+ }
+
+ async function debugCollections() {
+ try {
+ console.log('=== DEBUGGING COLLECTIONS ===');
+
+ // Try different collection names (only valid ones)
+ const collectionNames = ['jobs', 'Jobs'];
+
+ for (const collectionName of collectionNames) {
+ try {
+ console.log(`Trying collection: ${collectionName}`);
+ const records = await pb.collection(collectionName).getList(1, 10);
+ console.log(`${collectionName} found with ${records.totalItems} items`);
+ if (records.totalItems > 0) {
+ console.log(`${collectionName} sample item:`, records.items[0]);
+ }
+ } catch (error) {
+ console.log(`${collectionName} error:`, error.message);
+ }
+ }
+
+ console.log('=== END DEBUG ===');
+ } catch (error) {
+ console.warn('Debug collections failed:', error.message);
+ }
+ }
+
+ async function loadCollectionSchema() {
+ // Try to fetch real schema from PocketBase
+ try {
+ // Use API key for schema access if available (bypasses collection rules)
+ let collection;
+ if (API_KEY) {
+ // Make request with API key in header
+ const response = await fetch(`${pb.baseUrl}/api/collections/Jobs`, {
+ headers: {
+ 'Authorization': `Bearer ${API_KEY}`
+ }
+ });
+ if (!response.ok) {
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
+ }
+ collection = await response.json();
+ } else {
+ // Use standard PocketBase client (may fail with 403 if no permissions)
+ collection = await pb.collections.getOne('Jobs');
+ }
+
+ console.log('Fetched collection:', collection);
+ console.log('Collection schema:', collection.schema);
+
+ if (collection.schema && collection.schema.length > 0) {
+ // Map PocketBase schema to our format
+ collectionSchema = collection.schema.map(field => ({
+ name: field.name,
+ type: field.type,
+ required: field.required || false,
+ options: field.options || {}
+ }));
+ console.log('Real collection schema loaded with', collectionSchema.length, 'fields');
+ console.log('Schema details:', collectionSchema.map(f => ({
+ name: f.name,
+ type: f.type,
+ required: f.required
+ })));
+ } else {
+ throw new Error('Collection schema is empty');
+ }
+ } catch (error) {
+ console.warn('Could not fetch collection schema, using defaults:', error.message);
+ // Fallback to predefined schema
+ collectionSchema = [
+ { name: 'Job_Number', type: 'number' },
+ { name: 'Job_Name', type: 'text' },
+ { name: 'Job_Address', type: 'text' },
+ { name: 'updated', type: 'date' }
+ ];
+ console.log('Using predefined collection schema with', collectionSchema.length, 'fields');
+ }
+
+ updateUIBasedOnSchema();
+ }
+
+ function updateUIBasedOnSchema() {
+ // Schema is now fixed with just Job_Number, Job_Full_Name, and updated
+ // No dynamic UI updates needed
+ }
+
async function loadJobs() {
try {
showLoading(true);
+ console.log('Attempting to load jobs from collection...');
- // Fetch jobs from PocketBase
- const records = await pb.collection('jobs').getList(1, 200, {
+ const collectionUsed = 'Jobs';
+
+ // Use getFullList to fetch all records at once (better for client-side filtering)
+ // This provides instant filtering without server round-trips
+ const records = await pb.collection(collectionUsed).getFullList({
sort: '-updated'
});
- allJobs = records.items;
+ allJobs = records || [];
filteredJobs = [...allJobs];
- displayJobs();
- updatePagination();
+ console.log(`Jobs response from collection "${collectionUsed}":`, records);
+ console.log('Number of jobs found:', allJobs.length);
+
+ // Debug: Log each job's structure
+ if (allJobs.length > 0) {
+ console.log('Sample job structure:', allJobs[0]);
+ console.log('Job fields:', Object.keys(allJobs[0]));
+ }
+
+ // Apply existing search filter if any
+ const searchInput = document.getElementById('search-input');
+ if (searchInput && searchInput.value.trim()) {
+ filterJobs();
+ } else {
+ displayJobs();
+ updatePagination();
+ }
+
+ if (allJobs.length === 0) {
+ console.log('No jobs found - showing empty state');
+ } else {
+ console.log(`Loaded ${allJobs.length} job(s) successfully from collection "${collectionUsed}"`);
+ }
} catch (error) {
- console.error('Error loading jobs:', error.message);
- showError('Failed to load jobs. Please try again.');
+ console.error('Error loading jobs:', error);
+ console.error('Error details:', {
+ message: error.message,
+ status: error.response?.status,
+ data: error.response?.data,
+ url: error.config?.url
+ });
+
+ // Handle specific error cases
+ if (error.response?.status === 403) {
+ showError('Access denied to jobs collection. Please check your permissions in PocketBase.');
+ } else if (error.response?.status === 404) {
+ showError('Jobs collection not found. Please create the "jobs" collection in PocketBase. Tried: jobs, Jobs');
+ } else {
+ showError(`Failed to load jobs: ${error.message}`);
+ }
+
+ // Show empty state
+ allJobs = [];
+ filteredJobs = [];
+ displayJobs();
+ updatePagination();
} finally {
showLoading(false);
}
@@ -507,66 +848,127 @@
function displayJobs() {
const tbody = document.getElementById('jobs-tbody');
+ const thead = document.getElementById('table-headers');
const emptyState = document.getElementById('empty-state');
+ console.log('displayJobs called with filteredJobs:', filteredJobs);
+ console.log('filteredJobs length:', filteredJobs.length);
+
if (filteredJobs.length === 0) {
+ console.log('No jobs to display, showing empty state');
tbody.innerHTML = '';
emptyState.style.display = 'block';
return;
}
+ console.log('Hiding empty state and displaying jobs');
emptyState.style.display = 'none';
+ // Generate table headers dynamically
+ if (collectionSchema) {
+ const headerHTML = collectionSchema.map(field => {
+ let displayName;
+
+ // Custom display names for specific fields
+ switch(field.name) {
+ case 'Job_Number':
+ displayName = 'Job Number';
+ break;
+ case 'Job_Name':
+ displayName = 'Job Name';
+ break;
+ case 'Job_Address':
+ displayName = 'Address';
+ break;
+ default:
+ // Default formatting: replace underscores with spaces and capitalize words
+ displayName = field.name.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
+ }
+
+ return `| ${displayName} | `;
+ }).join('') + 'Actions | ';
+
+ thead.innerHTML = headerHTML;
+ }
+
// Get jobs for current page
const startIndex = (currentPage - 1) * itemsPerPage;
const endIndex = startIndex + itemsPerPage;
const pageJobs = filteredJobs.slice(startIndex, endIndex);
- tbody.innerHTML = pageJobs.map(job => `
-
- |
- ${job.name || 'Unnamed Job'}
- |
- ${job.client || 'N/A'} |
-
-
- ${job.status || 'active'}
-
- |
-
- $${job.budget ? job.budget.toLocaleString() : '0'}
- |
-
- ${job.start_date ? formatDate(job.start_date) : 'Not set'}
- |
-
- ${job.end_date ? formatDate(job.end_date) : 'Not set'}
- |
-
- ${formatDate(job.updated)}
- |
+ console.log('Displaying pageJobs:', pageJobs);
+
+ tbody.innerHTML = pageJobs.map(job => {
+ console.log('Processing job:', job);
+ const rowHTML = collectionSchema.map(field => {
+ const value = job[field.name];
+ const formattedValue = formatFieldValue(value, field);
+ console.log(`Field ${field.name}: ${value} -> ${formattedValue}`);
+ return `${formattedValue} | `;
+ }).join('') + `
|
-
- `).join('');
+ `;
+ return `${rowHTML}
`;
+ }).join('');
+ }
+
+ /**
+ * Builds a PocketBase filter string for server-side filtering
+ * Useful for large datasets where server-side filtering is more efficient
+ *
+ * Example usage:
+ * const filter = buildPocketBaseFilter('test');
+ * const records = await pb.collection('Jobs').getList(1, 50, { filter });
+ *
+ * @param {string} searchTerm - The search term to filter by
+ * @returns {string|null} PocketBase filter string or null if no search term
+ */
+ function buildPocketBaseFilter(searchTerm) {
+ if (!searchTerm || searchTerm.trim() === '') {
+ return null;
+ }
+
+ const trimmed = searchTerm.trim();
+
+ // Try to parse as number for Job_Number search
+ const numberMatch = parseInt(trimmed);
+ if (!isNaN(numberMatch)) {
+ // Search by Job_Number
+ return `Job_Number = ${numberMatch}`;
+ }
+
+ // Search in Job_Name (case-insensitive)
+ // PocketBase filter syntax: field ~ "value" for contains (case-insensitive)
+ return `Job_Name ~ "${trimmed}"`;
}
function filterJobs() {
- const searchTerm = document.getElementById('search-input').value.toLowerCase();
- const statusFilter = document.getElementById('status-filter').value;
+ const searchTerm = document.getElementById('search-input').value.toLowerCase().trim();
+ // Client-side filtering for instant results
filteredJobs = allJobs.filter(job => {
- const matchesSearch = !searchTerm ||
- (job.name && job.name.toLowerCase().includes(searchTerm)) ||
- (job.client && job.client.toLowerCase().includes(searchTerm));
+ if (!searchTerm) {
+ return true;
+ }
- const matchesStatus = !statusFilter || job.status === statusFilter;
+ // Search across Job_Number, Job_Name, and Job_Address
+ const matchesSearch =
+ (job.Job_Number && job.Job_Number.toString().toLowerCase().includes(searchTerm)) ||
+ (job.Job_Name && job.Job_Name.toLowerCase().includes(searchTerm)) ||
+ (job.Job_Address && (
+ // Handle Job_Address as object {lat, lon} or string
+ (typeof job.Job_Address === 'object' && job.Job_Address !== null
+ ? `${job.Job_Address.lat},${job.Job_Address.lon}`.toLowerCase()
+ : String(job.Job_Address).toLowerCase()
+ ).includes(searchTerm)
+ ));
- return matchesSearch && matchesStatus;
+ return matchesSearch;
});
currentPage = 1;
@@ -604,6 +1006,33 @@
}
}
+ function formatFieldValue(value, field) {
+ if (value === null || value === undefined || value === '') {
+ return 'N/A';
+ }
+
+ switch (field.type) {
+ case 'date':
+ return formatDate(value);
+ case 'number':
+ // Job_Number should be displayed as plain number
+ return Number(value).toString();
+ default:
+ // Handle text fields - Job_Name gets special styling
+ if (field.name === 'Job_Name') {
+ return `${value}
`;
+ }
+ // Handle Job_Address as object {lat, lon}
+ if (field.name === 'Job_Address' && typeof value === 'object' && value !== null) {
+ if (value.lat !== undefined && value.lon !== undefined) {
+ return `${value.lat.toFixed(6)}, ${value.lon.toFixed(6)}`;
+ }
+ return JSON.stringify(value);
+ }
+ return value;
+ }
+ }
+
function formatDate(dateString) {
const date = new Date(dateString);
return date.toLocaleDateString('en-US', {
@@ -617,9 +1046,254 @@
await loadJobs();
}
- function showAddJobModal() {
- // TODO: Implement add job modal
- alert('Add job functionality coming soon!');
+ async function showAddJobModal() {
+ // Generate next job number
+ const nextJobNumber = await getNextJobNumber();
+
+ // Create overlay popup HTML
+ const modalHTML = `
+
+
+
+ `;
+
+ // Add modal to page
+ document.body.insertAdjacentHTML('beforeend', modalHTML);
+
+ // Focus on first input
+ setTimeout(() => {
+ document.getElementById('job-name').focus();
+ }, 100);
+ }
+
+ function closeJobModal() {
+ const modal = document.getElementById('job-modal');
+ if (modal) {
+ modal.remove();
+ }
+ }
+
+ async function submitJobForm(event) {
+ event.preventDefault();
+
+ const submitBtn = document.getElementById('submit-btn');
+ const submitText = document.getElementById('submit-text');
+ const submitLoading = document.getElementById('submit-loading');
+
+ // Show loading state
+ submitBtn.disabled = true;
+ submitText.style.display = 'none';
+ submitLoading.style.display = 'inline-block';
+
+ try {
+ // Collect form data
+ const jobName = document.getElementById('job-name').value.trim();
+ const jobAddress = document.getElementById('job-address').value.trim();
+
+ // Convert address to coordinates if provided
+ let jobAddressCoords = null;
+ if (jobAddress) {
+ jobAddressCoords = await addressToCoordinates(jobAddress);
+ console.log('Address converted to coordinates:', jobAddress, '->', jobAddressCoords);
+ }
+
+ const jobData = {
+ Job_Number: parseInt(document.getElementById('job-number').value),
+ Job_Name: jobName
+ };
+
+ // Only add Job_Address if it was provided and successfully converted
+ // Job_Address must be an object with {lat, lon} format
+ if (jobAddressCoords && typeof jobAddressCoords === 'object') {
+ jobData.Job_Address = jobAddressCoords;
+ }
+
+ console.log('Sending job data:', jobData);
+ console.log('Job_Number type:', typeof jobData.Job_Number, 'value:', jobData.Job_Number);
+ console.log('Job_Name type:', typeof jobData.Job_Name, 'value:', jobData.Job_Name);
+ if (jobData.Job_Address) {
+ console.log('Job_Address:', jobData.Job_Address);
+ }
+
+ // Check if job number already exists (optional validation)
+ try {
+ const existingJobs = await pb.collection('Jobs').getList(1, 1, {
+ filter: `Job_Number = ${jobData.Job_Number}`
+ });
+
+ if (existingJobs.items.length > 0) {
+ throw new Error(`Job number ${jobData.Job_Number} already exists. Please refresh to get the next available number.`);
+ }
+ } catch (checkError) {
+ if (checkError.message.includes('already exists')) {
+ throw checkError;
+ }
+ // Ignore other errors from the check - continue with creation
+ console.warn('Could not check for existing job number:', checkError.message);
+ }
+
+ // Log the exact data structure being sent
+ console.log('=== CREATING JOB ===');
+ console.log('Collection: Jobs');
+ console.log('Data being sent:', JSON.stringify(jobData, null, 2));
+ console.log('Data types:', Object.entries(jobData).map(([k, v]) => `${k}: ${typeof v} = ${v}`));
+
+ // Try to get collection info for comparison
+ try {
+ let collection;
+ if (API_KEY) {
+ // Use API key for schema access
+ const response = await fetch(`${pb.baseUrl}/api/collections/Jobs`, {
+ headers: {
+ 'Authorization': `Bearer ${API_KEY}`
+ }
+ });
+ if (response.ok) {
+ collection = await response.json();
+ } else {
+ throw new Error(`HTTP ${response.status}`);
+ }
+ } else {
+ collection = await pb.collections.getOne('Jobs');
+ }
+ console.log('Collection fields:', collection.schema?.map(f => ({
+ name: f.name,
+ type: f.type,
+ required: f.required
+ })));
+ } catch (e) {
+ console.warn('Could not fetch collection info:', e.message);
+ }
+
+ // Create job in PocketBase
+ const createdJob = await pb.collection('Jobs').create(jobData);
+
+ console.log('Job created successfully:', createdJob);
+
+ // Close modal and refresh jobs list
+ closeJobModal();
+ await loadJobs();
+
+ // Show success message (you could implement a toast notification here)
+ alert('Job created successfully!');
+
+ } catch (error) {
+ console.error('Error creating job:', error);
+ console.error('Full error object:', JSON.stringify(error, null, 2));
+
+ // Show detailed error information
+ let errorMessage = 'Failed to create job.';
+
+ // PocketBase errors have different structures - check all possible locations
+ if (error.response) {
+ console.error('Error response:', error.response);
+ console.error('Error response.data:', error.response.data);
+ console.error('Error response.status:', error.response.status);
+ console.error('Error response.url:', error.response.url);
+
+ const errorData = error.response.data || {};
+
+ // Check for validation errors in different possible locations
+ if (errorData.data) {
+ const validationErrors = errorData.data;
+ console.error('Validation errors found:', validationErrors);
+ const errorDetails = Object.entries(validationErrors)
+ .map(([field, messages]) => {
+ const msg = Array.isArray(messages) ? messages.join(', ') : String(messages);
+ return `${field}: ${msg}`;
+ })
+ .join('\n');
+ errorMessage += '\n\nValidation errors:\n' + errorDetails;
+ }
+
+ // Check if errorData itself is an object with field-level errors
+ if (typeof errorData === 'object' && !errorData.message && !errorData.data) {
+ const fieldErrors = Object.entries(errorData)
+ .filter(([key]) => key !== 'code' && key !== 'status')
+ .map(([field, messages]) => {
+ const msg = Array.isArray(messages) ? messages.join(', ') : String(messages);
+ return `${field}: ${msg}`;
+ });
+ if (fieldErrors.length > 0) {
+ errorMessage += '\n\nField errors:\n' + fieldErrors.join('\n');
+ }
+ }
+
+ if (errorData.message) {
+ errorMessage += '\n\n' + errorData.message;
+ }
+ } else if (error.message) {
+ errorMessage += '\n\n' + error.message;
+ }
+
+ // Try to fetch collection schema for debugging
+ try {
+ let collection;
+ if (API_KEY) {
+ // Use API key for schema access
+ const response = await fetch(`${pb.baseUrl}/api/collections/Jobs`, {
+ headers: {
+ 'Authorization': `Bearer ${API_KEY}`
+ }
+ });
+ if (response.ok) {
+ collection = await response.json();
+ } else {
+ throw new Error(`HTTP ${response.status}`);
+ }
+ } else {
+ collection = await pb.collections.getOne('Jobs');
+ }
+ console.log('Collection schema:', collection);
+ console.log('Collection fields:', collection.schema?.map(f => ({
+ name: f.name,
+ type: f.type,
+ required: f.required,
+ options: f.options
+ })));
+ } catch (schemaError) {
+ console.warn('Could not fetch collection schema:', schemaError);
+ }
+
+ alert(errorMessage);
+
+ // Also suggest running the debug function
+ console.log('💡 Try running debugPocketBase() in the console to test permissions');
+ } finally {
+ // Reset loading state
+ submitBtn.disabled = false;
+ submitText.style.display = 'inline';
+ submitLoading.style.display = 'none';
+ }
}
function editJob(jobId) {
@@ -633,7 +1307,7 @@
}
try {
- await pb.collection('jobs').delete(jobId);
+ await pb.collection('Jobs').delete(jobId);
await loadJobs(); // Refresh the list
} catch (error) {
console.error('Error deleting job:', error.message);
@@ -661,13 +1335,64 @@
window.logout = logout;
window.refreshJobs = refreshJobs;
window.showAddJobModal = showAddJobModal;
+ window.closeJobModal = closeJobModal;
+ window.submitJobForm = submitJobForm;
window.editJob = editJob;
window.deleteJob = deleteJob;
window.changePage = changePage;
window.filterJobs = filterJobs;
+ // Debug function available in console
+ window.debugPocketBase = async function() {
+ console.log('=== POCKETBASE DEBUG ===');
+ console.log('Base URL:', pb.baseUrl);
+ console.log('Auth valid:', pb.authStore.isValid);
+ console.log('Auth model:', pb.authStore.model);
+
+ try {
+ // Try to get collections
+ const collections = await pb.collections.getFullList();
+ console.log('Available collections:', collections.map(c => c.name));
+ } catch (error) {
+ console.error('Cannot list collections:', error.message);
+ }
+
+ // Test the Jobs collection specifically
+ console.log('Testing Jobs collection...');
+
+ // Test read access
+ try {
+ const result = await pb.collection('Jobs').getList(1, 10);
+ console.log(`✅ Read access: ${result.totalItems} items found`);
+ if (result.items.length > 0) {
+ console.log('Sample item:', result.items[0]);
+ }
+ } catch (error) {
+ console.log('❌ Read access failed:', error.message);
+ }
+
+ // Test write access with dummy data
+ try {
+ const testData = { Job_Number: 9999, Job_Name: 'TEST_JOB_PLEASE_DELETE' };
+ const testResult = await pb.collection('Jobs').create(testData);
+ console.log('✅ Write access successful, created test record:', testResult);
+
+ // Clean up test record
+ await pb.collection('Jobs').delete(testResult.id);
+ console.log('✅ Test record cleaned up');
+ } catch (error) {
+ console.log('❌ Write access failed:', error.message);
+ if (error.response) {
+ console.log('Error details:', error.response.data);
+ }
+ }
+
+ console.log('=== END DEBUG ===');
+ };
+
// Initialize on page load
document.addEventListener('DOMContentLoaded', initJobsPage);