Skip to content

Commit a4f3ead

Browse files
committed
Add optional spool location display on dashboard (#56)
Add a Dashboard Display setting to show the Spoolman location field on spool cards. Off by default, users opt in via Settings page. Only displays for spools that have a location value set.
1 parent 3940e4a commit a4f3ead

5 files changed

Lines changed: 83 additions & 4 deletions

File tree

app/src/app/api/settings/route.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,9 @@ export async function GET() {
4545
}
4646
}
4747

48-
// Fetch optional QR base URL override
48+
// Fetch optional settings
4949
const qrBaseUrlSetting = await prisma.settings.findUnique({ where: { key: 'qr_base_url' } });
50+
const showLocationSetting = await prisma.settings.findUnique({ where: { key: 'show_spool_location' } });
5051

5152
return NextResponse.json({
5253
embeddedMode: false,
@@ -61,6 +62,7 @@ export async function GET() {
6162
connected: true,
6263
} : null,
6364
qrBaseUrl: qrBaseUrlSetting?.value || '',
65+
showSpoolLocation: showLocationSetting?.value === 'true',
6466
});
6567
}
6668

@@ -238,8 +240,9 @@ export async function GET() {
238240
}
239241
// else: null — HA hasn't been set up yet (first startup)
240242

241-
// Fetch optional QR base URL override
243+
// Fetch optional settings
242244
const qrBaseUrlSetting = await prisma.settings.findUnique({ where: { key: 'qr_base_url' } });
245+
const showLocationSetting = await prisma.settings.findUnique({ where: { key: 'show_spool_location' } });
243246

