diff --git a/src/components/JobCard.tsx b/src/components/JobCard.tsx index afbe3d6..0b865af 100644 --- a/src/components/JobCard.tsx +++ b/src/components/JobCard.tsx @@ -1,10 +1,10 @@ - import { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { HoverCard, HoverCardContent, HoverCardTrigger } from '@/components/ui/hover-card'; -import { Calendar, Factory, TrendingUp, TrendingDown, Clock, Import, Upload, Wrench, Check } from 'lucide-react'; +import { Calendar, Factory, TrendingUp, TrendingDown, Clock, Import, Upload, Check } from 'lucide-react'; import { formatISK } from '@/utils/priceUtils'; import { IndJob } from '@/lib/types'; import { Input } from '@/components/ui/input'; @@ -15,16 +15,24 @@ interface JobCardProps { onEdit: (job: any) => void; onDelete: (jobId: string) => void; onUpdateProduced?: (jobId: string, produced: number) => void; + onImportBOM?: (jobId: string, items: { name: string; quantity: number }[]) => void; isTracked?: boolean; } -const JobCard: React.FC = ({ job, onEdit, onDelete, onUpdateProduced, isTracked = false }) => { +const JobCard: React.FC = ({ + job, + onEdit, + onDelete, + onUpdateProduced, + onImportBOM, + isTracked = false +}) => { + const navigate = useNavigate(); const [isEditingProduced, setIsEditingProduced] = useState(false); const [producedValue, setProducedValue] = useState(job.produced?.toString() || '0'); const [copyingBom, setCopyingBom] = useState(false); const { toast } = useToast(); - // Sort transactions by date descending const sortedExpenditures = [...job.expenditures].sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime() ); @@ -32,7 +40,6 @@ const JobCard: React.FC = ({ job, onEdit, onDelete, onUpdateProduc new Date(b.date).getTime() - new Date(a.date).getTime() ); - // Calculate totals for this job (including tracked jobs) const totalExpenditure = sortedExpenditures.reduce((sum, tx) => sum + tx.totalPrice, 0); const totalIncome = sortedIncome.reduce((sum, tx) => sum + tx.totalPrice, 0); const profit = totalIncome - totalExpenditure; @@ -91,7 +98,7 @@ const JobCard: React.FC = ({ job, onEdit, onDelete, onUpdateProduc try { const clipboardText = await navigator.clipboard.readText(); const lines = clipboardText.split('\n').filter(line => line.trim()); - let importedCount = 0; + const items: { name: string; quantity: number }[] = []; for (const line of lines) { const parts = line.trim().split(/\s+/); @@ -99,16 +106,26 @@ const JobCard: React.FC = ({ job, onEdit, onDelete, onUpdateProduc const name = parts.slice(0, -1).join(' '); const quantity = parseInt(parts[parts.length - 1]); if (name && !isNaN(quantity)) { - importedCount++; + items.push({ name, quantity }); } } } - toast({ - title: "Import Preview", - description: `Found ${importedCount} items to import. This is just a preview - actual import functionality needs to be connected.`, - duration: 3000, - }); + if (items.length > 0 && onImportBOM) { + onImportBOM(job.id, items); + toast({ + title: "BOM Imported", + description: `Successfully imported ${items.length} items`, + duration: 3000, + }); + } else { + toast({ + title: "No Valid Items", + description: "No valid items found in clipboard. Format: 'Item Name Quantity' per line", + variant: "destructive", + duration: 3000, + }); + } } catch (err) { toast({ title: "Error", @@ -153,6 +170,10 @@ const JobCard: React.FC = ({ job, onEdit, onDelete, onUpdateProduc } }; + const handleCardClick = () => { + navigate(`/${job.id}`); + }; + const handleProducedClick = (e: React.MouseEvent) => { e.stopPropagation(); if (job.status !== 'Closed') { @@ -181,7 +202,10 @@ const JobCard: React.FC = ({ job, onEdit, onDelete, onUpdateProduc }; return ( - +
diff --git a/src/components/JobForm.tsx b/src/components/JobForm.tsx index 5de39df..633345c 100644 --- a/src/components/JobForm.tsx +++ b/src/components/JobForm.tsx @@ -15,14 +15,20 @@ interface JobFormProps { onCancel: () => void; } +const formatDateForInput = (dateString: string | undefined | null): string => { + if (!dateString) return ''; + // Convert ISO string to datetime-local format (YYYY-MM-DDTHH:MM) + return new Date(dateString).toISOString().slice(0, 16); +}; + const JobForm: React.FC = ({ job, onSubmit, onCancel }) => { const [formData, setFormData] = useState({ outputItem: job?.outputItem || '', outputQuantity: job?.outputQuantity || 0, - jobStart: job?.jobStart ? new Date(job.jobStart).toISOString().slice(0, 16) : '', - jobEnd: job?.jobEnd ? new Date(job.jobEnd).toISOString().slice(0, 16) : '', - saleStart: job?.saleStart ? new Date(job.saleStart).toISOString().slice(0, 16) : '', - saleEnd: job?.saleEnd ? new Date(job.saleEnd).toISOString().slice(0, 16) : '', + jobStart: formatDateForInput(job?.jobStart), + jobEnd: formatDateForInput(job?.jobEnd), + saleStart: formatDateForInput(job?.saleStart), + saleEnd: formatDateForInput(job?.saleEnd), status: job?.status || IndJobStatusOptions.Planned, projectedCost: job?.projectedCost || 0, projectedRevenue: job?.projectedRevenue || 0 @@ -34,15 +40,11 @@ const JobForm: React.FC = ({ job, onSubmit, onCancel }) => { onSubmit({ outputItem: formData.outputItem, outputQuantity: formData.outputQuantity, - jobStart: formData.jobStart ? formData.jobStart : undefined, - jobEnd: formData.jobEnd ? formData.jobEnd : undefined, - saleStart: formData.saleStart ? formData.saleStart : undefined, - saleEnd: formData.saleEnd ? formData.saleEnd : undefined, + jobStart: formData.jobStart || undefined, + jobEnd: formData.jobEnd || undefined, + saleStart: formData.saleStart || undefined, + saleEnd: formData.saleEnd || undefined, status: formData.status, - billOfMaterials: job?.billOfMaterials?.map(item => item.id) || [], - consumedMaterials: job?.consumedMaterials?.map(item => item.id) || [], - expenditures: job?.expenditures?.map(item => item.id) || [], - income: job?.income?.map(item => item.id) || [], projectedCost: formData.projectedCost, projectedRevenue: formData.projectedRevenue }); diff --git a/src/pages/Index.tsx b/src/pages/Index.tsx index fc61f1b..f69e5ff 100644 --- a/src/pages/Index.tsx +++ b/src/pages/Index.tsx @@ -1,6 +1,4 @@ - import { useState } from 'react'; -import { useNavigate } from 'react-router-dom'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Plus, Factory, TrendingUp, Briefcase, FileText } from 'lucide-react'; @@ -14,7 +12,6 @@ import BatchTransactionForm from '@/components/BatchTransactionForm'; import { useJobs } from '@/hooks/useDataService'; const Index = () => { - const navigate = useNavigate(); const { jobs, loading, @@ -22,7 +19,8 @@ const Index = () => { createJob, updateJob, deleteJob, - createMultipleTransactions + createMultipleTransactions, + createMultipleBillItems } = useJobs(); const [showJobForm, setShowJobForm] = useState(false); @@ -142,6 +140,19 @@ const Index = () => { } }; + 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 jobGroups = regularJobs.reduce((groups, job) => { const status = job.status; if (!groups[status]) { @@ -167,10 +178,6 @@ const Index = () => { } }; - const handleJobCardClick = (jobId: string) => { - navigate(`/${jobId}`); - }; - if (showJobForm) { return (
@@ -274,14 +281,14 @@ const Index = () => { {!collapsedGroups[status] && (
{statusJobs.map(job => ( -
handleJobCardClick(job.id)} className="cursor-pointer"> - -
+ ))}
)} @@ -309,15 +316,15 @@ const Index = () => { {!collapsedGroups['Tracked'] && (
{trackedJobs.map(job => ( -
handleJobCardClick(job.id)} className="cursor-pointer"> - -
+ ))}
)}