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

Add FileManager UI improvements - card and list view changes

Co-authored-by: cadtoolbox <12723486+cadtoolbox@users.noreply.github.com>
copilot-swe-agent[bot] 7 месяцев назад
Родитель
Сommit
7c8280a400
1 измененных файлов с 61 добавлено и 7 удалено
  1. 61 7
      frontend/src/pages/FileManagerPage.tsx

+ 61 - 7
frontend/src/pages/FileManagerPage.tsx

@@ -37,6 +37,7 @@ import {
   Pencil,
   Play,
   Image,
+  User,
 } from 'lucide-react';
 import { api } from '../api/client';
 import type {
@@ -963,7 +964,7 @@ function FileCard({ file, isSelected, isMobile, onSelect, onDelete, onDownload,
           )}
         </div>
         {file.sliced_for_model && (
-          <div className="mt-1 text-xs text-purple-400 flex items-center gap-1">
+          <div className="mt-1 text-xs text-bambu-gray flex items-center gap-1">
             <Printer className="w-3 h-3" />
             {file.sliced_for_model}
           </div>
@@ -974,8 +975,9 @@ function FileCard({ file, isSelected, isMobile, onSelect, onDelete, onDownload,
           </div>
         )}
         {file.created_by_username && (
-          <div className="mt-1 text-xs text-bambu-gray">
-            {t('fileManager.uploadedBy', { name: file.created_by_username })}
+          <div className="mt-1 text-xs text-bambu-gray flex items-center gap-1">
+            <User className="w-3 h-3" />
+            {file.created_by_username}
           </div>
         )}
       </div>
@@ -1163,6 +1165,7 @@ export function FileManagerPage() {
   // Filter and sort state (persist sort preferences to localStorage)
   const [searchQuery, setSearchQuery] = useState('');
   const [filterType, setFilterType] = useState<string>('all');
+  const [filterUsername, setFilterUsername] = useState('');
   const [sortField, setSortField] = useState<SortField>(() => {
     const saved = localStorage.getItem('library-sort-field');
     return (saved as SortField) || 'name';
@@ -1204,6 +1207,12 @@ export function FileManagerPage() {
     queryFn: () => api.getLibraryStats(),
   });
 
+  // Get users for the username filter autocomplete
+  const { data: users } = useQuery({
+    queryKey: ['users'],
+    queryFn: () => api.getUsers(),
+  });
+
   // Get unique file types for filter dropdown
   const fileTypes = useMemo(() => {
     if (!files) return [];
@@ -1232,6 +1241,14 @@ export function FileManagerPage() {
       result = result.filter((f) => f.file_type === filterType);
     }
 
+    // Apply username filter
+    if (filterUsername.trim()) {
+      const query = filterUsername.toLowerCase();
+      result = result.filter(
+        (f) => f.created_by_username && f.created_by_username.toLowerCase().includes(query)
+      );
+    }
+
     // Apply sorting
     result.sort((a, b) => {
       let comparison = 0;
@@ -1256,7 +1273,7 @@ export function FileManagerPage() {
     });
 
     return result;
-  }, [files, searchQuery, filterType, sortField, sortDirection]);
+  }, [files, searchQuery, filterType, filterUsername, sortField, sortDirection]);
 
   // Check if disk space is low
   const isDiskSpaceLow = useMemo(() => {
@@ -1745,6 +1762,31 @@ export function FileManagerPage() {
                 </select>
               </div>
 
+              {/* Username filter with autocomplete */}
+              <div className="relative">
+                <input
+                  type="text"
+                  placeholder={t('fileManager.filterByUser', { defaultValue: 'Filter by user' })}
+                  value={filterUsername}
+                  onChange={(e) => setFilterUsername(e.target.value)}
+                  list="usernames-list"
+                  className="w-32 sm:w-40 px-2 py-1.5 bg-bambu-dark border border-bambu-dark-tertiary rounded text-sm text-white placeholder-bambu-gray focus:outline-none focus:border-bambu-green"
+                />
+                {filterUsername && (
+                  <button
+                    onClick={() => setFilterUsername('')}
+                    className="absolute right-2 top-1/2 -translate-y-1/2 text-bambu-gray hover:text-white"
+                  >
+                    <X className="w-3 h-3" />
+                  </button>
+                )}
+                <datalist id="usernames-list">
+                  {users?.map((user) => (
+                    <option key={user.id} value={user.username} />
+                  ))}
+                </datalist>
+              </div>
+
               {/* Sort */}
               <div className="flex items-center gap-2">
                 <select
@@ -1780,7 +1822,7 @@ export function FileManagerPage() {
               </div>
 
               {/* Results count */}
-              {(searchQuery || filterType !== 'all') && (
+              {(searchQuery || filterType !== 'all' || filterUsername) && (
                 <span className="text-sm text-bambu-gray hidden sm:inline">
                   {t('fileManager.resultsCount', { showing: filteredAndSortedFiles.length, total: files.length })}
                 </span>
@@ -1960,19 +2002,20 @@ export function FileManagerPage() {
             <div className="flex-1 lg:overflow-y-auto">
               <div className="bg-bambu-card rounded-lg border border-bambu-dark-tertiary overflow-hidden">
                 {/* List header - hidden on mobile, show simplified on small screens */}
-                <div className="hidden sm:grid grid-cols-[auto_1fr_100px_100px_100px_80px] gap-4 px-4 py-2 bg-bambu-dark-secondary border-b border-bambu-dark-tertiary text-xs text-bambu-gray font-medium">
+                <div className="hidden sm:grid grid-cols-[auto_1fr_100px_100px_100px_120px_80px] gap-4 px-4 py-2 bg-bambu-dark-secondary border-b border-bambu-dark-tertiary text-xs text-bambu-gray font-medium">
                   <div className="w-6" />
                   <div>{t('common.name')}</div>
                   <div>{t('common.type')}</div>
                   <div>{t('fileManager.size')}</div>
                   <div>{t('fileManager.prints')}</div>
+                  <div>{t('fileManager.uploadedBy', { defaultValue: 'Uploaded By' })}</div>
                   <div />
                 </div>
                 {/* List rows */}
                 {filteredAndSortedFiles.map((file) => (
                   <div
                     key={file.id}
-                    className={`grid grid-cols-[auto_1fr_100px_100px_100px_80px] gap-4 px-4 py-3 items-center border-b border-bambu-dark-tertiary last:border-b-0 cursor-pointer hover:bg-bambu-dark/50 transition-colors ${
+                    className={`grid grid-cols-[auto_1fr_100px_100px_100px_120px_80px] gap-4 px-4 py-3 items-center border-b border-bambu-dark-tertiary last:border-b-0 cursor-pointer hover:bg-bambu-dark/50 transition-colors ${
                       selectedFiles.includes(file.id) ? 'bg-bambu-green/10' : ''
                     }`}
                     onClick={() => handleFileSelect(file.id)}
@@ -2033,6 +2076,17 @@ export function FileManagerPage() {
                     <div className="text-sm text-bambu-gray">{formatFileSize(file.file_size)}</div>
                     {/* Prints */}
                     <div className="text-sm text-bambu-gray">{file.print_count > 0 ? `${file.print_count}x` : '-'}</div>
+                    {/* Uploaded By */}
+                    <div className="text-sm text-bambu-gray flex items-center gap-1">
+                      {file.created_by_username ? (
+                        <>
+                          <User className="w-3 h-3" />
+                          <span className="truncate">{file.created_by_username}</span>
+                        </>
+                      ) : (
+                        '-'
+                      )}
+                    </div>
                     {/* Actions */}
                     <div className="flex items-center gap-1" onClick={(e) => e.stopPropagation()}>
                       {isSlicedFilename(file.filename) && (