addes cost and usage calculation
This commit is contained in:
@@ -11,7 +11,8 @@ import Badge from '../../components/ui/Badge';
|
||||
import Input from '../../components/ui/Input';
|
||||
import Modal from '../../components/ui/Modal';
|
||||
import FileUpload from '../../components/ui/FileUpload';
|
||||
import { Edit, Trash2, Copy, Eye, EyeOff, ArrowLeft, ArrowRight, Download, ExternalLink, Plus, ChevronDown, ChevronUp, Gauge, CheckCircle, Circle, ClipboardList, MessageSquare } from 'lucide-react';
|
||||
import { Edit, Trash2, Copy, Eye, EyeOff, ArrowLeft, ArrowRight, Download, ExternalLink, Plus, ChevronDown, ChevronUp, Gauge, CheckCircle, Circle, ClipboardList, MessageSquare, Calculator } from 'lucide-react';
|
||||
import { calculateConsumption, calculateCosts } from '../../utils/energyCalculations';
|
||||
import CopyButton, { CopyableBlock } from '../../components/ui/CopyButton';
|
||||
import type { ContractType, ContractStatus, SimCard, MeterReading, ContractTask, ContractTaskSubtask } from '../../types';
|
||||
|
||||
@@ -404,6 +405,144 @@ function MeterReadingModal({
|
||||
);
|
||||
}
|
||||
|
||||
// Energy Consumption and Cost Calculation Component
|
||||
function EnergyConsumptionCalculation({
|
||||
contractType,
|
||||
readings,
|
||||
startDate,
|
||||
endDate,
|
||||
basePrice,
|
||||
unitPrice,
|
||||
bonus,
|
||||
}: {
|
||||
contractType: 'ELECTRICITY' | 'GAS';
|
||||
readings: MeterReading[];
|
||||
startDate: string;
|
||||
endDate: string;
|
||||
basePrice?: number;
|
||||
unitPrice?: number;
|
||||
bonus?: number;
|
||||
}) {
|
||||
// Berechnung durchführen
|
||||
const consumption = calculateConsumption(readings, startDate, endDate, contractType);
|
||||
const costs = consumption.consumptionKwh > 0
|
||||
? calculateCosts(consumption.consumptionKwh, basePrice, unitPrice, bonus)
|
||||
: null;
|
||||
|
||||
// Nichts anzeigen wenn keine Daten
|
||||
if (consumption.type === 'none') return null;
|
||||
|
||||
const formatNumber = (num: number, decimals: number = 2) =>
|
||||
num.toLocaleString('de-DE', { minimumFractionDigits: decimals, maximumFractionDigits: decimals });
|
||||
|
||||
const formatDate = (dateStr: string) =>
|
||||
new Date(dateStr).toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' });
|
||||
|
||||
return (
|
||||
<div className="mt-4 pt-4 border-t">
|
||||
{/* Header */}
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<Calculator className="w-4 h-4 text-gray-500" />
|
||||
<h4 className="text-sm font-medium text-gray-700">Verbrauch & Kosten</h4>
|
||||
{consumption.type === 'exact' && (
|
||||
<Badge variant="success">Exakt</Badge>
|
||||
)}
|
||||
{consumption.type === 'projected' && (
|
||||
<Badge variant="warning">Hochrechnung</Badge>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Fall C: Unzureichende Daten */}
|
||||
{consumption.type === 'insufficient' ? (
|
||||
<p className="text-sm text-gray-500 italic">{consumption.message}</p>
|
||||
) : (
|
||||
<div className="bg-gray-50 rounded-lg p-4 space-y-4">
|
||||
{/* Verbrauchsanzeige */}
|
||||
<div>
|
||||
<h5 className="text-sm font-medium text-gray-600 mb-2">
|
||||
Berechneter Verbrauch
|
||||
{consumption.type === 'projected' && ' (hochgerechnet)'}
|
||||
</h5>
|
||||
<div className="text-lg font-semibold text-gray-900">
|
||||
{contractType === 'GAS' ? (
|
||||
<>
|
||||
<span className="font-mono">{formatNumber(consumption.consumptionM3 || 0)} m³</span>
|
||||
<span className="text-gray-500 text-sm ml-2">
|
||||
= {formatNumber(consumption.consumptionKwh)} kWh
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
<span className="font-mono">{formatNumber(consumption.consumptionKwh)} kWh</span>
|
||||
)}
|
||||
</div>
|
||||
{consumption.startReading && consumption.endReading && (
|
||||
<p className="text-xs text-gray-400 mt-1">
|
||||
Basierend auf Zählerständen vom {formatDate(consumption.startReading.readingDate)} bis {formatDate(consumption.endReading.readingDate)}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Kostenrechnung */}
|
||||
{costs && (
|
||||
<div className="border-t border-gray-200 pt-4">
|
||||
<h5 className="text-sm font-medium text-gray-600 mb-3">Kostenvorschau</h5>
|
||||
<div className="space-y-2 text-sm">
|
||||
{/* Grundpreis */}
|
||||
{basePrice != null && basePrice > 0 && (
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-600">
|
||||
Grundpreis: {formatNumber(basePrice)} €/Mon × 12
|
||||
</span>
|
||||
<span className="font-mono">{formatNumber(costs.annualBaseCost)} €</span>
|
||||
</div>
|
||||
)}
|
||||
{/* Arbeitspreis */}
|
||||
{unitPrice != null && unitPrice > 0 && (
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-600">
|
||||
Arbeitspreis: {formatNumber(consumption.consumptionKwh)} kWh × {formatNumber(unitPrice, 4)} €
|
||||
</span>
|
||||
<span className="font-mono">{formatNumber(costs.annualConsumptionCost)} €</span>
|
||||
</div>
|
||||
)}
|
||||
{/* Trennlinie */}
|
||||
<div className="border-t border-gray-300 pt-2">
|
||||
<div className="flex justify-between font-medium">
|
||||
<span className="text-gray-700">Jahreskosten</span>
|
||||
<span className="font-mono">{formatNumber(costs.annualTotalCost)} €</span>
|
||||
</div>
|
||||
</div>
|
||||
{/* Bonus */}
|
||||
{costs.bonus != null && costs.bonus > 0 && (
|
||||
<>
|
||||
<div className="flex justify-between text-green-600">
|
||||
<span>Bonus</span>
|
||||
<span className="font-mono">- {formatNumber(costs.bonus)} €</span>
|
||||
</div>
|
||||
<div className="border-t border-gray-300 pt-2">
|
||||
<div className="flex justify-between font-semibold">
|
||||
<span className="text-gray-800">Effektive Jahreskosten</span>
|
||||
<span className="font-mono">{formatNumber(costs.effectiveAnnualCost)} €</span>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{/* Monatlicher Abschlag */}
|
||||
<div className="border-t border-gray-300 pt-2 mt-2">
|
||||
<div className="flex justify-between text-blue-700 font-semibold">
|
||||
<span>Monatlicher Abschlag</span>
|
||||
<span className="font-mono">{formatNumber(costs.monthlyPayment)} €</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Contract Task Item Component (handles subtasks)
|
||||
function ContractTaskItem({
|
||||
task,
|
||||
@@ -1999,6 +2138,19 @@ export default function ContractDetail() {
|
||||
canEdit={hasPermission('contracts:update') && !isCustomer}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Verbrauchsberechnung & Kostenvorschau */}
|
||||
{c.energyDetails.meter && c.startDate && c.endDate && (
|
||||
<EnergyConsumptionCalculation
|
||||
contractType={c.type as 'ELECTRICITY' | 'GAS'}
|
||||
readings={c.energyDetails.meter.readings || []}
|
||||
startDate={c.startDate}
|
||||
endDate={c.endDate}
|
||||
basePrice={c.energyDetails.basePrice}
|
||||
unitPrice={c.energyDetails.unitPrice}
|
||||
bonus={c.energyDetails.bonus}
|
||||
/>
|
||||
)}
|
||||
</Card>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user