Martin Ziegler 6 месяцев назад
Родитель
Сommit
71570c6647

+ 38 - 0
backend/app/api/routes/archives.py

@@ -13,6 +13,7 @@ from sqlalchemy import select, func
 from backend.app.core.config import settings
 from backend.app.core.database import get_db
 from backend.app.models.archive import PrintArchive
+from backend.app.models.filament import Filament
 from backend.app.schemas.archive import ArchiveResponse, ArchiveUpdate, ArchiveStats
 from backend.app.services.archive import ArchiveService
 
@@ -323,11 +324,48 @@ async def rescan_archive(archive_id: int, db: AsyncSession = Depends(get_db)):
     if metadata.get("designer"):
         archive.designer = metadata["designer"]
 
+    # Calculate cost based on filament usage and type
+    if archive.filament_used_grams and archive.filament_type:
+        primary_type = archive.filament_type.split(",")[0].strip()
+        filament_result = await db.execute(
+            select(Filament).where(Filament.type == primary_type).limit(1)
+        )
+        filament = filament_result.scalar_one_or_none()
+        if filament:
+            archive.cost = round((archive.filament_used_grams / 1000) * filament.cost_per_kg, 2)
+        else:
+            archive.cost = round((archive.filament_used_grams / 1000) * 25.0, 2)
+
     await db.commit()
     await db.refresh(archive)
     return archive
 
 
+@router.post("/recalculate-costs")
+async def recalculate_all_costs(db: AsyncSession = Depends(get_db)):
+    """Recalculate costs for all archives based on filament usage and prices."""
+    result = await db.execute(select(PrintArchive))
+    archives = list(result.scalars().all())
+
+    # Load all filaments for lookup
+    filament_result = await db.execute(select(Filament))
+    filaments = {f.type: f.cost_per_kg for f in filament_result.scalars().all()}
+    default_cost_per_kg = 25.0
+
+    updated = 0
+    for archive in archives:
+        if archive.filament_used_grams and archive.filament_type:
+            primary_type = archive.filament_type.split(",")[0].strip()
+            cost_per_kg = filaments.get(primary_type, default_cost_per_kg)
+            new_cost = round((archive.filament_used_grams / 1000) * cost_per_kg, 2)
+            if archive.cost != new_cost:
+                archive.cost = new_cost
+                updated += 1
+
+    await db.commit()
+    return {"message": f"Recalculated costs for {updated} archives", "updated": updated}
+
+
 @router.post("/rescan-all")
 async def rescan_all_archives(db: AsyncSession = Depends(get_db)):
     """Rescan all archives and update their metadata."""

+ 21 - 0
backend/app/services/archive.py

@@ -12,6 +12,7 @@ from sqlalchemy import select, and_, or_
 from backend.app.core.config import settings
 from backend.app.models.archive import PrintArchive
 from backend.app.models.printer import Printer
+from backend.app.models.filament import Filament
 
 
 class ThreeMFParser:
@@ -618,6 +619,25 @@ class ArchiveService:
         started_at = datetime.now() if status == "printing" else None
         completed_at = datetime.now() if status in ("completed", "failed", "archived") else None
 
+        # Calculate cost based on filament usage and type
+        cost = None
+        filament_grams = metadata.get("filament_used_grams")
+        filament_type = metadata.get("filament_type")
+        if filament_grams and filament_type:
+            # For multi-material prints, use the first filament type for cost calculation
+            primary_type = filament_type.split(",")[0].strip()
+            # Look up filament cost_per_kg from database
+            filament_result = await self.db.execute(
+                select(Filament).where(Filament.type == primary_type).limit(1)
+            )
+            filament = filament_result.scalar_one_or_none()
+            if filament:
+                cost = round((filament_grams / 1000) * filament.cost_per_kg, 2)
+            else:
+                # Default cost_per_kg if filament type not found
+                default_cost_per_kg = 25.0
+                cost = round((filament_grams / 1000) * default_cost_per_kg, 2)
+
         # Create archive record
         archive = PrintArchive(
             printer_id=printer_id,
@@ -640,6 +660,7 @@ class ArchiveService:
             status=status,
             started_at=started_at,
             completed_at=completed_at,
+            cost=cost,
             extra_data=metadata,
         )
 

