Fix importing the bill of materials

This commit is contained in:
2025-07-06 22:24:01 +02:00
parent a598d6c15f
commit dc1c8f4136
5 changed files with 96 additions and 40 deletions

View File

@@ -19,13 +19,13 @@ interface JobCardProps {
isTracked?: boolean;
}
const JobCard: React.FC<JobCardProps> = ({
job,
onEdit,
onDelete,
onUpdateProduced,
const JobCard: React.FC<JobCardProps> = ({
job,
onEdit,
onDelete,
onUpdateProduced,
onImportBOM,
isTracked = false
isTracked = false
}) => {
const navigate = useNavigate();
const [isEditingProduced, setIsEditingProduced] = useState(false);
@@ -33,10 +33,10 @@ const JobCard: React.FC<JobCardProps> = ({
const [copyingBom, setCopyingBom] = useState(false);
const { toast } = useToast();
const sortedExpenditures = [...job.expenditures].sort((a, b) =>
const sortedExpenditures = [...job.expenditures].sort((a, b) =>
new Date(b.date).getTime() - new Date(a.date).getTime()
);
const sortedIncome = [...job.income].sort((a, b) =>
const sortedIncome = [...job.income].sort((a, b) =>
new Date(b.date).getTime() - new Date(a.date).getTime()
);
@@ -95,23 +95,34 @@ const JobCard: React.FC<JobCardProps> = ({
};
const importBillOfMaterials = async () => {
if (!onImportBOM) {
toast({
title: "Error",
description: "Import functionality is not available",
variant: "destructive",
duration: 2000,
});
return;
}
try {
const clipboardText = await navigator.clipboard.readText();
const lines = clipboardText.split('\n').filter(line => line.trim());
const items: { name: string; quantity: number }[] = [];
for (const line of lines) {
const parts = line.trim().split(/\s+/);
const parts = line.trim().split(/[\s\t]+/);
if (parts.length >= 2) {
const name = parts.slice(0, -1).join(' ');
const quantity = parseInt(parts[parts.length - 1]);
const quantityPart = parts[parts.length - 1].replace(/,/g, '');
const quantity = parseInt(quantityPart);
if (name && !isNaN(quantity)) {
items.push({ name, quantity });
}
}
}
if (items.length > 0 && onImportBOM) {
if (items.length > 0) {
onImportBOM(job.id, items);
toast({
title: "BOM Imported",
@@ -150,7 +161,7 @@ const JobCard: React.FC<JobCardProps> = ({
const text = job.billOfMaterials
.map(item => `${item.name}\t${item.quantity.toLocaleString()}`)
.join('\n');
try {
await navigator.clipboard.writeText(text);
setCopyingBom(true);
@@ -202,7 +213,7 @@ const JobCard: React.FC<JobCardProps> = ({
};
return (
<Card
<Card
className={`bg-gray-900 border-gray-700 text-white h-full flex flex-col cursor-pointer hover:bg-gray-800/50 transition-colors ${job.status === 'Tracked' ? 'border-l-4 border-l-cyan-600' : ''}`}
onClick={handleCardClick}
>
@@ -232,7 +243,7 @@ const JobCard: React.FC<JobCardProps> = ({
autoFocus
/>
) : (
<span
<span
onClick={handleProducedClick}
className={job.status !== 'Closed' ? "cursor-pointer hover:text-blue-400" : undefined}
title={job.status !== 'Closed' ? "Click to edit" : undefined}
@@ -266,18 +277,18 @@ const JobCard: React.FC<JobCardProps> = ({
</Button>
</div>
<div className="flex gap-1 justify-end">
<Button
variant="ghost"
size="sm"
<Button
variant="ghost"
size="sm"
className="p-1 h-6 w-6"
onClick={handleImportClick}
title="Import BOM from clipboard"
>
<Import className="w-4 h-4 text-blue-400" />
</Button>
<Button
variant="ghost"
size="sm"
<Button
variant="ghost"
size="sm"
className="p-1 h-6 w-6"
onClick={handleExportClick}
disabled={!job.billOfMaterials?.length}
@@ -359,8 +370,8 @@ const JobCard: React.FC<JobCardProps> = ({
{job.projectedCost > 0 && (
<div className="text-xs text-gray-400">
vs Projected: {formatISK(job.projectedCost)}
<Badge
variant={totalExpenditure <= job.projectedCost ? 'default' : 'destructive'}
<Badge
variant={totalExpenditure <= job.projectedCost ? 'default' : 'destructive'}
className="ml-1 text-xs"
>
{((totalExpenditure / job.projectedCost) * 100).toFixed(1)}%
@@ -377,8 +388,8 @@ const JobCard: React.FC<JobCardProps> = ({
{job.projectedRevenue > 0 && (
<div className="text-xs text-gray-400">
vs Projected: {formatISK(job.projectedRevenue)}
<Badge
variant={totalIncome >= job.projectedRevenue ? 'default' : 'destructive'}
<Badge
variant={totalIncome >= job.projectedRevenue ? 'default' : 'destructive'}
className="ml-1 text-xs"
>
{((totalIncome / job.projectedRevenue) * 100).toFixed(1)}%
@@ -399,8 +410,8 @@ const JobCard: React.FC<JobCardProps> = ({
{job.projectedRevenue > 0 && job.projectedCost > 0 && (
<div className="text-xs text-gray-400">
vs Projected: {formatISK(job.projectedRevenue - job.projectedCost)}
<Badge
variant={profit >= (job.projectedRevenue - job.projectedCost) ? 'default' : 'destructive'}
<Badge
variant={profit >= (job.projectedRevenue - job.projectedCost) ? 'default' : 'destructive'}
className="ml-1 text-xs"
>
{((profit / (job.projectedRevenue - job.projectedCost)) * 100).toFixed(1)}%