Add projected cost and revenue
This commit is contained in:
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
@@ -160,6 +159,17 @@ const JobCard: React.FC<JobCardProps> = ({ job, onEdit, onDelete }) => {
|
|||||||
<span className="text-sm">Expenditure</span>
|
<span className="text-sm">Expenditure</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="font-semibold">{formatISK(totalExpenditure)}</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>
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<div className="flex items-center justify-center gap-1 text-green-400">
|
<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>
|
<span className="text-sm">Income</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="font-semibold">{formatISK(totalIncome)}</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>
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<div className="text-sm text-gray-400">Profit</div>
|
<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">
|
<Badge variant={profit >= 0 ? 'default' : 'destructive'} className="text-xs">
|
||||||
{margin.toFixed(1)}%
|
{margin.toFixed(1)}%
|
||||||
</Badge>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
import React, { useState, useEffect } from 'react';
|
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
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 MaterialsImportExport from './MaterialsImportExport';
|
||||||
import { IndJob } from '@/lib/types';
|
import { IndJob } from '@/lib/types';
|
||||||
|
import { parseISKAmount } from '@/utils/priceUtils';
|
||||||
// import { getFacilities } from '@/services/facilityService';
|
// import { getFacilities } from '@/services/facilityService';
|
||||||
|
|
||||||
interface JobFormProps {
|
interface JobFormProps {
|
||||||
@@ -32,7 +32,9 @@ const JobForm: React.FC<JobFormProps> = ({ job, onSubmit, onCancel }) => {
|
|||||||
billOfMaterials: job?.billOfMaterials || [],
|
billOfMaterials: job?.billOfMaterials || [],
|
||||||
consumedMaterials: job?.consumedMaterials || [],
|
consumedMaterials: job?.consumedMaterials || [],
|
||||||
expenditures: job?.expenditures || [],
|
expenditures: job?.expenditures || [],
|
||||||
income: job?.income || []
|
income: job?.income || [],
|
||||||
|
projectedCost: job?.projectedCost || 0,
|
||||||
|
projectedRevenue: job?.projectedRevenue || 0
|
||||||
});
|
});
|
||||||
|
|
||||||
// useEffect(() => {
|
// useEffect(() => {
|
||||||
@@ -62,7 +64,9 @@ const JobForm: React.FC<JobFormProps> = ({ job, onSubmit, onCancel }) => {
|
|||||||
billOfMaterials: formData.billOfMaterials.map(item => item.id),
|
billOfMaterials: formData.billOfMaterials.map(item => item.id),
|
||||||
consumedMaterials: formData.consumedMaterials.map(item => item.id),
|
consumedMaterials: formData.consumedMaterials.map(item => item.id),
|
||||||
expenditures: formData.expenditures.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>
|
</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">
|
<div className="flex gap-2 pt-4">
|
||||||
<Button type="submit" className="flex-1 bg-blue-600 hover:bg-blue-700">
|
<Button type="submit" className="flex-1 bg-blue-600 hover:bg-blue-700">
|
||||||
{job ? 'Update Job' : 'Create Job'}
|
{job ? 'Update Job' : 'Create Job'}
|
||||||
|
|||||||
@@ -134,6 +134,8 @@ export type IndJobRecord = {
|
|||||||
jobStart?: IsoDateString
|
jobStart?: IsoDateString
|
||||||
outputItem: string
|
outputItem: string
|
||||||
outputQuantity: number
|
outputQuantity: number
|
||||||
|
projectedCost?: number
|
||||||
|
projectedRevenue?: number
|
||||||
saleEnd?: IsoDateString
|
saleEnd?: IsoDateString
|
||||||
saleStart?: IsoDateString
|
saleStart?: IsoDateString
|
||||||
status: IndJobStatusOptions
|
status: IndJobStatusOptions
|
||||||
|
|||||||
@@ -17,4 +17,6 @@ export type IndJob = {
|
|||||||
saleStart?: IsoDateString
|
saleStart?: IsoDateString
|
||||||
status: IndJobStatusOptions
|
status: IndJobStatusOptions
|
||||||
updated?: IsoDateString
|
updated?: IsoDateString
|
||||||
|
projectedCost?: number
|
||||||
|
projectedRevenue?: number
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user