mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-07 23:01:06 +02:00
Added power switch dropdown to printer card for offline printers
This commit is contained in:
@@ -17,6 +17,7 @@ import {
|
||||
PowerOff,
|
||||
Zap,
|
||||
Wrench,
|
||||
ChevronDown,
|
||||
} from 'lucide-react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { api } from '../api/client';
|
||||
@@ -710,11 +711,81 @@ function AddPrinterModal({
|
||||
);
|
||||
}
|
||||
|
||||
// Component to check if a printer is offline (for power dropdown)
|
||||
function usePrinterOfflineStatus(printerId: number) {
|
||||
const { data: status } = useQuery({
|
||||
queryKey: ['printerStatus', printerId],
|
||||
queryFn: () => api.getPrinterStatus(printerId),
|
||||
refetchInterval: 30000,
|
||||
});
|
||||
return !status?.connected;
|
||||
}
|
||||
|
||||
// Power dropdown item for an offline printer
|
||||
function PowerDropdownItem({
|
||||
printer,
|
||||
plug,
|
||||
onPowerOn,
|
||||
isPowering,
|
||||
}: {
|
||||
printer: Printer;
|
||||
plug: { id: number; name: string };
|
||||
onPowerOn: (plugId: number) => void;
|
||||
isPowering: boolean;
|
||||
}) {
|
||||
const isOffline = usePrinterOfflineStatus(printer.id);
|
||||
|
||||
// Fetch plug status
|
||||
const { data: plugStatus } = useQuery({
|
||||
queryKey: ['smartPlugStatus', plug.id],
|
||||
queryFn: () => api.getSmartPlugStatus(plug.id),
|
||||
refetchInterval: 10000,
|
||||
});
|
||||
|
||||
// Only show if printer is offline
|
||||
if (!isOffline) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between px-3 py-2 hover:bg-gray-100 dark:hover:bg-bambu-dark-tertiary">
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
<span className="text-sm text-gray-900 dark:text-white truncate">{printer.name}</span>
|
||||
{plugStatus && (
|
||||
<span
|
||||
className={`text-xs px-1.5 py-0.5 rounded ${
|
||||
plugStatus.state === 'ON'
|
||||
? 'bg-bambu-green/20 text-bambu-green'
|
||||
: 'bg-red-500/20 text-red-400'
|
||||
}`}
|
||||
>
|
||||
{plugStatus.state || '?'}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
onClick={() => onPowerOn(plug.id)}
|
||||
disabled={isPowering || plugStatus?.state === 'ON'}
|
||||
className={`px-2 py-1 text-xs rounded transition-colors flex items-center gap-1 ${
|
||||
plugStatus?.state === 'ON'
|
||||
? 'bg-bambu-green/20 text-bambu-green cursor-default'
|
||||
: 'bg-bambu-green/20 text-bambu-green hover:bg-bambu-green hover:text-white'
|
||||
}`}
|
||||
>
|
||||
<Power className="w-3 h-3" />
|
||||
{isPowering ? '...' : 'On'}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function PrintersPage() {
|
||||
const [showAddModal, setShowAddModal] = useState(false);
|
||||
const [hideDisconnected, setHideDisconnected] = useState(() => {
|
||||
return localStorage.getItem('hideDisconnectedPrinters') === 'true';
|
||||
});
|
||||
const [showPowerDropdown, setShowPowerDropdown] = useState(false);
|
||||
const [poweringOn, setPoweringOn] = useState<number | null>(null);
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { data: printers, isLoading } = useQuery({
|
||||
@@ -722,6 +793,12 @@ export function PrintersPage() {
|
||||
queryFn: api.getPrinters,
|
||||
});
|
||||
|
||||
// Fetch all smart plugs to know which printers have them
|
||||
const { data: smartPlugs } = useQuery({
|
||||
queryKey: ['smart-plugs'],
|
||||
queryFn: api.getSmartPlugs,
|
||||
});
|
||||
|
||||
// Fetch maintenance overview for all printers to show badges
|
||||
const { data: maintenanceOverview } = useQuery({
|
||||
queryKey: ['maintenanceOverview'],
|
||||
@@ -742,6 +819,17 @@ export function PrintersPage() {
|
||||
{} as Record<number, PrinterMaintenanceInfo>
|
||||
) || {};
|
||||
|
||||
// Create a map of printer_id -> smart plug
|
||||
const smartPlugByPrinter = smartPlugs?.reduce(
|
||||
(acc, plug) => {
|
||||
if (plug.printer_id) {
|
||||
acc[plug.printer_id] = plug;
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{} as Record<number, typeof smartPlugs[0]>
|
||||
) || {};
|
||||
|
||||
const addMutation = useMutation({
|
||||
mutationFn: api.createPrinter,
|
||||
onSuccess: () => {
|
||||
@@ -750,6 +838,17 @@ export function PrintersPage() {
|
||||
},
|
||||
});
|
||||
|
||||
const powerOnMutation = useMutation({
|
||||
mutationFn: (plugId: number) => api.controlSmartPlug(plugId, 'on'),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['smart-plugs'] });
|
||||
setPoweringOn(null);
|
||||
},
|
||||
onError: () => {
|
||||
setPoweringOn(null);
|
||||
},
|
||||
});
|
||||
|
||||
const toggleHideDisconnected = () => {
|
||||
const newValue = !hideDisconnected;
|
||||
setHideDisconnected(newValue);
|
||||
@@ -773,6 +872,50 @@ export function PrintersPage() {
|
||||
/>
|
||||
Hide offline
|
||||
</label>
|
||||
{/* Power dropdown for offline printers with smart plugs */}
|
||||
{hideDisconnected && Object.keys(smartPlugByPrinter).length > 0 && (
|
||||
<div className="relative">
|
||||
<button
|
||||
onClick={() => setShowPowerDropdown(!showPowerDropdown)}
|
||||
className="flex items-center gap-1.5 px-3 py-1.5 text-sm bg-white dark:bg-bambu-dark-secondary border border-gray-200 dark:border-bambu-dark-tertiary rounded-lg text-gray-600 dark:text-bambu-gray hover:text-gray-900 dark:hover:text-white hover:border-bambu-green transition-colors"
|
||||
>
|
||||
<Power className="w-4 h-4" />
|
||||
Power On
|
||||
<ChevronDown className={`w-3 h-3 transition-transform ${showPowerDropdown ? 'rotate-180' : ''}`} />
|
||||
</button>
|
||||
{showPowerDropdown && (
|
||||
<>
|
||||
{/* Backdrop to close dropdown */}
|
||||
<div
|
||||
className="fixed inset-0 z-10"
|
||||
onClick={() => setShowPowerDropdown(false)}
|
||||
/>
|
||||
<div className="absolute right-0 mt-2 w-56 bg-white dark:bg-bambu-dark-secondary border border-gray-200 dark:border-bambu-dark-tertiary rounded-lg shadow-lg z-20 py-1">
|
||||
<div className="px-3 py-2 text-xs text-gray-500 dark:text-bambu-gray border-b border-gray-200 dark:border-bambu-dark-tertiary">
|
||||
Offline printers with smart plugs
|
||||
</div>
|
||||
{printers?.filter(p => smartPlugByPrinter[p.id]).map(printer => (
|
||||
<PowerDropdownItem
|
||||
key={printer.id}
|
||||
printer={printer}
|
||||
plug={smartPlugByPrinter[printer.id]}
|
||||
onPowerOn={(plugId) => {
|
||||
setPoweringOn(plugId);
|
||||
powerOnMutation.mutate(plugId);
|
||||
}}
|
||||
isPowering={poweringOn === smartPlugByPrinter[printer.id]?.id}
|
||||
/>
|
||||
))}
|
||||
{printers?.filter(p => smartPlugByPrinter[p.id]).length === 0 && (
|
||||
<div className="px-3 py-2 text-sm text-bambu-gray">
|
||||
No printers with smart plugs
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<Button onClick={() => setShowAddModal(true)}>
|
||||
<Plus className="w-4 h-4" />
|
||||
Add Printer
|
||||
|
||||
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
@@ -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-DRrbGWbR.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-CVJMI5JU.css">
|
||||
<script type="module" crossorigin src="/assets/index-zCm77ErN.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-Cx4S-qVJ.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
Reference in New Issue
Block a user