mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-09 07:25:44 +02:00
Fix status indicators using accent color instead of semantic colors
Status badges (Connected/Offline, HMS errors, maintenance, success/failed) were using bambu-green which follows the user's accent color theme. This caused confusing UX - e.g., "Connected" appearing red with a red accent. Added fixed semantic status colors that never change with theme: - status-ok: #22c55e (green) - success, online, OK states - status-error: #ef4444 (red) - failed, offline, error states - status-warning: #f59e0b (amber) - warnings, pending states Updated components: - PrintersPage: connection badge, HMS errors, maintenance, WiFi signal - SmartPlugCard/SwitchbarPopover: plug connectivity status - StatsPage: success/failed counts, failure rate, time accuracy - QueuePage: queue item statuses (pending, completed, failed) - ArchivesPage: failed/aborted archive badge - ProjectDetailPage: child status, timeline events, BOM checklist - CompareArchivesModal: archive completion status - NotificationProviderCard: last success/error status Closes #136
This commit is contained in:
@@ -91,8 +91,8 @@ function ComparisonContent({ comparison }: { comparison: ArchiveComparison }) {
|
||||
{archive.print_name}
|
||||
</div>
|
||||
<div className={`text-xs ${
|
||||
archive.status === 'completed' ? 'text-bambu-green' :
|
||||
archive.status === 'failed' ? 'text-red-400' : 'text-bambu-gray'
|
||||
archive.status === 'completed' ? 'text-status-ok' :
|
||||
archive.status === 'failed' ? 'text-status-error' : 'text-bambu-gray'
|
||||
}`}>
|
||||
{archive.status}
|
||||
</div>
|
||||
|
||||
@@ -91,13 +91,13 @@ export function NotificationProviderCard({ provider, onEdit }: NotificationProvi
|
||||
{/* Quick enable/disable toggle + Status indicator */}
|
||||
<div className="flex items-center gap-3">
|
||||
{provider.last_success && (
|
||||
<span className="text-xs text-bambu-green hidden sm:inline">Last: {formatDateOnly(provider.last_success)}</span>
|
||||
<span className="text-xs text-status-ok hidden sm:inline">Last: {formatDateOnly(provider.last_success)}</span>
|
||||
)}
|
||||
{/* Only show error if it's more recent than last success */}
|
||||
{provider.last_error && provider.last_error_at && (
|
||||
!provider.last_success || (parseUTCDate(provider.last_error_at)?.getTime() || 0) > (parseUTCDate(provider.last_success)?.getTime() || 0)
|
||||
) && (
|
||||
<span className="text-xs text-red-400" title={provider.last_error}>Error</span>
|
||||
<span className="text-xs text-status-error" title={provider.last_error}>Error</span>
|
||||
)}
|
||||
<Toggle
|
||||
checked={provider.enabled}
|
||||
|
||||
@@ -136,11 +136,11 @@ export function SmartPlugCard({ plug, onEdit }: SmartPlugCardProps) {
|
||||
<Loader2 className="w-4 h-4 text-bambu-gray animate-spin" />
|
||||
) : isReachable ? (
|
||||
<div className="flex items-center gap-1 text-sm">
|
||||
<Wifi className="w-4 h-4 text-bambu-green" />
|
||||
<span className={isOn ? 'text-bambu-green' : 'text-bambu-gray'}>{status?.state || 'Unknown'}</span>
|
||||
<Wifi className="w-4 h-4 text-status-ok" />
|
||||
<span className={isOn ? 'text-status-ok' : 'text-bambu-gray'}>{status?.state || 'Unknown'}</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center gap-1 text-sm text-red-400">
|
||||
<div className="flex items-center gap-1 text-sm text-status-error">
|
||||
<WifiOff className="w-4 h-4" />
|
||||
<span>Offline</span>
|
||||
</div>
|
||||
|
||||
@@ -53,8 +53,8 @@ function SwitchItem({ plug }: { plug: SmartPlug }) {
|
||||
<Loader2 className="w-3 h-3 text-bambu-gray animate-spin" />
|
||||
) : isReachable ? (
|
||||
<>
|
||||
<Wifi className="w-3 h-3 text-bambu-green" />
|
||||
<span className={isOn ? 'text-bambu-green' : 'text-bambu-gray'}>{status?.state || 'Unknown'}</span>
|
||||
<Wifi className="w-3 h-3 text-status-ok" />
|
||||
<span className={isOn ? 'text-status-ok' : 'text-bambu-gray'}>{status?.state || 'Unknown'}</span>
|
||||
{status?.energy?.power !== null && status?.energy?.power !== undefined && (
|
||||
<>
|
||||
<span className="text-bambu-gray mx-1">|</span>
|
||||
@@ -65,8 +65,8 @@ function SwitchItem({ plug }: { plug: SmartPlug }) {
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<WifiOff className="w-3 h-3 text-red-400" />
|
||||
<span className="text-red-400">Offline</span>
|
||||
<WifiOff className="w-3 h-3 text-status-error" />
|
||||
<span className="text-status-error">Offline</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -10,6 +10,11 @@
|
||||
--color-bambu-green-light: var(--accent-light);
|
||||
--color-bambu-green-dark: var(--accent-dark);
|
||||
|
||||
/* Semantic status colors - fixed, don't change with accent */
|
||||
--color-status-ok: var(--status-ok);
|
||||
--color-status-error: var(--status-error);
|
||||
--color-status-warning: var(--status-warning);
|
||||
|
||||
/* Theme-aware colors via CSS variables */
|
||||
--color-bambu-dark: var(--bg-primary);
|
||||
--color-bambu-dark-secondary: var(--bg-secondary);
|
||||
@@ -29,6 +34,11 @@
|
||||
--accent-light: #00c64d;
|
||||
--accent-dark: #009438;
|
||||
|
||||
/* Semantic status colors - these never change with accent theme */
|
||||
--status-ok: #22c55e; /* green-500 - always green for success/online/ok */
|
||||
--status-error: #ef4444; /* red-500 - always red for error/offline/failed */
|
||||
--status-warning: #f59e0b; /* amber-500 - always amber for warnings */
|
||||
|
||||
/* Default light mode background (neutral) */
|
||||
--bg-primary: #f5f5f5;
|
||||
--bg-secondary: #ffffff;
|
||||
|
||||
@@ -543,7 +543,7 @@ function ArchiveCard({
|
||||
/>
|
||||
</button>
|
||||
{(archive.status === 'failed' || archive.status === 'aborted') && (
|
||||
<div className="absolute top-2 left-12 px-2 py-1 rounded text-xs bg-red-500/80 text-white">
|
||||
<div className="absolute top-2 left-12 px-2 py-1 rounded text-xs bg-status-error/80 text-white">
|
||||
{archive.status === 'aborted' ? 'cancelled' : 'failed'}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1354,7 +1354,7 @@ function PrinterCard({
|
||||
{viewMode === 'compact' && (
|
||||
<div
|
||||
className={`w-2 h-2 rounded-full flex-shrink-0 ${
|
||||
status?.connected ? 'bg-bambu-green' : 'bg-red-500'
|
||||
status?.connected ? 'bg-status-ok' : 'bg-status-error'
|
||||
}`}
|
||||
title={status?.connected ? 'Connected' : 'Offline'}
|
||||
/>
|
||||
@@ -1440,8 +1440,8 @@ function PrinterCard({
|
||||
<span
|
||||
className={`flex items-center gap-1.5 px-2 py-1 rounded-full text-xs ${
|
||||
status?.connected
|
||||
? 'bg-bambu-green/20 text-bambu-green'
|
||||
: 'bg-red-500/20 text-red-400'
|
||||
? 'bg-status-ok/20 text-status-ok'
|
||||
: 'bg-status-error/20 text-status-error'
|
||||
}`}
|
||||
>
|
||||
{status?.connected ? (
|
||||
@@ -1456,14 +1456,14 @@ function PrinterCard({
|
||||
<span
|
||||
className={`flex items-center gap-1 px-2 py-1 rounded-full text-xs ${
|
||||
wifiSignal >= -50
|
||||
? 'bg-bambu-green/20 text-bambu-green'
|
||||
? 'bg-status-ok/20 text-status-ok'
|
||||
: wifiSignal >= -60
|
||||
? 'bg-bambu-green/20 text-bambu-green'
|
||||
? 'bg-status-ok/20 text-status-ok'
|
||||
: wifiSignal >= -70
|
||||
? 'bg-amber-500/20 text-amber-600'
|
||||
? 'bg-status-warning/20 text-status-warning'
|
||||
: wifiSignal >= -80
|
||||
? 'bg-orange-500/20 text-orange-600'
|
||||
: 'bg-red-500/20 text-red-600'
|
||||
: 'bg-status-error/20 text-status-error'
|
||||
}`}
|
||||
title={`WiFi: ${wifiSignal} dBm - ${getWifiStrength(wifiSignal).label}`}
|
||||
>
|
||||
@@ -1480,9 +1480,9 @@ function PrinterCard({
|
||||
className={`flex items-center gap-1 px-2 py-1 rounded-full text-xs cursor-pointer hover:opacity-80 transition-opacity ${
|
||||
knownErrors.length > 0
|
||||
? knownErrors.some(e => e.severity <= 2)
|
||||
? 'bg-red-500/20 text-red-400'
|
||||
: 'bg-orange-500/20 text-orange-400'
|
||||
: 'bg-bambu-green/20 text-bambu-green'
|
||||
? 'bg-status-error/20 text-status-error'
|
||||
: 'bg-status-warning/20 text-status-warning'
|
||||
: 'bg-status-ok/20 text-status-ok'
|
||||
}`}
|
||||
title="Click to view HMS errors"
|
||||
>
|
||||
@@ -1497,10 +1497,10 @@ function PrinterCard({
|
||||
onClick={() => navigate('/maintenance')}
|
||||
className={`flex items-center gap-1 px-2 py-1 rounded-full text-xs cursor-pointer hover:opacity-80 transition-opacity ${
|
||||
maintenanceInfo.due_count > 0
|
||||
? 'bg-red-500/20 text-red-400'
|
||||
? 'bg-status-error/20 text-status-error'
|
||||
: maintenanceInfo.warning_count > 0
|
||||
? 'bg-orange-500/20 text-orange-400'
|
||||
: 'bg-bambu-green/20 text-bambu-green'
|
||||
? 'bg-status-warning/20 text-status-warning'
|
||||
: 'bg-status-ok/20 text-status-ok'
|
||||
}`}
|
||||
title={
|
||||
maintenanceInfo.due_count > 0 || maintenanceInfo.warning_count > 0
|
||||
@@ -3487,8 +3487,8 @@ function FirmwareUpdateModal({
|
||||
<div className="w-full bg-bambu-dark-tertiary rounded-full h-2">
|
||||
<div
|
||||
className={`h-2 rounded-full transition-all ${
|
||||
uploadStatus.status === 'error' ? 'bg-red-500' :
|
||||
uploadStatus.status === 'complete' ? 'bg-bambu-green' : 'bg-orange-500'
|
||||
uploadStatus.status === 'error' ? 'bg-status-error' :
|
||||
uploadStatus.status === 'complete' ? 'bg-status-ok' : 'bg-orange-500'
|
||||
} ${uploadStatus.status === 'uploading' ? 'animate-pulse' : ''}`}
|
||||
style={{ width: `${uploadStatus.progress}%` }}
|
||||
/>
|
||||
|
||||
@@ -520,7 +520,7 @@ export function ProjectDetailPage() {
|
||||
<p className="text-sm text-bambu-gray">Print Jobs</p>
|
||||
<p className="text-xl font-semibold text-white">{stats.total_archives} <span className="text-sm font-normal text-bambu-gray">total</span></p>
|
||||
{stats.failed_prints > 0 && (
|
||||
<p className="text-sm text-red-400">{stats.failed_prints} failed</p>
|
||||
<p className="text-sm text-status-error">{stats.failed_prints} failed</p>
|
||||
)}
|
||||
<p className="text-sm text-bambu-gray">{stats.completed_prints} parts printed</p>
|
||||
</div>
|
||||
@@ -610,7 +610,7 @@ export function ProjectDetailPage() {
|
||||
/>
|
||||
<span className="text-white">{child.name}</span>
|
||||
<span className={`text-xs px-2 py-0.5 rounded ${
|
||||
child.status === 'completed' ? 'bg-bambu-green/20 text-bambu-green' :
|
||||
child.status === 'completed' ? 'bg-status-ok/20 text-status-ok' :
|
||||
child.status === 'archived' ? 'bg-bambu-gray/20 text-bambu-gray' :
|
||||
'bg-blue-500/20 text-blue-400'
|
||||
}`}>
|
||||
@@ -900,7 +900,7 @@ export function ProjectDetailPage() {
|
||||
<div
|
||||
key={item.id}
|
||||
className={`p-3 rounded-lg transition-colors ${
|
||||
item.is_complete ? 'bg-bambu-green/10' : 'bg-bambu-dark'
|
||||
item.is_complete ? 'bg-status-ok/10' : 'bg-bambu-dark'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-start gap-3">
|
||||
@@ -909,7 +909,7 @@ export function ProjectDetailPage() {
|
||||
disabled={updateBomMutation.isPending}
|
||||
className={`w-5 h-5 mt-0.5 rounded border-2 flex items-center justify-center transition-colors flex-shrink-0 ${
|
||||
item.is_complete
|
||||
? 'bg-bambu-green border-bambu-green text-white'
|
||||
? 'bg-status-ok border-status-ok text-white'
|
||||
: 'border-bambu-gray hover:border-bambu-green'
|
||||
}`}
|
||||
>
|
||||
@@ -1006,8 +1006,8 @@ export function ProjectDetailPage() {
|
||||
{timeline.slice(0, 10).map((event, index) => (
|
||||
<div key={index} className="flex gap-3">
|
||||
<div className={`w-8 h-8 rounded-full flex items-center justify-center flex-shrink-0 ${
|
||||
event.event_type === 'print_completed' ? 'bg-bambu-green/20 text-bambu-green' :
|
||||
event.event_type === 'print_failed' ? 'bg-red-500/20 text-red-400' :
|
||||
event.event_type === 'print_completed' ? 'bg-status-ok/20 text-status-ok' :
|
||||
event.event_type === 'print_failed' ? 'bg-status-error/20 text-status-error' :
|
||||
event.event_type === 'print_started' ? 'bg-yellow-500/20 text-yellow-400' :
|
||||
'bg-bambu-dark-tertiary text-bambu-gray'
|
||||
}`}>
|
||||
|
||||
@@ -76,10 +76,10 @@ function formatRelativeTime(dateString: string | null, timeFormat: TimeFormat =
|
||||
|
||||
function StatusBadge({ status }: { status: PrintQueueItem['status'] }) {
|
||||
const config = {
|
||||
pending: { icon: Clock, color: 'text-yellow-400 bg-yellow-400/10 border-yellow-400/20', label: 'Pending' },
|
||||
pending: { icon: Clock, color: 'text-status-warning bg-status-warning/10 border-status-warning/20', label: 'Pending' },
|
||||
printing: { icon: Play, color: 'text-blue-400 bg-blue-400/10 border-blue-400/20', label: 'Printing' },
|
||||
completed: { icon: CheckCircle, color: 'text-green-400 bg-green-400/10 border-green-400/20', label: 'Completed' },
|
||||
failed: { icon: XCircle, color: 'text-red-400 bg-red-400/10 border-red-400/20', label: 'Failed' },
|
||||
completed: { icon: CheckCircle, color: 'text-status-ok bg-status-ok/10 border-status-ok/20', label: 'Completed' },
|
||||
failed: { icon: XCircle, color: 'text-status-error bg-status-error/10 border-status-error/20', label: 'Failed' },
|
||||
skipped: { icon: SkipForward, color: 'text-orange-400 bg-orange-400/10 border-orange-400/20', label: 'Skipped' },
|
||||
cancelled: { icon: X, color: 'text-gray-400 bg-gray-400/10 border-gray-400/20', label: 'Cancelled' },
|
||||
};
|
||||
|
||||
@@ -155,12 +155,12 @@ function SuccessRateWidget({
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<CheckCircle className="w-4 h-4 text-bambu-green flex-shrink-0" />
|
||||
<CheckCircle className="w-4 h-4 text-status-ok flex-shrink-0" />
|
||||
<span className="text-sm text-bambu-gray">Successful:</span>
|
||||
<span className="text-sm text-white font-medium">{stats?.successful_prints || 0}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<XCircle className="w-4 h-4 text-red-400 flex-shrink-0" />
|
||||
<XCircle className="w-4 h-4 text-status-error flex-shrink-0" />
|
||||
<span className="text-sm text-bambu-gray">Failed:</span>
|
||||
<span className="text-sm text-white font-medium">{stats?.failed_prints || 0}</span>
|
||||
</div>
|
||||
@@ -276,8 +276,8 @@ function TimeAccuracyWidget({
|
||||
{printerMap.get(printerId) || `Printer ${printerId}`}
|
||||
</span>
|
||||
<span className={`font-medium ${
|
||||
acc >= 95 && acc <= 105 ? 'text-bambu-green' :
|
||||
acc > 105 ? 'text-blue-400' : 'text-orange-400'
|
||||
acc >= 95 && acc <= 105 ? 'text-status-ok' :
|
||||
acc > 105 ? 'text-blue-400' : 'text-status-warning'
|
||||
}`}>
|
||||
{acc.toFixed(0)}%
|
||||
</span>
|
||||
@@ -452,7 +452,7 @@ function FailureAnalysisWidget({ size = 1 }: { size?: 1 | 2 | 4 }) {
|
||||
<div className={size >= 2 ? 'flex-shrink-0' : ''}>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<AlertTriangle className={`w-5 h-5 ${analysis.failure_rate > 20 ? 'text-red-400' : analysis.failure_rate > 10 ? 'text-yellow-400' : 'text-bambu-green'}`} />
|
||||
<AlertTriangle className={`w-5 h-5 ${analysis.failure_rate > 20 ? 'text-status-error' : analysis.failure_rate > 10 ? 'text-status-warning' : 'text-status-ok'}`} />
|
||||
<span className={`font-bold text-white ${size >= 2 ? 'text-3xl' : 'text-2xl'}`}>{analysis.failure_rate.toFixed(1)}%</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -465,8 +465,8 @@ function FailureAnalysisWidget({ size = 1 }: { size?: 1 | 2 | 4 }) {
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
<TrendingDown className={`w-4 h-4 ${
|
||||
analysis.trend[analysis.trend.length - 1].failure_rate < analysis.trend[analysis.trend.length - 2].failure_rate
|
||||
? 'text-bambu-green'
|
||||
: 'text-red-400'
|
||||
? 'text-status-ok'
|
||||
: 'text-status-error'
|
||||
}`} />
|
||||
<span className="text-bambu-gray">
|
||||
Last week: {analysis.trend[analysis.trend.length - 1].failure_rate.toFixed(1)}%
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2
-2
@@ -23,8 +23,8 @@
|
||||
|
||||
<!-- Splash screens for iOS -->
|
||||
<link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
|
||||
<script type="module" crossorigin src="/assets/index-SsFUChPG.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-CVDQtTMh.css">
|
||||
<script type="module" crossorigin src="/assets/index-oIN8PllK.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-DVKqcow3.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
Reference in New Issue
Block a user