Add projected cost and revenue

This commit is contained in:
2025-07-04 17:04:11 +02:00
parent 63f3db6197
commit 6f6e599db0
4 changed files with 69 additions and 6 deletions

View File

@@ -1,4 +1,3 @@
import React from 'react';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
@@ -160,6 +159,17 @@ const JobCard: React.FC<JobCardProps> = ({ job, onEdit, onDelete }) => {
<span className="text-sm">Expenditure</span>
</div>
<div className="font-semibold">{formatISK(totalExpenditure)}</div>
{job.projectedCost > 0 && (
<div className="text-xs text-gray-400">
vs Projected: {formatISK(job.projectedCost)}
<Badge
variant={totalExpenditure <= job.projectedCost ? 'default' : 'destructive'}
className="ml-1 text-xs"
>
{((totalExpenditure / job.projectedCost) * 100).toFixed(1)}%
</Badge>
</div>
)}
</div>
<div className="text-center">
<div className="flex items-center justify-center gap-1 text-green-400">
@@ -167,6 +177,17 @@ const JobCard: React.FC<JobCardProps> = ({ job, onEdit, onDelete }) => {
<span className="text-sm">Income</span>
</div>
<div className="font-semibold">{formatISK(totalIncome)}</div>
{job.projectedRevenue > 0 && (
<div className="text-xs text-gray-400">
vs Projected: {formatISK(job.projectedRevenue)}
<Badge
variant={totalIncome >= job.projectedRevenue ? 'default' : 'destructive'}
className="ml-1 text-xs"
>
{((totalIncome / job.projectedRevenue) * 100).toFixed(1)}%
</Badge>
</div>
)}
</div>
<div className="text-center">
<div className="text-sm text-gray-400">Profit</div>
@@ -176,6 +197,11 @@ const JobCard: React.FC<JobCardProps> = ({ job, onEdit, onDelete }) => {
<Badge variant={profit >= 0 ? 'default' : 'destructive'} className="text-xs">
{margin.toFixed(1)}%
</Badge>
{job.projectedRevenue > 0 && job.projectedCost > 0 && (
<div className="text-xs text-gray-400 mt-1">
vs Projected: {formatISK(job.projectedRevenue - job.projectedCost)}
</div>
)}
</div>
</div>
</CardContent>

View File

@@ -1,14 +1,14 @@
import React, { useState, useEffect } from 'react';
import React, { useState } 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 { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { IndBillitemRecordNoId, IndJobStatusOptions, IndJobRecordNoId, IndFacilityRecord, IndBillitemRecord } from '@/lib/pbtypes';
import { IndJobStatusOptions, IndJobRecordNoId, IndBillitemRecord } from '@/lib/pbtypes';
import MaterialsImportExport from './MaterialsImportExport';
import { IndJob } from '@/lib/types';
import { parseISKAmount } from '@/utils/priceUtils';
// import { getFacilities } from '@/services/facilityService';
interface JobFormProps {
@@ -32,7 +32,9 @@ const JobForm: React.FC<JobFormProps> = ({ job, onSubmit, onCancel }) => {
billOfMaterials: job?.billOfMaterials || [],
consumedMaterials: job?.consumedMaterials || [],
expenditures: job?.expenditures || [],
income: job?.income || []
income: job?.income || [],
projectedCost: job?.projectedCost || 0,
projectedRevenue: job?.projectedRevenue || 0
});
// useEffect(() => {
@@ -62,7 +64,9 @@ const JobForm: React.FC<JobFormProps> = ({ job, onSubmit, onCancel }) => {
billOfMaterials: formData.billOfMaterials.map(item => item.id),
consumedMaterials: formData.consumedMaterials.map(item => item.id),
expenditures: formData.expenditures.map(item => item.id),
income: formData.income.map(item => item.id)
income: formData.income.map(item => item.id),
projectedCost: formData.projectedCost,
projectedRevenue: formData.projectedRevenue
});
};
@@ -191,6 +195,35 @@ const JobForm: React.FC<JobFormProps> = ({ job, onSubmit, onCancel }) => {
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="projectedCost" className="text-gray-300">Projected Cost</Label>
<Input
id="projectedCost"
type="text"
value={formData.projectedCost ? `${formData.projectedCost.toLocaleString()} ISK` : ''}
onChange={(e) => setFormData({
...formData,
projectedCost: parseISKAmount(e.target.value)
})}
className="bg-gray-800 border-gray-600 text-white"
/>
</div>
<div className="space-y-2">
<Label htmlFor="projectedRevenue" className="text-gray-300">Projected Revenue</Label>
<Input
id="projectedRevenue"
type="text"
value={formData.projectedRevenue ? `${formData.projectedRevenue.toLocaleString()} ISK` : ''}
onChange={(e) => setFormData({
...formData,
projectedRevenue: parseISKAmount(e.target.value)
})}
className="bg-gray-800 border-gray-600 text-white"
/>
</div>
</div>
<div className="flex gap-2 pt-4">
<Button type="submit" className="flex-1 bg-blue-600 hover:bg-blue-700">
{job ? 'Update Job' : 'Create Job'}

View File

@@ -134,6 +134,8 @@ export type IndJobRecord = {
jobStart?: IsoDateString
outputItem: string
outputQuantity: number
projectedCost?: number
projectedRevenue?: number
saleEnd?: IsoDateString
saleStart?: IsoDateString
status: IndJobStatusOptions

View File

@@ -17,4 +17,6 @@ export type IndJob = {
saleStart?: IsoDateString
status: IndJobStatusOptions
updated?: IsoDateString
projectedCost?: number
projectedRevenue?: number
}