mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-09-30 03:01:21 +02:00
Fix inconsistent and confusing project statistics display
Backend: - Count print job records instead of summing quantities for stats - Include "archived" status as successful (completed + archived) - Include all failure states (failed, aborted, cancelled, stopped) - Add completed_count and failed_count to ProjectListResponse Frontend ProjectsPage: - Show completed_count instead of total_items in progress displays - Progress bar now shows "X / Y completed" to match percentage - Add failed count indicator (red) when failures exist - Footer stats show completed (green checkmark) and failed (red) separately Frontend ProjectDetailPage: - Replace confusing "Total Items" card with "Print Jobs" card - Show "X successful" as main value, "Y failed" below in red - Show "Z in progress" in yellow for unfinished prints - Progress bar text changed from "items" to "completed" - Add hint prop to StatCard component for tooltips This ensures stats are consistent between: - Project list page and detail page - Stats cards and archive list counts - Progress percentages and displayed fractions Fixes #85
This commit is contained in:
@@ -412,7 +412,9 @@ export interface ProjectListItem {
|
||||
target_count: number | null;
|
||||
created_at: string;
|
||||
archive_count: number; // Number of print jobs
|
||||
total_items: number; // Sum of quantities (total items printed)
|
||||
total_items: number; // Sum of quantities (total items printed, including failed)
|
||||
completed_count: number; // Sum of quantities for completed prints only
|
||||
failed_count: number; // Sum of quantities for failed prints
|
||||
queue_count: number;
|
||||
progress_percent: number | null;
|
||||
archives: ArchivePreview[];
|
||||
|
||||
@@ -79,18 +79,20 @@ function StatCard({
|
||||
label,
|
||||
value,
|
||||
subValue,
|
||||
hint,
|
||||
color = 'text-bambu-gray',
|
||||
}: {
|
||||
icon: React.ElementType;
|
||||
label: string;
|
||||
value: string | number;
|
||||
subValue?: string;
|
||||
hint?: string;
|
||||
color?: string;
|
||||
}) {
|
||||
return (
|
||||
<Card>
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex items-center gap-3" title={hint}>
|
||||
<div className={`p-2 rounded-lg bg-bambu-dark ${color}`}>
|
||||
<Icon className="w-5 h-5" />
|
||||
</div>
|
||||
@@ -435,9 +437,6 @@ export function ProjectDetailPage() {
|
||||
|
||||
const stats = project.stats;
|
||||
const progressPercent = stats?.progress_percent ?? 0;
|
||||
const successRate = stats && stats.total_items > 0
|
||||
? ((stats.completed_prints / stats.total_items) * 100).toFixed(0)
|
||||
: null;
|
||||
|
||||
return (
|
||||
<div className="p-4 md:p-8 space-y-8">
|
||||
@@ -486,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} items
|
||||
{stats?.completed_prints || 0} / {project.target_count} completed
|
||||
</span>
|
||||
</div>
|
||||
<div className="h-3 bg-bambu-dark rounded-full overflow-hidden">
|
||||
@@ -515,20 +514,25 @@ export function ProjectDetailPage() {
|
||||
{/* Stats grid */}
|
||||
{stats && (
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
<StatCard
|
||||
icon={Package}
|
||||
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` : (successRate ? `${successRate}% success` : undefined)}
|
||||
color="text-blue-400"
|
||||
/>
|
||||
<Card>
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="p-2 rounded-lg bg-bambu-dark text-bambu-green">
|
||||
<Package className="w-5 h-5" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-bambu-gray">Print Jobs</p>
|
||||
<p className="text-xl font-semibold text-white">{stats.completed_prints} <span className="text-sm font-normal text-bambu-gray">successful</span></p>
|
||||
{stats.failed_prints > 0 && (
|
||||
<p className="text-sm text-red-400">{stats.failed_prints} failed</p>
|
||||
)}
|
||||
{stats.total_archives - stats.completed_prints - stats.failed_prints > 0 && (
|
||||
<p className="text-sm text-yellow-400">{stats.total_archives - stats.completed_prints - stats.failed_prints} in progress</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<StatCard
|
||||
icon={Clock}
|
||||
label="Print Time"
|
||||
@@ -1145,6 +1149,8 @@ export function ProjectDetailPage() {
|
||||
...project,
|
||||
archive_count: stats?.total_archives || 0,
|
||||
total_items: stats?.total_items || 0,
|
||||
completed_count: stats?.completed_prints || 0,
|
||||
failed_count: stats?.failed_prints || 0,
|
||||
queue_count: stats?.queued_prints || 0,
|
||||
progress_percent: stats?.progress_percent || null,
|
||||
archives: [],
|
||||
|
||||
@@ -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.total_items}/{project.target_count} items
|
||||
{project.completed_count}/{project.target_count} completed
|
||||
</span>
|
||||
) : project.total_items > 0 ? (
|
||||
) : project.completed_count > 0 ? (
|
||||
<span className="text-xs px-2 py-0.5 rounded-full whitespace-nowrap font-medium bg-bambu-dark text-bambu-gray">
|
||||
{project.total_items} item{project.total_items !== 1 ? 's' : ''}
|
||||
{project.completed_count} completed
|
||||
</span>
|
||||
) : null}
|
||||
{isCompleted && (
|
||||
@@ -377,7 +377,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.total_items} / {project.target_count}
|
||||
{project.completed_count} / {project.target_count}
|
||||
</span>
|
||||
</div>
|
||||
<div className="h-2.5 bg-bambu-dark/80 rounded-full overflow-hidden backdrop-blur-sm">
|
||||
@@ -392,16 +392,27 @@ function ProjectCard({ project, onClick, onEdit, onDelete }: ProjectCardProps) {
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="text-right text-xs text-bambu-gray/60 mt-1">
|
||||
{progressPercent.toFixed(0)}% complete
|
||||
<div className="flex justify-between text-xs text-bambu-gray/60 mt-1">
|
||||
<span>
|
||||
{project.failed_count > 0 && `${project.failed_count} failed`}
|
||||
</span>
|
||||
<span>{progressPercent.toFixed(0)}% complete</span>
|
||||
</div>
|
||||
</>
|
||||
) : project.total_items > 0 ? (
|
||||
) : project.completed_count > 0 || project.failed_count > 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.total_items} item{project.total_items !== 1 ? 's' : ''} completed</span>
|
||||
</div>
|
||||
{project.completed_count > 0 && (
|
||||
<div className="flex items-center gap-1.5 text-bambu-gray">
|
||||
<Archive className="w-3.5 h-3.5" />
|
||||
<span>{project.completed_count} completed</span>
|
||||
</div>
|
||||
)}
|
||||
{project.failed_count > 0 && (
|
||||
<div className="flex items-center gap-1.5 text-red-400">
|
||||
<AlertTriangle className="w-3.5 h-3.5" />
|
||||
<span>{project.failed_count} failed</span>
|
||||
</div>
|
||||
)}
|
||||
{project.queue_count > 0 && (
|
||||
<div className="flex items-center gap-1.5 text-blue-400">
|
||||
<Clock className="w-3.5 h-3.5" />
|
||||
@@ -456,10 +467,16 @@ 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="Total items printed">
|
||||
<Archive className="w-3.5 h-3.5" />
|
||||
<span>{project.total_items}</span>
|
||||
<div className="flex items-center gap-1.5" title="Completed prints">
|
||||
<CheckCircle2 className="w-3.5 h-3.5 text-bambu-green" />
|
||||
<span>{project.completed_count}</span>
|
||||
</div>
|
||||
{project.failed_count > 0 && (
|
||||
<div className="flex items-center gap-1.5 text-red-400" title="Failed prints">
|
||||
<AlertTriangle className="w-3.5 h-3.5" />
|
||||
<span>{project.failed_count}</span>
|
||||
</div>
|
||||
)}
|
||||
{project.queue_count > 0 && (
|
||||
<div className="flex items-center gap-1.5 text-blue-400" title="In queue">
|
||||
<ListTodo className="w-3.5 h-3.5" />
|
||||
|
||||
Reference in New Issue
Block a user