diff --git a/src/hooks/useDashboard.ts b/src/hooks/useDashboard.ts new file mode 100644 index 0000000..2d6e557 --- /dev/null +++ b/src/hooks/useDashboard.ts @@ -0,0 +1,92 @@ + +import { useState, useEffect, useRef } from 'react'; +import { IndJob } from '@/lib/types'; +import { IndTransactionRecordNoId, IndJobRecordNoId } from '@/lib/pbtypes'; +import { useJobs } from '@/hooks/useDataService'; + +export function useDashboard() { + const { + jobs, + loading, + error, + loadingStatuses, + createJob, + updateJob, + deleteJob, + createMultipleTransactions, + createMultipleBillItems, + loadJobsForStatuses + } = useJobs(); + + const [showJobForm, setShowJobForm] = useState(false); + const [editingJob, setEditingJob] = useState(null); + const [showBatchForm, setShowBatchForm] = useState(false); + const [showBatchExpenditureForm, setShowBatchExpenditureForm] = useState(false); + const [searchOpen, setSearchOpen] = useState(false); + const [searchQuery, setSearchQuery] = useState(''); + const [totalRevenueChartOpen, setTotalRevenueChartOpen] = useState(false); + const [totalProfitChartOpen, setTotalProfitChartOpen] = useState(false); + const [collapsedGroups, setCollapsedGroups] = useState>(() => { + const saved = localStorage.getItem('jobGroupsCollapsed'); + return saved ? JSON.parse(saved) : {}; + }); + + const scrollPositionRef = useRef(0); + const containerRef = useRef(null); + + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if ((e.ctrlKey || e.metaKey) && e.key === 'f') { + e.preventDefault(); + setSearchOpen(true); + } + }; + + window.addEventListener('keydown', handleKeyDown); + return () => window.removeEventListener('keydown', handleKeyDown); + }, []); + + useEffect(() => { + const handleScroll = () => { + scrollPositionRef.current = window.scrollY; + }; + + window.addEventListener('scroll', handleScroll); + return () => window.removeEventListener('scroll', handleScroll); + }, []); + + return { + // State + jobs, + loading, + error, + loadingStatuses, + showJobForm, + setShowJobForm, + editingJob, + setEditingJob, + showBatchForm, + setShowBatchForm, + showBatchExpenditureForm, + setShowBatchExpenditureForm, + searchOpen, + setSearchOpen, + searchQuery, + setSearchQuery, + totalRevenueChartOpen, + setTotalRevenueChartOpen, + totalProfitChartOpen, + setTotalProfitChartOpen, + collapsedGroups, + setCollapsedGroups, + scrollPositionRef, + containerRef, + // Methods + createJob, + updateJob, + deleteJob, + createMultipleTransactions, + createMultipleBillItems, + loadJobsForStatuses + }; +} diff --git a/src/hooks/useDashboardHandlers.ts b/src/hooks/useDashboardHandlers.ts new file mode 100644 index 0000000..6981dc9 --- /dev/null +++ b/src/hooks/useDashboardHandlers.ts @@ -0,0 +1,147 @@ + +import { IndJob } from '@/lib/types'; +import { IndTransactionRecordNoId, IndJobRecordNoId } from '@/lib/pbtypes'; + +interface DashboardHandlersProps { + createJob: (jobData: IndJobRecordNoId) => Promise; + updateJob: (id: string, updates: Partial) => Promise; + deleteJob: (id: string) => Promise; + createMultipleTransactions: (jobId: string, transactions: IndTransactionRecordNoId[], type: 'expenditure' | 'income') => Promise; + createMultipleBillItems: (jobId: string, items: { name: string; quantity: number; unitPrice: number }[], type: 'billOfMaterials' | 'consumedMaterials') => Promise; + loadJobsForStatuses: (statuses: string[]) => Promise; + setShowJobForm: (show: boolean) => void; + setEditingJob: (job: IndJob | null) => void; + collapsedGroups: Record; + setCollapsedGroups: (groups: Record) => void; + loadingStatuses: Set; +} + +export function useDashboardHandlers({ + createJob, + updateJob, + deleteJob, + createMultipleTransactions, + createMultipleBillItems, + loadJobsForStatuses, + setShowJobForm, + setEditingJob, + collapsedGroups, + setCollapsedGroups, + loadingStatuses +}: DashboardHandlersProps) { + + const handleCreateJob = async (jobData: IndJobRecordNoId, billOfMaterials?: { name: string; quantity: number }[]) => { + try { + const newJob = await createJob(jobData); + + if (billOfMaterials && billOfMaterials.length > 0) { + const billItems = billOfMaterials.map(item => ({ + name: item.name, + quantity: item.quantity, + unitPrice: 0 + })); + await createMultipleBillItems(newJob.id, billItems, 'billOfMaterials'); + } + + setShowJobForm(false); + } catch (error) { + console.error('Error creating job:', error); + } + }; + + const handleEditJob = (job: IndJob) => { + setEditingJob(job); + setShowJobForm(true); + }; + + const handleUpdateJob = async (jobData: IndJobRecordNoId, editingJob: IndJob | null) => { + if (!editingJob) return; + + try { + await updateJob(editingJob.id, jobData); + setShowJobForm(false); + setEditingJob(null); + } catch (error) { + console.error('Error updating job:', error); + } + }; + + const handleDeleteJob = async (jobId: string) => { + if (confirm('Are you sure you want to delete this job?')) { + try { + await deleteJob(jobId); + } catch (error) { + console.error('Error deleting job:', error); + } + } + }; + + const handleUpdateProduced = async (jobId: string, produced: number) => { + try { + await updateJob(jobId, { produced }); + } catch (error) { + console.error('Error updating produced quantity:', error); + } + }; + + const handleImportBOM = async (jobId: string, items: { name: string; quantity: number }[]) => { + try { + const billItems = items.map(item => ({ + name: item.name, + quantity: item.quantity, + unitPrice: 0 + })); + await createMultipleBillItems(jobId, billItems, 'billOfMaterials'); + } catch (error) { + console.error('Error importing BOM:', error); + } + }; + + const toggleGroup = async (status: string) => { + const currentScrollY = window.scrollY; + + const newState = { ...collapsedGroups, [status]: !collapsedGroups[status] }; + setCollapsedGroups(newState); + localStorage.setItem('jobGroupsCollapsed', JSON.stringify(newState)); + + if (collapsedGroups[status] && !loadingStatuses.has(status)) { + await loadJobsForStatuses([status]); + + setTimeout(() => { + window.scrollTo(0, currentScrollY); + }, 50); + } + }; + + const handleBatchTransactionsAssigned = async (assignments: { jobId: string, transactions: IndTransactionRecordNoId[] }[]) => { + try { + for (const { jobId, transactions } of assignments) { + await createMultipleTransactions(jobId, transactions, 'income'); + } + } catch (error) { + console.error('Error assigning batch transactions:', error); + } + }; + + const handleBatchExpendituresAssigned = async (assignments: { jobId: string, transactions: IndTransactionRecordNoId[] }[]) => { + try { + for (const { jobId, transactions } of assignments) { + await createMultipleTransactions(jobId, transactions, 'expenditure'); + } + } catch (error) { + console.error('Error assigning batch expenditures:', error); + } + }; + + return { + handleCreateJob, + handleEditJob, + handleUpdateJob, + handleDeleteJob, + handleUpdateProduced, + handleImportBOM, + toggleGroup, + handleBatchTransactionsAssigned, + handleBatchExpendituresAssigned + }; +} diff --git a/src/pages/Index.tsx b/src/pages/Index.tsx index 693a557..2d3299f 100644 --- a/src/pages/Index.tsx +++ b/src/pages/Index.tsx @@ -1,18 +1,16 @@ -import { useState, useEffect, useRef } from 'react'; -import { IndTransactionRecordNoId, IndJobRecordNoId } from '@/lib/pbtypes'; -import { getStatusPriority } from '@/utils/jobStatusUtils'; import JobForm from '@/components/JobForm'; -import { IndJob } from '@/lib/types'; import BatchTransactionForm from '@/components/BatchTransactionForm'; import BatchExpenditureForm from '@/components/BatchExpenditureForm'; -import { useJobs } from '@/hooks/useDataService'; -import { useJobMetrics } from '@/hooks/useJobMetrics'; import SearchOverlay from '@/components/SearchOverlay'; import TransactionChart from '@/components/TransactionChart'; import DashboardStats from '@/components/DashboardStats'; import JobsToolbar from '@/components/JobsToolbar'; import JobsSection from '@/components/JobsSection'; +import { useDashboard } from '@/hooks/useDashboard'; +import { useDashboardHandlers } from '@/hooks/useDashboardHandlers'; +import { useJobMetrics } from '@/hooks/useJobMetrics'; +import { categorizeJobs } from '@/utils/jobFiltering'; const Index = () => { const { @@ -20,52 +18,57 @@ const Index = () => { loading, error, loadingStatuses, + showJobForm, + setShowJobForm, + editingJob, + setEditingJob, + showBatchForm, + setShowBatchForm, + showBatchExpenditureForm, + setShowBatchExpenditureForm, + searchOpen, + setSearchOpen, + searchQuery, + setSearchQuery, + totalRevenueChartOpen, + setTotalRevenueChartOpen, + totalProfitChartOpen, + setTotalProfitChartOpen, + collapsedGroups, + setCollapsedGroups, + containerRef, createJob, updateJob, deleteJob, createMultipleTransactions, createMultipleBillItems, loadJobsForStatuses - } = useJobs(); + } = useDashboard(); - const [showJobForm, setShowJobForm] = useState(false); - const [editingJob, setEditingJob] = useState(null); - const [showBatchForm, setShowBatchForm] = useState(false); - const [showBatchExpenditureForm, setShowBatchExpenditureForm] = useState(false); - const [searchOpen, setSearchOpen] = useState(false); - const [searchQuery, setSearchQuery] = useState(''); - const [totalRevenueChartOpen, setTotalRevenueChartOpen] = useState(false); - const [totalProfitChartOpen, setTotalProfitChartOpen] = useState(false); - const [collapsedGroups, setCollapsedGroups] = useState>(() => { - const saved = localStorage.getItem('jobGroupsCollapsed'); - return saved ? JSON.parse(saved) : {}; + const { + handleCreateJob, + handleEditJob, + handleUpdateJob, + handleDeleteJob, + handleUpdateProduced, + handleImportBOM, + toggleGroup, + handleBatchTransactionsAssigned, + handleBatchExpendituresAssigned + } = useDashboardHandlers({ + createJob, + updateJob, + deleteJob, + createMultipleTransactions, + createMultipleBillItems, + loadJobsForStatuses, + setShowJobForm, + setEditingJob, + collapsedGroups, + setCollapsedGroups, + loadingStatuses }); - // Track scroll position to prevent jarring jumps - const scrollPositionRef = useRef(0); - const containerRef = useRef(null); - - useEffect(() => { - const handleKeyDown = (e: KeyboardEvent) => { - if ((e.ctrlKey || e.metaKey) && e.key === 'f') { - e.preventDefault(); - setSearchOpen(true); - } - }; - - window.addEventListener('keydown', handleKeyDown); - return () => window.removeEventListener('keydown', handleKeyDown); - }, []); - - useEffect(() => { - const handleScroll = () => { - scrollPositionRef.current = window.scrollY; - }; - - window.addEventListener('scroll', handleScroll); - return () => window.removeEventListener('scroll', handleScroll); - }, []); - if (loading) { return (
@@ -82,138 +85,16 @@ const Index = () => { ); } - const filterJobs = (jobs: IndJob[]) => { - if (!searchQuery) return jobs; - const query = searchQuery.toLowerCase(); - return jobs.filter(job => - job.outputItem.toLowerCase().includes(query) - ); - }; - - const sortedJobs = [...jobs].sort((a, b) => { - const priorityA = getStatusPriority(a.status); - const priorityB = getStatusPriority(b.status); - if (priorityA === priorityB) { - return new Date(b.created || '').getTime() - new Date(a.created || '').getTime(); - } - return priorityA - priorityB; - }); - - const regularJobs = filterJobs(sortedJobs.filter(job => job.status !== 'Tracked')); - const trackedJobs = filterJobs(sortedJobs.filter(job => job.status === 'Tracked')); - + const { regularJobs, trackedJobs } = categorizeJobs(jobs, searchQuery); const { totalJobs, totalProfit, totalRevenue, calculateJobRevenue, calculateJobProfit } = useJobMetrics(regularJobs); - const handleCreateJob = async (jobData: IndJobRecordNoId, billOfMaterials?: { name: string; quantity: number }[]) => { - try { - const newJob = await createJob(jobData); - - if (billOfMaterials && billOfMaterials.length > 0) { - const billItems = billOfMaterials.map(item => ({ - name: item.name, - quantity: item.quantity, - unitPrice: 0 - })); - await createMultipleBillItems(newJob.id, billItems, 'billOfMaterials'); - } - - setShowJobForm(false); - } catch (error) { - console.error('Error creating job:', error); - } - }; - - const handleEditJob = (job: IndJob) => { - setEditingJob(job); - setShowJobForm(true); - }; - - const handleUpdateJob = async (jobData: IndJobRecordNoId) => { - if (!editingJob) return; - - try { - await updateJob(editingJob.id, jobData); - setShowJobForm(false); - setEditingJob(null); - } catch (error) { - console.error('Error updating job:', error); - } - }; - - const handleDeleteJob = async (jobId: string) => { - if (confirm('Are you sure you want to delete this job?')) { - try { - await deleteJob(jobId); - } catch (error) { - console.error('Error deleting job:', error); - } - } - }; - - const handleUpdateProduced = async (jobId: string, produced: number) => { - try { - await updateJob(jobId, { produced }); - } catch (error) { - console.error('Error updating produced quantity:', error); - } - }; - - const handleImportBOM = async (jobId: string, items: { name: string; quantity: number }[]) => { - try { - const billItems = items.map(item => ({ - name: item.name, - quantity: item.quantity, - unitPrice: 0 - })); - await createMultipleBillItems(jobId, billItems, 'billOfMaterials'); - } catch (error) { - console.error('Error importing BOM:', error); - } - }; - - const toggleGroup = async (status: string) => { - const currentScrollY = window.scrollY; - - const newState = { ...collapsedGroups, [status]: !collapsedGroups[status] }; - setCollapsedGroups(newState); - localStorage.setItem('jobGroupsCollapsed', JSON.stringify(newState)); - - if (collapsedGroups[status] && !loadingStatuses.has(status)) { - await loadJobsForStatuses([status]); - - setTimeout(() => { - window.scrollTo(0, currentScrollY); - }, 50); - } - }; - - const handleBatchTransactionsAssigned = async (assignments: { jobId: string, transactions: IndTransactionRecordNoId[] }[]) => { - try { - for (const { jobId, transactions } of assignments) { - await createMultipleTransactions(jobId, transactions, 'income'); - } - } catch (error) { - console.error('Error assigning batch transactions:', error); - } - }; - - const handleBatchExpendituresAssigned = async (assignments: { jobId: string, transactions: IndTransactionRecordNoId[] }[]) => { - try { - for (const { jobId, transactions } of assignments) { - await createMultipleTransactions(jobId, transactions, 'expenditure'); - } - } catch (error) { - console.error('Error assigning batch expenditures:', error); - } - }; - if (showJobForm) { return (
handleUpdateJob(jobData, editingJob) : handleCreateJob} onCancel={() => { setShowJobForm(false); setEditingJob(null); diff --git a/src/utils/jobFiltering.ts b/src/utils/jobFiltering.ts new file mode 100644 index 0000000..8f11912 --- /dev/null +++ b/src/utils/jobFiltering.ts @@ -0,0 +1,30 @@ + +import { IndJob } from '@/lib/types'; +import { getStatusPriority } from '@/utils/jobStatusUtils'; + +export const filterJobs = (jobs: IndJob[], searchQuery: string) => { + if (!searchQuery) return jobs; + const query = searchQuery.toLowerCase(); + return jobs.filter(job => + job.outputItem.toLowerCase().includes(query) + ); +}; + +export const sortJobs = (jobs: IndJob[]) => { + return [...jobs].sort((a, b) => { + const priorityA = getStatusPriority(a.status); + const priorityB = getStatusPriority(b.status); + if (priorityA === priorityB) { + return new Date(b.created || '').getTime() - new Date(a.created || '').getTime(); + } + return priorityA - priorityB; + }); +}; + +export const categorizeJobs = (jobs: IndJob[], searchQuery: string) => { + const sortedJobs = sortJobs(jobs); + const regularJobs = filterJobs(sortedJobs.filter(job => job.status !== 'Tracked'), searchQuery); + const trackedJobs = filterJobs(sortedJobs.filter(job => job.status === 'Tracked'), searchQuery); + + return { regularJobs, trackedJobs }; +};