Refactor: Split Index.tsx into smaller components

This commit refactors the `Index.tsx` file into smaller, more manageable components to improve code organization and readability.
This commit is contained in:
gpt-engineer-app[bot]
2025-07-09 19:16:27 +00:00
committed by PhatPhuckDave
parent 15280ecdab
commit 81e9a98315
5 changed files with 327 additions and 209 deletions

View File

@@ -0,0 +1,103 @@
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Factory, TrendingUp, Briefcase, BarChart3 } from 'lucide-react';
import { formatISK } from '@/utils/priceUtils';
import RecapPopover from './RecapPopover';
import { IndJob } from '@/lib/types';
interface DashboardStatsProps {
totalJobs: number;
totalRevenue: number;
totalProfit: number;
jobs: IndJob[];
calculateJobRevenue: (job: IndJob) => number;
calculateJobProfit: (job: IndJob) => number;
onTotalRevenueChart: () => void;
onTotalProfitChart: () => void;
}
const DashboardStats = ({
totalJobs,
totalRevenue,
totalProfit,
jobs,
calculateJobRevenue,
calculateJobProfit,
onTotalRevenueChart,
onTotalProfitChart
}: DashboardStatsProps) => {
return (
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<Card className="bg-gray-900 border-gray-700 text-white">
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Factory className="w-5 h-5" />
Active Jobs
</CardTitle>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{totalJobs}</div>
</CardContent>
</Card>
<Card className="bg-gray-900 border-gray-700 text-white">
<CardHeader>
<CardTitle className="flex items-center gap-2">
<TrendingUp className="w-5 h-5" />
Total Revenue
<Button
variant="ghost"
size="sm"
className="h-6 w-6 p-0 ml-auto"
onClick={onTotalRevenueChart}
>
<BarChart3 className="w-4 h-4" />
</Button>
</CardTitle>
</CardHeader>
<CardContent>
<RecapPopover
title="Revenue Breakdown"
jobs={jobs}
calculateJobValue={calculateJobRevenue}
>
<div className="text-2xl font-bold text-green-400 cursor-pointer hover:text-green-300 transition-colors">
{formatISK(totalRevenue)}
</div>
</RecapPopover>
</CardContent>
</Card>
<Card className="bg-gray-900 border-gray-700 text-white">
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Briefcase className="w-5 h-5" />
Total Profit
<Button
variant="ghost"
size="sm"
className="h-6 w-6 p-0 ml-auto"
onClick={onTotalProfitChart}
>
<BarChart3 className="w-4 h-4" />
</Button>
</CardTitle>
</CardHeader>
<CardContent>
<RecapPopover
title="Profit Breakdown"
jobs={jobs}
calculateJobValue={calculateJobProfit}
>
<div className={`text-2xl font-bold cursor-pointer transition-colors ${totalProfit >= 0 ? 'text-green-400 hover:text-green-300' : 'text-red-400 hover:text-red-300'}`}>
{formatISK(totalProfit)}
</div>
</RecapPopover>
</CardContent>
</Card>
</div>
);
};
export default DashboardStats;

View File

