This commit is contained in:
2025-07-07 16:03:37 +02:00
parent 12a3537028
commit ef42a8ad0b
9 changed files with 129 additions and 29 deletions

1
.gitignore vendored
View File

@@ -2,3 +2,4 @@ build/bin
node_modules node_modules
frontend/dist frontend/dist
frontend/src/lib/pocketbase.ts frontend/src/lib/pocketbase.ts
frontend/src/lib/pocketbaseAdmin.ts

View File

@@ -0,0 +1,44 @@
import { useState, useEffect } from 'react';
import { adminLogin } from '@/lib/pocketbase';
export function useAdmin() {
const [isLoggedIn, setIsLoggedIn] = useState(false);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
let mounted = true;
const initializeAdmin = async () => {
try {
setLoading(true);
await adminLogin();
if (mounted) {
setIsLoggedIn(true);
setError(null);
}
} catch (err) {
if (mounted) {
setError(err instanceof Error ? err.message : 'Failed to login as admin');
setIsLoggedIn(false);
}
} finally {
if (mounted) {
setLoading(false);
}
}
};
initializeAdmin();
return () => {
mounted = false;
};
}, []); // Empty dependency array ensures this only runs once on mount
return {
isLoggedIn,
loading,
error
};
}

View File

@@ -1,5 +1,4 @@
import { useState, useEffect, useCallback } from 'react';
import { useState, useEffect } from 'react';
import { dataService } from '@/services/dataService'; import { dataService } from '@/services/dataService';
import { IndJob } from '@/lib/types'; import { IndJob } from '@/lib/types';
@@ -9,42 +8,72 @@ export function useJobs() {
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
useEffect(() => { useEffect(() => {
let mounted = true;
const loadJobs = async () => { const loadJobs = async () => {
try { try {
setLoading(true); setLoading(true);
await dataService.loadJobs(); const loadedJobs = await dataService.loadJobs();
setError(null); if (mounted) {
setJobs(loadedJobs);
setError(null);
}
} catch (err) { } catch (err) {
setError(err instanceof Error ? err.message : 'Failed to load jobs'); if (mounted) {
setError(err instanceof Error ? err.message : 'Failed to load jobs');
}
} finally { } finally {
setLoading(false); if (mounted) {
setLoading(false);
}
} }
}; };
loadJobs(); loadJobs();
// Set up subscription for updates
const unsubscribe = dataService.subscribe(() => { const unsubscribe = dataService.subscribe(() => {
setJobs(dataService.getJobs()); if (mounted) {
const currentJobs = dataService.getJobs();
setJobs(prevJobs => {
// Only update if the jobs have actually changed
const prevJson = JSON.stringify(prevJobs);
const currentJson = JSON.stringify(currentJobs);
return prevJson !== currentJson ? currentJobs : prevJobs;
});
}
}); });
return () => { return () => {
mounted = false;
unsubscribe(); unsubscribe();
}; };
}, []); }, []); // Empty dependency array ensures this only runs once on mount
// Memoize the methods to prevent unnecessary re-renders
const createJob = useCallback(dataService.createJob.bind(dataService), []);
const updateJob = useCallback(dataService.updateJob.bind(dataService), []);
const deleteJob = useCallback(dataService.deleteJob.bind(dataService), []);
const createTransaction = useCallback(dataService.createTransaction.bind(dataService), []);
const createMultipleTransactions = useCallback(dataService.createMultipleTransactions.bind(dataService), []);
const updateTransaction = useCallback(dataService.updateTransaction.bind(dataService), []);
const deleteTransaction = useCallback(dataService.deleteTransaction.bind(dataService), []);
const createBillItem = useCallback(dataService.createBillItem.bind(dataService), []);
const createMultipleBillItems = useCallback(dataService.createMultipleBillItems.bind(dataService), []);
return { return {
jobs, jobs,
loading, loading,
error, error,
createJob: dataService.createJob.bind(dataService), createJob,
updateJob: dataService.updateJob.bind(dataService), updateJob,
deleteJob: dataService.deleteJob.bind(dataService), deleteJob,
createTransaction: dataService.createTransaction.bind(dataService), createTransaction,
createMultipleTransactions: dataService.createMultipleTransactions.bind(dataService), createMultipleTransactions,
updateTransaction: dataService.updateTransaction.bind(dataService), updateTransaction,
deleteTransaction: dataService.deleteTransaction.bind(dataService), deleteTransaction,
createBillItem: dataService.createBillItem.bind(dataService), createBillItem,
createMultipleBillItems: dataService.createMultipleBillItems.bind(dataService) createMultipleBillItems
}; };
} }

View File

