fix one issues still needs work on pulling info for pocketbase
This commit is contained in:
+398
@@ -0,0 +1,398 @@
|
|||||||
|
import PocketBase from '/pocketbase.es.mjs';
|
||||||
|
|
||||||
|
// Initialize PocketBase
|
||||||
|
const pb = new PocketBase('https://pocketbase.ccllc.pro');
|
||||||
|
|
||||||
|
// API key for admin operations (fetched from 'api' collection)
|
||||||
|
let API_KEY = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if user is authenticated
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
export function isAuthenticated() {
|
||||||
|
return pb.authStore.isValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current authenticated user
|
||||||
|
* @returns {object|null}
|
||||||
|
*/
|
||||||
|
export function getCurrentUser() {
|
||||||
|
return pb.authStore.model;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logout current user
|
||||||
|
*/
|
||||||
|
export function logout() {
|
||||||
|
pb.authStore.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch API key from the 'api' collection
|
||||||
|
* The key is stored in a field called 'API_KEY'
|
||||||
|
*/
|
||||||
|
export 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 API_KEY; // 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.');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get API key (returns null if not loaded)
|
||||||
|
* @returns {string|null}
|
||||||
|
*/
|
||||||
|
export function getApiKey() {
|
||||||
|
return API_KEY;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load collection schema from PocketBase
|
||||||
|
* @param {string} collectionName - Name of the collection
|
||||||
|
* @returns {Promise<Array>} Collection schema
|
||||||
|
*/
|
||||||
|
export async function loadCollectionSchema(collectionName = 'Jobs') {
|
||||||
|
// 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/${collectionName}`, {
|
||||||
|
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(collectionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Fetched collection:', collection);
|
||||||
|
console.log('Collection schema:', collection.schema);
|
||||||
|
|
||||||
|
if (collection.schema && collection.schema.length > 0) {
|
||||||
|
// Map PocketBase schema to our format
|
||||||
|
const schema = collection.schema.map(field => ({
|
||||||
|
name: field.name,
|
||||||
|
type: field.type,
|
||||||
|
required: field.required || false,
|
||||||
|
options: field.options || {}
|
||||||
|
}));
|
||||||
|
console.log('Real collection schema loaded with', schema.length, 'fields');
|
||||||
|
console.log('Schema details:', schema.map(f => ({
|
||||||
|
name: f.name,
|
||||||
|
type: f.type,
|
||||||
|
required: f.required
|
||||||
|
})));
|
||||||
|
return schema;
|
||||||
|
} 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
|
||||||
|
const defaultSchema = [
|
||||||
|
{ 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', defaultSchema.length, 'fields');
|
||||||
|
return defaultSchema;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get collection info (for debugging)
|
||||||
|
* @param {string} collectionName - Name of the collection
|
||||||
|
* @returns {Promise<object>} Collection info
|
||||||
|
*/
|
||||||
|
export async function getCollectionInfo(collectionName = 'Jobs') {
|
||||||
|
try {
|
||||||
|
let collection;
|
||||||
|
if (API_KEY) {
|
||||||
|
// Use API key for schema access
|
||||||
|
const response = await fetch(`${pb.baseUrl}/api/collections/${collectionName}`, {
|
||||||
|
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(collectionName);
|
||||||
|
}
|
||||||
|
return collection;
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Could not fetch collection info:', error.message);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load all jobs from the Jobs collection
|
||||||
|
* @returns {Promise<Array>} Array of job records
|
||||||
|
*/
|
||||||
|
export async function loadJobs() {
|
||||||
|
try {
|
||||||
|
console.log('Attempting to load jobs from collection...');
|
||||||
|
|
||||||
|
const collectionUsed = 'Jobs';
|
||||||
|
|
||||||
|
// Use getFullList to fetch all records at once (better for client-side filtering)
|
||||||
|
const records = await pb.collection(collectionUsed).getFullList({
|
||||||
|
sort: '-updated'
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`Jobs response from collection "${collectionUsed}":`, records);
|
||||||
|
console.log('Number of jobs found:', records?.length || 0);
|
||||||
|
|
||||||
|
// Debug: Log each job's structure
|
||||||
|
if (records && records.length > 0) {
|
||||||
|
console.log('Sample job structure:', records[0]);
|
||||||
|
console.log('Job fields:', Object.keys(records[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return records || [];
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
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) {
|
||||||
|
throw new Error('Access denied to jobs collection. Please check your permissions in PocketBase.');
|
||||||
|
} else if (error.response?.status === 404) {
|
||||||
|
throw new Error('Jobs collection not found. Please create the "jobs" collection in PocketBase.');
|
||||||
|
} else {
|
||||||
|
throw new Error(`Failed to load jobs: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the next available job number
|
||||||
|
* @returns {Promise<number>} Next job number (1000-9999)
|
||||||
|
*/
|
||||||
|
export 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new job
|
||||||
|
* @param {object} jobData - Job data object
|
||||||
|
* @returns {Promise<object>} Created job record
|
||||||
|
*/
|
||||||
|
export async function createJob(jobData) {
|
||||||
|
try {
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create job in PocketBase
|
||||||
|
const createdJob = await pb.collection('Jobs').create(jobData);
|
||||||
|
console.log('Job created successfully:', createdJob);
|
||||||
|
return createdJob;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error creating job:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a job
|
||||||
|
* @param {string} jobId - Job record ID
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
export async function deleteJob(jobId) {
|
||||||
|
try {
|
||||||
|
await pb.collection('Jobs').delete(jobId);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error deleting job:', error.message);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Debug function to test PocketBase connection and permissions
|
||||||
|
*/
|
||||||
|
export async function debugPocketBase() {
|
||||||
|
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 ===');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Debug collections
|
||||||
|
*/
|
||||||
|
export 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export PocketBase instance for advanced usage if needed
|
||||||
|
export { pb };
|
||||||
|
|
||||||
+32
-308
@@ -516,14 +516,25 @@
|
|||||||
</main>
|
</main>
|
||||||
|
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import PocketBase from '/pocketbase.es.mjs';
|
|
||||||
import { initMenuBar, getCurrentPage } from '/components.js';
|
import { initMenuBar, getCurrentPage } from '/components.js';
|
||||||
|
import {
|
||||||
|
isAuthenticated,
|
||||||
|
getCurrentUser,
|
||||||
|
logout,
|
||||||
|
loadApiKey,
|
||||||
|
getApiKey,
|
||||||
|
loadCollectionSchema,
|
||||||
|
getCollectionInfo,
|
||||||
|
loadJobs as dbLoadJobs,
|
||||||
|
getNextJobNumber,
|
||||||
|
createJob,
|
||||||
|
deleteJob as dbDeleteJob,
|
||||||
|
debugPocketBase,
|
||||||
|
debugCollections
|
||||||
|
} from '/db.js';
|
||||||
|
|
||||||
// Initialize PocketBase
|
// Initialize menu bar immediately (before any async operations)
|
||||||
const pb = new PocketBase('https://pocketbase.ccllc.pro');
|
const menuBar = initMenuBar(getCurrentPage());
|
||||||
|
|
||||||
// API key for admin operations (fetched from 'api' collection)
|
|
||||||
let API_KEY = null;
|
|
||||||
|
|
||||||
// State management
|
// State management
|
||||||
let allJobs = [];
|
let allJobs = [];
|
||||||
@@ -536,21 +547,17 @@
|
|||||||
async function initJobsPage() {
|
async function initJobsPage() {
|
||||||
try {
|
try {
|
||||||
// Check authentication
|
// Check authentication
|
||||||
if (!pb.authStore.isValid) {
|
if (!isAuthenticated()) {
|
||||||
window.location.href = '/';
|
window.location.href = '/';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize menu bar
|
const user = getCurrentUser();
|
||||||
const menuBar = initMenuBar(getCurrentPage());
|
|
||||||
|
|
||||||
const user = pb.authStore.model;
|
|
||||||
menuBar.updateUserName(user.name || user.email);
|
menuBar.updateUserName(user.name || user.email);
|
||||||
|
|
||||||
// Debug: Check authentication and user details
|
// Debug: Check authentication and user details
|
||||||
console.log('Current user:', user);
|
console.log('Current user:', user);
|
||||||
console.log('Auth store:', pb.authStore);
|
console.log('Is authenticated:', isAuthenticated());
|
||||||
console.log('Is authenticated:', pb.authStore.isValid);
|
|
||||||
|
|
||||||
// Debug: Check what collections are available
|
// Debug: Check what collections are available
|
||||||
await debugCollections();
|
await debugCollections();
|
||||||
@@ -559,7 +566,7 @@
|
|||||||
await loadApiKey();
|
await loadApiKey();
|
||||||
|
|
||||||
// Load collection schema first, then jobs
|
// Load collection schema first, then jobs
|
||||||
await loadCollectionSchema();
|
collectionSchema = await loadCollectionSchema();
|
||||||
await loadJobs();
|
await loadJobs();
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -571,59 +578,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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) {
|
async function addressToCoordinates(address) {
|
||||||
// Check if input is already coordinates (format: "lat,lng" or "lat, lng")
|
// Check if input is already coordinates (format: "lat,lng" or "lat, lng")
|
||||||
@@ -659,120 +613,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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() {
|
function updateUIBasedOnSchema() {
|
||||||
// Schema is now fixed with just Job_Number, Job_Full_Name, and updated
|
// Schema is now fixed with just Job_Number, Job_Full_Name, and updated
|
||||||
// No dynamic UI updates needed
|
// No dynamic UI updates needed
|
||||||
@@ -783,18 +623,10 @@
|
|||||||
showLoading(true);
|
showLoading(true);
|
||||||
console.log('Attempting to load jobs from collection...');
|
console.log('Attempting to load jobs from collection...');
|
||||||
|
|
||||||
const collectionUsed = 'Jobs';
|
const records = await dbLoadJobs();
|
||||||
|
|
||||||
// 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 || [];
|
allJobs = records || [];
|
||||||
filteredJobs = [...allJobs];
|
filteredJobs = [...allJobs];
|
||||||
|
|
||||||
console.log(`Jobs response from collection "${collectionUsed}":`, records);
|
|
||||||
console.log('Number of jobs found:', allJobs.length);
|
console.log('Number of jobs found:', allJobs.length);
|
||||||
|
|
||||||
// Debug: Log each job's structure
|
// Debug: Log each job's structure
|
||||||
@@ -815,26 +647,12 @@
|
|||||||
if (allJobs.length === 0) {
|
if (allJobs.length === 0) {
|
||||||
console.log('No jobs found - showing empty state');
|
console.log('No jobs found - showing empty state');
|
||||||
} else {
|
} else {
|
||||||
console.log(`Loaded ${allJobs.length} job(s) successfully from collection "${collectionUsed}"`);
|
console.log(`Loaded ${allJobs.length} job(s) successfully`);
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading jobs:', error);
|
console.error('Error loading jobs:', error);
|
||||||
console.error('Error details:', {
|
showError(error.message || 'Failed to load jobs');
|
||||||
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
|
// Show empty state
|
||||||
allJobs = [];
|
allJobs = [];
|
||||||
@@ -1144,23 +962,6 @@
|
|||||||
console.log('Job_Address:', 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
|
// Log the exact data structure being sent
|
||||||
console.log('=== CREATING JOB ===');
|
console.log('=== CREATING JOB ===');
|
||||||
console.log('Collection: Jobs');
|
console.log('Collection: Jobs');
|
||||||
@@ -1169,22 +970,7 @@
|
|||||||
|
|
||||||
// Try to get collection info for comparison
|
// Try to get collection info for comparison
|
||||||
try {
|
try {
|
||||||
let collection;
|
const collection = await getCollectionInfo('Jobs');
|
||||||
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 => ({
|
console.log('Collection fields:', collection.schema?.map(f => ({
|
||||||
name: f.name,
|
name: f.name,
|
||||||
type: f.type,
|
type: f.type,
|
||||||
@@ -1195,7 +981,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create job in PocketBase
|
// Create job in PocketBase
|
||||||
const createdJob = await pb.collection('Jobs').create(jobData);
|
const createdJob = await createJob(jobData);
|
||||||
|
|
||||||
console.log('Job created successfully:', createdJob);
|
console.log('Job created successfully:', createdJob);
|
||||||
|
|
||||||
@@ -1257,22 +1043,7 @@
|
|||||||
|
|
||||||
// Try to fetch collection schema for debugging
|
// Try to fetch collection schema for debugging
|
||||||
try {
|
try {
|
||||||
let collection;
|
const collection = await getCollectionInfo('Jobs');
|
||||||
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 schema:', collection);
|
||||||
console.log('Collection fields:', collection.schema?.map(f => ({
|
console.log('Collection fields:', collection.schema?.map(f => ({
|
||||||
name: f.name,
|
name: f.name,
|
||||||
@@ -1307,7 +1078,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await pb.collection('Jobs').delete(jobId);
|
await dbDeleteJob(jobId);
|
||||||
await loadJobs(); // Refresh the list
|
await loadJobs(); // Refresh the list
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error deleting job:', error.message);
|
console.error('Error deleting job:', error.message);
|
||||||
@@ -1315,8 +1086,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function logout() {
|
function handleLogout() {
|
||||||
pb.authStore.clear();
|
logout();
|
||||||
window.location.href = '/';
|
window.location.href = '/';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1332,7 +1103,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Make functions globally available
|
// Make functions globally available
|
||||||
window.logout = logout;
|
window.logout = handleLogout;
|
||||||
window.refreshJobs = refreshJobs;
|
window.refreshJobs = refreshJobs;
|
||||||
window.showAddJobModal = showAddJobModal;
|
window.showAddJobModal = showAddJobModal;
|
||||||
window.closeJobModal = closeJobModal;
|
window.closeJobModal = closeJobModal;
|
||||||
@@ -1341,54 +1112,7 @@
|
|||||||
window.deleteJob = deleteJob;
|
window.deleteJob = deleteJob;
|
||||||
window.changePage = changePage;
|
window.changePage = changePage;
|
||||||
window.filterJobs = filterJobs;
|
window.filterJobs = filterJobs;
|
||||||
|
window.debugPocketBase = debugPocketBase;
|
||||||
// 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
|
// Initialize on page load
|
||||||
document.addEventListener('DOMContentLoaded', initJobsPage);
|
document.addEventListener('DOMContentLoaded', initJobsPage);
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ app.use('/public/*', serveStatic({ root: './public' }))
|
|||||||
app.use('/dashboard.html', serveStatic({ path: './public/dashboard.html' }))
|
app.use('/dashboard.html', serveStatic({ path: './public/dashboard.html' }))
|
||||||
app.use('/jobs.html', serveStatic({ path: './public/jobs.html' }))
|
app.use('/jobs.html', serveStatic({ path: './public/jobs.html' }))
|
||||||
app.use('/components.js', serveStatic({ path: './public/components.js' }))
|
app.use('/components.js', serveStatic({ path: './public/components.js' }))
|
||||||
|
app.use('/db.js', serveStatic({ path: './public/db.js' }))
|
||||||
app.use('/pocketbase.es.mjs', serveStatic({ path: './public/pocketbase.es.mjs' }))
|
app.use('/pocketbase.es.mjs', serveStatic({ path: './public/pocketbase.es.mjs' }))
|
||||||
app.use('/pocketbase.es.mjs.map', serveStatic({ path: './public/pocketbase.es.mjs.map' }))
|
app.use('/pocketbase.es.mjs.map', serveStatic({ path: './public/pocketbase.es.mjs.map' }))
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user