@@ -0,0 +1,76 @@
import { IndJob } from '@/lib/types';
import JobGroup from './JobGroup';
interface JobsSectionProps {
regularJobs: IndJob[];
trackedJobs: IndJob[];
collapsedGroups: Record<string, boolean>;
loadingStatuses: Set<string>;
onToggleGroup: (status: string) => void;
onEdit: (job: IndJob) => void;
onDelete: (jobId: string) => void;
onUpdateProduced: (jobId: string, produced: number) => void;
onImportBOM: (jobId: string, items: { name: string; quantity: number }[]) => void;
}
const JobsSection = ({
regularJobs,
trackedJobs,
collapsedGroups,
loadingStatuses,
onToggleGroup,
onEdit,
onDelete,
onUpdateProduced,
onImportBOM
}: JobsSectionProps) => {
const jobGroups = regularJobs.reduce((groups, job) => {
const status = job.status;
if (!groups[status]) {
groups[status] = [];
}
groups[status].push(job);
return groups;
}, {} as Record<string, IndJob[]>);
return (
<div className="space-y-4">
<div className="space-y-6">
{Object.entries(jobGroups).map(([status, statusJobs]) => (
<JobGroup
key={status}
status={status}
jobs={statusJobs}
isCollapsed={collapsedGroups[status] || false}
onToggle={onToggleGroup}
onEdit={onEdit}
onDelete={onDelete}
onUpdateProduced={onUpdateProduced}
onImportBOM={onImportBOM}
isLoading={loadingStatuses.has(status)}
/>
))}
</div>
{trackedJobs.length > 0 && (
<div className="space-y-4 mt-8 pt-8 border-t border-gray-700">
<JobGroup
status="Tracked"
jobs={trackedJobs}
isCollapsed={collapsedGroups['Tracked'] || false}
onToggle={onToggleGroup}
onEdit={onEdit}
onDelete={onDelete}
onUpdateProduced={onUpdateProduced}
onImportBOM={onImportBOM}
isTracked={true}
isLoading={loadingStatuses.has('Tracked')}
/>
</div>
)}
</div>
);
};
export default JobsSection;

View File

@@ -0,0 +1,46 @@
import { Button } from '@/components/ui/button';
import { Plus, FileText, ShoppingCart } from 'lucide-react';
import SalesTaxConfig from './SalesTaxConfig';
interface JobsToolbarProps {
onNewJob: () => void;
onBatchIncome: () => void;
onBatchExpenditure: () => void;
}
const JobsToolbar = ({ onNewJob, onBatchIncome, onBatchExpenditure }: JobsToolbarProps) => {
return (
<div className="flex justify-between items-center">
<h2 className="text-xl font-bold text-white">Jobs</h2>
<div className="flex gap-2">
<SalesTaxConfig />
<Button
variant="outline"
onClick={onBatchIncome}
className="border-gray-600 hover:bg-gray-800"
>
<FileText className="w-4 h-4 mr-2" />
Batch Income
</Button>
<Button
variant="outline"
onClick={onBatchExpenditure}
className="border-gray-600 hover:bg-gray-800"
>
<ShoppingCart className="w-4 h-4 mr-2" />
Batch Expenditure
</Button>
<Button
onClick={onNewJob}
className="bg-blue-600 hover:bg-blue-700"
>
<Plus className="w-4 h-4 mr-2" />
New Job
</Button>
</div>
</div>
);
};
export default JobsToolbar;

View File