@@ -7,7 +7,6 @@ import { formatISK } from '@/utils/priceUtils';
import JobCard from '@/components/JobCard'; import JobCard from '@/components/JobCard';
import JobForm from '@/components/JobForm'; import JobForm from '@/components/JobForm';
import { IndJob } from '@/lib/types'; import { IndJob } from '@/lib/types';
import { Badge } from '@/components/ui/badge';
import BatchTransactionForm from '@/components/BatchTransactionForm'; import BatchTransactionForm from '@/components/BatchTransactionForm';
import { useJobs } from '@/hooks/useDataService'; import { useJobs } from '@/hooks/useDataService';
import SearchOverlay from '@/components/SearchOverlay'; import SearchOverlay from '@/components/SearchOverlay';

View File

@@ -1,5 +1,5 @@
import { IndBillitemRecord, IndBillitemRecordNoId } from "@/lib/pbtypes"; import { IndBillitemRecord, IndBillitemRecordNoId } from "@/lib/pbtypes";
import pb from "@/lib/pocketbase"; import { pb } from "@/lib/pocketbase";
export async function addBillItem( export async function addBillItem(
jobId: string, jobId: string,

View File

@@ -3,15 +3,24 @@ import { IndJobRecord, IndJobRecordNoId, IndTransactionRecord, IndTransactionRec
import * as jobService from './jobService'; import * as jobService from './jobService';
import * as transactionService from './transactionService'; import * as transactionService from './transactionService';
import * as billItemService from './billItemService'; import * as billItemService from './billItemService';
import { adminLogin } from '@/lib/pocketbase';
export class DataService { export class DataService {
private static instance: DataService; private static instance: DataService;
private jobs: IndJob[] = []; private jobs: IndJob[] = [];
private listeners: Set<() => void> = new Set(); private listeners: Set<() => void> = new Set();
private loadPromise: Promise<IndJob[]> | null = null;
private initialized: Promise<void>;
private constructor() { } private constructor() {
// Initialize with admin login
this.initialized = adminLogin().catch(error => {
console.error('Failed to initialize DataService:', error);
throw error;
});
}
static getInstance(): DataService { public static getInstance(): DataService {
if (!DataService.instance) { if (!DataService.instance) {
DataService.instance = new DataService(); DataService.instance = new DataService();
} }
@@ -36,10 +45,30 @@ export class DataService {
} }
async loadJobs(): Promise<IndJob[]> { async loadJobs(): Promise<IndJob[]> {
// Wait for initialization first
await this.initialized;
// If there's already a load in progress, return that promise
if (this.loadPromise) {
return this.loadPromise;
}
// If we already have jobs loaded, return them immediately
if (this.jobs.length > 0) {
return Promise.resolve(this.getJobs());
}
// Start a new load
console.log('Loading jobs from database'); console.log('Loading jobs from database');
this.jobs = await jobService.getJobs(); this.loadPromise = jobService.getJobs().then(jobs => {
this.notifyListeners(); this.jobs = jobs;
return this.getJobs(); this.notifyListeners();
return this.getJobs();
}).finally(() => {
this.loadPromise = null;
});
return this.loadPromise;
} }
async createJob(jobData: IndJobRecordNoId): Promise<IndJob> { async createJob(jobData: IndJobRecordNoId): Promise<IndJob> {

View File

@@ -1,5 +1,5 @@
import type { IndFacilityRecord, IndFacilityResponse } from '../lib/pbtypes'; import type { IndFacilityRecord, IndFacilityResponse } from '../lib/pbtypes';
import pb from '../lib/pocketbase'; import { pb } from '../lib/pocketbase';
export type { IndFacilityRecord as Facility } from '../lib/pbtypes'; export type { IndFacilityRecord as Facility } from '../lib/pbtypes';

View File

@@ -1,7 +1,6 @@
import { IndJob } from '@/lib/types'; import { IndJob } from '@/lib/types';
import type { IndJobRecord, IndJobRecordNoId } from '../lib/pbtypes'; import type { IndJobRecord, IndJobRecordNoId } from '../lib/pbtypes';
import pb from '../lib/pocketbase'; import { pb } from '../lib/pocketbase';
export type { IndJobRecord as Job } from '../lib/pbtypes'; export type { IndJobRecord as Job } from '../lib/pbtypes';
export type { IndTransactionRecord as Transaction } from '../lib/pbtypes'; export type { IndTransactionRecord as Transaction } from '../lib/pbtypes';

View File

@@ -1,7 +1,6 @@
import { IndJob } from '@/lib/types'; import { IndJob } from '@/lib/types';
import type { IndTransactionRecord, IndTransactionRecordNoId } from '../lib/pbtypes'; import type { IndTransactionRecord, IndTransactionRecordNoId } from '../lib/pbtypes';
import pb from '../lib/pocketbase'; import { pb } from '../lib/pocketbase';
import { updateJob } from './jobService'; import { updateJob } from './jobService';
export async function createTransaction( export async function createTransaction(