244247
return NextResponse.json({
245248
embeddedMode,
@@ -250,6 +253,7 @@ export async function GET() {
250253
connected: true,
251254
} : null,
252255
qrBaseUrl: qrBaseUrlSetting?.value || '',
256+
showSpoolLocation: showLocationSetting?.value === 'true',
253257
});
254258
} catch (error) {
255259
console.error('Error fetching settings:', error);
@@ -323,6 +327,16 @@ export async function POST(request: NextRequest) {
323327
return NextResponse.json({ success: true });
324328
}
325329

330+
if (type === 'show_spool_location') {
331+
const enabled = body.enabled === true;
332+
await prisma.settings.upsert({
333+
where: { key: 'show_spool_location' },
334+
create: { key: 'show_spool_location', value: String(enabled) },
335+
update: { value: String(enabled) },
336+
});
337+
return NextResponse.json({ success: true });
338+
}
339+
326340
if (type === 'qr_base_url') {
327341
// Save QR code base URL override
328342
const qrBaseUrl = (url || '').trim().replace(/\/+$/, '');

app/src/app/page.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ interface PrinterWithSpools extends HAPrinter {
4040
interface Settings {
4141
homeassistant: { url: string; connected: boolean } | null;
4242
spoolman: { url: string; connected: boolean } | null;
43+
showSpoolLocation?: boolean;
4344
}
4445

4546
export default function Dashboard() {
@@ -474,6 +475,7 @@ export default function Dashboard() {
474475
spools={spools}
475476
onSpoolAssign={handleSpoolAssign}
476477
onSpoolUnassign={handleSpoolUnassign}
478+
showSpoolLocation={settings?.showSpoolLocation}
477479
/>
478480
))}
479481
</div>

app/src/app/settings/page.tsx

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ function SettingsContent() {
7878
const [enabledFilters, setEnabledFilters] = useState<string[]>([]);
7979
const [savingFilters, setSavingFilters] = useState(false);
8080

81+
// Dashboard display settings
82+
const [showSpoolLocation, setShowSpoolLocation] = useState(false);
83+
8184
// QR base URL state
8285
const [qrBaseUrl, setQrBaseUrl] = useState('');
8386
const [savingQrUrl, setSavingQrUrl] = useState(false);
@@ -158,6 +161,9 @@ function SettingsContent() {
158161
if (data.qrBaseUrl !== undefined) {
159162
setQrBaseUrl(data.qrBaseUrl);
160163
}
164+
if (data.showSpoolLocation !== undefined) {
165+
setShowSpoolLocation(data.showSpoolLocation);
166+
}
161167
} catch {
162168
toast.error('Failed to load settings');
163169
} finally {
@@ -810,6 +816,53 @@ function SettingsContent() {
810816
</CardContent>
811817
</Card>
812818

819+
{/* Dashboard Display Settings */}
820+
{settings?.spoolman && (
821+
<>
822+
<Separator />
823+
<Card>
824+
<CardHeader>
825+
<CardTitle>Dashboard Display</CardTitle>
826+
<CardDescription>
827+
Configure what information is shown on the dashboard spool cards.
828+
</CardDescription>
829+
</CardHeader>
830+
<CardContent>
831+
<div className="flex items-center space-x-3">
832+
<Checkbox
833+
id="show-spool-location"
834+
checked={showSpoolLocation}
835+
onCheckedChange={async (checked) => {
836+
const enabled = checked === true;
837+
setShowSpoolLocation(enabled);
838+
try {
839+
const res = await fetch('/api/settings', {
840+
method: 'POST',
841+
headers: { 'Content-Type': 'application/json' },
842+
body: JSON.stringify({ type: 'show_spool_location', enabled }),
843+
});
844+
if (!res.ok) throw new Error();
845+
toast.success(enabled ? 'Spool location enabled on dashboard' : 'Spool location hidden on dashboard');
846+
} catch {
847+
setShowSpoolLocation(!enabled);
848+
toast.error('Failed to save setting');
849+
}
850+
}}
851+
/>
852+
<div>
853+
<Label htmlFor="show-spool-location" className="text-sm font-medium cursor-pointer">
854+
Show spool location
855+
</Label>
856+
<p className="text-xs text-muted-foreground">
857+
Display the Spoolman location field on each spool card (e.g., shelf, dry box, bin number)
858+
</p>
859+
</div>
860+
</div>
861+
</CardContent>
862+
</Card>
863+
</>
864+
)}
865+
813866
{/* Spool Filter Configuration */}
814867
{settings?.spoolman && (
815868
<>

app/src/components/dashboard/printer-card.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,10 @@ interface PrinterCardProps {
4242
spools: Spool[];
4343
onSpoolAssign: (trayId: string, spoolId: number) => void;
4444
onSpoolUnassign: (spoolId: number) => void;
45+
showSpoolLocation?: boolean;
4546
}
4647

47-
export function PrinterCard({ printer, spools, onSpoolAssign, onSpoolUnassign }: PrinterCardProps) {
48+
export function PrinterCard({ printer, spools, onSpoolAssign, onSpoolUnassign, showSpoolLocation }: PrinterCardProps) {
4849
return (
4950
<Card className="w-full">
5051
<CardHeader className="pb-3">
@@ -69,6 +70,7 @@ export function PrinterCard({ printer, spools, onSpoolAssign, onSpoolUnassign }:
6970
onAssign={(spoolId) => onSpoolAssign(tray.unique_id || tray.entity_id, spoolId)}
7071
onUnassign={onSpoolUnassign}
7172
mismatch={tray.mismatch}
73+
showLocation={showSpoolLocation}
7274
/>
7375
))}
7476
</div>
@@ -92,6 +94,7 @@ export function PrinterCard({ printer, spools, onSpoolAssign, onSpoolUnassign }:
9294
onSpoolAssign(extSpool.unique_id || extSpool.entity_id, spoolId);
9395
}}
9496
onUnassign={onSpoolUnassign}
97+
showLocation={showSpoolLocation}
9598
/>
9699
))}
97100
</div>

app/src/components/dashboard/tray-slot.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ interface TraySlotProps {
6161
onAssign: (spoolId: number) => void;
6262
onUnassign?: (spoolId: number) => void;
6363
mismatch?: MismatchInfo;
64+
showLocation?: boolean;
6465
}
6566

6667
/**
@@ -101,7 +102,7 @@ function sortSpools(spools: Spool[], sortBy: SortBy): Spool[] {
101102
});
102103
}
103104

104-
export function TraySlot({ tray, assignedSpool, spools, onAssign, onUnassign, mismatch }: TraySlotProps) {
105+
export function TraySlot({ tray, assignedSpool, spools, onAssign, onUnassign, mismatch, showLocation }: TraySlotProps) {
105106
const [open, setOpen] = useState(false);
106107
const [filters, setFilters] = useState<Record<string, string | null>>({});
107108
const [enabledFields, setEnabledFields] = useState<FilterField[]>([]);
@@ -233,6 +234,12 @@ export function TraySlot({ tray, assignedSpool, spools, onAssign, onUnassign, mi
233234
<span className="text-[9px] font-medium text-muted-foreground uppercase">Vendor:</span>
234235
<span className="text-xs font-medium truncate">{assignedSpool.filament.vendor?.name || 'Unknown'}</span>
235236
</div>
237+
{showLocation && assignedSpool.location && (
238+
<div className="flex items-baseline gap-1">
239+
<span className="text-[9px] font-medium text-muted-foreground uppercase">Location:</span>
240+
<span className="text-xs font-medium truncate">{assignedSpool.location}</span>
241+
</div>
242+
)}
236243
</div>
237244

238245
{/* Footer: spool ID and weight */}

0 commit comments

Comments
 (0)