From ef42a8ad0b22faeb3ced5bff2769cddd483ab8a8 Mon Sep 17 00:00:00 2001 From: PhatPhuckDave Date: Mon, 7 Jul 2025 16:03:37 +0200 Subject: [PATCH] Sync --- .gitignore | 1 + frontend/src/hooks/useAdmin.ts | 44 ++++++++++++++ frontend/src/hooks/useDataService.ts | 63 +++++++++++++++------ frontend/src/pages/Index.tsx | 1 - frontend/src/services/billItemService.ts | 2 +- frontend/src/services/dataService.ts | 39 +++++++++++-- frontend/src/services/facilityService.ts | 2 +- frontend/src/services/jobService.ts | 3 +- frontend/src/services/transactionService.ts | 3 +- 9 files changed, 129 insertions(+), 29 deletions(-) create mode 100644 frontend/src/hooks/useAdmin.ts diff --git a/.gitignore b/.gitignore index 9feeaec..c326533 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ build/bin node_modules frontend/dist frontend/src/lib/pocketbase.ts +frontend/src/lib/pocketbaseAdmin.ts diff --git a/frontend/src/hooks/useAdmin.ts b/frontend/src/hooks/useAdmin.ts new file mode 100644 index 0000000..d364ba2 --- /dev/null +++ b/frontend/src/hooks/useAdmin.ts @@ -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(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 + }; +} \ No newline at end of file diff --git a/frontend/src/hooks/useDataService.ts b/frontend/src/hooks/useDataService.ts index 866b281..a132ee5 100644 --- a/frontend/src/hooks/useDataService.ts +++ b/frontend/src/hooks/useDataService.ts @@ -1,5 +1,4 @@ - -import { useState, useEffect } from 'react'; +import { useState, useEffect, useCallback } from 'react'; import { dataService } from '@/services/dataService'; import { IndJob } from '@/lib/types'; @@ -9,42 +8,72 @@ export function useJobs() { const [error, setError] = useState(null); useEffect(() => { + let mounted = true; + const loadJobs = async () => { try { setLoading(true); - await dataService.loadJobs(); - setError(null); + const loadedJobs = await dataService.loadJobs(); + if (mounted) { + setJobs(loadedJobs); + setError(null); + } } 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 { - setLoading(false); + if (mounted) { + setLoading(false); + } } }; loadJobs(); + // Set up subscription for updates 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 () => { + mounted = false; 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 { jobs, loading, error, - createJob: dataService.createJob.bind(dataService), - updateJob: dataService.updateJob.bind(dataService), - deleteJob: dataService.deleteJob.bind(dataService), - createTransaction: dataService.createTransaction.bind(dataService), - createMultipleTransactions: dataService.createMultipleTransactions.bind(dataService), - updateTransaction: dataService.updateTransaction.bind(dataService), - deleteTransaction: dataService.deleteTransaction.bind(dataService), - createBillItem: dataService.createBillItem.bind(dataService), - createMultipleBillItems: dataService.createMultipleBillItems.bind(dataService) + createJob, + updateJob, + deleteJob, + createTransaction, + createMultipleTransactions, + updateTransaction, + deleteTransaction, + createBillItem, + createMultipleBillItems }; } diff --git a/frontend/src/pages/Index.tsx b/frontend/src/pages/Index.tsx index 4c0c2e3..fec09b1 100644 --- a/frontend/src/pages/Index.tsx +++ b/frontend/src/pages/Index.tsx @@ -7,7 +7,6 @@ import { formatISK } from '@/utils/priceUtils'; import JobCard from '@/components/JobCard'; import JobForm from '@/components/JobForm'; import { IndJob } from '@/lib/types'; -import { Badge } from '@/components/ui/badge'; import BatchTransactionForm from '@/components/BatchTransactionForm'; import { useJobs } from '@/hooks/useDataService'; import SearchOverlay from '@/components/SearchOverlay'; diff --git a/frontend/src/services/billItemService.ts b/frontend/src/services/billItemService.ts index 96c7466..0411cff 100644 --- a/frontend/src/services/billItemService.ts +++ b/frontend/src/services/billItemService.ts @@ -1,5 +1,5 @@ import { IndBillitemRecord, IndBillitemRecordNoId } from "@/lib/pbtypes"; -import pb from "@/lib/pocketbase"; +import { pb } from "@/lib/pocketbase"; export async function addBillItem( jobId: string, diff --git a/frontend/src/services/dataService.ts b/frontend/src/services/dataService.ts index dd0e3fe..4650420 100644 --- a/frontend/src/services/dataService.ts +++ b/frontend/src/services/dataService.ts @@ -3,15 +3,24 @@ import { IndJobRecord, IndJobRecordNoId, IndTransactionRecord, IndTransactionRec import * as jobService from './jobService'; import * as transactionService from './transactionService'; import * as billItemService from './billItemService'; +import { adminLogin } from '@/lib/pocketbase'; export class DataService { private static instance: DataService; private jobs: IndJob[] = []; private listeners: Set<() => void> = new Set(); + private loadPromise: Promise | null = null; + private initialized: Promise; - 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) { DataService.instance = new DataService(); } @@ -36,10 +45,30 @@ export class DataService { } async loadJobs(): Promise { + // 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'); - this.jobs = await jobService.getJobs(); - this.notifyListeners(); - return this.getJobs(); + this.loadPromise = jobService.getJobs().then(jobs => { + this.jobs = jobs; + this.notifyListeners(); + return this.getJobs(); + }).finally(() => { + this.loadPromise = null; + }); + + return this.loadPromise; } async createJob(jobData: IndJobRecordNoId): Promise { diff --git a/frontend/src/services/facilityService.ts b/frontend/src/services/facilityService.ts index 7bd7527..dd2b2ec 100644 --- a/frontend/src/services/facilityService.ts +++ b/frontend/src/services/facilityService.ts @@ -1,5 +1,5 @@ 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'; diff --git a/frontend/src/services/jobService.ts b/frontend/src/services/jobService.ts index d713cf6..d418225 100644 --- a/frontend/src/services/jobService.ts +++ b/frontend/src/services/jobService.ts @@ -1,7 +1,6 @@ - import { IndJob } from '@/lib/types'; 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 { IndTransactionRecord as Transaction } from '../lib/pbtypes'; diff --git a/frontend/src/services/transactionService.ts b/frontend/src/services/transactionService.ts index 4eef613..73aa81b 100644 --- a/frontend/src/services/transactionService.ts +++ b/frontend/src/services/transactionService.ts @@ -1,7 +1,6 @@ - import { IndJob } from '@/lib/types'; import type { IndTransactionRecord, IndTransactionRecordNoId } from '../lib/pbtypes'; -import pb from '../lib/pocketbase'; +import { pb } from '../lib/pocketbase'; import { updateJob } from './jobService'; export async function createTransaction(