Browse Source

Add Unassign button to edit spool modal

  Allows removing a spool's AMS slot assignment directly from the
  edit modal, clearing the location column in the inventory table.
maziggy 2 months ago
parent
commit
c52ff1dcee

+ 1 - 0
CHANGELOG.md

@@ -12,6 +12,7 @@ All notable changes to Bambuddy will be documented in this file.
 
 ### Improved
 - **SpoolBuddy AMS Slot Action Picker** — Clicking an AMS slot on the SpoolBuddy AMS page now shows a picker with contextual actions: Configure AMS Slot (set filament preset, K-profile, color), and either Assign Spool / Link to Spoolman (when no spool is mapped) or Unassign / Unlink (when one is). Works with both internal inventory and Spoolman. Previously the slot click went straight to the configure modal with no way to manage spool assignments.
+- **Unassign Button in Edit Spool Modal** — The edit spool modal now has an "Unassign" button next to "Delete Tag" that removes the spool's AMS slot assignment, clearing the location column in the inventory table.
 - **SpoolBuddy Settings Device Tab No Longer Scrolls** — Removed the branding card, folded Device ID into the Device Info card, placed Backend/Auth config and diagnostic buttons side by side in a 2-column layout, removed the redundant online/offline status row from Device Info, and tightened spacing throughout. The Device tab now fits on the small SpoolBuddy touchscreen without scrolling.
 - **Spool Notes in Assign Spool Modal** ([#793](https://github.com/maziggy/bambuddy/issues/793)) — Spool cards in the Assign Spool modal now show the spool's note as a hover tooltip, making it easier to identify spools by tracking IDs or other metadata stored in notes. Works with both internal inventory and Spoolman-synced spools. Requested by @LegionCanadian.
 - **WiFi Safeguard for SpoolBuddy Pi** — The install script now drops an APT hook (`/etc/apt/apt.conf.d/80-preserve-wifi`) that backs up NetworkManager WiFi connections before every `apt upgrade` and restores them if they get wiped. Prevents headless SpoolBuddy Pis from losing WiFi connectivity after Raspberry Pi OS package upgrades (observed with Bookworm kernel/raspi-config updates that clear `/etc/NetworkManager/system-connections/`).

+ 44 - 12
frontend/src/components/SpoolFormModal.tsx

@@ -1,7 +1,7 @@
 import { useState, useEffect, useMemo } from 'react';
-import { useMutation, useQueryClient } from '@tanstack/react-query';
+import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
 import { useTranslation } from 'react-i18next';
-import { X, Loader2, Save, Beaker, Palette, Zap, Tag } from 'lucide-react';
+import { X, Loader2, Save, Beaker, Palette, Zap, Tag, Unlink } from 'lucide-react';
 import { api } from '../api/client';
 import type { InventorySpool, SlicerSetting, SpoolCatalogEntry, LocalPreset } from '../api/client';
 import { Button } from './Button';
@@ -383,6 +383,29 @@ export function SpoolFormModal({
     },
   });
 
+  // Fetch assignment for this spool (to show Unassign button)
+  const { data: assignments } = useQuery({
+    queryKey: ['spool-assignments'],
+    queryFn: () => api.getAssignments(),
+    enabled: isOpen && isEditing,
+  });
+  const spoolAssignment = spool ? assignments?.find(a => a.spool_id === spool.id) : undefined;
+
+  const unassignMutation = useMutation({
+    mutationFn: () => {
+      if (!spoolAssignment) throw new Error('No assignment');
+      return api.unassignSpool(spoolAssignment.printer_id, spoolAssignment.ams_id, spoolAssignment.tray_id);
+    },
+    onSuccess: async () => {
+      await queryClient.invalidateQueries({ queryKey: ['spool-assignments'] });
+      showToast(t('inventory.unassignSuccess', 'Spool unassigned'), 'success');
+      onClose();
+    },
+    onError: (error: Error) => {
+      showToast(error.message, 'error');
+    },
+  });
+
   // Save K-profiles for selected calibrations
   const saveKProfiles = async (spoolId: number) => {
     if (selectedProfiles.size === 0) {
@@ -487,7 +510,7 @@ export function SpoolFormModal({
     }
   };
 
-  const isPending = createMutation.isPending || bulkCreateMutation.isPending || updateMutation.isPending || deleteTagMutation.isPending;
+  const isPending = createMutation.isPending || bulkCreateMutation.isPending || updateMutation.isPending || deleteTagMutation.isPending || unassignMutation.isPending;
 
   return (
     <div className="fixed inset-0 z-50 flex items-center justify-center">
@@ -646,15 +669,24 @@ export function SpoolFormModal({
         {/* Footer */}
         <div className="flex gap-2 p-4 border-t border-bambu-dark-tertiary flex-shrink-0">
           {isEditing && (
-            <Button
-              variant="secondary"
-              onClick={() => deleteTagMutation.mutate()}
-              disabled={isPending || !spool?.tag_uid}
-              className="mr-auto"
-            >
-              <Tag className="w-4 h-4" />
-              {t('inventory.deleteTag', 'Delete Tag')}
-            </Button>
+            <div className="flex gap-2 mr-auto">
+              <Button
+                variant="secondary"
+                onClick={() => deleteTagMutation.mutate()}
+                disabled={isPending || !spool?.tag_uid}
+              >
+                <Tag className="w-4 h-4" />
+                {t('inventory.deleteTag', 'Delete Tag')}
+              </Button>
+              <Button
+                variant="secondary"
+                onClick={() => unassignMutation.mutate()}
+                disabled={isPending || !spoolAssignment}
+              >
+                <Unlink className="w-4 h-4" />
+                {t('inventory.unassignSpool', 'Unassign')}
+              </Button>
+            </div>
           )}
           <div className="flex gap-2 ml-auto">
           <Button variant="secondary" onClick={onClose}>

+ 2 - 1
frontend/src/pages/spoolbuddy/SpoolBuddyAmsPage.tsx

@@ -338,7 +338,8 @@ export function SpoolBuddyAmsPage() {
 
   const openConfigureFromPicker = useCallback(() => {
     if (!slotActionPicker) return;
-    const { tray: _t, location: _l, ...configData } = slotActionPicker;
+    // eslint-disable-next-line @typescript-eslint/no-unused-vars
+    const { tray, location, ...configData } = slotActionPicker;
     setSlotActionPicker(null);
     setConfigureSlotModal(configData);
   }, [slotActionPicker]);

File diff suppressed because it is too large
+ 0 - 0
static/assets/index-DaGDR5jv.js


+ 1 - 1
static/index.html

@@ -23,7 +23,7 @@
 
     <!-- Splash screens for iOS -->
     <link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
-    <script type="module" crossorigin src="/assets/index-CRbRFB2L.js"></script>
+    <script type="module" crossorigin src="/assets/index-DaGDR5jv.js"></script>
     <link rel="stylesheet" crossorigin href="/assets/index-CZLTApPU.css">
   </head>
   <body>

Some files were not shown because too many files changed in this diff