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:
103
src/components/DashboardStats.tsx
Normal file
103
src/components/DashboardStats.tsx
Normal 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;
|
76
src/components/JobsSection.tsx
Normal file
76
src/components/JobsSection.tsx
Normal 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;
|
46
src/components/JobsToolbar.tsx
Normal file
46
src/components/JobsToolbar.tsx
Normal 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;
|
68
src/components/SalesTaxConfig.tsx
Normal file
68
src/components/SalesTaxConfig.tsx
Normal 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;
|
@@ -1,23 +1,18 @@
|
|||||||
|
|
||||||
import { useState, useEffect, useRef } from 'react';
|
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 { IndTransactionRecordNoId, IndJobRecordNoId } from '@/lib/pbtypes';
|
||||||
import { formatISK } from '@/utils/priceUtils';
|
|
||||||
import { getStatusPriority } from '@/utils/jobStatusUtils';
|
import { getStatusPriority } from '@/utils/jobStatusUtils';
|
||||||
import JobForm from '@/components/JobForm';
|
import JobForm from '@/components/JobForm';
|
||||||
import JobGroup from '@/components/JobGroup';
|
|
||||||
import { IndJob } from '@/lib/types';
|
import { IndJob } from '@/lib/types';
|
||||||
import BatchTransactionForm from '@/components/BatchTransactionForm';
|
import BatchTransactionForm from '@/components/BatchTransactionForm';
|
||||||
import BatchExpenditureForm from '@/components/BatchExpenditureForm';
|
import BatchExpenditureForm from '@/components/BatchExpenditureForm';
|
||||||
import { useJobs } from '@/hooks/useDataService';
|
import { useJobs } from '@/hooks/useDataService';
|
||||||
import { useJobMetrics } from '@/hooks/useJobMetrics';
|
import { useJobMetrics } from '@/hooks/useJobMetrics';
|
||||||
import SearchOverlay from '@/components/SearchOverlay';
|
import SearchOverlay from '@/components/SearchOverlay';
|
||||||
import RecapPopover from '@/components/RecapPopover';
|
|
||||||
import TransactionChart from '@/components/TransactionChart';
|
import TransactionChart from '@/components/TransactionChart';
|
||||||
|
import DashboardStats from '@/components/DashboardStats';
|
||||||
|
import JobsToolbar from '@/components/JobsToolbar';
|
||||||
|
import JobsSection from '@/components/JobsSection';
|
||||||
|
|
||||||
const Index = () => {
|
const Index = () => {
|
||||||
const {
|
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 toggleGroup = async (status: string) => {
|
||||||
const currentScrollY = window.scrollY;
|
const currentScrollY = window.scrollY;
|
||||||
|
|
||||||
@@ -248,143 +234,41 @@ const Index = () => {
|
|||||||
}}
|
}}
|
||||||
onSearch={setSearchQuery}
|
onSearch={setSearchQuery}
|
||||||
/>
|
/>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
||||||
<Card className="bg-gray-900 border-gray-700 text-white">
|
<DashboardStats
|
||||||
<CardHeader>
|
totalJobs={totalJobs}
|
||||||
<CardTitle className="flex items-center gap-2">
|
totalRevenue={totalRevenue}
|
||||||
<Factory className="w-5 h-5" />
|
totalProfit={totalProfit}
|
||||||
Active Jobs
|
jobs={regularJobs}
|
||||||
</CardTitle>
|
calculateJobRevenue={calculateJobRevenue}
|
||||||
</CardHeader>
|
calculateJobProfit={calculateJobProfit}
|
||||||
<CardContent>
|
onTotalRevenueChart={() => setTotalRevenueChartOpen(true)}
|
||||||
<div className="text-2xl font-bold">{totalJobs}</div>
|
onTotalProfitChart={() => setTotalProfitChartOpen(true)}
|
||||||
</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"
|
|
||||||
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>
|
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex justify-between items-center">
|
<JobsToolbar
|
||||||
<h2 className="text-xl font-bold text-white">Jobs</h2>
|
onNewJob={() => {
|
||||||
<div className="flex gap-2">
|
setEditingJob(null);
|
||||||
<SalesTaxConfig />
|
setShowJobForm(true);
|
||||||
<Button
|
}}
|
||||||
variant="outline"
|
onBatchIncome={() => setShowBatchForm(true)}
|
||||||
onClick={() => setShowBatchForm(true)}
|
onBatchExpenditure={() => setShowBatchExpenditureForm(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={() => {
|
|
||||||
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>
|
|
||||||
|
|
||||||
<div className="space-y-6">
|
<JobsSection
|
||||||
{Object.entries(jobGroups).map(([status, statusJobs]) => (
|
regularJobs={regularJobs}
|
||||||
<JobGroup
|
trackedJobs={trackedJobs}
|
||||||
key={status}
|
collapsedGroups={collapsedGroups}
|
||||||
status={status}
|
loadingStatuses={loadingStatuses}
|
||||||
jobs={statusJobs}
|
onToggleGroup={toggleGroup}
|
||||||
isCollapsed={collapsedGroups[status] || false}
|
onEdit={handleEditJob}
|
||||||
onToggle={toggleGroup}
|
onDelete={handleDeleteJob}
|
||||||
onEdit={handleEditJob}
|
onUpdateProduced={handleUpdateProduced}
|
||||||
onDelete={handleDeleteJob}
|
onImportBOM={handleImportBOM}
|
||||||
onUpdateProduced={handleUpdateProduced}
|
/>
|
||||||
onImportBOM={handleImportBOM}
|
|
||||||
isLoading={loadingStatuses.has(status)}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</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 && (
|
{showBatchForm && (
|
||||||
<BatchTransactionForm
|
<BatchTransactionForm
|
||||||
jobs={jobs}
|
jobs={jobs}
|
||||||
@@ -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;
|
export default Index;
|
||||||
|
Reference in New Issue
Block a user