Sync
This commit is contained in:
		
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -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
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										44
									
								
								frontend/src/hooks/useAdmin.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								frontend/src/hooks/useAdmin.ts
									
									
									
									
									
										Normal 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
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					} 
 | 
				
			||||||
@@ -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();
 | 
				
			||||||
 | 
					        if (mounted) {
 | 
				
			||||||
 | 
					          setJobs(loadedJobs);
 | 
				
			||||||
          setError(null);
 | 
					          setError(null);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
      } catch (err) {
 | 
					      } catch (err) {
 | 
				
			||||||
 | 
					        if (mounted) {
 | 
				
			||||||
          setError(err instanceof Error ? err.message : 'Failed to load jobs');
 | 
					          setError(err instanceof Error ? err.message : 'Failed to load jobs');
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
      } finally {
 | 
					      } finally {
 | 
				
			||||||
 | 
					        if (mounted) {
 | 
				
			||||||
          setLoading(false);
 | 
					          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
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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';
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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.jobs = jobs;
 | 
				
			||||||
      this.notifyListeners();
 | 
					      this.notifyListeners();
 | 
				
			||||||
      return this.getJobs();
 | 
					      return this.getJobs();
 | 
				
			||||||
 | 
					    }).finally(() => {
 | 
				
			||||||
 | 
					      this.loadPromise = null;
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return this.loadPromise;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async createJob(jobData: IndJobRecordNoId): Promise<IndJob> {
 | 
					  async createJob(jobData: IndJobRecordNoId): Promise<IndJob> {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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';
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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(
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user