maziggy 8 месяцев назад
Родитель
Сommit
4e25377e5b

+ 5 - 1
frontend/src/api/client.ts

@@ -223,6 +223,7 @@ export interface Archive {
   cost: number | null;
   photos: string[] | null;
   failure_reason: string | null;
+  quantity: number;
   energy_kwh: number | null;
   energy_cost: number | null;
   created_at: string;
@@ -333,6 +334,7 @@ export interface SimilarArchive {
 // Project types
 export interface ProjectStats {
   total_archives: number;
+  total_items: number;  // Sum of quantities (total items printed)
   completed_prints: number;
   failed_prints: number;
   queued_prints: number;
@@ -403,7 +405,8 @@ export interface ProjectListItem {
   status: string;
   target_count: number | null;
   created_at: string;
-  archive_count: number;
+  archive_count: number;  // Number of print jobs
+  total_items: number;  // Sum of quantities (total items printed)
   queue_count: number;
   progress_percent: number | null;
   archives: ArchivePreview[];
@@ -1407,6 +1410,7 @@ export const api = {
     cost?: number;
     failure_reason?: string | null;
     status?: string;
+    quantity?: number;
   }) =>
     request<Archive>(`/archives/${id}`, {
       method: 'PATCH',

+ 22 - 1
frontend/src/components/EditArchiveModal.tsx

@@ -1,6 +1,6 @@
 import { useState, useEffect, useRef } from 'react';
 import { useMutation, useQueryClient, useQuery } from '@tanstack/react-query';
-import { X, Save, Tag, Camera, Trash2, Loader2, Plus, FolderKanban } from 'lucide-react';
+import { X, Save, Tag, Camera, Trash2, Loader2, Plus, FolderKanban, Hash } from 'lucide-react';
 import { api } from '../api/client';
 import type { Archive } from '../api/client';
 import { Button } from './Button';
@@ -49,6 +49,7 @@ export function EditArchiveModal({ archive, onClose, existingTags = [] }: EditAr
   const [tags, setTags] = useState(archive.tags || '');
   const [failureReason, setFailureReason] = useState(archive.failure_reason || '');
   const [status, setStatus] = useState(archive.status);
+  const [quantity, setQuantity] = useState(archive.quantity ?? 1);
   const [photos, setPhotos] = useState<string[]>(archive.photos || []);
   const [uploadingPhoto, setUploadingPhoto] = useState(false);
   const [showTagSuggestions, setShowTagSuggestions] = useState(false);
@@ -153,6 +154,7 @@ export function EditArchiveModal({ archive, onClose, existingTags = [] }: EditAr
       project_id: projectId,
       notes: notes || undefined,
       tags: tags || undefined,
+      quantity: quantity,
     };
 
     // Only include status if changed
@@ -242,6 +244,25 @@ export function EditArchiveModal({ archive, onClose, existingTags = [] }: EditAr
             </select>
           </div>
 
+          {/* Quantity - number of items printed */}
+          <div>
+            <label className="block text-sm text-bambu-gray mb-1">
+              <Hash className="w-4 h-4 inline mr-1" />
+              Items Printed
+            </label>
+            <input
+              type="number"
+              min={1}
+              value={quantity}
+              onChange={(e) => setQuantity(Math.max(1, parseInt(e.target.value) || 1))}
+              className="w-full px-3 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white focus:border-bambu-green focus:outline-none"
+              placeholder="1"
+            />
+            <p className="text-xs text-bambu-gray mt-1">
+              Number of items produced in this print job
+            </p>
+          </div>
+
           {/* Notes */}
           <div>
             <label className="block text-sm text-bambu-gray mb-1">Notes</label>

+ 8 - 7
frontend/src/pages/ProjectDetailPage.tsx

@@ -434,8 +434,8 @@ export function ProjectDetailPage() {
 
   const stats = project.stats;
   const progressPercent = stats?.progress_percent ?? 0;
-  const successRate = stats && stats.total_archives > 0
-    ? ((stats.completed_prints / stats.total_archives) * 100).toFixed(0)
+  const successRate = stats && stats.total_items > 0
+    ? ((stats.completed_prints / stats.total_items) * 100).toFixed(0)
     : null;
 
   return (
@@ -485,7 +485,7 @@ export function ProjectDetailPage() {
             <div className="flex items-center justify-between mb-2">
               <span className="text-sm text-bambu-gray">Progress</span>
               <span className="text-sm font-medium text-white">
-                {stats?.completed_prints || 0} / {project.target_count} prints
+                {stats?.completed_prints || 0} / {project.target_count} items
               </span>
             </div>
             <div className="h-3 bg-bambu-dark rounded-full overflow-hidden">
@@ -516,16 +516,16 @@ export function ProjectDetailPage() {
         <div className="grid grid-cols-2 md:grid-cols-4 gap-4">
           <StatCard
             icon={Package}
-            label="Total Prints"
-            value={stats.total_archives}
-            subValue={successRate ? `${successRate}% success rate` : undefined}
+            label="Total Items"
+            value={stats.total_items}
+            subValue={`${stats.total_archives} print job${stats.total_archives !== 1 ? 's' : ''}`}
             color="text-bambu-green"
           />
           <StatCard
             icon={CheckCircle}
             label="Completed"
             value={stats.completed_prints}
-            subValue={stats.failed_prints > 0 ? `${stats.failed_prints} failed` : undefined}
+            subValue={stats.failed_prints > 0 ? `${stats.failed_prints} failed` : (successRate ? `${successRate}% success` : undefined)}
             color="text-blue-400"
           />
           <StatCard
@@ -1143,6 +1143,7 @@ export function ProjectDetailPage() {
           project={{
             ...project,
             archive_count: stats?.total_archives || 0,
+            total_items: stats?.total_items || 0,
             queue_count: stats?.queued_prints || 0,
             progress_percent: stats?.progress_percent || null,
             archives: [],

+ 8 - 8
frontend/src/pages/ProjectsPage.tsx

@@ -268,11 +268,11 @@ function ProjectCard({ project, onClick, onEdit, onDelete }: ProjectCardProps) {
                       ? 'bg-bambu-green/20 text-bambu-green'
                       : 'bg-bambu-dark text-bambu-gray'
                   }`}>
-                    {project.archive_count}/{project.target_count} parts
+                    {project.total_items}/{project.target_count} items
                   </span>
-                ) : project.archive_count > 0 ? (
+                ) : project.total_items > 0 ? (
                   <span className="text-xs px-2 py-0.5 rounded-full whitespace-nowrap font-medium bg-bambu-dark text-bambu-gray">
-                    {project.archive_count} print{project.archive_count !== 1 ? 's' : ''}
+                    {project.total_items} item{project.total_items !== 1 ? 's' : ''}
                   </span>
                 ) : null}
                 {isCompleted && (
@@ -365,7 +365,7 @@ function ProjectCard({ project, onClick, onEdit, onDelete }: ProjectCardProps) {
               <div className="flex items-center justify-between text-xs mb-2">
                 <span className="text-bambu-gray">Progress</span>
                 <span className={progressPercent >= 100 ? 'text-bambu-green font-medium' : 'text-white'}>
-                  {project.archive_count} / {project.target_count}
+                  {project.total_items} / {project.target_count}
                 </span>
               </div>
               <div className="h-2.5 bg-bambu-dark/80 rounded-full overflow-hidden backdrop-blur-sm">
@@ -384,11 +384,11 @@ function ProjectCard({ project, onClick, onEdit, onDelete }: ProjectCardProps) {
                 {progressPercent.toFixed(0)}% complete
               </div>
             </>
-          ) : project.archive_count > 0 ? (
+          ) : project.total_items > 0 ? (
             <div className="flex items-center gap-4 text-xs">
               <div className="flex items-center gap-1.5 text-bambu-gray">
                 <Archive className="w-3.5 h-3.5" />
-                <span>{project.archive_count} print{project.archive_count !== 1 ? 's' : ''} completed</span>
+                <span>{project.total_items} item{project.total_items !== 1 ? 's' : ''} completed</span>
               </div>
               {project.queue_count > 0 && (
                 <div className="flex items-center gap-1.5 text-blue-400">
@@ -444,9 +444,9 @@ function ProjectCard({ project, onClick, onEdit, onDelete }: ProjectCardProps) {
         {/* Stats footer */}
         <div className="flex items-center justify-between pt-3 border-t border-bambu-dark-tertiary">
           <div className="flex items-center gap-4 text-xs text-bambu-gray">
-            <div className="flex items-center gap-1.5" title="Completed prints">
+            <div className="flex items-center gap-1.5" title="Total items printed">
               <Archive className="w-3.5 h-3.5" />
-              <span>{project.archive_count}</span>
+              <span>{project.total_items}</span>
             </div>
             {project.queue_count > 0 && (
               <div className="flex items-center gap-1.5 text-blue-400" title="In queue">