From 29b5647ea2781688e0d74dcf493335209e0478e0 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Mon, 7 Jul 2025 12:09:21 +0000 Subject: [PATCH] Enhance job card with status colors and editability - Added background colors to job cards based on status. - Implemented editable date fields and projected costs/profit. - Added a dropdown for status selection. --- src/components/JobCard.tsx | 15 +++- src/components/JobCardDetails.tsx | 128 +++++++++++++++++++++++++----- src/components/JobCardHeader.tsx | 53 ++++++++++++- src/components/JobCardMetrics.tsx | 88 +++++++++++++++++++- 4 files changed, 255 insertions(+), 29 deletions(-) diff --git a/src/components/JobCard.tsx b/src/components/JobCard.tsx index eb81e79..c0b5945 100644 --- a/src/components/JobCard.tsx +++ b/src/components/JobCard.tsx @@ -29,9 +29,22 @@ const JobCard: React.FC = ({ navigate(`/${job.id}`); }; + const getStatusBackgroundColor = (status: string) => { + switch (status) { + case 'Planned': return 'bg-gray-600/20'; + case 'Acquisition': return 'bg-yellow-600/20'; + case 'Running': return 'bg-blue-600/20'; + case 'Done': return 'bg-purple-600/20'; + case 'Selling': return 'bg-orange-600/20'; + case 'Closed': return 'bg-green-600/20'; + case 'Tracked': return 'bg-cyan-600/20'; + default: return 'bg-gray-600/20'; + } + }; + return ( diff --git a/src/components/JobCardDetails.tsx b/src/components/JobCardDetails.tsx index 050ee6a..5df153e 100644 --- a/src/components/JobCardDetails.tsx +++ b/src/components/JobCardDetails.tsx @@ -1,13 +1,22 @@ +import { useState } from 'react'; import { Calendar, Factory, Clock } from 'lucide-react'; import { HoverCard, HoverCardContent, HoverCardTrigger } from '@/components/ui/hover-card'; +import { Input } from '@/components/ui/input'; import { IndJob } from '@/lib/types'; +import { useJobs } from '@/hooks/useDataService'; +import { useToast } from '@/hooks/use-toast'; interface JobCardDetailsProps { job: IndJob; } const JobCardDetails: React.FC = ({ job }) => { + const [editingField, setEditingField] = useState(null); + const [tempValues, setTempValues] = useState<{ [key: string]: string }>({}); + const { updateJob } = useJobs(); + const { toast } = useToast(); + const formatDateTime = (dateString: string | null | undefined) => { if (!dateString) return 'Not set'; return new Date(dateString).toLocaleString('en-CA', { @@ -19,6 +28,41 @@ const JobCardDetails: React.FC = ({ job }) => { }).replace(',', ''); }; + const handleFieldClick = (fieldName: string, currentValue: string | null, e: React.MouseEvent) => { + e.stopPropagation(); + setEditingField(fieldName); + setTempValues({ ...tempValues, [fieldName]: currentValue || '' }); + }; + + const handleFieldUpdate = async (fieldName: string, value: string) => { + try { + const dateValue = value ? new Date(value).toISOString() : null; + await updateJob(job.id, { [fieldName]: dateValue }); + setEditingField(null); + toast({ + title: "Updated", + description: `${fieldName} updated successfully`, + duration: 2000, + }); + } catch (error) { + console.error('Error updating field:', error); + toast({ + title: "Error", + description: "Failed to update field", + variant: "destructive", + duration: 2000, + }); + } + }; + + const handleKeyPress = (fieldName: string, e: React.KeyboardEvent) => { + if (e.key === 'Enter') { + handleFieldUpdate(fieldName, tempValues[fieldName]); + } else if (e.key === 'Escape') { + setEditingField(null); + } + }; + const sortedIncome = [...job.income].sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime() ); @@ -28,33 +72,77 @@ const JobCardDetails: React.FC = ({ job }) => { const daysSinceStart = saleStartTime ? Math.max(1, Math.ceil((Date.now() - saleStartTime) / (1000 * 60 * 60 * 24))) : 0; const itemsPerDay = daysSinceStart > 0 ? itemsSold / daysSinceStart : 0; + const DateField = ({ label, value, fieldName, icon }: { label: string; value: string | null; fieldName: string; icon: React.ReactNode }) => ( +
+ {icon} + {label}: + {editingField === fieldName ? ( + setTempValues({ ...tempValues, [fieldName]: e.target.value })} + onBlur={() => handleFieldUpdate(fieldName, tempValues[fieldName])} + onKeyDown={(e) => handleKeyPress(fieldName, e)} + onClick={(e) => e.stopPropagation()} + className="h-6 px-2 py-1 bg-gray-800 border-gray-600 text-white text-xs" + autoFocus + /> + ) : ( + handleFieldClick(fieldName, value, e)} + className="cursor-pointer hover:text-blue-400 flex-1" + title="Click to edit" + > + {formatDateTime(value)} + + )} +
+ ); + return (
-
- - Created: {formatDateTime(job.created)} -
-
- - Start: {formatDateTime(job.jobStart)} -
- Job ID: {job.id} + Job ID: + {job.id}
+
+ + Created: + {formatDateTime(job.created)} +
+ + } + /> + } + /> + + } + /> + } + />
- {job.saleStart && ( -
-
- Sale Period: {formatDateTime(job.saleStart)} - {formatDateTime(job.saleEnd)} -
- {itemsPerDay > 0 && ( -
- Items/Day: {itemsPerDay.toFixed(2)} -
- )} + {itemsPerDay > 0 && ( +
+ Items/Day: {itemsPerDay.toFixed(2)}
)} @@ -65,7 +153,7 @@ const JobCardDetails: React.FC = ({ job }) => { BOM: {job.billOfMaterials.length} items (hover to view)
- +

Bill of Materials

diff --git a/src/components/JobCardHeader.tsx b/src/components/JobCardHeader.tsx index 213205c..3bab29d 100644 --- a/src/components/JobCardHeader.tsx +++ b/src/components/JobCardHeader.tsx @@ -1,11 +1,17 @@ - import { useState } from 'react'; import { CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Import, Upload, Check, Copy } from 'lucide-react'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu'; import { IndJob } from '@/lib/types'; import { useToast } from '@/hooks/use-toast'; +import { useJobs } from '@/hooks/useDataService'; interface JobCardHeaderProps { job: IndJob; @@ -27,6 +33,9 @@ const JobCardHeader: React.FC = ({ const [copyingBom, setCopyingBom] = useState(false); const [copyingName, setCopyingName] = useState(false); const { toast } = useToast(); + const { updateJob } = useJobs(); + + const statuses = ['Planned', 'Acquisition', 'Running', 'Done', 'Selling', 'Closed', 'Tracked']; const getStatusColor = (status: string) => { switch (status) { @@ -41,6 +50,26 @@ const JobCardHeader: React.FC = ({ } }; + const handleStatusChange = async (newStatus: string, e: React.MouseEvent) => { + e.stopPropagation(); + try { + await updateJob(job.id, { status: newStatus }); + toast({ + title: "Status Updated", + description: `Job status changed to ${newStatus}`, + duration: 2000, + }); + } catch (error) { + console.error('Error updating status:', error); + toast({ + title: "Error", + description: "Failed to update status", + variant: "destructive", + duration: 2000, + }); + } + }; + const handleProducedUpdate = () => { const newValue = parseInt(producedValue); if (!isNaN(newValue) && onUpdateProduced) { @@ -248,9 +277,25 @@ const JobCardHeader: React.FC = ({
-
- {job.status} -
+ + +
+ {job.status} +
+
+ + {statuses.map((status) => ( + handleStatusChange(status, e)} + className="hover:bg-gray-700 cursor-pointer" + > +
+ {status} + + ))} + +