diff --git a/src/components/JobCard.tsx b/src/components/JobCard.tsx index 1d2b5af..6ac4bba 100644 --- a/src/components/JobCard.tsx +++ b/src/components/JobCard.tsx @@ -1,9 +1,9 @@ - import React from 'react'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; -import { Calendar, Factory, TrendingUp, TrendingDown, Clock } from 'lucide-react'; +import { HoverCard, HoverCardContent, HoverCardTrigger } from '@/components/ui/hover-card'; +import { Calendar, Factory, TrendingUp, TrendingDown, Clock, Package, Wrench } from 'lucide-react'; import { Job } from '@/services/jobService'; import { formatISK } from '@/utils/priceUtils'; @@ -57,6 +57,50 @@ const JobCard: React.FC = ({ job, onEdit, onDelete }) => { {job.status} + {job.billOfMaterials && job.billOfMaterials.length > 0 && ( + + + + + +
+

Bill of Materials

+
+ {job.billOfMaterials.map((item, index) => ( +
+ {item.name} + {item.quantity.toLocaleString()} +
+ ))} +
+
+
+
+ )} + {job.consumedMaterials && job.consumedMaterials.length > 0 && ( + + + + + +
+

Consumed Materials

+
+ {job.consumedMaterials.map((item, index) => ( +
+ {item.name} + {item.required.toLocaleString()} +
+ ))} +
+
+
+
+ )}

Quantity: {job.outputItem.quantity.toLocaleString()}

diff --git a/src/components/JobForm.tsx b/src/components/JobForm.tsx index 16c7f28..faa54fe 100644 --- a/src/components/JobForm.tsx +++ b/src/components/JobForm.tsx @@ -1,12 +1,13 @@ - import React, { useState, useEffect } from 'react'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; -import { Job, Facility } from '@/services/jobService'; +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { Job, Facility, BillOfMaterialsItem, ConsumedMaterialsItem } from '@/services/jobService'; import { facilityService } from '@/services/facilityService'; +import MaterialsImportExport from './MaterialsImportExport'; interface JobFormProps { job?: Job; @@ -16,6 +17,8 @@ interface JobFormProps { const JobForm: React.FC = ({ job, onSubmit, onCancel }) => { const [facilities, setFacilities] = useState([]); + const [billOfMaterials, setBillOfMaterials] = useState(job?.billOfMaterials || []); + const [consumedMaterials, setConsumedMaterials] = useState(job?.consumedMaterials || []); const [formData, setFormData] = useState({ outputItem: { id: job?.outputItem.id || '', @@ -59,7 +62,9 @@ const JobForm: React.FC = ({ job, onSubmit, onCancel }) => { saleEnd: formData.dates.saleEnd ? new Date(formData.dates.saleEnd) : null }, status: formData.status, - facilityId: formData.facilityId + facilityId: formData.facilityId, + billOfMaterials, + consumedMaterials }); }; @@ -80,163 +85,195 @@ const JobForm: React.FC = ({ job, onSubmit, onCancel }) => { -
-
-
- - setFormData({ - ...formData, - outputItem: { ...formData.outputItem, name: e.target.value } - })} - className="bg-gray-800 border-gray-600 text-white" - required - /> -
-
- - setFormData({ - ...formData, - outputItem: { ...formData.outputItem, quantity: parseInt(e.target.value) || 0 } - })} - className="bg-gray-800 border-gray-600 text-white" - required - /> -
-
+ + + Basic Info + Materials + + + + +
+
+ + setFormData({ + ...formData, + outputItem: { ...formData.outputItem, name: e.target.value } + })} + className="bg-gray-800 border-gray-600 text-white" + required + /> +
+
+ + setFormData({ + ...formData, + outputItem: { ...formData.outputItem, quantity: parseInt(e.target.value) || 0 } + })} + className="bg-gray-800 border-gray-600 text-white" + required + /> +
+
-
-
- - -
-
- - -
-
+
+
+ + +
+
+ + +
+
-
- - setFormData({ - ...formData, - dates: { ...formData.dates, creation: e.target.value } - })} - className="bg-gray-800 border-gray-600 text-white" - required +
+ + setFormData({ + ...formData, + dates: { ...formData.dates, creation: e.target.value } + })} + className="bg-gray-800 border-gray-600 text-white" + required + /> +
+ +
+
+ + setFormData({ + ...formData, + dates: { ...formData.dates, start: e.target.value } + })} + className="bg-gray-800 border-gray-600 text-white" + /> +
+
+ + setFormData({ + ...formData, + dates: { ...formData.dates, end: e.target.value } + })} + className="bg-gray-800 border-gray-600 text-white" + /> +
+
+ +
+
+ + setFormData({ + ...formData, + dates: { ...formData.dates, saleStart: e.target.value } + })} + className="bg-gray-800 border-gray-600 text-white" + /> +
+
+ + setFormData({ + ...formData, + dates: { ...formData.dates, saleEnd: e.target.value } + })} + className="bg-gray-800 border-gray-600 text-white" + /> +
+
+ +
+ + +
+ + + + + -
- -
-
- - setFormData({ - ...formData, - dates: { ...formData.dates, start: e.target.value } - })} - className="bg-gray-800 border-gray-600 text-white" - /> + +
+ +
-
- - setFormData({ - ...formData, - dates: { ...formData.dates, end: e.target.value } - })} - className="bg-gray-800 border-gray-600 text-white" - /> -
-
- -
-
- - setFormData({ - ...formData, - dates: { ...formData.dates, saleStart: e.target.value } - })} - className="bg-gray-800 border-gray-600 text-white" - /> -
-
- - setFormData({ - ...formData, - dates: { ...formData.dates, saleEnd: e.target.value } - })} - className="bg-gray-800 border-gray-600 text-white" - /> -
-
- -
- - -
- + + ); diff --git a/src/components/MaterialsImportExport.tsx b/src/components/MaterialsImportExport.tsx new file mode 100644 index 0000000..59e8fca --- /dev/null +++ b/src/components/MaterialsImportExport.tsx @@ -0,0 +1,179 @@ + +import React, { useState } from 'react'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Textarea } from '@/components/ui/textarea'; +import { Label } from '@/components/ui/label'; +import { Import, Export, FileText } from 'lucide-react'; +import { BillOfMaterialsItem, ConsumedMaterialsItem } from '@/services/jobService'; + +interface MaterialsImportExportProps { + billOfMaterials: BillOfMaterialsItem[]; + consumedMaterials: ConsumedMaterialsItem[]; + onBillOfMaterialsUpdate: (materials: BillOfMaterialsItem[]) => void; + onConsumedMaterialsUpdate: (materials: ConsumedMaterialsItem[]) => void; +} + +const MaterialsImportExport: React.FC = ({ + billOfMaterials, + consumedMaterials, + onBillOfMaterialsUpdate, + onConsumedMaterialsUpdate +}) => { + const [bomInput, setBomInput] = useState(''); + const [consumedInput, setConsumedInput] = useState(''); + + const parseBillOfMaterials = (text: string): BillOfMaterialsItem[] => { + return text.split('\n') + .filter(line => line.trim()) + .map(line => { + const parts = line.trim().split(/\s+/); + const quantity = parseInt(parts[parts.length - 1]); + const name = parts.slice(0, -1).join(' '); + return { name, quantity }; + }) + .filter(item => item.name && !isNaN(item.quantity)); + }; + + const parseConsumedMaterials = (text: string): ConsumedMaterialsItem[] => { + const lines = text.split('\n').filter(line => line.trim()); + const materials: ConsumedMaterialsItem[] = []; + + for (const line of lines) { + const parts = line.trim().split('\t'); + if (parts.length >= 2) { + const name = parts[0]; + const required = parseInt(parts[1]); + if (name && !isNaN(required)) { + materials.push({ name, required }); + } + } + } + + return materials; + }; + + const exportBillOfMaterials = (): string => { + return billOfMaterials.map(item => `${item.name} ${item.quantity}`).join('\n'); + }; + + const exportConsumedMaterials = (): string => { + return consumedMaterials.map(item => `${item.name}\t${item.required}`).join('\n'); + }; + + const handleImportBom = () => { + const parsed = parseBillOfMaterials(bomInput); + onBillOfMaterialsUpdate(parsed); + setBomInput(''); + }; + + const handleImportConsumed = () => { + const parsed = parseConsumedMaterials(consumedInput); + onConsumedMaterialsUpdate(parsed); + setConsumedInput(''); + }; + + const handleExportBom = () => { + const exported = exportBillOfMaterials(); + navigator.clipboard.writeText(exported); + }; + + const handleExportConsumed = () => { + const exported = exportConsumedMaterials(); + navigator.clipboard.writeText(exported); + }; + + return ( + + + + + Materials Management + + + +
+
+ + +
+