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 { 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">
|
||||
|
@@ -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"
|
||||
>
|
||||
|
@@ -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>
|
||||
|
@@ -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([]);
|
||||
|
@@ -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'
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
);
|
||||
|
||||
|
@@ -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> {
|
||||
|
Reference in New Issue
Block a user