mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-18 21:47:39 +02:00
Merge branch '0.2.1b' into refactor-all-pages
This commit is contained in:
@@ -177,7 +177,7 @@ export function AssignSpoolModal({ isOpen, onClose, printerId, amsId, trayId, tr
|
||||
<p className="text-xs text-bambu-gray">
|
||||
{spool.color_name || ''}
|
||||
{spool.label_weight ? ` - ${spool.label_weight}g` : ''}
|
||||
{spool.weight_used > 0 ? ` (${Math.round(spool.weight_used)}g used)` : ''}
|
||||
{spool.label_weight ? ` (${Math.max(0, Math.round(spool.label_weight - spool.weight_used))}g ${t('ams.remainingUnit')})` : ''}
|
||||
</p>
|
||||
</div>
|
||||
{selectedSpoolId === spool.id && (
|
||||
|
||||
@@ -24,7 +24,7 @@ interface SpoolmanConfig {
|
||||
interface InventoryConfig {
|
||||
onAssignSpool?: () => void;
|
||||
onUnassignSpool?: () => void;
|
||||
assignedSpool?: { id: number; material: string; brand: string | null; color_name: string | null } | null;
|
||||
assignedSpool?: { id: number; material: string; brand: string | null; color_name: string | null; remainingWeightGrams?: number | null } | null;
|
||||
}
|
||||
|
||||
interface ConfigureSlotConfig {
|
||||
@@ -148,6 +148,7 @@ export function FilamentHoverCard({ data, children, disabled, className = '', sp
|
||||
};
|
||||
|
||||
const colorHex = data.colorHex ? `#${data.colorHex.replace('#', '')}` : null;
|
||||
const assignedRemainingWeight = inventory?.assignedSpool?.remainingWeightGrams ?? null;
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -238,7 +239,10 @@ export function FilamentHoverCard({ data, children, disabled, className = '', sp
|
||||
{t('ams.fill')}
|
||||
</span>
|
||||
<span className="text-xs text-white font-semibold flex items-center gap-1">
|
||||
{data.fillLevel !== null ? `${data.fillLevel}%` : '—'}
|
||||
<span>{data.fillLevel !== null ? `${data.fillLevel}%` : '—'}</span>
|
||||
{assignedRemainingWeight !== null && data.fillLevel !== null && (
|
||||
<span className="text-[9px] text-bambu-gray font-normal">• {assignedRemainingWeight}g</span>
|
||||
)}
|
||||
{data.fillSource === 'spoolman' && data.fillLevel !== null && (
|
||||
<span className="text-[9px] text-bambu-gray font-normal">{t('spoolman.fillSourceLabel')}</span>
|
||||
)}
|
||||
|
||||
@@ -263,17 +263,29 @@ export function ModelViewerModal({ archiveId, libraryFileId, title, fileType, on
|
||||
|
||||
const canOpenInSlicer = isLibrary ? (fileType || '').toLowerCase() === '3mf' : true;
|
||||
|
||||
const handleOpenInSlicer = () => {
|
||||
const handleOpenInSlicer = async () => {
|
||||
if (!canOpenInSlicer) return;
|
||||
// URL must include .3mf filename for Bambu Studio to recognize the format
|
||||
const filename = title || 'model';
|
||||
if (isLibrary) {
|
||||
const downloadUrl = `${window.location.origin}${api.getLibraryFileDownloadUrl(libraryFileId!)}`;
|
||||
openInSlicer(downloadUrl, preferredSlicer);
|
||||
return;
|
||||
try {
|
||||
if (isLibrary) {
|
||||
const { token } = await api.createLibrarySlicerToken(libraryFileId!);
|
||||
const path = api.getLibrarySlicerDownloadUrl(libraryFileId!, token, filename);
|
||||
openInSlicer(`${window.location.origin}${path}`, preferredSlicer);
|
||||
} else {
|
||||
const { token } = await api.createArchiveSlicerToken(archiveId!);
|
||||
const path = api.getArchiveSlicerDownloadUrl(archiveId!, token, filename);
|
||||
openInSlicer(`${window.location.origin}${path}`, preferredSlicer);
|
||||
}
|
||||
} catch {
|
||||
// Fallback to direct URL (works when auth is disabled)
|
||||
if (isLibrary) {
|
||||
const downloadUrl = `${window.location.origin}${api.getLibraryFileDownloadUrl(libraryFileId!)}`;
|
||||
openInSlicer(downloadUrl, preferredSlicer);
|
||||
} else {
|
||||
const downloadUrl = `${window.location.origin}${api.getArchiveForSlicer(archiveId!, filename)}`;
|
||||
openInSlicer(downloadUrl, preferredSlicer);
|
||||
}
|
||||
}
|
||||
const downloadUrl = `${window.location.origin}${api.getArchiveForSlicer(archiveId!, filename)}`;
|
||||
openInSlicer(downloadUrl, preferredSlicer);
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -320,7 +320,7 @@ export function SpoolFormModal({ isOpen, onClose, spool, printersWithCalibration
|
||||
if (!validation.isValid) {
|
||||
setErrors(validation.errors);
|
||||
// Switch to filament tab if there are errors there
|
||||
if (validation.errors.slicer_filament || validation.errors.material) {
|
||||
if (validation.errors.slicer_filament || validation.errors.material || validation.errors.brand || validation.errors.subtype) {
|
||||
setActiveTab('filament');
|
||||
}
|
||||
return;
|
||||
@@ -438,6 +438,12 @@ export function SpoolFormModal({ isOpen, onClose, spool, printersWithCalibration
|
||||
{errors.material && (
|
||||
<p className="mt-1 text-xs text-red-400">{errors.material}</p>
|
||||
)}
|
||||
{errors.brand && (
|
||||
<p className="mt-1 text-xs text-red-400">{errors.brand}</p>
|
||||
)}
|
||||
{errors.subtype && (
|
||||
<p className="mt-1 text-xs text-red-400">{errors.subtype}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Color Section */}
|
||||
|
||||
@@ -161,7 +161,7 @@ export function FilamentSection({
|
||||
|
||||
{/* Brand (dropdown with search) */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-bambu-gray mb-1">{t('inventory.brand')}</label>
|
||||
<label className="block text-sm font-medium text-bambu-gray mb-1">{t('inventory.brand')} *</label>
|
||||
<div className="relative" ref={brandRef}>
|
||||
<input
|
||||
type="text"
|
||||
@@ -221,7 +221,7 @@ export function FilamentSection({
|
||||
|
||||
{/* Variant / Subtype */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-bambu-gray mb-1">{t('inventory.subtype')}</label>
|
||||
<label className="block text-sm font-medium text-bambu-gray mb-1">{t('inventory.subtype')} *</label>
|
||||
<div className="relative" ref={subtypeRef}>
|
||||
<input
|
||||
type="text"
|
||||
|
||||
@@ -117,6 +117,14 @@ export function validateForm(formData: SpoolFormData): ValidationResult {
|
||||
errors.material = 'Material is required';
|
||||
}
|
||||
|
||||
if (!formData.brand) {
|
||||
errors.brand = 'Brand is required';
|
||||
}
|
||||
|
||||
if (!formData.subtype) {
|
||||
errors.subtype = 'Subtype is required';
|
||||
}
|
||||
|
||||
return {
|
||||
isValid: Object.keys(errors).length === 0,
|
||||
errors,
|
||||
|
||||
Reference in New Issue
Block a user