mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-07 14:41:24 +02:00
New setting "Auto-add unknown RFID spools" under Settings -> Filament -> Filament Tracking,
default ON for back-compat. When turned off, the backend stops auto-creating an inventory
record for an unknown RFID tag and instead broadcasts an unknown_tag WS event that pops
a global confirmation modal in the Bambuddy UI showing the printer / AMS-X label / slot /
material / colour. Add or Cancel; no nag on every MQTT push.
Backend
- Module-level _unknown_tag_last_broadcast dict dedupes per (printer, slot, tag). Set is
committed AFTER ws_manager.broadcast() returns so a crashed broadcast doesn't poison
the dedup and permanently silence the slot.
- Empty-slot MQTT push clears that slot's entry, so remove+reinsert reliably re-prompts.
- Successful matches via get_spool_by_tag / find_matching_untagged_spool / create_spool
also clear the entry so a future tag swap re-prompts.
- Tray data (tray_type, tray_color, tray_sub_brands, tray_count) shipped in the WS payload
directly so the modal renders the real material / colour instead of relying on the
React Query cache that lags the WS event by several seconds.
- Two new endpoints back the modal's confirm action:
POST /api/v1/inventory/spools/from-slot (INVENTORY_UPDATE)
POST /api/v1/spoolman/spools/from-slot (FILAMENTS_UPDATE)
Both look up the slot's tray data server-side and create + auto-assign atomically.
- Spoolman /from-slot now raises HTTP 500 when the slot-assignment INSERT fails instead
of returning success while the DB rolled back the binding.
- sync_ams_tray gained an optional auto_add_unknown_rfid kwarg (default True so existing
callers are unaffected); auto-sync and both manual sync routes thread the setting.
Frontend
- useUnknownTagPrompt hook listens for the unknown-tag CustomEvent, reads the tray fields
out of the event detail, and feeds a single-modal queue. No long-lived dismissed set;
the backend dedup handles spam suppression.
- UnknownSpoolModal wraps the existing ConfirmModal with a material + colour-swatch
preview block.
- Mounted in Layout.tsx alongside useSponsorPrompt so SpoolBuddy kiosk / login / setup
routes are excluded.
- getAmsLabel moved to utils/amsHelpers.ts; ConfigureAmsSlotModal.tsx and PrintersPage.tsx
both import the shared version (canonical AMS-A / HT-A / External labels).
- AppSettings TS interface gained spoolman_enabled, auto_add_unknown_rfid, spoolman_url
so the runtime cast in the hook is no longer needed.
- SpoolmanSettings.tsx gets a new toggle row in the Filament Tracking card, visible in
both built-in and Spoolman branches; auto-save + toast already wired.
52 lines
1.9 KiB
TypeScript
52 lines
1.9 KiB
TypeScript
import { useTranslation } from 'react-i18next';
|
|
import { ConfirmModal } from './ConfirmModal';
|
|
import { getSwatchStyle } from '../utils/colors';
|
|
import type { UnknownSpoolPrompt } from '../hooks/useUnknownTagPrompt';
|
|
|
|
interface UnknownSpoolModalProps {
|
|
prompt: UnknownSpoolPrompt | null;
|
|
isPending: boolean;
|
|
onConfirm: () => void;
|
|
onCancel: () => void;
|
|
}
|
|
|
|
export function UnknownSpoolModal({ prompt, isPending, onConfirm, onCancel }: UnknownSpoolModalProps) {
|
|
const { t } = useTranslation();
|
|
if (!prompt) return null;
|
|
|
|
const location = `${prompt.printer_name} • ${prompt.ams_label} • ${prompt.slot_label}`;
|
|
const swatchStyle = prompt.color_hex ? getSwatchStyle(prompt.color_hex) : undefined;
|
|
|
|
return (
|
|
<ConfirmModal
|
|
title={t('inventory.unknownSpoolTitle')}
|
|
message={t('inventory.unknownSpoolMessage', { location })}
|
|
confirmText={t('inventory.addToInventory')}
|
|
cancelText={t('common.cancel')}
|
|
variant="default"
|
|
isLoading={isPending}
|
|
loadingText={t('inventory.addToInventoryPending')}
|
|
onConfirm={onConfirm}
|
|
onCancel={onCancel}
|
|
>
|
|
<div className="flex items-center gap-3 p-3 rounded-lg bg-bambu-dark-secondary border border-bambu-dark-tertiary">
|
|
{swatchStyle && (
|
|
<div
|
|
className="w-8 h-8 rounded-full border border-black/20 flex-shrink-0"
|
|
style={swatchStyle}
|
|
aria-label={prompt.color_hex ?? undefined}
|
|
/>
|
|
)}
|
|
<div className="min-w-0 flex-1">
|
|
<p className="text-white text-sm font-medium truncate">
|
|
{prompt.brand ? `${prompt.brand} ${prompt.material ?? ''}`.trim() : prompt.material ?? '—'}
|
|
</p>
|
|
{prompt.color_hex && (
|
|
<p className="text-xs text-bambu-gray font-mono uppercase">#{prompt.color_hex.replace(/^#/, '').slice(0, 6)}</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</ConfirmModal>
|
|
);
|
|
}
|