Просмотр исходного кода

Fix inventory date format and AMS-HT slot labels (#463)

Two bugs in the inventory page:

1. Dates always displayed as DD/MM/YY regardless of the user's date
   format setting. The page had a local formatDate() hardcoding en-GB
   locale. Replaced with the shared formatDateOnly() from utils/date.ts.

2. AMS-HT slot locations showed accented characters (e.g., "Á1") instead
   of proper labels (e.g., "HT-A"). The code used String.fromCharCode(65
   + ams_id), which produces garbled chars for AMS-HT IDs (128+). Now
   uses the shared formatSlotLabel() from utils/amsHelpers.ts.
maziggy 3 месяцев назад
Родитель
Сommit
40403ae584
4 измененных файлов с 31 добавлено и 16 удалено
  1. 2 0
      CHANGELOG.md
  2. 28 15
      frontend/src/pages/InventoryPage.tsx
  3. 0 0
      static/assets/index-uDg4paLx.js
  4. 1 1
      static/index.html

+ 2 - 0
CHANGELOG.md

@@ -10,6 +10,8 @@ All notable changes to Bambuddy will be documented in this file.
 - **Queue Stuck on "Busy" for "Any Model" Jobs** ([#435](https://github.com/maziggy/bambuddy/issues/435)) — When a print was queued with "Any [Model]" (e.g., "Any P1S"), it was created with `printer_id=NULL` and `target_model="P1S"`. After the assigned printer finished, the queue widget queried only for items matching `printer_id=X`, missing the next pending model-based item (`printer_id IS NULL`). With no next item found, the "Clear Plate & Start Next" button never appeared, leaving the scheduler stuck reporting "Busy". The queue API now accepts an optional `target_model` parameter; when combined with `printer_id`, it uses OR logic to also return unassigned items whose `target_model` matches the printer's model. The frontend passes the printer's model through to this query. Additionally, the backend now resolves the printer's model server-side from the database when the frontend doesn't provide `target_model` (e.g., when the printer was added without selecting a model), ensuring the OR logic works regardless of whether the client knows the printer's model.
 - **Queue "Any Model" Jobs Stuck in "Waiting" After Plate Clear** ([#435](https://github.com/maziggy/bambuddy/issues/435)) — After the queue visibility fix above, "Any Model" jobs were correctly assigned to an idle printer but immediately crashed with `'>=' not supported between instances of 'str' and 'int'` when computing AMS filament mapping. MQTT raw data returns AMS unit and tray IDs as strings, but `_build_loaded_filaments()` compared them to integers without casting. The crash prevented the assignment from committing, so the scheduler retried every 30 seconds in an infinite loop. Cast `ams_id` and `tray_id` to `int()` to match the pattern already used for external spool IDs.
 - **SD Card Cleanup After Print Never Runs** ([#374](https://github.com/maziggy/bambuddy/issues/374)) — The post-print SD card cleanup (which deletes uploaded gcode from the printer root to prevent phantom prints on power cycle) used `printer_manager.get_printer()`, which returns a `PrinterInfo` with only `name` and `serial_number`. Accessing `.ip_address`, `.access_code`, and `.model` raised `AttributeError`, silently caught by the outer exception handler. Replaced with a DB query for the `Printer` model, matching the pattern used everywhere else in `on_print_complete()`.
+- **Inventory Date Format Ignores Settings** ([#463](https://github.com/maziggy/bambuddy/issues/463)) — The inventory page used a local `formatDate()` that hardcoded the `en-GB` locale, always displaying dates in a fixed format regardless of the date format setting. Now fetches the `date_format` setting and uses the shared `formatDateInput()` utility which formats as MM/DD/YYYY, DD/MM/YYYY, YYYY-MM-DD, or browser locale based on the user's choice.
+- **Inventory Location Shows Garbled Characters for AMS-HT Slots** ([#463](https://github.com/maziggy/bambuddy/issues/463)) — The inventory location column computed slot letters via `String.fromCharCode(65 + ams_id)`, which produced accented characters (e.g., `Á`) for AMS-HT units (ams_id ≥ 128). Now uses the shared `formatSlotLabel()` utility which correctly handles AMS-HT and external spool slots.
 
 ### New Features
 - **Background Print Dispatch** ([#408](https://github.com/maziggy/bambuddy/pull/408), [#112](https://github.com/maziggy/bambuddy/issues/112)) — Printing from archives and the file manager now runs in the background via an async dispatch service. FTP uploads and print-start commands are decoupled from API request latency, so the UI responds immediately. Real-time progress is streamed to all clients via WebSocket, rendered as a persistent toast with per-job upload progress bars, status badges (dispatched/processing/completed/failed/cancelled), and a cancel button. The dispatcher supports concurrent uploads to different printers with per-printer queuing to prevent conflicts. Cancellation is cooperative — uploads abort at the next chunk boundary and clean up partial files on the printer. Batch progress tracking shows overall completion across multi-printer dispatches. Translations added for all 6 locales (en, de, fr, it, ja, pt-BR).

+ 28 - 15
frontend/src/pages/InventoryPage.tsx

@@ -15,6 +15,8 @@ import { ConfirmModal } from '../components/ConfirmModal';
 import { ColumnConfigModal, type ColumnConfig } from '../components/ColumnConfigModal';
 import { useToast } from '../contexts/ToastContext';
 import { resolveSpoolColorName } from '../utils/colors';
+import { formatDateInput, parseUTCDate, type DateFormat } from '../utils/date';
+import { formatSlotLabel } from '../utils/amsHelpers';
 
 type ArchiveFilter = 'active' | 'archived';
 type UsageFilter = 'all' | 'used' | 'new';
@@ -98,10 +100,11 @@ const MATERIAL_COLORS: Record<string, string> = {
 
 type TFn = (key: string) => string;
 
-function formatDate(dateStr: string | null): string {
+function formatInventoryDate(dateStr: string | null, dateFormat: DateFormat = 'system'): string {
   if (!dateStr) return '-';
-  const date = new Date(dateStr);
-  return date.toLocaleDateString('en-GB', { day: '2-digit', month: '2-digit', year: '2-digit' });
+  const date = parseUTCDate(dateStr);
+  if (!date) return '-';
+  return formatDateInput(date, dateFormat);
 }
 
 type CellCtx = {
@@ -109,6 +112,7 @@ type CellCtx = {
   remaining: number;
   pct: number;
   assignmentMap: Record<number, SpoolAssignment>;
+  dateFormat: DateFormat;
 };
 
 // Column header labels (25 columns — matching SpoolBuddy exactly)
@@ -144,14 +148,14 @@ const columnCells: Record<string, (ctx: CellCtx) => ReactNode> = {
   id: ({ spool }) => (
     <span className="text-sm font-medium text-white">{spool.id}</span>
   ),
-  added_time: ({ spool }) => (
-    <span className="text-sm text-bambu-gray">{formatDate(spool.created_at)}</span>
+  added_time: ({ spool, dateFormat }) => (
+    <span className="text-sm text-bambu-gray">{formatInventoryDate(spool.created_at, dateFormat)}</span>
   ),
-  encode_time: ({ spool }) => (
-    <span className="text-sm text-bambu-gray">{formatDate(spool.encode_time)}</span>
+  encode_time: ({ spool, dateFormat }) => (
+    <span className="text-sm text-bambu-gray">{formatInventoryDate(spool.encode_time, dateFormat)}</span>
   ),
-  last_used_time: ({ spool }) => (
-    <span className="text-sm text-bambu-gray">{spool.last_used ? formatDate(spool.last_used) : 'Never'}</span>
+  last_used_time: ({ spool, dateFormat }) => (
+    <span className="text-sm text-bambu-gray">{spool.last_used ? formatInventoryDate(spool.last_used, dateFormat) : 'Never'}</span>
   ),
   rgba: ({ spool }) => (
     <div className="flex items-center justify-center">
@@ -183,12 +187,12 @@ const columnCells: Record<string, (ctx: CellCtx) => ReactNode> = {
     const assignment = assignmentMap[spool.id];
     if (!assignment) return <span className="text-sm text-bambu-gray">-</span>;
     const printerLabel = assignment.printer_name || `Printer ${assignment.printer_id}`;
-    // Bambu slot notation: AMS 0=A, 1=B, 2=C, 3=D; tray 0-based → 1-based
-    const slotLetter = String.fromCharCode(65 + assignment.ams_id);
-    const slotNumber = assignment.tray_id + 1;
+    const isExternal = assignment.ams_id === 254 || assignment.ams_id === 255;
+    const isHt = !isExternal && assignment.ams_id >= 128;
+    const slotLabel = formatSlotLabel(assignment.ams_id, assignment.tray_id, isHt, isExternal);
     return (
       <span className="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-purple-500/20 text-purple-400">
-        {printerLabel} {slotLetter}{slotNumber}
+        {printerLabel} {slotLabel}
       </span>
     );
   },
@@ -267,7 +271,9 @@ const columnSortValues: Record<string, (spool: InventorySpool, assignmentMap: Re
   location: (s, am) => {
     const a = am[s.id];
     if (!a) return '';
-    return `${a.printer_name || ''} ${String.fromCharCode(65 + a.ams_id)}${a.tray_id + 1}`;
+    const isExt = a.ams_id === 254 || a.ams_id === 255;
+    const isHt = !isExt && a.ams_id >= 128;
+    return `${a.printer_name || ''} ${formatSlotLabel(a.ams_id, a.tray_id, isHt, isExt)}`;
   },
   label_weight: (s) => s.label_weight,
   net: (s) => Math.max(0, s.label_weight - s.weight_used),
@@ -330,6 +336,13 @@ export default function InventoryPage() {
     return 15;
   });
 
+  const { data: settings } = useQuery({
+    queryKey: ['settings'],
+    queryFn: api.getSettings,
+  });
+
+  const dateFormat: DateFormat = settings?.date_format || 'system';
+
   const { data: spools, isLoading } = useQuery({
     queryKey: ['inventory-spools'],
     queryFn: () => api.getSpools(true), // Always fetch all, filter client-side
@@ -927,7 +940,7 @@ export default function InventoryPage() {
                       >
                         {visibleColumns.map((colId) => (
                           <td key={colId} className="py-3 px-4">
-                            {columnCells[colId]?.({ spool, remaining, pct, assignmentMap })}
+                            {columnCells[colId]?.({ spool, remaining, pct, assignmentMap, dateFormat })}
                           </td>
                         ))}
                         <td className="py-3 px-4">

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
static/assets/index-uDg4paLx.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-BHkAk1mx.js"></script>
+    <script type="module" crossorigin src="/assets/index-uDg4paLx.js"></script>
     <link rel="stylesheet" crossorigin href="/assets/index-DJax8qcY.css">
   </head>
   <body>

Некоторые файлы не были показаны из-за большого количества измененных файлов