Make the rest of the application use "new" services

This commit is contained in:
2025-07-04 16:19:26 +02:00
parent 70d6979b47
commit 83fa902b4c
8 changed files with 159 additions and 179 deletions

View File

@@ -5,16 +5,11 @@ import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { HoverCard, HoverCardContent, HoverCardTrigger } from '@/components/ui/hover-card';
import { Calendar, Factory, TrendingUp, TrendingDown, Clock, Package, Wrench } from 'lucide-react';
import { IndJobResponse, IndTransactionResponse, IndBillitemResponse } from '@/lib/pbtypes';
import { formatISK } from '@/utils/priceUtils';
import { IndJob } from '@/lib/types';
interface JobCardProps {
job: IndJobResponse & {
expenditures: IndTransactionResponse[];
income: IndTransactionResponse[];
billOfMaterials: IndBillitemResponse[];
consumedMaterials: { name: string; required: number }[];
};
job: IndJob;
onEdit: (job: any) => 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 profit = totalIncome - totalExpenditure;
const margin = totalIncome > 0 ? ((profit / totalIncome) * 100) : 0;
const itemsSold = job.income.reduce((sum, tx) => sum + tx.quantity, 0);
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;
@@ -44,9 +39,9 @@ const JobCard: React.FC<JobCardProps> = ({ job, onEdit, onDelete }) => {
const formatDateTime = (dateString: string | null | undefined) => {
if (!dateString) return 'Not set';
return new Date(dateString).toLocaleString('en-CA', {
year: 'numeric',
month: '2-digit',
return new Date(dateString).toLocaleString('en-CA', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
@@ -99,7 +94,7 @@ const JobCard: React.FC<JobCardProps> = ({ job, onEdit, onDelete }) => {
{job.consumedMaterials.map((item, index) => (
<div key={index} className="flex justify-between">
<span>{item.name}</span>
<span className="text-gray-300">{item.required.toLocaleString()}</span>
<span className="text-gray-300">{item.quantity.toLocaleString()}</span>
</div>
))}
</div>
@@ -111,17 +106,17 @@ const JobCard: React.FC<JobCardProps> = ({ job, onEdit, onDelete }) => {
<p className="text-gray-400">Quantity: {job.outputQuantity.toLocaleString()}</p>
</div>
<div className="flex gap-2">
<Button
variant="outline"
size="sm"
<Button
variant="outline"
size="sm"
onClick={() => onEdit(job)}
className="border-gray-600 hover:bg-gray-800"
>
Edit
</Button>
<Button
variant="destructive"
size="sm"
<Button
variant="destructive"
size="sm"
onClick={() => onDelete(job.id)}
>
Delete
@@ -144,7 +139,7 @@ const JobCard: React.FC<JobCardProps> = ({ job, onEdit, onDelete }) => {
Job ID: {job.id}
</div>
</div>
{job.saleStart && (
<div className="space-y-2">
<div className="text-sm text-gray-400">
@@ -157,7 +152,7 @@ const JobCard: React.FC<JobCardProps> = ({ job, onEdit, onDelete }) => {
)}
</div>
)}
<div className="grid grid-cols-3 gap-4 pt-4 border-t border-gray-700">
<div className="text-center">
<div className="flex items-center justify-center gap-1 text-red-400">

View File

@@ -6,23 +6,21 @@ 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 { IndJobRecord, IndFacilityResponse, IndBillitemRecord, IndJobStatusOptions } from '@/lib/pbtypes';
import { facilityService } from '@/services/facilityService';
import { IndBillitemRecordNoId, IndJobStatusOptions, IndJobRecordNoId, IndFacilityRecord } from '@/lib/pbtypes';
import MaterialsImportExport from './MaterialsImportExport';
import { IndJob } from '@/lib/types';
// import { getFacilities } from '@/services/facilityService';
interface JobFormProps {
job?: IndJobRecord & {
billOfMaterials?: IndBillitemRecord[];
consumedMaterials?: { name: string; required: number }[];
};
onSubmit: (job: Omit<IndJobRecord, 'id' | 'created' | 'updated'>) => void;
job?: IndJob;
onSubmit: (job: IndJobRecordNoId) => void;
onCancel: () => void;
}
const JobForm: React.FC<JobFormProps> = ({ job, onSubmit, onCancel }) => {
const [facilities, setFacilities] = useState<IndFacilityResponse[]>([]);
const [billOfMaterials, setBillOfMaterials] = useState<IndBillitemRecord[]>(job?.billOfMaterials || []);
const [consumedMaterials, setConsumedMaterials] = useState<{ name: string; required: number }[]>(job?.consumedMaterials || []);
// const [facilities, setFacilities] = useState<IndFacilityRecord[]>([]);
const [billOfMaterials, setBillOfMaterials] = useState<IndBillitemRecordNoId[]>(job?.billOfMaterials || []);
const [consumedMaterials, setConsumedMaterials] = useState<IndBillitemRecordNoId[]>(job?.consumedMaterials || []);
const [formData, setFormData] = useState({
outputItem: job?.outputItem || '',
outputQuantity: job?.outputQuantity || 0,
@@ -37,22 +35,22 @@ const JobForm: React.FC<JobFormProps> = ({ job, onSubmit, onCancel }) => {
income: job?.income || []
});
useEffect(() => {
const loadFacilities = async () => {
try {
const fetchedFacilities = await facilityService.getFacilities();
setFacilities(fetchedFacilities);
} catch (error) {
console.error('Error loading facilities:', error);
}
};
loadFacilities();
}, []);
// useEffect(() => {
// const loadFacilities = async () => {
// try {
// const fetchedFacilities = await getFacilities();
// setFacilities(fetchedFacilities);
// } catch (error) {
// console.error('Error loading facilities:', error);
// }
// };
// loadFacilities();
// }, []);
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
onSubmit({
outputItem: formData.outputItem,
outputQuantity: formData.outputQuantity,
@@ -61,10 +59,10 @@ const JobForm: React.FC<JobFormProps> = ({ job, onSubmit, onCancel }) => {
saleStart: formData.saleStart ? formData.saleStart : undefined,
saleEnd: formData.saleEnd ? formData.saleEnd : undefined,
status: formData.status,
billOfMaterials: formData.billOfMaterials,
consumedMaterials: formData.consumedMaterials,
expenditures: formData.expenditures,
income: formData.income
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)
});
};
@@ -83,7 +81,7 @@ const JobForm: React.FC<JobFormProps> = ({ job, onSubmit, onCancel }) => {
<TabsTrigger value="basic" className="text-white">Basic Info</TabsTrigger>
<TabsTrigger value="materials" className="text-white">Materials</TabsTrigger>
</TabsList>
<TabsContent value="basic" className="space-y-4">
<form onSubmit={handleSubmit} className="space-y-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">
<Label htmlFor="status" className="text-gray-300">Status</Label>
<Select
value={formData.status}
<Select
value={formData.status}
onValueChange={(value) => setFormData({ ...formData, status: value as IndJobStatusOptions })}
>
<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">
{job ? 'Update Job' : 'Create Job'}
</Button>
<Button
type="button"
variant="outline"
<Button
type="button"
variant="outline"
onClick={onCancel}
className="border-gray-600 hover:bg-gray-800"
>
@@ -208,22 +206,23 @@ const JobForm: React.FC<JobFormProps> = ({ job, onSubmit, onCancel }) => {
</div>
</form>
</TabsContent>
<TabsContent value="materials" className="space-y-4">
<MaterialsImportExport
jobId={job?.id || ''}
billOfMaterials={billOfMaterials}
consumedMaterials={consumedMaterials}
onBillOfMaterialsUpdate={setBillOfMaterials}
onConsumedMaterialsUpdate={setConsumedMaterials}
/>
<div className="flex gap-2 pt-4">
<Button onClick={handleSubmit} className="flex-1 bg-blue-600 hover:bg-blue-700">
{job ? 'Update Job' : 'Create Job'}
</Button>
<Button
type="button"
variant="outline"
<Button
type="button"
variant="outline"
onClick={onCancel}
className="border-gray-600 hover:bg-gray-800"
>

View File

@@ -4,16 +4,19 @@ import { Button } from '@/components/ui/button';
import { Textarea } from '@/components/ui/textarea';
import { Label } from '@/components/ui/label';
import { Import, Download, FileText } from 'lucide-react';
import { IndBillitemRecord } from '@/lib/pbtypes';
import { IndBillitemRecordNoId } from '@/lib/pbtypes';
import { addBillItem } from '@/services/billItemService';
interface MaterialsImportExportProps {
billOfMaterials: IndBillitemRecord[];
consumedMaterials: { name: string; required: number }[];
onBillOfMaterialsUpdate: (materials: IndBillitemRecord[]) => void;
onConsumedMaterialsUpdate: (materials: { name: string; required: number }[]) => void;
jobId: string;
billOfMaterials: IndBillitemRecordNoId[];
consumedMaterials: IndBillitemRecordNoId[];
onBillOfMaterialsUpdate: (materials: IndBillitemRecordNoId[]) => void;
onConsumedMaterialsUpdate: (materials: IndBillitemRecordNoId[]) => void;
}
const MaterialsImportExport: React.FC<MaterialsImportExportProps> = ({
jobId,
billOfMaterials,
consumedMaterials,
onBillOfMaterialsUpdate,
@@ -22,39 +25,37 @@ const MaterialsImportExport: React.FC<MaterialsImportExportProps> = ({
const [bomInput, setBomInput] = useState('');
const [consumedInput, setConsumedInput] = useState('');
const parseBillOfMaterials = (text: string): IndBillitemRecord[] => {
return text.split('\n')
.filter(line => line.trim())
.map(line => {
const parts = line.trim().split(/\s+/);
const quantity = parseInt(parts[parts.length - 1]);
const parseBillOfMaterials = (text: string): IndBillitemRecordNoId[] => {
const lines = text.split('\n').filter(line => line.trim());
const materials: IndBillitemRecordNoId[] = [];
for (const line of lines) {
const parts = line.trim().split(/\s+/);
if (parts.length >= 2) {
const name = parts.slice(0, -1).join(' ');
return {
id: '',
name,
quantity,
created: undefined,
updated: undefined
};
})
.filter(item => item.name && !isNaN(item.quantity));
const quantity = parseInt(parts[parts.length - 1]);
if (name && !isNaN(quantity)) {
materials.push({ name, quantity });
}
}
}
return materials;
};
const parseConsumedMaterials = (text: string): { name: string; required: number }[] => {
const parseConsumedMaterials = (text: string): IndBillitemRecordNoId[] => {
const lines = text.split('\n').filter(line => line.trim());
const materials: { name: string; required: number }[] = [];
const materials: IndBillitemRecordNoId[] = [];
for (const line of lines) {
const parts = line.trim().split('\t');
if (parts.length >= 2) {
const name = parts[0];
const required = parseInt(parts[1]);
if (name && !isNaN(required)) {
materials.push({ name, required });
const quantity = parseInt(parts[1]);
if (name && !isNaN(quantity)) {
materials.push({ name, quantity });
}
}
}
return materials;
};
@@ -63,17 +64,17 @@ const MaterialsImportExport: React.FC<MaterialsImportExportProps> = ({
};
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 parsed = parseBillOfMaterials(bomInput);
const handleImportBom = async () => {
const parsed = await parseBillOfMaterials(bomInput);
onBillOfMaterialsUpdate(parsed);
setBomInput('');
};
const handleImportConsumed = () => {
const parsed = parseConsumedMaterials(consumedInput);
const handleImportConsumed = async () => {
const parsed = await parseConsumedMaterials(consumedInput);
onConsumedMaterialsUpdate(parsed);
setConsumedInput('');
};
@@ -171,7 +172,7 @@ const MaterialsImportExport: React.FC<MaterialsImportExportProps> = ({
<Label className="text-gray-300">Current Consumed Materials:</Label>
<div className="text-sm text-gray-400 max-h-32 overflow-y-auto">
{consumedMaterials.map((item, index) => (
<div key={index}>{item.name}: {item.required.toLocaleString()}</div>
<div key={index}>{item.name}: {item.quantity.toLocaleString()}</div>
))}
</div>
</div>

View File

@@ -7,30 +7,29 @@ import { Badge } from '@/components/ui/badge';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
import { parseTransactionLine, formatISK } from '@/utils/priceUtils';
import { IndTransactionRecord } from '@/lib/pbtypes';
import { IndTransactionRecord, IndTransactionRecordNoId } from '@/lib/pbtypes';
import { Check, X } from 'lucide-react';
interface TransactionFormProps {
jobId: string;
onTransactionsAdded: (transactions: IndTransactionRecord[], type: 'expenditure' | 'income') => void;
onTransactionsAdded: (transactions: IndTransactionRecordNoId[], type: 'expenditure' | 'income') => void;
}
const TransactionForm: React.FC<TransactionFormProps> = ({ jobId, onTransactionsAdded }) => {
const [pastedData, setPastedData] = useState('');
const [parsedTransactions, setParsedTransactions] = useState<IndTransactionRecord[]>([]);
const [parsedTransactions, setParsedTransactions] = useState<IndTransactionRecordNoId[]>([]);
const [transactionType, setTransactionType] = useState<'expenditure' | 'income'>('expenditure');
const handlePaste = (value: string) => {
setPastedData(value);
const lines = value.trim().split('\n');
const transactions: IndTransactionRecord[] = [];
const transactions: IndTransactionRecordNoId[] = [];
lines.forEach((line, index) => {
const parsed = parseTransactionLine(line);
if (parsed) {
transactions.push({
id: `temp-${index}`,
date: parsed.date.toISOString(),
quantity: parsed.quantity,
itemName: parsed.itemName,
@@ -40,13 +39,11 @@ const TransactionForm: React.FC<TransactionFormProps> = ({ jobId, onTransactions
location: parsed.location,
corporation: parsed.corporation,
wallet: parsed.wallet,
job: jobId,
created: undefined,
updated: undefined
job: jobId
});
}
});
setParsedTransactions(transactions);
};
@@ -75,7 +72,7 @@ const TransactionForm: React.FC<TransactionFormProps> = ({ jobId, onTransactions
Income
</TabsTrigger>
</TabsList>
<TabsContent value={transactionType} className="space-y-4">
<div className="space-y-2">
<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"
/>
</div>
{parsedTransactions.length > 0 && (
<div className="space-y-4">
<div className="flex items-center justify-between">
@@ -99,7 +96,7 @@ const TransactionForm: React.FC<TransactionFormProps> = ({ jobId, onTransactions
Total: {formatISK(totalAmount)}
</Badge>
</div>
<div className="max-h-64 overflow-y-auto">
<Table>
<TableHeader>
@@ -132,17 +129,17 @@ const TransactionForm: React.FC<TransactionFormProps> = ({ jobId, onTransactions
</TableBody>
</Table>
</div>
<div className="flex gap-2">
<Button
<Button
onClick={handleSubmit}
className="flex-1 bg-blue-600 hover:bg-blue-700"
>
<Check className="w-4 h-4 mr-2" />
Add Transactions
</Button>
<Button
variant="outline"
<Button
variant="outline"
onClick={() => {
setPastedData('');
setParsedTransactions([]);

View File

@@ -1,19 +1,18 @@
import React, { useState } from 'react';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
import { Badge } from '@/components/ui/badge';
import { Input } from '@/components/ui/input';
import { IndTransactionResponse } from '@/lib/pbtypes';
import { IndTransactionRecord } from '@/lib/pbtypes';
import { formatISK } from '@/utils/priceUtils';
import { Edit, Save, X, Trash2 } from 'lucide-react';
interface TransactionTableProps {
title: string;
transactions: IndTransactionResponse[];
transactions: IndTransactionRecord[];
type: 'expenditure' | 'income';
onUpdateTransaction: (transactionId: string, updates: Partial<IndTransactionResponse>) => void;
onUpdateTransaction: (transactionId: string, updates: Partial<IndTransactionRecord>) => void;
onDeleteTransaction: (transactionId: string) => void;
}
@@ -25,11 +24,11 @@ const TransactionTable: React.FC<TransactionTableProps> = ({
onDeleteTransaction
}) => {
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 handleEdit = (transaction: IndTransactionResponse) => {
const handleEdit = (transaction: IndTransactionRecord) => {
setEditingId(transaction.id);
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) {
setEditingTransaction({
...editingTransaction,
@@ -102,9 +101,9 @@ const TransactionTable: React.FC<TransactionTableProps> = ({
className="bg-gray-800 border-gray-600 text-white text-xs"
/>
) : (
new Date(transaction.date).toLocaleString('sv-SE', {
year: 'numeric',
month: '2-digit',
new Date(transaction.date).toLocaleString('sv-SE', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'

View File

@@ -29,8 +29,8 @@ export type HTMLString = string
type ExpandType<T> = unknown extends T
? T extends unknown
? { expand?: unknown }
: { expand: T }
? { expand?: unknown }
: { expand: T }
: { expand: T }
// System fields
@@ -105,6 +105,7 @@ export type IndBillitemRecord = {
quantity: number
updated?: IsoDateString
}
export type IndBillitemRecordNoId = Omit<IndBillitemRecord, 'id' | 'created' | 'updated'>
export type IndFacilityRecord = {
created?: IsoDateString
@@ -138,6 +139,7 @@ export type IndJobRecord = {
status: IndJobStatusOptions
updated?: IsoDateString
}
export type IndJobRecordNoId = Omit<IndJobRecord, 'id' | 'created' | 'updated'>
export type IndTransactionRecord = {
buyer?: string
@@ -154,6 +156,7 @@ export type IndTransactionRecord = {
updated?: IsoDateString
wallet?: string
}
export type IndTransactionRecordNoId = Omit<IndTransactionRecord, 'id' | 'created' | 'updated'>
export type RegionviewRecord = {
id: string

View File

@@ -1,28 +1,25 @@
import React, { useState, useEffect } from 'react';
import { useState, useEffect } from 'react';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
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 transactionService from '@/services/transactionService';
import * as billItemService from '@/services/billItemService';
import { formatISK } from '@/utils/priceUtils';
import JobCard from '@/components/JobCard';
import JobForm from '@/components/JobForm';
import TransactionForm from '@/components/TransactionForm';
import TransactionTable from '@/components/TransactionTable';
import { IndJob } from '@/lib/types';
import { createJob } from '@/services/jobService';
// Extended job type for UI components
interface JobWithRelations extends IndJobResponse {
expenditures: IndTransactionResponse[];
income: IndTransactionResponse[];
billOfMaterials: any[];
consumedMaterials: { name: string; required: number }[];
}
const Index = () => {
const [jobs, setJobs] = useState<JobWithRelations[]>([]);
const [jobs, setJobs] = useState<IndJob[]>([]);
const [showJobForm, setShowJobForm] = useState(false);
const [editingJob, setEditingJob] = useState<JobWithRelations | null>(null);
const [selectedJob, setSelectedJob] = useState<JobWithRelations | null>(null);
const [editingJob, setEditingJob] = useState<IndJob | null>(null);
const [selectedJob, setSelectedJob] = useState<IndJob | null>(null);
useEffect(() => {
loadJobs();
@@ -30,15 +27,9 @@ const Index = () => {
const loadJobs = async () => {
try {
const fetchedJobs = await jobService.getJobs();
const fetchedJobs = await jobService.getJobsFull();
// Convert to JobWithRelations format
const jobsWithRelations: JobWithRelations[] = fetchedJobs.map(job => ({
...job,
expenditures: [],
income: [],
billOfMaterials: [],
consumedMaterials: []
}));
const jobsWithRelations: IndJob[] = fetchedJobs;
setJobs(jobsWithRelations);
} catch (error) {
console.error('Error loading jobs:', error);
@@ -47,8 +38,8 @@ const Index = () => {
const handleCreateJob = async (jobData: Omit<IndJobRecord, 'id' | 'created' | 'updated'>) => {
try {
const newJob = await jobService.createJob(jobData);
const jobWithRelations: JobWithRelations = {
const newJob = await createJob(jobData);
const jobWithRelations: IndJob = {
...newJob,
expenditures: [],
income: [],
@@ -62,28 +53,28 @@ const Index = () => {
}
};
const handleEditJob = (job: JobWithRelations) => {
const handleEditJob = (job: IndJob) => {
setEditingJob(job);
setShowJobForm(true);
};
const handleUpdateJob = async (jobData: Omit<IndJobRecord, 'id' | 'created' | 'updated'>) => {
if (!editingJob) return;
try {
const updatedJob = await jobService.updateJob(editingJob.id, jobData);
const updatedJobWithRelations: JobWithRelations = {
const updatedJobWithRelations: IndJob = {
...updatedJob,
expenditures: editingJob.expenditures,
income: editingJob.income,
billOfMaterials: editingJob.billOfMaterials,
consumedMaterials: editingJob.consumedMaterials
};
setJobs(jobs.map(job => job.id === editingJob.id ? updatedJobWithRelations : job));
setShowJobForm(false);
setEditingJob(null);
if (selectedJob?.id === editingJob.id) {
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;
try {
let updatedJob = selectedJob;
for (const transaction of transactions) {
await jobService.addTransaction(selectedJob.id, transaction, type);
updatedJob = await transactionService.createTransaction(updatedJob, transaction, type);
}
// 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);
setJobs(jobs.map(job => job.id === selectedJob.id ? updatedJob : job));
} catch (error) {
@@ -135,17 +118,9 @@ const Index = () => {
if (!selectedJob) return;
try {
await jobService.updateTransaction(selectedJob.id, transactionId, updates);
let updatedJob = selectedJob;
updatedJob = await transactionService.updateTransaction(updatedJob, transactionId, updates);
// 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);
setJobs(jobs.map(job => job.id === selectedJob.id ? updatedJob : job));
} catch (error) {
@@ -157,13 +132,13 @@ const Index = () => {
if (!selectedJob) return;
try {
await jobService.deleteTransaction(selectedJob.id, transactionId);
await transactionService.deleteTransaction(selectedJob, transactionId);
// Update local state
const updatedJob = { ...selectedJob };
updatedJob.expenditures = updatedJob.expenditures.filter(tx => tx.id !== transactionId);
updatedJob.income = updatedJob.income.filter(tx => tx.id !== transactionId);
setSelectedJob(updatedJob);
setJobs(jobs.map(job => job.id === selectedJob.id ? updatedJob : job));
} catch (error) {
@@ -178,7 +153,7 @@ const Index = () => {
return sum + (income - expenditure);
}, 0);
const totalRevenue = jobs.reduce((sum, job) =>
const totalRevenue = jobs.reduce((sum, job) =>
sum + job.income.reduce((sum, tx) => sum + tx.totalPrice, 0), 0
);

View File

@@ -7,7 +7,7 @@ export async function createTransaction(
job: IndJob,
transaction: IndTransactionRecordNoId,
type: 'expenditure' | 'income'
): Promise<void> {
): Promise<IndJob> {
console.log('Creating transaction:', transaction);
// Create the transaction
transaction.job = job.id;
@@ -15,19 +15,30 @@ export async function createTransaction(
// Update the job to include the new transaction
const field = type === 'expenditure' ? 'expenditures' : 'income';
const currentIds = job[field] || [];
const currentIds = (job[field] || []).map(tr => tr.id);
await updateJob(job.id, {
[field]: [...currentIds, createdTransaction.id]
});
if (type === 'expenditure') {
job.expenditures.push(createdTransaction);
} else {
job.income.push(createdTransaction);
}
return job;
}
export async function updateTransaction(
job: IndJob,
transactionId: string,
updates: Partial<IndTransactionRecord>
): Promise<void> {
): Promise<IndJob> {
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> {