mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-22 03:49:18 +02:00
Added auto app update; Added maintenance module with notifications
This commit is contained in:
@@ -4,6 +4,7 @@ import { X, Save, Loader2, Send, CheckCircle, XCircle } from 'lucide-react';
|
||||
import { api } from '../api/client';
|
||||
import type { NotificationProvider, NotificationProviderCreate, NotificationProviderUpdate, ProviderType } from '../api/client';
|
||||
import { Button } from './Button';
|
||||
import { Toggle } from './Toggle';
|
||||
|
||||
interface AddNotificationModalProps {
|
||||
provider?: NotificationProvider | null;
|
||||
@@ -350,15 +351,10 @@ export function AddNotificationModal({ provider, onClose }: AddNotificationModal
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<label className="text-sm text-white">Quiet Hours (Do Not Disturb)</label>
|
||||
<label className="relative inline-flex items-center cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={quietHoursEnabled}
|
||||
onChange={(e) => setQuietHoursEnabled(e.target.checked)}
|
||||
className="sr-only peer"
|
||||
/>
|
||||
<div className="w-9 h-5 bg-bambu-dark-tertiary peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-bambu-green"></div>
|
||||
</label>
|
||||
<Toggle
|
||||
checked={quietHoursEnabled}
|
||||
onChange={setQuietHoursEnabled}
|
||||
/>
|
||||
</div>
|
||||
{quietHoursEnabled && (
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState, useEffect, useCallback, useRef } from 'react';
|
||||
import { NavLink, Outlet, useNavigate, useLocation } from 'react-router-dom';
|
||||
import { Printer, Archive, Calendar, BarChart3, Cloud, Settings, Sun, Moon, ChevronLeft, ChevronRight, Keyboard, Github, GripVertical, ArrowUpCircle, type LucideIcon } from 'lucide-react';
|
||||
import { Printer, Archive, Calendar, BarChart3, Cloud, Settings, Sun, Moon, ChevronLeft, ChevronRight, Keyboard, Github, GripVertical, ArrowUpCircle, Wrench, type LucideIcon } from 'lucide-react';
|
||||
import { useTheme } from '../contexts/ThemeContext';
|
||||
import { KeyboardShortcutsModal } from './KeyboardShortcutsModal';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
@@ -19,6 +19,7 @@ export const defaultNavItems: NavItem[] = [
|
||||
{ id: 'queue', to: '/queue', icon: Calendar, label: 'Queue' },
|
||||
{ id: 'stats', to: '/stats', icon: BarChart3, label: 'Statistics' },
|
||||
{ id: 'profiles', to: '/profiles', icon: Cloud, label: 'Profiles' },
|
||||
{ id: 'maintenance', to: '/maintenance', icon: Wrench, label: 'Maintenance' },
|
||||
{ id: 'settings', to: '/settings', icon: Settings, label: 'Settings' },
|
||||
];
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import type { NotificationProvider, NotificationProviderUpdate } from '../api/cl
|
||||
import { Card, CardContent } from './Card';
|
||||
import { Button } from './Button';
|
||||
import { ConfirmModal } from './ConfirmModal';
|
||||
import { Toggle } from './Toggle';
|
||||
|
||||
interface NotificationProviderCardProps {
|
||||
provider: NotificationProvider;
|
||||
@@ -134,6 +135,9 @@ export function NotificationProviderCard({ provider, onEdit }: NotificationProvi
|
||||
{provider.on_filament_low && (
|
||||
<span className="px-2 py-0.5 bg-amber-500/20 text-amber-400 text-xs rounded">Low Filament</span>
|
||||
)}
|
||||
{provider.on_maintenance_due && (
|
||||
<span className="px-2 py-0.5 bg-purple-500/20 text-purple-400 text-xs rounded">Maintenance</span>
|
||||
)}
|
||||
{provider.quiet_hours_enabled && (
|
||||
<span className="px-2 py-0.5 bg-purple-500/20 text-purple-400 text-xs rounded flex items-center gap-1">
|
||||
<Moon className="w-3 h-3" />
|
||||
@@ -204,15 +208,10 @@ export function NotificationProviderCard({ provider, onEdit }: NotificationProvi
|
||||
<p className="text-sm text-white">Enabled</p>
|
||||
<p className="text-xs text-bambu-gray">Send notifications from this provider</p>
|
||||
</div>
|
||||
<label className="relative inline-flex items-center cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={provider.enabled}
|
||||
onChange={(e) => updateMutation.mutate({ enabled: e.target.checked })}
|
||||
className="sr-only peer"
|
||||
/>
|
||||
<div className="w-9 h-5 bg-bambu-dark-tertiary peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-bambu-green"></div>
|
||||
</label>
|
||||
<Toggle
|
||||
checked={provider.enabled}
|
||||
onChange={(checked) => updateMutation.mutate({ enabled: checked })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Print Lifecycle Events */}
|
||||
@@ -221,54 +220,34 @@ export function NotificationProviderCard({ provider, onEdit }: NotificationProvi
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-sm text-white">Print Started</p>
|
||||
<label className="relative inline-flex items-center cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={provider.on_print_start}
|
||||
onChange={(e) => updateMutation.mutate({ on_print_start: e.target.checked })}
|
||||
className="sr-only peer"
|
||||
/>
|
||||
<div className="w-9 h-5 bg-bambu-dark-tertiary peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-bambu-green"></div>
|
||||
</label>
|
||||
<Toggle
|
||||
checked={provider.on_print_start}
|
||||
onChange={(checked) => updateMutation.mutate({ on_print_start: checked })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-sm text-white">Print Completed</p>
|
||||
<label className="relative inline-flex items-center cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={provider.on_print_complete}
|
||||
onChange={(e) => updateMutation.mutate({ on_print_complete: e.target.checked })}
|
||||
className="sr-only peer"
|
||||
/>
|
||||
<div className="w-9 h-5 bg-bambu-dark-tertiary peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-bambu-green"></div>
|
||||
</label>
|
||||
<Toggle
|
||||
checked={provider.on_print_complete}
|
||||
onChange={(checked) => updateMutation.mutate({ on_print_complete: checked })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-sm text-white">Print Failed</p>
|
||||
<label className="relative inline-flex items-center cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={provider.on_print_failed}
|
||||
onChange={(e) => updateMutation.mutate({ on_print_failed: e.target.checked })}
|
||||
className="sr-only peer"
|
||||
/>
|
||||
<div className="w-9 h-5 bg-bambu-dark-tertiary peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-bambu-green"></div>
|
||||
</label>
|
||||
<Toggle
|
||||
checked={provider.on_print_failed}
|
||||
onChange={(checked) => updateMutation.mutate({ on_print_failed: checked })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-sm text-white">Print Stopped</p>
|
||||
<label className="relative inline-flex items-center cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={provider.on_print_stopped}
|
||||
onChange={(e) => updateMutation.mutate({ on_print_stopped: e.target.checked })}
|
||||
className="sr-only peer"
|
||||
/>
|
||||
<div className="w-9 h-5 bg-bambu-dark-tertiary peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-bambu-green"></div>
|
||||
</label>
|
||||
<Toggle
|
||||
checked={provider.on_print_stopped}
|
||||
onChange={(checked) => updateMutation.mutate({ on_print_stopped: checked })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
@@ -276,15 +255,10 @@ export function NotificationProviderCard({ provider, onEdit }: NotificationProvi
|
||||
<p className="text-sm text-white">Progress Milestones</p>
|
||||
<p className="text-xs text-bambu-gray">Notify at 25%, 50%, 75%</p>
|
||||
</div>
|
||||
<label className="relative inline-flex items-center cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={provider.on_print_progress}
|
||||
onChange={(e) => updateMutation.mutate({ on_print_progress: e.target.checked })}
|
||||
className="sr-only peer"
|
||||
/>
|
||||
<div className="w-9 h-5 bg-bambu-dark-tertiary peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-bambu-green"></div>
|
||||
</label>
|
||||
<Toggle
|
||||
checked={provider.on_print_progress}
|
||||
onChange={(checked) => updateMutation.mutate({ on_print_progress: checked })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -294,41 +268,37 @@ export function NotificationProviderCard({ provider, onEdit }: NotificationProvi
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-sm text-white">Printer Offline</p>
|
||||
<label className="relative inline-flex items-center cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={provider.on_printer_offline}
|
||||
onChange={(e) => updateMutation.mutate({ on_printer_offline: e.target.checked })}
|
||||
className="sr-only peer"
|
||||
/>
|
||||
<div className="w-9 h-5 bg-bambu-dark-tertiary peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-bambu-green"></div>
|
||||
</label>
|
||||
<Toggle
|
||||
checked={provider.on_printer_offline}
|
||||
onChange={(checked) => updateMutation.mutate({ on_printer_offline: checked })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-sm text-white">Printer Error</p>
|
||||
<label className="relative inline-flex items-center cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={provider.on_printer_error}
|
||||
onChange={(e) => updateMutation.mutate({ on_printer_error: e.target.checked })}
|
||||
className="sr-only peer"
|
||||
/>
|
||||
<div className="w-9 h-5 bg-bambu-dark-tertiary peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-bambu-green"></div>
|
||||
</label>
|
||||
<Toggle
|
||||
checked={provider.on_printer_error}
|
||||
onChange={(checked) => updateMutation.mutate({ on_printer_error: checked })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-sm text-white">Low Filament</p>
|
||||
<label className="relative inline-flex items-center cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={provider.on_filament_low}
|
||||
onChange={(e) => updateMutation.mutate({ on_filament_low: e.target.checked })}
|
||||
className="sr-only peer"
|
||||
/>
|
||||
<div className="w-9 h-5 bg-bambu-dark-tertiary peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-bambu-green"></div>
|
||||
</label>
|
||||
<Toggle
|
||||
checked={provider.on_filament_low}
|
||||
onChange={(checked) => updateMutation.mutate({ on_filament_low: checked })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-white">Maintenance Due</p>
|
||||
<p className="text-xs text-bambu-gray">Notify when maintenance is needed</p>
|
||||
</div>
|
||||
<Toggle
|
||||
checked={provider.on_maintenance_due ?? false}
|
||||
onChange={(checked) => updateMutation.mutate({ on_maintenance_due: checked })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -339,15 +309,10 @@ export function NotificationProviderCard({ provider, onEdit }: NotificationProvi
|
||||
<Moon className="w-4 h-4 text-purple-400" />
|
||||
<p className="text-sm text-white">Quiet Hours</p>
|
||||
</div>
|
||||
<label className="relative inline-flex items-center cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={provider.quiet_hours_enabled}
|
||||
onChange={(e) => updateMutation.mutate({ quiet_hours_enabled: e.target.checked })}
|
||||
className="sr-only peer"
|
||||
/>
|
||||
<div className="w-9 h-5 bg-bambu-dark-tertiary peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-bambu-green"></div>
|
||||
</label>
|
||||
<Toggle
|
||||
checked={provider.quiet_hours_enabled}
|
||||
onChange={(checked) => updateMutation.mutate({ quiet_hours_enabled: checked })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{provider.quiet_hours_enabled && (
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
interface ToggleProps {
|
||||
checked: boolean;
|
||||
onChange: (checked: boolean) => void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export function Toggle({ checked, onChange, disabled }: ToggleProps) {
|
||||
const handleClick = (e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (!disabled) {
|
||||
onChange(!checked);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
role="switch"
|
||||
aria-checked={checked}
|
||||
disabled={disabled}
|
||||
onClick={handleClick}
|
||||
className={`relative inline-flex w-9 h-5 rounded-full transition-colors flex-shrink-0 focus:outline-none focus:ring-2 focus:ring-bambu-green focus:ring-offset-2 focus:ring-offset-bambu-dark ${
|
||||
disabled
|
||||
? 'bg-bambu-dark-tertiary/50 cursor-not-allowed opacity-50'
|
||||
: checked
|
||||
? 'bg-bambu-green cursor-pointer'
|
||||
: 'bg-bambu-dark-tertiary cursor-pointer hover:bg-bambu-dark-tertiary/80'
|
||||
}`}
|
||||
>
|
||||
<span
|
||||
className={`pointer-events-none absolute top-[2px] left-[2px] w-4 h-4 bg-white rounded-full shadow transition-transform duration-200 ease-in-out ${
|
||||
checked ? 'translate-x-4' : 'translate-x-0'
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user