@@ -0,0 +1,68 @@
import { useState } from 'react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import { Settings } from 'lucide-react';
const SalesTaxConfig = () => {
const [salesTax, setSalesTax] = useState(() => {
return localStorage.getItem('salesTax') || '0';
});
const [isOpen, setIsOpen] = useState(false);
const handleSave = () => {
localStorage.setItem('salesTax', salesTax);
setIsOpen(false);
window.dispatchEvent(new StorageEvent('storage', {
key: 'salesTax',
newValue: salesTax
}));
};
return (
<Popover open={isOpen} onOpenChange={setIsOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
className="border-gray-600 hover:bg-gray-800"
>
<Settings className="w-4 h-4 mr-2" />
Tax Config
</Button>
</PopoverTrigger>
<PopoverContent className="w-80 bg-gray-900 border-gray-700 text-white">
<div className="space-y-4">
<div className="space-y-2">
<Label htmlFor="salesTax" className="text-sm font-medium text-gray-300">
Sales Tax (%)
</Label>
<Input
id="salesTax"
type="number"
value={salesTax}
onChange={(e) => setSalesTax(e.target.value)}
onBlur={handleSave}
onKeyDown={(e) => {
if (e.key === 'Enter') {
handleSave();
}
}}
placeholder="0"
min="0"
max="100"
step="0.1"
className="bg-gray-800 border-gray-600 text-white"
/>
<p className="text-xs text-gray-400">
Applied to minimum price calculations
</p>
</div>
</div>
</PopoverContent>
</Popover>
);
};
export default SalesTaxConfig;

View File

@@ -1,23 +1,18 @@
import { useState, useEffect, useRef } from 'react';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import { Plus, Factory, TrendingUp, Briefcase, FileText, Settings, BarChart3, ShoppingCart } from 'lucide-react';
import { IndTransactionRecordNoId, IndJobRecordNoId } from '@/lib/pbtypes';
import { formatISK } from '@/utils/priceUtils';
import { getStatusPriority } from '@/utils/jobStatusUtils';
import JobForm from '@/components/JobForm';
import JobGroup from '@/components/JobGroup';
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 RecapPopover from '@/components/RecapPopover';
import TransactionChart from '@/components/TransactionChart';
import DashboardStats from '@/components/DashboardStats';
import JobsToolbar from '@/components/JobsToolbar';
import JobsSection from '@/components/JobsSection';
const Index = () => {
const {
@@ -176,15 +171,6 @@ const Index = () => {
}
};
const jobGroups = regularJobs.reduce((groups, job) => {
const status = job.status;
if (!groups[status]) {
groups[status] = [];
}
groups[status].push(job);
return groups;
}, {} as Record<string, IndJob[]>);
const toggleGroup = async (status: string) => {
const currentScrollY = window.scrollY;
@@ -248,142 +234,40 @@ const Index = () => {
}}
onSearch={setSearchQuery}
/>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<Card className="bg-gray-900 border-gray-700 text-white">
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Factory className="w-5 h-5" />
Active Jobs
</CardTitle>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{totalJobs}</div>
</CardContent>
</Card>
<Card className="bg-gray-900 border-gray-700 text-white">
<CardHeader>
<CardTitle className="flex items-center gap-2">
<TrendingUp className="w-5 h-5" />
Total Revenue
<Button
variant="ghost"
size="sm"
className="h-6 w-6 p-0 ml-auto"
onClick={() => setTotalRevenueChartOpen(true)}
>
<BarChart3 className="w-4 h-4" />
</Button>
</CardTitle>
</CardHeader>
<CardContent>
<RecapPopover
title="Revenue Breakdown"
<DashboardStats
totalJobs={totalJobs}
totalRevenue={totalRevenue}
totalProfit={totalProfit}
jobs={regularJobs}
calculateJobValue={calculateJobRevenue}
>
<div className="text-2xl font-bold text-green-400 cursor-pointer hover:text-green-300 transition-colors">
{formatISK(totalRevenue)}
</div>
</RecapPopover>
</CardContent>
</Card>
<Card className="bg-gray-900 border-gray-700 text-white">
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Briefcase className="w-5 h-5" />
Total Profit
<Button
variant="ghost"
size="sm"
className="h-6 w-6 p-0 ml-auto"
onClick={() => setTotalProfitChartOpen(true)}
>
<BarChart3 className="w-4 h-4" />
</Button>
</CardTitle>
</CardHeader>
<CardContent>
<RecapPopover
title="Profit Breakdown"
jobs={regularJobs}
calculateJobValue={calculateJobProfit}
>
<div className={`text-2xl font-bold cursor-pointer transition-colors ${totalProfit >= 0 ? 'text-green-400 hover:text-green-300' : 'text-red-400 hover:text-red-300'}`}>
{formatISK(totalProfit)}
</div>
</RecapPopover>
</CardContent>
</Card>
</div>
calculateJobRevenue={calculateJobRevenue}
calculateJobProfit={calculateJobProfit}
onTotalRevenueChart={() => setTotalRevenueChartOpen(true)}
onTotalProfitChart={() => setTotalProfitChartOpen(true)}
/>
<div className="space-y-4">
<div className="flex justify-between items-center">
<h2 className="text-xl font-bold text-white">Jobs</h2>
<div className="flex gap-2">
<SalesTaxConfig />
<Button
variant="outline"
onClick={() => setShowBatchForm(true)}
className="border-gray-600 hover:bg-gray-800"
>
<FileText className="w-4 h-4 mr-2" />
Batch Income
</Button>
<Button
variant="outline"
onClick={() => setShowBatchExpenditureForm(true)}
className="border-gray-600 hover:bg-gray-800"
>
<ShoppingCart className="w-4 h-4 mr-2" />
Batch Expenditure
</Button>
<Button
onClick={() => {
<JobsToolbar
onNewJob={() => {
setEditingJob(null);
setShowJobForm(true);
}}
className="bg-blue-600 hover:bg-blue-700"
>
<Plus className="w-4 h-4 mr-2" />
New Job
</Button>
</div>
</div>
onBatchIncome={() => setShowBatchForm(true)}
onBatchExpenditure={() => setShowBatchExpenditureForm(true)}
/>
<div className="space-y-6">
{Object.entries(jobGroups).map(([status, statusJobs]) => (
<JobGroup
key={status}
status={status}
jobs={statusJobs}
isCollapsed={collapsedGroups[status] || false}
onToggle={toggleGroup}
<JobsSection
regularJobs={regularJobs}
trackedJobs={trackedJobs}
collapsedGroups={collapsedGroups}
loadingStatuses={loadingStatuses}
onToggleGroup={toggleGroup}
onEdit={handleEditJob}
onDelete={handleDeleteJob}
onUpdateProduced={handleUpdateProduced}
onImportBOM={handleImportBOM}
isLoading={loadingStatuses.has(status)}
/>
))}
</div>
</div>
{trackedJobs.length > 0 && (
<div className="space-y-4 mt-8 pt-8 border-t border-gray-700">
<JobGroup
status="Tracked"
jobs={trackedJobs}
isCollapsed={collapsedGroups['Tracked'] || false}
onToggle={toggleGroup}
onEdit={handleEditJob}
onDelete={handleDeleteJob}
onUpdateProduced={handleUpdateProduced}
onImportBOM={handleImportBOM}
isTracked={true}
isLoading={loadingStatuses.has('Tracked')}
/>
</div>
)}
{showBatchForm && (
<BatchTransactionForm
@@ -418,63 +302,4 @@ const Index = () => {
);
};
const SalesTaxConfig = () => {
const [salesTax, setSalesTax] = useState(() => {
return localStorage.getItem('salesTax') || '0';
});
const [isOpen, setIsOpen] = useState(false);
const handleSave = () => {
localStorage.setItem('salesTax', salesTax);
setIsOpen(false);
window.dispatchEvent(new StorageEvent('storage', {
key: 'salesTax',
newValue: salesTax
}));
};
return (
<Popover open={isOpen} onOpenChange={setIsOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
className="border-gray-600 hover:bg-gray-800"
>
<Settings className="w-4 h-4 mr-2" />
Tax Config
</Button>
</PopoverTrigger>
<PopoverContent className="w-80 bg-gray-900 border-gray-700 text-white">
<div className="space-y-4">
<div className="space-y-2">
<Label htmlFor="salesTax" className="text-sm font-medium text-gray-300">
Sales Tax (%)
</Label>
<Input
id="salesTax"
type="number"
value={salesTax}
onChange={(e) => setSalesTax(e.target.value)}
onBlur={handleSave}
onKeyDown={(e) => {
if (e.key === 'Enter') {
handleSave();
}
}}
placeholder="0"
min="0"
max="100"
step="0.1"
className="bg-gray-800 border-gray-600 text-white"
/>
<p className="text-xs text-gray-400">
Applied to minimum price calculations
</p>
</div>
</div>
</PopoverContent>
</Popover>
);
};
export default Index;