mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-18 03:54:17 +02:00
Add cost tracking for spools and usage history
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
import { useState } from 'react';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { Circle, Check, AlertTriangle, RefreshCw, ChevronDown, ChevronUp } from 'lucide-react';
|
||||
import { api } from '../../api/client';
|
||||
import { useFilamentMapping } from '../../hooks/useFilamentMapping';
|
||||
import { getGlobalTrayId } from '../../utils/amsHelpers';
|
||||
import { getColorName } from '../../utils/colors';
|
||||
import type { FilamentMappingProps } from './types';
|
||||
|
||||
@@ -16,6 +17,8 @@ export function FilamentMapping({
|
||||
filamentReqs,
|
||||
manualMappings,
|
||||
onManualMappingChange,
|
||||
currencySymbol,
|
||||
defaultCostPerKg,
|
||||
defaultExpanded = false,
|
||||
}: FilamentMappingProps & { defaultExpanded?: boolean }) {
|
||||
const { t } = useTranslation();
|
||||
@@ -30,9 +33,41 @@ export function FilamentMapping({
|
||||
enabled: !!printerId,
|
||||
});
|
||||
|
||||
const { data: assignments } = useQuery({
|
||||
queryKey: ['spool-assignments', printerId],
|
||||
queryFn: () => api.getAssignments(printerId),
|
||||
enabled: !!printerId,
|
||||
});
|
||||
|
||||
const { loadedFilaments, filamentComparison, hasTypeMismatch, hasColorMismatch } =
|
||||
useFilamentMapping(filamentReqs, printerStatus, manualMappings);
|
||||
|
||||
const trayCostMap = useMemo(() => {
|
||||
const map = new Map<number, number | null>();
|
||||
for (const assignment of assignments || []) {
|
||||
const isExternal = assignment.ams_id === 255;
|
||||
const globalTrayId = isExternal
|
||||
? 254 + assignment.tray_id
|
||||
: getGlobalTrayId(assignment.ams_id, assignment.tray_id, false);
|
||||
map.set(globalTrayId, assignment.spool?.cost_per_kg ?? null);
|
||||
}
|
||||
return map;
|
||||
}, [assignments]);
|
||||
|
||||
const totalCost = useMemo(() => {
|
||||
let total = 0;
|
||||
for (const item of filamentComparison) {
|
||||
const trayId = item.loaded?.globalTrayId;
|
||||
if (trayId == null) continue;
|
||||
const assignedCost = trayCostMap.get(trayId) ?? null;
|
||||
const costPerKg = assignedCost ?? defaultCostPerKg;
|
||||
if (costPerKg > 0) {
|
||||
total += (item.used_grams / 1000) * costPerKg;
|
||||
}
|
||||
}
|
||||
return total;
|
||||
}, [filamentComparison, trayCostMap, defaultCostPerKg]);
|
||||
|
||||
const hasFilamentReqs = filamentReqs?.filaments && filamentReqs.filaments.length > 0;
|
||||
const isDualNozzle = filamentReqs?.filaments?.some((f) => f.nozzle_id != null) ?? false;
|
||||
|
||||
@@ -90,7 +125,7 @@ export function FilamentMapping({
|
||||
className="flex items-center gap-2 text-sm text-bambu-gray hover:text-white transition-colors w-full"
|
||||
>
|
||||
<Circle className="w-4 h-4" fill={statusColor} stroke="none" />
|
||||
<span>Filament Mapping</span>
|
||||
<span>{t('printModal.filamentMapping')}</span>
|
||||
{hasTypeMismatch ? (
|
||||
<span className="text-xs text-orange-400">(Type not found)</span>
|
||||
) : hasColorMismatch ? (
|
||||
@@ -181,6 +216,9 @@ export function FilamentMapping({
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
<div className="text-xs text-bambu-gray">
|
||||
{t('printModal.totalCost')} <span className="text-white">{currencySymbol}{totalCost.toFixed(2)}</span>
|
||||
</div>
|
||||
{hasTypeMismatch && (
|
||||
<p className="text-xs text-orange-400 mt-2">Required filament type not found in printer.</p>
|
||||
)}
|
||||
|
||||
@@ -10,6 +10,7 @@ import { useToast } from '../../contexts/ToastContext';
|
||||
import { useFilamentMapping } from '../../hooks/useFilamentMapping';
|
||||
import { useMultiPrinterFilamentMapping, type PerPrinterConfig } from '../../hooks/useMultiPrinterFilamentMapping';
|
||||
import { isPlaceholderDate } from '../../utils/amsHelpers';
|
||||
import { getCurrencySymbol } from '../../utils/currency';
|
||||
import { toDateTimeLocalValue } from '../../utils/date';
|
||||
import { PrinterSelector } from './PrinterSelector';
|
||||
import { PlateSelector } from './PlateSelector';
|
||||
@@ -170,6 +171,9 @@ export function PrintModal({
|
||||
queryFn: api.getSettings,
|
||||
});
|
||||
|
||||
const currencySymbol = getCurrencySymbol(settings?.currency || 'USD');
|
||||
const defaultCostPerKg = settings?.default_filament_cost ?? 0;
|
||||
|
||||
const { data: printers, isLoading: loadingPrinters } = useQuery({
|
||||
queryKey: ['printers'],
|
||||
queryFn: api.getPrinters,
|
||||
@@ -687,6 +691,8 @@ export function PrintModal({
|
||||
manualMappings={manualMappings}
|
||||
onManualMappingChange={setManualMappings}
|
||||
defaultExpanded={settings?.per_printer_mapping_expanded ?? false}
|
||||
currencySymbol={currencySymbol}
|
||||
defaultCostPerKg={defaultCostPerKg}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -171,6 +171,8 @@ export interface FilamentMappingProps {
|
||||
filamentReqs: FilamentReqsData | undefined;
|
||||
manualMappings: Record<number, number>;
|
||||
onManualMappingChange: (mappings: Record<number, number>) => void;
|
||||
currencySymbol: string;
|
||||
defaultCostPerKg: number;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user