Make the rest of the application use "new" services
This commit is contained in:
@@ -5,16 +5,11 @@ import { Badge } from '@/components/ui/badge';
|
|||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { HoverCard, HoverCardContent, HoverCardTrigger } from '@/components/ui/hover-card';
|
import { HoverCard, HoverCardContent, HoverCardTrigger } from '@/components/ui/hover-card';
|
||||||
import { Calendar, Factory, TrendingUp, TrendingDown, Clock, Package, Wrench } from 'lucide-react';
|
import { Calendar, Factory, TrendingUp, TrendingDown, Clock, Package, Wrench } from 'lucide-react';
|
||||||
import { IndJobResponse, IndTransactionResponse, IndBillitemResponse } from '@/lib/pbtypes';
|
|
||||||
import { formatISK } from '@/utils/priceUtils';
|
import { formatISK } from '@/utils/priceUtils';
|
||||||
|
import { IndJob } from '@/lib/types';
|
||||||
|
|
||||||
interface JobCardProps {
|
interface JobCardProps {
|
||||||
job: IndJobResponse & {
|
job: IndJob;
|
||||||
expenditures: IndTransactionResponse[];
|
|
||||||
income: IndTransactionResponse[];
|
|
||||||
billOfMaterials: IndBillitemResponse[];
|
|
||||||
consumedMaterials: { name: string; required: number }[];
|
|
||||||
};
|
|
||||||
onEdit: (job: any) => void;
|
onEdit: (job: any) => void;
|
||||||
onDelete: (jobId: string) => void;
|
onDelete: (jobId: string) => void;
|
||||||
}
|
}
|
||||||
@@ -24,7 +19,7 @@ const JobCard: React.FC<JobCardProps> = ({ job, onEdit, onDelete }) => {
|
|||||||
const totalIncome = job.income.reduce((sum, tx) => sum + tx.totalPrice, 0);
|
const totalIncome = job.income.reduce((sum, tx) => sum + tx.totalPrice, 0);
|
||||||
const profit = totalIncome - totalExpenditure;
|
const profit = totalIncome - totalExpenditure;
|
||||||
const margin = totalIncome > 0 ? ((profit / totalIncome) * 100) : 0;
|
const margin = totalIncome > 0 ? ((profit / totalIncome) * 100) : 0;
|
||||||
|
|
||||||
const itemsSold = job.income.reduce((sum, tx) => sum + tx.quantity, 0);
|
const itemsSold = job.income.reduce((sum, tx) => sum + tx.quantity, 0);
|
||||||
const saleStartTime = job.saleStart ? new Date(job.saleStart).getTime() : null;
|
const saleStartTime = job.saleStart ? new Date(job.saleStart).getTime() : null;
|
||||||
const daysSinceStart = saleStartTime ? Math.max(1, Math.ceil((Date.now() - saleStartTime) / (1000 * 60 * 60 * 24))) : 0;
|
const daysSinceStart = saleStartTime ? Math.max(1, Math.ceil((Date.now() - saleStartTime) / (1000 * 60 * 60 * 24))) : 0;
|
||||||
@@ -44,9 +39,9 @@ const JobCard: React.FC<JobCardProps> = ({ job, onEdit, onDelete }) => {
|
|||||||
|
|
||||||
const formatDateTime = (dateString: string | null | undefined) => {
|
const formatDateTime = (dateString: string | null | undefined) => {
|
||||||
if (!dateString) return 'Not set';
|
if (!dateString) return 'Not set';
|
||||||
return new Date(dateString).toLocaleString('en-CA', {
|
return new Date(dateString).toLocaleString('en-CA', {
|
||||||
year: 'numeric',
|
year: 'numeric',
|
||||||
month: '2-digit',
|
month: '2-digit',
|
||||||
day: '2-digit',
|
day: '2-digit',
|
||||||
hour: '2-digit',
|
hour: '2-digit',
|
||||||
minute: '2-digit'
|
minute: '2-digit'
|
||||||
@@ -99,7 +94,7 @@ const JobCard: React.FC<JobCardProps> = ({ job, onEdit, onDelete }) => {
|
|||||||
{job.consumedMaterials.map((item, index) => (
|
{job.consumedMaterials.map((item, index) => (
|
||||||
<div key={index} className="flex justify-between">
|
<div key={index} className="flex justify-between">
|
||||||
<span>{item.name}</span>
|
<span>{item.name}</span>
|
||||||
<span className="text-gray-300">{item.required.toLocaleString()}</span>
|
<span className="text-gray-300">{item.quantity.toLocaleString()}</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -111,17 +106,17 @@ const JobCard: React.FC<JobCardProps> = ({ job, onEdit, onDelete }) => {
|
|||||||
<p className="text-gray-400">Quantity: {job.outputQuantity.toLocaleString()}</p>
|
<p className="text-gray-400">Quantity: {job.outputQuantity.toLocaleString()}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => onEdit(job)}
|
onClick={() => onEdit(job)}
|
||||||
className="border-gray-600 hover:bg-gray-800"
|
className="border-gray-600 hover:bg-gray-800"
|
||||||
>
|
>
|
||||||
Edit
|
Edit
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => onDelete(job.id)}
|
onClick={() => onDelete(job.id)}
|
||||||
>
|
>
|
||||||
Delete
|
Delete
|
||||||
@@ -144,7 +139,7 @@ const JobCard: React.FC<JobCardProps> = ({ job, onEdit, onDelete }) => {
|
|||||||
Job ID: {job.id}
|
Job ID: {job.id}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{job.saleStart && (
|
{job.saleStart && (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="text-sm text-gray-400">
|
<div className="text-sm text-gray-400">
|
||||||
@@ -157,7 +152,7 @@ const JobCard: React.FC<JobCardProps> = ({ job, onEdit, onDelete }) => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="grid grid-cols-3 gap-4 pt-4 border-t border-gray-700">
|
<div className="grid grid-cols-3 gap-4 pt-4 border-t border-gray-700">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<div className="flex items-center justify-center gap-1 text-red-400">
|
<div className="flex items-center justify-center gap-1 text-red-400">
|
||||||
|
|||||||
@@ -6,23 +6,21 @@ 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 { IndJobRecord, IndFacilityResponse, IndBillitemRecord, IndJobStatusOptions } from '@/lib/pbtypes';
|
import { IndBillitemRecordNoId, IndJobStatusOptions, IndJobRecordNoId, IndFacilityRecord } from '@/lib/pbtypes';
|
||||||
import { facilityService } from '@/services/facilityService';
|
|
||||||
import MaterialsImportExport from './MaterialsImportExport';
|
import MaterialsImportExport from './MaterialsImportExport';
|
||||||
|
import { IndJob } from '@/lib/types';
|
||||||
|
// import { getFacilities } from '@/services/facilityService';
|
||||||
|
|
||||||
interface JobFormProps {
|
interface JobFormProps {
|
||||||
job?: IndJobRecord & {
|
job?: IndJob;
|
||||||
billOfMaterials?: IndBillitemRecord[];
|
onSubmit: (job: IndJobRecordNoId) => void;
|
||||||
consumedMaterials?: { name: string; required: number }[];
|
|
||||||
};
|
|
||||||
onSubmit: (job: Omit<IndJobRecord, 'id' | 'created' | 'updated'>) => void;
|
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const JobForm: React.FC<JobFormProps> = ({ job, onSubmit, onCancel }) => {
|
const JobForm: React.FC<JobFormProps> = ({ job, onSubmit, onCancel }) => {
|
||||||
const [facilities, setFacilities] = useState<IndFacilityResponse[]>([]);
|
// const [facilities, setFacilities] = useState<IndFacilityRecord[]>([]);
|
||||||
const [billOfMaterials, setBillOfMaterials] = useState<IndBillitemRecord[]>(job?.billOfMaterials || []);
|
const [billOfMaterials, setBillOfMaterials] = useState<IndBillitemRecordNoId[]>(job?.billOfMaterials || []);
|
||||||
const [consumedMaterials, setConsumedMaterials] = useState<{ name: string; required: number }[]>(job?.consumedMaterials || []);
|
const [consumedMaterials, setConsumedMaterials] = useState<IndBillitemRecordNoId[]>(job?.consumedMaterials || []);
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
outputItem: job?.outputItem || '',
|
outputItem: job?.outputItem || '',
|
||||||
outputQuantity: job?.outputQuantity || 0,
|
outputQuantity: job?.outputQuantity || 0,
|
||||||
@@ -37,22 +35,22 @@ const JobForm: React.FC<JobFormProps> = ({ job, onSubmit, onCancel }) => {
|
|||||||
income: job?.income || []
|
income: job?.income || []
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
// useEffect(() => {
|
||||||
const loadFacilities = async () => {
|
// const loadFacilities = async () => {
|
||||||
try {
|
// try {
|
||||||
const fetchedFacilities = await facilityService.getFacilities();
|
// const fetchedFacilities = await getFacilities();
|
||||||
setFacilities(fetchedFacilities);
|
// setFacilities(fetchedFacilities);
|
||||||
} catch (error) {
|
// } catch (error) {
|
||||||
console.error('Error loading facilities:', error);
|
// console.error('Error loading facilities:', error);
|
||||||
}
|
// }
|
||||||
};
|
// };
|
||||||
|
|
||||||
loadFacilities();
|
// loadFacilities();
|
||||||
}, []);
|
// }, []);
|
||||||
|
|
||||||
const handleSubmit = (e: React.FormEvent) => {
|
const handleSubmit = (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
onSubmit({
|
onSubmit({
|
||||||
outputItem: formData.outputItem,
|
outputItem: formData.outputItem,
|
||||||
outputQuantity: formData.outputQuantity,
|
outputQuantity: formData.outputQuantity,
|
||||||
@@ -61,10 +59,10 @@ const JobForm: React.FC<JobFormProps> = ({ job, onSubmit, onCancel }) => {
|
|||||||
saleStart: formData.saleStart ? formData.saleStart : undefined,
|
saleStart: formData.saleStart ? formData.saleStart : undefined,
|
||||||
saleEnd: formData.saleEnd ? formData.saleEnd : undefined,
|
saleEnd: formData.saleEnd ? formData.saleEnd : undefined,
|
||||||
status: formData.status,
|
status: formData.status,
|
||||||
billOfMaterials: formData.billOfMaterials,
|
billOfMaterials: formData.billOfMaterials.map(item => item.id),
|
||||||
consumedMaterials: formData.consumedMaterials,
|
consumedMaterials: formData.consumedMaterials.map(item => item.id),
|
||||||
expenditures: formData.expenditures,
|
expenditures: formData.expenditures.map(item => item.id),
|
||||||
income: formData.income
|
income: formData.income.map(item => item.id)
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -83,7 +81,7 @@ const JobForm: React.FC<JobFormProps> = ({ job, onSubmit, onCancel }) => {
|
|||||||
<TabsTrigger value="basic" className="text-white">Basic Info</TabsTrigger>
|
<TabsTrigger value="basic" className="text-white">Basic Info</TabsTrigger>
|
||||||
<TabsTrigger value="materials" className="text-white">Materials</TabsTrigger>
|
<TabsTrigger value="materials" className="text-white">Materials</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
|
|
||||||
<TabsContent value="basic" className="space-y-4">
|
<TabsContent value="basic" className="space-y-4">
|
||||||
<form onSubmit={handleSubmit} className="space-y-4">
|
<form onSubmit={handleSubmit} className="space-y-4">
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
@@ -118,8 +116,8 @@ const JobForm: React.FC<JobFormProps> = ({ job, onSubmit, onCancel }) => {
|
|||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="status" className="text-gray-300">Status</Label>
|
<Label htmlFor="status" className="text-gray-300">Status</Label>
|
||||||
<Select
|
<Select
|
||||||
value={formData.status}
|
value={formData.status}
|
||||||
onValueChange={(value) => setFormData({ ...formData, status: value as IndJobStatusOptions })}
|
onValueChange={(value) => setFormData({ ...formData, status: value as IndJobStatusOptions })}
|
||||||
>
|
>
|
||||||
<SelectTrigger className="bg-gray-800 border-gray-600 text-white">
|
<SelectTrigger className="bg-gray-800 border-gray-600 text-white">
|
||||||
@@ -197,9 +195,9 @@ const JobForm: React.FC<JobFormProps> = ({ job, onSubmit, onCancel }) => {
|
|||||||
<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'}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={onCancel}
|
onClick={onCancel}
|
||||||
className="border-gray-600 hover:bg-gray-800"
|
className="border-gray-600 hover:bg-gray-800"
|
||||||
>
|
>
|
||||||
@@ -208,22 +206,23 @@ const JobForm: React.FC<JobFormProps> = ({ job, onSubmit, onCancel }) => {
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value="materials" className="space-y-4">
|
<TabsContent value="materials" className="space-y-4">
|
||||||
<MaterialsImportExport
|
<MaterialsImportExport
|
||||||
|
jobId={job?.id || ''}
|
||||||
billOfMaterials={billOfMaterials}
|
billOfMaterials={billOfMaterials}
|
||||||
consumedMaterials={consumedMaterials}
|
consumedMaterials={consumedMaterials}
|
||||||
onBillOfMaterialsUpdate={setBillOfMaterials}
|
onBillOfMaterialsUpdate={setBillOfMaterials}
|
||||||
onConsumedMaterialsUpdate={setConsumedMaterials}
|
onConsumedMaterialsUpdate={setConsumedMaterials}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="flex gap-2 pt-4">
|
<div className="flex gap-2 pt-4">
|
||||||
<Button onClick={handleSubmit} className="flex-1 bg-blue-600 hover:bg-blue-700">
|
<Button onClick={handleSubmit} className="flex-1 bg-blue-600 hover:bg-blue-700">
|
||||||
{job ? 'Update Job' : 'Create Job'}
|
{job ? 'Update Job' : 'Create Job'}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={onCancel}
|
onClick={onCancel}
|
||||||
className="border-gray-600 hover:bg-gray-800"
|
className="border-gray-600 hover:bg-gray-800"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -4,16 +4,19 @@ import { Button } from '@/components/ui/button';
|
|||||||
import { Textarea } from '@/components/ui/textarea';
|
import { Textarea } from '@/components/ui/textarea';
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { Import, Download, FileText } from 'lucide-react';
|
import { Import, Download, FileText } from 'lucide-react';
|
||||||
import { IndBillitemRecord } from '@/lib/pbtypes';
|
import { IndBillitemRecordNoId } from '@/lib/pbtypes';
|
||||||
|
import { addBillItem } from '@/services/billItemService';
|
||||||
|
|
||||||
interface MaterialsImportExportProps {
|
interface MaterialsImportExportProps {
|
||||||
billOfMaterials: IndBillitemRecord[];
|
jobId: string;
|
||||||
consumedMaterials: { name: string; required: number }[];
|
billOfMaterials: IndBillitemRecordNoId[];
|
||||||
onBillOfMaterialsUpdate: (materials: IndBillitemRecord[]) => void;
|
consumedMaterials: IndBillitemRecordNoId[];
|
||||||
onConsumedMaterialsUpdate: (materials: { name: string; required: number }[]) => void;
|
onBillOfMaterialsUpdate: (materials: IndBillitemRecordNoId[]) => void;
|
||||||
|
onConsumedMaterialsUpdate: (materials: IndBillitemRecordNoId[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const MaterialsImportExport: React.FC<MaterialsImportExportProps> = ({
|
const MaterialsImportExport: React.FC<MaterialsImportExportProps> = ({
|
||||||
|
jobId,
|
||||||
billOfMaterials,
|
billOfMaterials,
|
||||||
consumedMaterials,
|
consumedMaterials,
|
||||||
onBillOfMaterialsUpdate,
|
onBillOfMaterialsUpdate,
|
||||||
@@ -22,39 +25,37 @@ const MaterialsImportExport: React.FC<MaterialsImportExportProps> = ({
|
|||||||
const [bomInput, setBomInput] = useState('');
|
const [bomInput, setBomInput] = useState('');
|
||||||
const [consumedInput, setConsumedInput] = useState('');
|
const [consumedInput, setConsumedInput] = useState('');
|
||||||
|
|
||||||
const parseBillOfMaterials = (text: string): IndBillitemRecord[] => {
|
const parseBillOfMaterials = (text: string): IndBillitemRecordNoId[] => {
|
||||||
return text.split('\n')
|
const lines = text.split('\n').filter(line => line.trim());
|
||||||
.filter(line => line.trim())
|
const materials: IndBillitemRecordNoId[] = [];
|
||||||
.map(line => {
|
|
||||||
const parts = line.trim().split(/\s+/);
|
for (const line of lines) {
|
||||||
const quantity = parseInt(parts[parts.length - 1]);
|
const parts = line.trim().split(/\s+/);
|
||||||
|
if (parts.length >= 2) {
|
||||||
const name = parts.slice(0, -1).join(' ');
|
const name = parts.slice(0, -1).join(' ');
|
||||||
return {
|
const quantity = parseInt(parts[parts.length - 1]);
|
||||||
id: '',
|
if (name && !isNaN(quantity)) {
|
||||||
name,
|
materials.push({ name, quantity });
|
||||||
quantity,
|
}
|
||||||
created: undefined,
|
}
|
||||||
updated: undefined
|
}
|
||||||
};
|
return materials;
|
||||||
})
|
|
||||||
.filter(item => item.name && !isNaN(item.quantity));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const parseConsumedMaterials = (text: string): { name: string; required: number }[] => {
|
const parseConsumedMaterials = (text: string): IndBillitemRecordNoId[] => {
|
||||||
const lines = text.split('\n').filter(line => line.trim());
|
const lines = text.split('\n').filter(line => line.trim());
|
||||||
const materials: { name: string; required: number }[] = [];
|
const materials: IndBillitemRecordNoId[] = [];
|
||||||
|
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
const parts = line.trim().split('\t');
|
const parts = line.trim().split('\t');
|
||||||
if (parts.length >= 2) {
|
if (parts.length >= 2) {
|
||||||
const name = parts[0];
|
const name = parts[0];
|
||||||
const required = parseInt(parts[1]);
|
const quantity = parseInt(parts[1]);
|
||||||
if (name && !isNaN(required)) {
|
if (name && !isNaN(quantity)) {
|
||||||
materials.push({ name, required });
|
materials.push({ name, quantity });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return materials;
|
return materials;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -63,17 +64,17 @@ const MaterialsImportExport: React.FC<MaterialsImportExportProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const exportConsumedMaterials = (): string => {
|
const exportConsumedMaterials = (): string => {
|
||||||
return consumedMaterials.map(item => `${item.name}\t${item.required}`).join('\n');
|
return consumedMaterials.map(item => `${item.name}\t${item.quantity}`).join('\n');
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleImportBom = () => {
|
const handleImportBom = async () => {
|
||||||
const parsed = parseBillOfMaterials(bomInput);
|
const parsed = await parseBillOfMaterials(bomInput);
|
||||||
onBillOfMaterialsUpdate(parsed);
|
onBillOfMaterialsUpdate(parsed);
|
||||||
setBomInput('');
|
setBomInput('');
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleImportConsumed = () => {
|
const handleImportConsumed = async () => {
|
||||||
const parsed = parseConsumedMaterials(consumedInput);
|
const parsed = await parseConsumedMaterials(consumedInput);
|
||||||
onConsumedMaterialsUpdate(parsed);
|
onConsumedMaterialsUpdate(parsed);
|
||||||
setConsumedInput('');
|
setConsumedInput('');
|
||||||
};
|
};
|
||||||
@@ -171,7 +172,7 @@ const MaterialsImportExport: React.FC<MaterialsImportExportProps> = ({
|
|||||||
<Label className="text-gray-300">Current Consumed Materials:</Label>
|
<Label className="text-gray-300">Current Consumed Materials:</Label>
|
||||||
<div className="text-sm text-gray-400 max-h-32 overflow-y-auto">
|
<div className="text-sm text-gray-400 max-h-32 overflow-y-auto">
|
||||||
{consumedMaterials.map((item, index) => (
|
{consumedMaterials.map((item, index) => (
|
||||||
<div key={index}>{item.name}: {item.required.toLocaleString()}</div>
|
<div key={index}>{item.name}: {item.quantity.toLocaleString()}</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,30 +7,29 @@ import { Badge } from '@/components/ui/badge';
|
|||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
|
||||||
import { parseTransactionLine, formatISK } from '@/utils/priceUtils';
|
import { parseTransactionLine, formatISK } from '@/utils/priceUtils';
|
||||||
import { IndTransactionRecord } from '@/lib/pbtypes';
|
import { IndTransactionRecord, IndTransactionRecordNoId } from '@/lib/pbtypes';
|
||||||
import { Check, X } from 'lucide-react';
|
import { Check, X } from 'lucide-react';
|
||||||
|
|
||||||
interface TransactionFormProps {
|
interface TransactionFormProps {
|
||||||
jobId: string;
|
jobId: string;
|
||||||
onTransactionsAdded: (transactions: IndTransactionRecord[], type: 'expenditure' | 'income') => void;
|
onTransactionsAdded: (transactions: IndTransactionRecordNoId[], type: 'expenditure' | 'income') => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TransactionForm: React.FC<TransactionFormProps> = ({ jobId, onTransactionsAdded }) => {
|
const TransactionForm: React.FC<TransactionFormProps> = ({ jobId, onTransactionsAdded }) => {
|
||||||
const [pastedData, setPastedData] = useState('');
|
const [pastedData, setPastedData] = useState('');
|
||||||
const [parsedTransactions, setParsedTransactions] = useState<IndTransactionRecord[]>([]);
|
const [parsedTransactions, setParsedTransactions] = useState<IndTransactionRecordNoId[]>([]);
|
||||||
const [transactionType, setTransactionType] = useState<'expenditure' | 'income'>('expenditure');
|
const [transactionType, setTransactionType] = useState<'expenditure' | 'income'>('expenditure');
|
||||||
|
|
||||||
const handlePaste = (value: string) => {
|
const handlePaste = (value: string) => {
|
||||||
setPastedData(value);
|
setPastedData(value);
|
||||||
|
|
||||||
const lines = value.trim().split('\n');
|
const lines = value.trim().split('\n');
|
||||||
const transactions: IndTransactionRecord[] = [];
|
const transactions: IndTransactionRecordNoId[] = [];
|
||||||
|
|
||||||
lines.forEach((line, index) => {
|
lines.forEach((line, index) => {
|
||||||
const parsed = parseTransactionLine(line);
|
const parsed = parseTransactionLine(line);
|
||||||
if (parsed) {
|
if (parsed) {
|
||||||
transactions.push({
|
transactions.push({
|
||||||
id: `temp-${index}`,
|
|
||||||
date: parsed.date.toISOString(),
|
date: parsed.date.toISOString(),
|
||||||
quantity: parsed.quantity,
|
quantity: parsed.quantity,
|
||||||
itemName: parsed.itemName,
|
itemName: parsed.itemName,
|
||||||
@@ -40,13 +39,11 @@ const TransactionForm: React.FC<TransactionFormProps> = ({ jobId, onTransactions
|
|||||||
location: parsed.location,
|
location: parsed.location,
|
||||||
corporation: parsed.corporation,
|
corporation: parsed.corporation,
|
||||||
wallet: parsed.wallet,
|
wallet: parsed.wallet,
|
||||||
job: jobId,
|
job: jobId
|
||||||
created: undefined,
|
|
||||||
updated: undefined
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
setParsedTransactions(transactions);
|
setParsedTransactions(transactions);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -75,7 +72,7 @@ const TransactionForm: React.FC<TransactionFormProps> = ({ jobId, onTransactions
|
|||||||
Income
|
Income
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
|
|
||||||
<TabsContent value={transactionType} className="space-y-4">
|
<TabsContent value={transactionType} className="space-y-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="text-sm font-medium text-gray-300">
|
<label className="text-sm font-medium text-gray-300">
|
||||||
@@ -88,7 +85,7 @@ const TransactionForm: React.FC<TransactionFormProps> = ({ jobId, onTransactions
|
|||||||
className="min-h-32 bg-gray-800 border-gray-600 text-white"
|
className="min-h-32 bg-gray-800 border-gray-600 text-white"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{parsedTransactions.length > 0 && (
|
{parsedTransactions.length > 0 && (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
@@ -99,7 +96,7 @@ const TransactionForm: React.FC<TransactionFormProps> = ({ jobId, onTransactions
|
|||||||
Total: {formatISK(totalAmount)}
|
Total: {formatISK(totalAmount)}
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="max-h-64 overflow-y-auto">
|
<div className="max-h-64 overflow-y-auto">
|
||||||
<Table>
|
<Table>
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
@@ -132,17 +129,17 @@ const TransactionForm: React.FC<TransactionFormProps> = ({ jobId, onTransactions
|
|||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Button
|
<Button
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
className="flex-1 bg-blue-600 hover:bg-blue-700"
|
className="flex-1 bg-blue-600 hover:bg-blue-700"
|
||||||
>
|
>
|
||||||
<Check className="w-4 h-4 mr-2" />
|
<Check className="w-4 h-4 mr-2" />
|
||||||
Add Transactions
|
Add Transactions
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setPastedData('');
|
setPastedData('');
|
||||||
setParsedTransactions([]);
|
setParsedTransactions([]);
|
||||||
|
|||||||
@@ -1,19 +1,18 @@
|
|||||||
|
|
||||||
import React, { useState } from 'react';
|
import React, { useState } 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 { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { IndTransactionResponse } from '@/lib/pbtypes';
|
import { IndTransactionRecord } from '@/lib/pbtypes';
|
||||||
import { formatISK } from '@/utils/priceUtils';
|
import { formatISK } from '@/utils/priceUtils';
|
||||||
import { Edit, Save, X, Trash2 } from 'lucide-react';
|
import { Edit, Save, X, Trash2 } from 'lucide-react';
|
||||||
|
|
||||||
interface TransactionTableProps {
|
interface TransactionTableProps {
|
||||||
title: string;
|
title: string;
|
||||||
transactions: IndTransactionResponse[];
|
transactions: IndTransactionRecord[];
|
||||||
type: 'expenditure' | 'income';
|
type: 'expenditure' | 'income';
|
||||||
onUpdateTransaction: (transactionId: string, updates: Partial<IndTransactionResponse>) => void;
|
onUpdateTransaction: (transactionId: string, updates: Partial<IndTransactionRecord>) => void;
|
||||||
onDeleteTransaction: (transactionId: string) => void;
|
onDeleteTransaction: (transactionId: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,11 +24,11 @@ const TransactionTable: React.FC<TransactionTableProps> = ({
|
|||||||
onDeleteTransaction
|
onDeleteTransaction
|
||||||
}) => {
|
}) => {
|
||||||
const [editingId, setEditingId] = useState<string | null>(null);
|
const [editingId, setEditingId] = useState<string | null>(null);
|
||||||
const [editingTransaction, setEditingTransaction] = useState<IndTransactionResponse | null>(null);
|
const [editingTransaction, setEditingTransaction] = useState<IndTransactionRecord | null>(null);
|
||||||
|
|
||||||
const totalAmount = transactions.reduce((sum, tx) => sum + tx.totalPrice, 0);
|
const totalAmount = transactions.reduce((sum, tx) => sum + tx.totalPrice, 0);
|
||||||
|
|
||||||
const handleEdit = (transaction: IndTransactionResponse) => {
|
const handleEdit = (transaction: IndTransactionRecord) => {
|
||||||
setEditingId(transaction.id);
|
setEditingId(transaction.id);
|
||||||
setEditingTransaction({ ...transaction });
|
setEditingTransaction({ ...transaction });
|
||||||
};
|
};
|
||||||
@@ -53,7 +52,7 @@ const TransactionTable: React.FC<TransactionTableProps> = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateEditingField = (field: keyof IndTransactionResponse, value: any) => {
|
const updateEditingField = (field: keyof IndTransactionRecord, value: any) => {
|
||||||
if (editingTransaction) {
|
if (editingTransaction) {
|
||||||
setEditingTransaction({
|
setEditingTransaction({
|
||||||
...editingTransaction,
|
...editingTransaction,
|
||||||
@@ -102,9 +101,9 @@ const TransactionTable: React.FC<TransactionTableProps> = ({
|
|||||||
className="bg-gray-800 border-gray-600 text-white text-xs"
|
className="bg-gray-800 border-gray-600 text-white text-xs"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
new Date(transaction.date).toLocaleString('sv-SE', {
|
new Date(transaction.date).toLocaleString('sv-SE', {
|
||||||
year: 'numeric',
|
year: 'numeric',
|
||||||
month: '2-digit',
|
month: '2-digit',
|
||||||
day: '2-digit',
|
day: '2-digit',
|
||||||
hour: '2-digit',
|
hour: '2-digit',
|
||||||
minute: '2-digit'
|
minute: '2-digit'
|
||||||
|
|||||||
@@ -29,8 +29,8 @@ export type HTMLString = string
|
|||||||
|
|
||||||
type ExpandType<T> = unknown extends T
|
type ExpandType<T> = unknown extends T
|
||||||
? T extends unknown
|
? T extends unknown
|
||||||
? { expand?: unknown }
|
? { expand?: unknown }
|
||||||
: { expand: T }
|
: { expand: T }
|
||||||
: { expand: T }
|
: { expand: T }
|
||||||
|
|
||||||
// System fields
|
// System fields
|
||||||
@@ -105,6 +105,7 @@ export type IndBillitemRecord = {
|
|||||||
quantity: number
|
quantity: number
|
||||||
updated?: IsoDateString
|
updated?: IsoDateString
|
||||||
}
|
}
|
||||||
|
export type IndBillitemRecordNoId = Omit<IndBillitemRecord, 'id' | 'created' | 'updated'>
|
||||||
|
|
||||||
export type IndFacilityRecord = {
|
export type IndFacilityRecord = {
|
||||||
created?: IsoDateString
|
created?: IsoDateString
|
||||||
@@ -138,6 +139,7 @@ export type IndJobRecord = {
|
|||||||
status: IndJobStatusOptions
|
status: IndJobStatusOptions
|
||||||
updated?: IsoDateString
|
updated?: IsoDateString
|
||||||
}
|
}
|
||||||
|
export type IndJobRecordNoId = Omit<IndJobRecord, 'id' | 'created' | 'updated'>
|
||||||
|
|
||||||
export type IndTransactionRecord = {
|
export type IndTransactionRecord = {
|
||||||
buyer?: string
|
buyer?: string
|
||||||
@@ -154,6 +156,7 @@ export type IndTransactionRecord = {
|
|||||||
updated?: IsoDateString
|
updated?: IsoDateString
|
||||||
wallet?: string
|
wallet?: string
|
||||||
}
|
}
|
||||||
|
export type IndTransactionRecordNoId = Omit<IndTransactionRecord, 'id' | 'created' | 'updated'>
|
||||||
|
|
||||||
export type RegionviewRecord = {
|
export type RegionviewRecord = {
|
||||||
id: string
|
id: string
|
||||||
|
|||||||
@@ -1,28 +1,25 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { Plus, Factory, TrendingUp, Briefcase } from 'lucide-react';
|
import { Plus, Factory, TrendingUp, Briefcase } from 'lucide-react';
|
||||||
import { IndJobResponse, IndTransactionResponse, IndJobRecord, IndTransactionRecord } from '@/lib/pbtypes';
|
import { IndTransactionResponse, IndJobRecord, IndTransactionRecord, IndTransactionRecordNoId } from '@/lib/pbtypes';
|
||||||
import * as jobService from '@/services/jobService';
|
import * as jobService from '@/services/jobService';
|
||||||
|
import * as transactionService from '@/services/transactionService';
|
||||||
|
import * as billItemService from '@/services/billItemService';
|
||||||
import { formatISK } from '@/utils/priceUtils';
|
import { formatISK } from '@/utils/priceUtils';
|
||||||
import JobCard from '@/components/JobCard';
|
import JobCard from '@/components/JobCard';
|
||||||
import JobForm from '@/components/JobForm';
|
import JobForm from '@/components/JobForm';
|
||||||
import TransactionForm from '@/components/TransactionForm';
|
import TransactionForm from '@/components/TransactionForm';
|
||||||
import TransactionTable from '@/components/TransactionTable';
|
import TransactionTable from '@/components/TransactionTable';
|
||||||
|
import { IndJob } from '@/lib/types';
|
||||||
|
import { createJob } from '@/services/jobService';
|
||||||
|
|
||||||
// Extended job type for UI components
|
// Extended job type for UI components
|
||||||
interface JobWithRelations extends IndJobResponse {
|
|
||||||
expenditures: IndTransactionResponse[];
|
|
||||||
income: IndTransactionResponse[];
|
|
||||||
billOfMaterials: any[];
|
|
||||||
consumedMaterials: { name: string; required: number }[];
|
|
||||||
}
|
|
||||||
|
|
||||||
const Index = () => {
|
const Index = () => {
|
||||||
const [jobs, setJobs] = useState<JobWithRelations[]>([]);
|
const [jobs, setJobs] = useState<IndJob[]>([]);
|
||||||
const [showJobForm, setShowJobForm] = useState(false);
|
const [showJobForm, setShowJobForm] = useState(false);
|
||||||
const [editingJob, setEditingJob] = useState<JobWithRelations | null>(null);
|
const [editingJob, setEditingJob] = useState<IndJob | null>(null);
|
||||||
const [selectedJob, setSelectedJob] = useState<JobWithRelations | null>(null);
|
const [selectedJob, setSelectedJob] = useState<IndJob | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadJobs();
|
loadJobs();
|
||||||
@@ -30,15 +27,9 @@ const Index = () => {
|
|||||||
|
|
||||||
const loadJobs = async () => {
|
const loadJobs = async () => {
|
||||||
try {
|
try {
|
||||||
const fetchedJobs = await jobService.getJobs();
|
const fetchedJobs = await jobService.getJobsFull();
|
||||||
// Convert to JobWithRelations format
|
// Convert to JobWithRelations format
|
||||||
const jobsWithRelations: JobWithRelations[] = fetchedJobs.map(job => ({
|
const jobsWithRelations: IndJob[] = fetchedJobs;
|
||||||
...job,
|
|
||||||
expenditures: [],
|
|
||||||
income: [],
|
|
||||||
billOfMaterials: [],
|
|
||||||
consumedMaterials: []
|
|
||||||
}));
|
|
||||||
setJobs(jobsWithRelations);
|
setJobs(jobsWithRelations);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading jobs:', error);
|
console.error('Error loading jobs:', error);
|
||||||
@@ -47,8 +38,8 @@ const Index = () => {
|
|||||||
|
|
||||||
const handleCreateJob = async (jobData: Omit<IndJobRecord, 'id' | 'created' | 'updated'>) => {
|
const handleCreateJob = async (jobData: Omit<IndJobRecord, 'id' | 'created' | 'updated'>) => {
|
||||||
try {
|
try {
|
||||||
const newJob = await jobService.createJob(jobData);
|
const newJob = await createJob(jobData);
|
||||||
const jobWithRelations: JobWithRelations = {
|
const jobWithRelations: IndJob = {
|
||||||
...newJob,
|
...newJob,
|
||||||
expenditures: [],
|
expenditures: [],
|
||||||
income: [],
|
income: [],
|
||||||
@@ -62,28 +53,28 @@ const Index = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEditJob = (job: JobWithRelations) => {
|
const handleEditJob = (job: IndJob) => {
|
||||||
setEditingJob(job);
|
setEditingJob(job);
|
||||||
setShowJobForm(true);
|
setShowJobForm(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUpdateJob = async (jobData: Omit<IndJobRecord, 'id' | 'created' | 'updated'>) => {
|
const handleUpdateJob = async (jobData: Omit<IndJobRecord, 'id' | 'created' | 'updated'>) => {
|
||||||
if (!editingJob) return;
|
if (!editingJob) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const updatedJob = await jobService.updateJob(editingJob.id, jobData);
|
const updatedJob = await jobService.updateJob(editingJob.id, jobData);
|
||||||
const updatedJobWithRelations: JobWithRelations = {
|
const updatedJobWithRelations: IndJob = {
|
||||||
...updatedJob,
|
...updatedJob,
|
||||||
expenditures: editingJob.expenditures,
|
expenditures: editingJob.expenditures,
|
||||||
income: editingJob.income,
|
income: editingJob.income,
|
||||||
billOfMaterials: editingJob.billOfMaterials,
|
billOfMaterials: editingJob.billOfMaterials,
|
||||||
consumedMaterials: editingJob.consumedMaterials
|
consumedMaterials: editingJob.consumedMaterials
|
||||||
};
|
};
|
||||||
|
|
||||||
setJobs(jobs.map(job => job.id === editingJob.id ? updatedJobWithRelations : job));
|
setJobs(jobs.map(job => job.id === editingJob.id ? updatedJobWithRelations : job));
|
||||||
setShowJobForm(false);
|
setShowJobForm(false);
|
||||||
setEditingJob(null);
|
setEditingJob(null);
|
||||||
|
|
||||||
if (selectedJob?.id === editingJob.id) {
|
if (selectedJob?.id === editingJob.id) {
|
||||||
setSelectedJob(updatedJobWithRelations);
|
setSelectedJob(updatedJobWithRelations);
|
||||||
}
|
}
|
||||||
@@ -106,24 +97,16 @@ const Index = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleTransactionsAdded = async (transactions: IndTransactionRecord[], type: 'expenditure' | 'income') => {
|
const handleTransactionsAdded = async (transactions: IndTransactionRecordNoId[], type: 'expenditure' | 'income') => {
|
||||||
if (!selectedJob) return;
|
if (!selectedJob) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
let updatedJob = selectedJob;
|
||||||
for (const transaction of transactions) {
|
for (const transaction of transactions) {
|
||||||
await jobService.addTransaction(selectedJob.id, transaction, type);
|
updatedJob = await transactionService.createTransaction(updatedJob, transaction, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update local state
|
// Update local state
|
||||||
const updatedJob = { ...selectedJob };
|
|
||||||
const newTransactions = transactions as unknown as IndTransactionResponse[];
|
|
||||||
|
|
||||||
if (type === 'expenditure') {
|
|
||||||
updatedJob.expenditures = [...updatedJob.expenditures, ...newTransactions];
|
|
||||||
} else {
|
|
||||||
updatedJob.income = [...updatedJob.income, ...newTransactions];
|
|
||||||
}
|
|
||||||
|
|
||||||
setSelectedJob(updatedJob);
|
setSelectedJob(updatedJob);
|
||||||
setJobs(jobs.map(job => job.id === selectedJob.id ? updatedJob : job));
|
setJobs(jobs.map(job => job.id === selectedJob.id ? updatedJob : job));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -135,17 +118,9 @@ const Index = () => {
|
|||||||
if (!selectedJob) return;
|
if (!selectedJob) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await jobService.updateTransaction(selectedJob.id, transactionId, updates);
|
let updatedJob = selectedJob;
|
||||||
|
updatedJob = await transactionService.updateTransaction(updatedJob, transactionId, updates);
|
||||||
// Update local state
|
// Update local state
|
||||||
const updatedJob = { ...selectedJob };
|
|
||||||
updatedJob.expenditures = updatedJob.expenditures.map(tx =>
|
|
||||||
tx.id === transactionId ? { ...tx, ...updates } : tx
|
|
||||||
);
|
|
||||||
updatedJob.income = updatedJob.income.map(tx =>
|
|
||||||
tx.id === transactionId ? { ...tx, ...updates } : tx
|
|
||||||
);
|
|
||||||
|
|
||||||
setSelectedJob(updatedJob);
|
setSelectedJob(updatedJob);
|
||||||
setJobs(jobs.map(job => job.id === selectedJob.id ? updatedJob : job));
|
setJobs(jobs.map(job => job.id === selectedJob.id ? updatedJob : job));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -157,13 +132,13 @@ const Index = () => {
|
|||||||
if (!selectedJob) return;
|
if (!selectedJob) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await jobService.deleteTransaction(selectedJob.id, transactionId);
|
await transactionService.deleteTransaction(selectedJob, transactionId);
|
||||||
|
|
||||||
// Update local state
|
// Update local state
|
||||||
const updatedJob = { ...selectedJob };
|
const updatedJob = { ...selectedJob };
|
||||||
updatedJob.expenditures = updatedJob.expenditures.filter(tx => tx.id !== transactionId);
|
updatedJob.expenditures = updatedJob.expenditures.filter(tx => tx.id !== transactionId);
|
||||||
updatedJob.income = updatedJob.income.filter(tx => tx.id !== transactionId);
|
updatedJob.income = updatedJob.income.filter(tx => tx.id !== transactionId);
|
||||||
|
|
||||||
setSelectedJob(updatedJob);
|
setSelectedJob(updatedJob);
|
||||||
setJobs(jobs.map(job => job.id === selectedJob.id ? updatedJob : job));
|
setJobs(jobs.map(job => job.id === selectedJob.id ? updatedJob : job));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -178,7 +153,7 @@ const Index = () => {
|
|||||||
return sum + (income - expenditure);
|
return sum + (income - expenditure);
|
||||||
}, 0);
|
}, 0);
|
||||||
|
|
||||||
const totalRevenue = jobs.reduce((sum, job) =>
|
const totalRevenue = jobs.reduce((sum, job) =>
|
||||||
sum + job.income.reduce((sum, tx) => sum + tx.totalPrice, 0), 0
|
sum + job.income.reduce((sum, tx) => sum + tx.totalPrice, 0), 0
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export async function createTransaction(
|
|||||||
job: IndJob,
|
job: IndJob,
|
||||||
transaction: IndTransactionRecordNoId,
|
transaction: IndTransactionRecordNoId,
|
||||||
type: 'expenditure' | 'income'
|
type: 'expenditure' | 'income'
|
||||||
): Promise<void> {
|
): Promise<IndJob> {
|
||||||
console.log('Creating transaction:', transaction);
|
console.log('Creating transaction:', transaction);
|
||||||
// Create the transaction
|
// Create the transaction
|
||||||
transaction.job = job.id;
|
transaction.job = job.id;
|
||||||
@@ -15,19 +15,30 @@ export async function createTransaction(
|
|||||||
|
|
||||||
// Update the job to include the new transaction
|
// Update the job to include the new transaction
|
||||||
const field = type === 'expenditure' ? 'expenditures' : 'income';
|
const field = type === 'expenditure' ? 'expenditures' : 'income';
|
||||||
const currentIds = job[field] || [];
|
const currentIds = (job[field] || []).map(tr => tr.id);
|
||||||
|
|
||||||
await updateJob(job.id, {
|
await updateJob(job.id, {
|
||||||
[field]: [...currentIds, createdTransaction.id]
|
[field]: [...currentIds, createdTransaction.id]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (type === 'expenditure') {
|
||||||
|
job.expenditures.push(createdTransaction);
|
||||||
|
} else {
|
||||||
|
job.income.push(createdTransaction);
|
||||||
|
}
|
||||||
|
return job;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateTransaction(
|
export async function updateTransaction(
|
||||||
|
job: IndJob,
|
||||||
transactionId: string,
|
transactionId: string,
|
||||||
updates: Partial<IndTransactionRecord>
|
updates: Partial<IndTransactionRecord>
|
||||||
): Promise<void> {
|
): Promise<IndJob> {
|
||||||
console.log('Updating transaction:', transactionId, updates);
|
console.log('Updating transaction:', transactionId, updates);
|
||||||
await pb.collection<IndTransactionRecord>('ind_transaction').update(transactionId, updates);
|
const updatedTransaction = await pb.collection<IndTransactionRecord>('ind_transaction').update(transactionId, updates);
|
||||||
|
job.expenditures = job.expenditures.map(exp => exp.id === transactionId ? updatedTransaction : exp);
|
||||||
|
job.income = job.income.map(inc => inc.id === transactionId ? updatedTransaction : inc);
|
||||||
|
return job;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteTransaction(job: IndJob, transactionId: string): Promise<void> {
|
export async function deleteTransaction(job: IndJob, transactionId: string): Promise<void> {
|
||||||
|
|||||||
Reference in New Issue
Block a user