|
@@ -4,10 +4,11 @@ import { useQuery, useQueries, useMutation, useQueryClient } from '@tanstack/rea
|
|
|
import { useTranslation } from 'react-i18next';
|
|
import { useTranslation } from 'react-i18next';
|
|
|
import type { SpoolBuddyOutletContext } from '../../components/spoolbuddy/SpoolBuddyLayout';
|
|
import type { SpoolBuddyOutletContext } from '../../components/spoolbuddy/SpoolBuddyLayout';
|
|
|
import { api, ApiError, type InventorySpool, type Printer, type PrinterStatus } from '../../api/client';
|
|
import { api, ApiError, type InventorySpool, type Printer, type PrinterStatus } from '../../api/client';
|
|
|
-import type { MatchedSpool } from '../../hooks/useSpoolBuddyState';
|
|
|
|
|
|
|
+import type { MatchedSpool, UnknownTagData } from '../../hooks/useSpoolBuddyState';
|
|
|
import { useToast } from '../../contexts/ToastContext';
|
|
import { useToast } from '../../contexts/ToastContext';
|
|
|
import { SpoolIcon } from '../../components/spoolbuddy/SpoolIcon';
|
|
import { SpoolIcon } from '../../components/spoolbuddy/SpoolIcon';
|
|
|
import { SpoolInfoCard, UnknownTagCard } from '../../components/spoolbuddy/SpoolInfoCard';
|
|
import { SpoolInfoCard, UnknownTagCard } from '../../components/spoolbuddy/SpoolInfoCard';
|
|
|
|
|
+import { spoolColorString } from '../../utils/colors';
|
|
|
import { AssignToAmsModal } from '../../components/spoolbuddy/AssignToAmsModal';
|
|
import { AssignToAmsModal } from '../../components/spoolbuddy/AssignToAmsModal';
|
|
|
import { LinkSpoolModal } from '../../components/spoolbuddy/LinkSpoolModal';
|
|
import { LinkSpoolModal } from '../../components/spoolbuddy/LinkSpoolModal';
|
|
|
|
|
|
|
@@ -232,6 +233,9 @@ export function SpoolBuddyDashboard() {
|
|
|
// displayedTagId, so quick-add and link still save it after the spool is
|
|
// displayedTagId, so quick-add and link still save it after the spool is
|
|
|
// lifted off the reader (#984).
|
|
// lifted off the reader (#984).
|
|
|
const [displayedTrayUuid, setDisplayedTrayUuid] = useState<string | null>(null);
|
|
const [displayedTrayUuid, setDisplayedTrayUuid] = useState<string | null>(null);
|
|
|
|
|
+ // What the displayed unknown tag's own blocks say about the roll, kept with
|
|
|
|
|
+ // the card for the same reason, so Quick Add creates that spool (#2685).
|
|
|
|
|
+ const [displayedTagData, setDisplayedTagData] = useState<UnknownTagData | null>(null);
|
|
|
const [displayedWeight, setDisplayedWeight] = useState<number | null>(null);
|
|
const [displayedWeight, setDisplayedWeight] = useState<number | null>(null);
|
|
|
const [hiddenTagId, setHiddenTagId] = useState<string | null>(null);
|
|
const [hiddenTagId, setHiddenTagId] = useState<string | null>(null);
|
|
|
const [showLinkModal, setShowLinkModal] = useState(false);
|
|
const [showLinkModal, setShowLinkModal] = useState(false);
|
|
@@ -243,6 +247,7 @@ export function SpoolBuddyDashboard() {
|
|
|
// Track current tag from state
|
|
// Track current tag from state
|
|
|
const currentTagId = sbState.matchedSpool?.tag_uid ?? sbState.unknownTagUid ?? null;
|
|
const currentTagId = sbState.matchedSpool?.tag_uid ?? sbState.unknownTagUid ?? null;
|
|
|
const currentTrayUuid = sbState.unknownTagUid ? sbState.unknownTrayUuid : null;
|
|
const currentTrayUuid = sbState.unknownTagUid ? sbState.unknownTrayUuid : null;
|
|
|
|
|
+ const currentTagData = sbState.unknownTagUid ? sbState.unknownTagData : null;
|
|
|
const currentWeight = sbState.weight;
|
|
const currentWeight = sbState.weight;
|
|
|
const weightStable = sbState.weightStable;
|
|
const weightStable = sbState.weightStable;
|
|
|
|
|
|
|
@@ -333,9 +338,16 @@ export function SpoolBuddyDashboard() {
|
|
|
if (isDifferentTag || (!isHidden && displayedTagId !== currentTagId)) {
|
|
if (isDifferentTag || (!isHidden && displayedTagId !== currentTagId)) {
|
|
|
setDisplayedTagId(currentTagId);
|
|
setDisplayedTagId(currentTagId);
|
|
|
setDisplayedTrayUuid(currentTrayUuid);
|
|
setDisplayedTrayUuid(currentTrayUuid);
|
|
|
|
|
+ setDisplayedTagData(currentTagData);
|
|
|
setDisplayedWeight(null);
|
|
setDisplayedWeight(null);
|
|
|
setHiddenTagId(null);
|
|
setHiddenTagId(null);
|
|
|
setJustLinkedSpool(null);
|
|
setJustLinkedSpool(null);
|
|
|
|
|
+ } else if (displayedTagId === currentTagId) {
|
|
|
|
|
+ // The same tag read again can carry what an earlier read missed: a
|
|
|
|
|
+ // failed Bambu read sends no tag data or tray UUID. Take them when this
|
|
|
|
|
+ // read has them, but never drop what a good read already gave (#2685).
|
|
|
|
|
+ if (currentTagData) setDisplayedTagData(currentTagData);
|
|
|
|
|
+ if (currentTrayUuid) setDisplayedTrayUuid(currentTrayUuid);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Update weight when stable and card is visible
|
|
// Update weight when stable and card is visible
|
|
@@ -347,12 +359,13 @@ export function SpoolBuddyDashboard() {
|
|
|
if (hiddenTagId) {
|
|
if (hiddenTagId) {
|
|
|
setDisplayedTagId(null);
|
|
setDisplayedTagId(null);
|
|
|
setDisplayedTrayUuid(null);
|
|
setDisplayedTrayUuid(null);
|
|
|
|
|
+ setDisplayedTagData(null);
|
|
|
setHiddenTagId(null);
|
|
setHiddenTagId(null);
|
|
|
setDisplayedWeight(null);
|
|
setDisplayedWeight(null);
|
|
|
setJustLinkedSpool(null);
|
|
setJustLinkedSpool(null);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
- }, [currentTagId, currentTrayUuid, currentWeight, weightStable, displayedTagId, hiddenTagId]);
|
|
|
|
|
|
|
+ }, [currentTagId, currentTrayUuid, currentTagData, currentWeight, weightStable, displayedTagId, hiddenTagId]);
|
|
|
|
|
|
|
|
// Auto-sync weight once when known spool first detected
|
|
// Auto-sync weight once when known spool first detected
|
|
|
|
|
|
|
@@ -427,6 +440,23 @@ export function SpoolBuddyDashboard() {
|
|
|
setQuickAddBusy(true);
|
|
setQuickAddBusy(true);
|
|
|
try {
|
|
try {
|
|
|
const weight = liveWeight ?? displayedWeight;
|
|
const weight = liveWeight ?? displayedWeight;
|
|
|
|
|
+ // A decoded Bambu tag gives the spool AMS sync would create; without one
|
|
|
|
|
+ // it stays a basic PLA entry to edit later (#2685).
|
|
|
|
|
+ const td = displayedTagData;
|
|
|
|
|
+ const fromTag = {
|
|
|
|
|
+ material: td?.material ?? 'PLA',
|
|
|
|
|
+ subtype: td?.subtype ?? null,
|
|
|
|
|
+ color_name: td?.color_name ?? null,
|
|
|
|
|
+ rgba: td?.rgba ?? null,
|
|
|
|
|
+ extra_colors: td?.extra_colors ?? null,
|
|
|
|
|
+ effect_type: td?.effect_type ?? null,
|
|
|
|
|
+ brand: td?.brand ?? null,
|
|
|
|
|
+ label_weight: td?.label_weight ?? 1000,
|
|
|
|
|
+ slicer_filament: td?.slicer_filament ?? null,
|
|
|
|
|
+ slicer_filament_name: td?.slicer_filament_name ?? null,
|
|
|
|
|
+ nozzle_temp_min: td?.nozzle_temp_min ?? null,
|
|
|
|
|
+ nozzle_temp_max: td?.nozzle_temp_max ?? null,
|
|
|
|
|
+ };
|
|
|
if (spoolmanMode) {
|
|
if (spoolmanMode) {
|
|
|
// No tare is sent. The quick-create has no input for one, so the 250
|
|
// No tare is sent. The quick-create has no input for one, so the 250
|
|
|
// that used to go here was a placeholder, not a user's choice -- and
|
|
// that used to go here was a placeholder, not a user's choice -- and
|
|
@@ -434,19 +464,8 @@ export function SpoolBuddyDashboard() {
|
|
|
// which would stamp every spool the kiosk creates and stop it
|
|
// which would stamp every spool the kiosk creates and stop it
|
|
|
// inheriting the filament type's.
|
|
// inheriting the filament type's.
|
|
|
const created = await api.createSpoolmanInventorySpool({
|
|
const created = await api.createSpoolmanInventorySpool({
|
|
|
- material: 'PLA',
|
|
|
|
|
- subtype: null,
|
|
|
|
|
- color_name: null,
|
|
|
|
|
- rgba: null,
|
|
|
|
|
- extra_colors: null,
|
|
|
|
|
- effect_type: null,
|
|
|
|
|
- brand: null,
|
|
|
|
|
- label_weight: 1000,
|
|
|
|
|
|
|
+ ...fromTag,
|
|
|
weight_used: 0,
|
|
weight_used: 0,
|
|
|
- slicer_filament: null,
|
|
|
|
|
- slicer_filament_name: null,
|
|
|
|
|
- nozzle_temp_min: null,
|
|
|
|
|
- nozzle_temp_max: null,
|
|
|
|
|
note: null,
|
|
note: null,
|
|
|
added_full: null,
|
|
added_full: null,
|
|
|
last_used: null,
|
|
last_used: null,
|
|
@@ -467,21 +486,10 @@ export function SpoolBuddyDashboard() {
|
|
|
});
|
|
});
|
|
|
} else {
|
|
} else {
|
|
|
await api.createSpool({
|
|
await api.createSpool({
|
|
|
- material: 'PLA',
|
|
|
|
|
- subtype: null,
|
|
|
|
|
- color_name: null,
|
|
|
|
|
- rgba: null,
|
|
|
|
|
- extra_colors: null,
|
|
|
|
|
- effect_type: null,
|
|
|
|
|
- brand: null,
|
|
|
|
|
- label_weight: 1000,
|
|
|
|
|
- core_weight: 250,
|
|
|
|
|
- core_weight_catalog_id: null,
|
|
|
|
|
|
|
+ ...fromTag,
|
|
|
|
|
+ core_weight: td?.core_weight ?? 250,
|
|
|
|
|
+ core_weight_catalog_id: td?.core_weight_catalog_id ?? null,
|
|
|
weight_used: 0,
|
|
weight_used: 0,
|
|
|
- slicer_filament: null,
|
|
|
|
|
- slicer_filament_name: null,
|
|
|
|
|
- nozzle_temp_min: null,
|
|
|
|
|
- nozzle_temp_max: null,
|
|
|
|
|
note: null,
|
|
note: null,
|
|
|
added_full: null,
|
|
added_full: null,
|
|
|
last_used: null,
|
|
last_used: null,
|
|
@@ -687,6 +695,7 @@ export function SpoolBuddyDashboard() {
|
|
|
<UnknownTagCard
|
|
<UnknownTagCard
|
|
|
tagUid={displayedTagId}
|
|
tagUid={displayedTagId}
|
|
|
scaleWeight={liveWeight ?? displayedWeight}
|
|
scaleWeight={liveWeight ?? displayedWeight}
|
|
|
|
|
+ tagData={displayedTagData}
|
|
|
onLinkSpool={untaggedSpools.length > 0 ? () => setShowLinkModal(true) : undefined}
|
|
onLinkSpool={untaggedSpools.length > 0 ? () => setShowLinkModal(true) : undefined}
|
|
|
onAddToInventory={() => setShowQuickAddModal(true)}
|
|
onAddToInventory={() => setShowQuickAddModal(true)}
|
|
|
onClose={handleCloseSpoolCard}
|
|
onClose={handleCloseSpoolCard}
|
|
@@ -734,6 +743,31 @@ export function SpoolBuddyDashboard() {
|
|
|
{t('spoolbuddy.modal.addToInventory', 'Add to Inventory')}
|
|
{t('spoolbuddy.modal.addToInventory', 'Add to Inventory')}
|
|
|
</h3>
|
|
</h3>
|
|
|
|
|
|
|
|
|
|
+ {displayedTagData ? (
|
|
|
|
|
+ <>
|
|
|
|
|
+ <p className="text-sm text-zinc-400 mb-3">
|
|
|
|
|
+ {t('spoolbuddy.modal.quickAddFromTag', 'Adds this spool with the details read from its Bambu tag. You can edit them later in Bambuddy.')}
|
|
|
|
|
+ </p>
|
|
|
|
|
+ <div className="flex items-center gap-3 p-3 bg-zinc-900/60 border border-zinc-700 rounded-lg mb-1">
|
|
|
|
|
+ <SpoolIcon color={spoolColorString(displayedTagData.rgba)} isEmpty={false} size={40} spool={displayedTagData} />
|
|
|
|
|
+ <div className="min-w-0">
|
|
|
|
|
+ <p className="text-sm font-medium text-zinc-100 truncate">
|
|
|
|
|
+ {[displayedTagData.brand, displayedTagData.material, displayedTagData.subtype].filter(Boolean).join(' ')}
|
|
|
|
|
+ </p>
|
|
|
|
|
+ <p className="text-xs text-zinc-400 truncate">
|
|
|
|
|
+ {[
|
|
|
|
|
+ displayedTagData.color_name,
|
|
|
|
|
+ `${displayedTagData.label_weight} g`,
|
|
|
|
|
+ displayedTagData.diameter ? `${displayedTagData.diameter} mm` : null,
|
|
|
|
|
+ ]
|
|
|
|
|
+ .filter(Boolean)
|
|
|
|
|
+ .join(' · ')}
|
|
|
|
|
+ </p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </>
|
|
|
|
|
+ ) : (
|
|
|
|
|
+ <>
|
|
|
{/* Hint */}
|
|
{/* Hint */}
|
|
|
<div className="flex gap-2.5 p-3 bg-amber-500/10 border border-amber-500/20 rounded-lg mb-4">
|
|
<div className="flex gap-2.5 p-3 bg-amber-500/10 border border-amber-500/20 rounded-lg mb-4">
|
|
|
<svg className="w-5 h-5 text-amber-500 shrink-0 mt-0.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<svg className="w-5 h-5 text-amber-500 shrink-0 mt-0.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
@@ -747,6 +781,8 @@ export function SpoolBuddyDashboard() {
|
|
|
<p className="text-sm text-zinc-400 mb-1">
|
|
<p className="text-sm text-zinc-400 mb-1">
|
|
|
{t('spoolbuddy.modal.quickAddDesc', 'This will create a basic PLA spool entry with this NFC tag. You can edit the details later in Bambuddy.')}
|
|
{t('spoolbuddy.modal.quickAddDesc', 'This will create a basic PLA spool entry with this NFC tag. You can edit the details later in Bambuddy.')}
|
|
|
</p>
|
|
</p>
|
|
|
|
|
+ </>
|
|
|
|
|
+ )}
|
|
|
<p className="text-xs text-zinc-500 font-mono mb-5">{displayedTagId}</p>
|
|
<p className="text-xs text-zinc-500 font-mono mb-5">{displayedTagId}</p>
|
|
|
|
|
|
|
|
<div className="flex gap-3">
|
|
<div className="flex gap-3">
|
|
@@ -761,7 +797,11 @@ export function SpoolBuddyDashboard() {
|
|
|
disabled={quickAddBusy}
|
|
disabled={quickAddBusy}
|
|
|
className="flex-1 px-4 py-2.5 rounded-lg text-sm font-medium bg-green-600 text-white hover:bg-green-700 disabled:opacity-50 transition-colors min-h-[44px]"
|
|
className="flex-1 px-4 py-2.5 rounded-lg text-sm font-medium bg-green-600 text-white hover:bg-green-700 disabled:opacity-50 transition-colors min-h-[44px]"
|
|
|
>
|
|
>
|
|
|
- {quickAddBusy ? t('common.saving', 'Saving...') : t('spoolbuddy.modal.addAnyway', 'Add Anyway')}
|
|
|
|
|
|
|
+ {quickAddBusy
|
|
|
|
|
+ ? t('common.saving', 'Saving...')
|
|
|
|
|
+ : displayedTagData
|
|
|
|
|
+ ? t('common.add', 'Add')
|
|
|
|
|
+ : t('spoolbuddy.modal.addAnyway', 'Add Anyway')}
|
|
|
</button>
|
|
</button>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|