+ 44 - 28
frontend/src/components/FilamentTrends.tsx

@@ -254,35 +254,51 @@ export function FilamentTrends({ archives, currency = '$' }: FilamentTrendsProps
         <div className="bg-bambu-dark rounded-lg p-4">
           <h4 className="text-sm font-medium text-bambu-gray mb-4">By Filament Type</h4>
           {filamentTypeData.length > 0 ? (
-            <ResponsiveContainer width="100%" height={200}>
-              <PieChart>
-                <Pie
-                  data={filamentTypeData}
-                  cx="50%"
-                  cy="50%"
-                  innerRadius={50}
-                  outerRadius={80}
-                  paddingAngle={2}
-                  dataKey="value"
-                  label={({ name, percent }) => `${name} ${((percent || 0) * 100).toFixed(0)}%`}
-                  labelLine={false}
-                >
-                  {filamentTypeData.map((_, index) => (
-                    <Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />
-                  ))}
-                </Pie>
-                <Tooltip
-                  contentStyle={{
-                    backgroundColor: '#2d2d2d',
-                    border: '1px solid #3d3d3d',
-                    borderRadius: '8px',
-                  }}
-                  formatter={(value: number) => [`${value}g`, 'Usage']}
-                />
-              </PieChart>
-            </ResponsiveContainer>
+            <div className="flex items-center gap-4">
+              <ResponsiveContainer width={160} height={160}>
+                <PieChart>
+                  <Pie
+                    data={filamentTypeData}
+                    cx="50%"
+                    cy="50%"
+                    innerRadius={40}
+                    outerRadius={70}
+                    paddingAngle={2}
+                    dataKey="value"
+                  >
+                    {filamentTypeData.map((_, index) => (
+                      <Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />
+                    ))}
+                  </Pie>
+                  <Tooltip
+                    contentStyle={{
+                      backgroundColor: '#2d2d2d',
+                      border: '1px solid #3d3d3d',
+                      borderRadius: '8px',
+                    }}
+                    formatter={(value: number) => [`${value}g`, 'Usage']}
+                  />
+                </PieChart>
+              </ResponsiveContainer>
+              <div className="flex-1 space-y-2 overflow-hidden">
+                {filamentTypeData.map((entry, index) => {
+                  const total = filamentTypeData.reduce((sum, e) => sum + e.value, 0);
+                  const percent = total > 0 ? ((entry.value / total) * 100).toFixed(0) : 0;
+                  return (
+                    <div key={entry.name} className="flex items-center gap-2 text-sm">
+                      <div
+                        className="w-3 h-3 rounded-sm flex-shrink-0"
+                        style={{ backgroundColor: COLORS[index % COLORS.length] }}
+                      />
+                      <span className="text-white truncate flex-1">{entry.name}</span>
+                      <span className="text-bambu-gray flex-shrink-0">{percent}%</span>
+                    </div>
+                  );
+                })}
+              </div>
+            </div>
           ) : (
-            <div className="h-[200px] flex items-center justify-center text-bambu-gray">
+            <div className="h-[160px] flex items-center justify-center text-bambu-gray">
               No filament data
             </div>
           )}

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
static/assets/index-DdfMIrxk.css


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
static/assets/index-IoRlmG2j.js


+ 2 - 2
static/index.html

@@ -7,8 +7,8 @@
     <link rel="icon" type="image/png" sizes="32x32" href="/img/favicon-32x32.png" />
     <link rel="icon" type="image/png" sizes="16x16" href="/img/favicon-16x16.png" />
     <link rel="apple-touch-icon" sizes="180x180" href="/img/apple-touch-icon.png" />
-    <script type="module" crossorigin src="/assets/index-0UYIv_0v.js"></script>
-    <link rel="stylesheet" crossorigin href="/assets/index-LyIdyh0d.css">
+    <script type="module" crossorigin src="/assets/index-IoRlmG2j.js"></script>
+    <link rel="stylesheet" crossorigin href="/assets/index-DdfMIrxk.css">
   </head>
   <body>
     <div id="root"></div>

Некоторые файлы не были показаны из-за большого количества измененных файлов