Explorar el Código

fix(profiles): keep the Local Profiles search bar mounted when nothing matches (#1470)

  The search bar was rendered only when totalCount > 0, but totalCount is
  the sum of the post-filter preset lists. A query that matched nothing
  dropped it to 0 and unmounted the search bar along with the columns,
  so the user couldn't clear the query without a page refresh.

  Gate the search bar on hasAnyPresets (pre-filter count) instead, so it
  stays visible as long as any preset is imported. Split the empty state:
  "No local presets yet" when nothing is imported, vs a new "No presets
  match your search" message when a query filtered everything out.
maziggy hace 6 días
padre
commit
016e01781a

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 2 - 0
CHANGELOG.md


+ 18 - 0
frontend/src/__tests__/components/LocalProfilesView.test.tsx

@@ -141,6 +141,24 @@ describe('LocalProfilesView', () => {
     expect(screen.getByText('eSUN PETG @Bambu Lab H2D')).toBeInTheDocument();
     expect(screen.getByText('eSUN PETG @Bambu Lab H2D')).toBeInTheDocument();
   });
   });
 
 
+  it('keeps the search bar visible when no presets match the query (#1470)', async () => {
+    render(<LocalProfilesView />);
+
+    await waitFor(() => {
+      expect(screen.getByText('Overture PLA Matte @BBL X1C')).toBeInTheDocument();
+    });
+
+    const searchInput = screen.getByPlaceholderText(/search/i);
+    fireEvent.change(searchInput, { target: { value: 'zzz-nothing-matches' } });
+
+    // The search bar must stay mounted so the query can be cleared/edited
+    // without a page refresh, and it keeps the typed value.
+    expect(screen.getByPlaceholderText(/search/i)).toBeInTheDocument();
+    expect(screen.getByPlaceholderText(/search/i)).toHaveValue('zzz-nothing-matches');
+    // A no-matches message replaces the columns (not the "import some" empty state).
+    expect(screen.getByText(/no presets match your search/i)).toBeInTheDocument();
+  });
+
   it('shows empty state when no presets', async () => {
   it('shows empty state when no presets', async () => {
     server.use(
     server.use(
       http.get('/api/v1/local-presets/', () => {
       http.get('/api/v1/local-presets/', () => {

+ 20 - 3
frontend/src/components/LocalProfilesView.tsx

@@ -303,6 +303,15 @@ export function LocalProfilesView() {
   const printers = useMemo(() => filterPresets(presets?.printer || []), [presets?.printer, filterPresets]);
   const printers = useMemo(() => filterPresets(presets?.printer || []), [presets?.printer, filterPresets]);
   const processes = useMemo(() => filterPresets(presets?.process || []), [presets?.process, filterPresets]);
   const processes = useMemo(() => filterPresets(presets?.process || []), [presets?.process, filterPresets]);
   const totalCount = filaments.length + printers.length + processes.length;
   const totalCount = filaments.length + printers.length + processes.length;
+  // Count of imported presets BEFORE the search filter — drives whether the
+  // search bar shows at all. Gating the search bar on totalCount (post-filter)
+  // made it vanish the moment a query matched nothing, leaving the user unable
+  // to clear or edit their search without a page refresh (#1470).
+  const hasAnyPresets =
+    (presets?.filament?.length ?? 0) +
+      (presets?.printer?.length ?? 0) +
+      (presets?.process?.length ?? 0) >
+    0;
 
 
   if (isLoading) {
   if (isLoading) {
     return (
     return (
@@ -349,7 +358,7 @@ export function LocalProfilesView() {
       )}
       )}
 
 
       {/* Search Bar */}
       {/* Search Bar */}
-      {totalCount > 0 && (
+      {hasAnyPresets && (
         <div className="relative">
         <div className="relative">
           <Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-bambu-gray" />
           <Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-bambu-gray" />
           <input
           <input
@@ -362,8 +371,8 @@ export function LocalProfilesView() {
         </div>
         </div>
       )}
       )}
 
 
-      {/* No Presets */}
-      {totalCount === 0 && !isLoading && (
+      {/* No presets imported at all */}
+      {!hasAnyPresets && !isLoading && (
         <div className="text-center py-12">
         <div className="text-center py-12">
           <HardDrive className="w-12 h-12 text-bambu-gray mx-auto mb-3 opacity-50" />
           <HardDrive className="w-12 h-12 text-bambu-gray mx-auto mb-3 opacity-50" />
           <p className="text-bambu-gray">{t('profiles.localProfiles.noPresets')}</p>
           <p className="text-bambu-gray">{t('profiles.localProfiles.noPresets')}</p>
@@ -371,6 +380,14 @@ export function LocalProfilesView() {
         </div>
         </div>
       )}
       )}
 
 
+      {/* Presets exist, but the search query matched none of them */}
+      {hasAnyPresets && totalCount === 0 && !isLoading && (
+        <div className="text-center py-12">
+          <Search className="w-12 h-12 text-bambu-gray mx-auto mb-3 opacity-50" />
+          <p className="text-bambu-gray">{t('profiles.localProfiles.noSearchResults')}</p>
+        </div>
+      )}
+
       {/* 3-Column Preset Lists */}
       {/* 3-Column Preset Lists */}
       {totalCount > 0 && (
       {totalCount > 0 && (
         <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
         <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">

+ 1 - 0
frontend/src/i18n/locales/de.ts

@@ -2834,6 +2834,7 @@ export default {
       importing: 'Importiere...',
       importing: 'Importiere...',
       search: 'Lokale Voreinstellungen durchsuchen...',
       search: 'Lokale Voreinstellungen durchsuchen...',
       noPresets: 'Noch keine lokalen Voreinstellungen',
       noPresets: 'Noch keine lokalen Voreinstellungen',
+      noSearchResults: 'Keine Voreinstellungen entsprechen deiner Suche',
       badge: 'Lokal',
       badge: 'Lokal',
       edit: 'Bearbeiten',
       edit: 'Bearbeiten',
       delete: 'Löschen',
       delete: 'Löschen',

+ 1 - 0
frontend/src/i18n/locales/en.ts

@@ -2837,6 +2837,7 @@ export default {
       importing: 'Importing...',
       importing: 'Importing...',
       search: 'Search local presets...',
       search: 'Search local presets...',
       noPresets: 'No local presets yet',
       noPresets: 'No local presets yet',
+      noSearchResults: 'No presets match your search',
       badge: 'Local',
       badge: 'Local',
       edit: 'Edit',
       edit: 'Edit',
       delete: 'Delete',
       delete: 'Delete',

+ 1 - 0
frontend/src/i18n/locales/fr.ts

@@ -2823,6 +2823,7 @@ export default {
       importing: 'Importation...',
       importing: 'Importation...',
       search: 'Chercher un preset...',
       search: 'Chercher un preset...',
       noPresets: 'Aucun preset local',
       noPresets: 'Aucun preset local',
+      noSearchResults: 'Aucun preset ne correspond à votre recherche',
       badge: 'Local',
       badge: 'Local',
       edit: 'Modifier',
       edit: 'Modifier',
       delete: 'Supprimer',
       delete: 'Supprimer',

+ 1 - 0
frontend/src/i18n/locales/it.ts

@@ -2822,6 +2822,7 @@ export default {
       importing: 'Importazione...',
       importing: 'Importazione...',
       search: 'Cerca preset locali...',
       search: 'Cerca preset locali...',
       noPresets: 'Nessun preset locale ancora',
       noPresets: 'Nessun preset locale ancora',
+      noSearchResults: 'Nessun preset corrisponde alla ricerca',
       badge: 'Locale',
       badge: 'Locale',
       edit: 'Modifica',
       edit: 'Modifica',
       delete: 'Elimina',
       delete: 'Elimina',

+ 1 - 0
frontend/src/i18n/locales/ja.ts

@@ -2834,6 +2834,7 @@ export default {
       importing: 'インポート中...',
       importing: 'インポート中...',
       search: 'ローカルプリセットを検索...',
       search: 'ローカルプリセットを検索...',
       noPresets: 'ローカルプリセットがまだありません',
       noPresets: 'ローカルプリセットがまだありません',
+      noSearchResults: '検索に一致するプリセットがありません',
       badge: 'ローカル',
       badge: 'ローカル',
       edit: '編集',
       edit: '編集',
       delete: '削除',
       delete: '削除',

+ 1 - 0
frontend/src/i18n/locales/pt-BR.ts

@@ -2822,6 +2822,7 @@ export default {
       importing: 'Importando...',
       importing: 'Importando...',
       search: 'Pesquisar presets locais...',
       search: 'Pesquisar presets locais...',
       noPresets: 'Nenhum preset local ainda',
       noPresets: 'Nenhum preset local ainda',
+      noSearchResults: 'Nenhum preset corresponde à sua busca',
       badge: 'Local',
       badge: 'Local',
       edit: 'Editar',
       edit: 'Editar',
       delete: 'Excluir',
       delete: 'Excluir',

+ 1 - 0
frontend/src/i18n/locales/zh-CN.ts

@@ -2822,6 +2822,7 @@ export default {
       importing: '导入中...',
       importing: '导入中...',
       search: '搜索本地预设...',
       search: '搜索本地预设...',
       noPresets: '暂无本地预设',
       noPresets: '暂无本地预设',
+      noSearchResults: '没有与搜索匹配的预设',
       badge: '本地',
       badge: '本地',
       edit: '编辑',
       edit: '编辑',
       delete: '删除',
       delete: '删除',

+ 1 - 0
frontend/src/i18n/locales/zh-TW.ts

@@ -2822,6 +2822,7 @@ export default {
       importing: '匯入中...',
       importing: '匯入中...',
       search: '搜尋本機預設...',
       search: '搜尋本機預設...',
       noPresets: '尚無本機預設',
       noPresets: '尚無本機預設',
+      noSearchResults: '沒有符合搜尋的預設',
       badge: '本機',
       badge: '本機',
       edit: '編輯',
       edit: '編輯',
       delete: '刪除',
       delete: '刪除',

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
static/assets/index-DvIJbzgj.js


+ 1 - 1
static/index.html

@@ -26,7 +26,7 @@
 
 
     <!-- Splash screens for iOS -->
     <!-- Splash screens for iOS -->
     <link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
     <link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
-    <script type="module" crossorigin src="/assets/index-CY4xdITS.js"></script>
+    <script type="module" crossorigin src="/assets/index-DvIJbzgj.js"></script>
     <link rel="stylesheet" crossorigin href="/assets/index-CyXgRo_7.css">
     <link rel="stylesheet" crossorigin href="/assets/index-CyXgRo_7.css">
   </head>
   </head>
   <body>
   <body>

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio