From fac51bc33961078e13ff65a17f11b43db1be9bad Mon Sep 17 00:00:00 2001 From: Reece Date: Tue, 24 Mar 2026 15:52:42 +0000 Subject: [PATCH] filters and error handling --- .../api/pipeline/PipelineController.java | 9 +- .../smartFolders/CardExpansionModal.tsx | 8 + .../smartFolders/SmartFolderWorkbenchView.tsx | 213 +++++++++++++++++- .../smartFolders/WatchFolderFileList.tsx | 4 +- .../src/core/hooks/useFolderAutomation.ts | 13 ++ 5 files changed, 236 insertions(+), 11 deletions(-) diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/pipeline/PipelineController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/pipeline/PipelineController.java index b2c717ca84..64937bff74 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/pipeline/PipelineController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/pipeline/PipelineController.java @@ -9,6 +9,7 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import org.springframework.core.io.Resource; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ModelAttribute; @@ -63,7 +64,7 @@ public class PipelineController { MultipartFile[] files = request.getFileInput(); String jsonString = request.getJson(); if (files == null) { - return null; + return ResponseEntity.status(HttpStatus.BAD_REQUEST).build(); } PipelineConfig config = objectMapper.readValue(jsonString, PipelineConfig.class); log.info("Received POST request to /handleData with {} files", files.length); @@ -80,7 +81,7 @@ public class PipelineController { try { List inputFiles = processor.generateInputFiles(files); if (inputFiles == null || inputFiles.isEmpty()) { - return null; + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } PipelineResult result = processor.runPipelineAgainstFiles(inputFiles, config); List outputFiles = result.getOutputFiles(); @@ -102,7 +103,7 @@ public class PipelineController { throw e; } } else if (outputFiles == null) { - return null; + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } // Multiple files: stream into a zip TempFile TempFile zipTempFile = new TempFile(tempFileManager, ".zip"); @@ -137,7 +138,7 @@ public class PipelineController { } } catch (Exception e) { log.error("Error handling data: ", e); - return null; + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } } } diff --git a/frontend/src/core/components/smartFolders/CardExpansionModal.tsx b/frontend/src/core/components/smartFolders/CardExpansionModal.tsx index ecf20b4eb8..dc1b0130b7 100644 --- a/frontend/src/core/components/smartFolders/CardExpansionModal.tsx +++ b/frontend/src/core/components/smartFolders/CardExpansionModal.tsx @@ -20,6 +20,8 @@ interface CardExpansionModalProps { children: React.ReactNode; /** Footer content */ footer?: React.ReactNode; + /** Non-scrolling toolbar rendered between the header and the list */ + toolbar?: React.ReactNode; } const MODAL_W_REM = 56; @@ -39,6 +41,7 @@ export function CardExpansionModal({ labelPlural, children, footer, + toolbar, }: CardExpansionModalProps) { const [viewportW, setViewportW] = useState(window.innerWidth); const [viewportH, setViewportH] = useState(window.innerHeight); @@ -160,6 +163,11 @@ export function CardExpansionModal({ {/* Body — only mount once open */} {showBody && (
+ {toolbar && ( +
+ {toolbar} +
+ )}
{children} diff --git a/frontend/src/core/components/smartFolders/SmartFolderWorkbenchView.tsx b/frontend/src/core/components/smartFolders/SmartFolderWorkbenchView.tsx index 35e70f95b2..7e7c4e876c 100644 --- a/frontend/src/core/components/smartFolders/SmartFolderWorkbenchView.tsx +++ b/frontend/src/core/components/smartFolders/SmartFolderWorkbenchView.tsx @@ -1,4 +1,4 @@ -import { useState, useCallback, useRef, useEffect } from 'react'; +import React, { useState, useCallback, useRef, useEffect, useMemo } from 'react'; import { Box, Text, @@ -7,12 +7,15 @@ import { Button, ScrollArea, Loader, + TextInput, + Select, } from '@mantine/core'; import { useCardModalAnimation } from '@app/hooks/useCardModalAnimation'; import { CardExpansionModal } from '@app/components/smartFolders/CardExpansionModal'; import { StatCard } from '@app/components/smartFolders/StatCard'; import { useTranslation } from 'react-i18next'; +import SearchIcon from '@mui/icons-material/Search'; import DownloadIcon from '@mui/icons-material/Download'; import HistoryIcon from '@mui/icons-material/History'; import ArrowBackIcon from '@mui/icons-material/ArrowBack'; @@ -43,6 +46,59 @@ import { SmartFolderHomePage, humaniseOp } from '@app/components/smartFolders/Sm import { useNavigationActions } from '@app/contexts/NavigationContext'; import { FilePreviewModal } from '@app/components/smartFolders/FilePreviewModal'; +const SORT_OPTIONS = [ + { value: 'newest', label: 'Newest' }, + { value: 'oldest', label: 'Oldest' }, + { value: 'name-asc', label: 'A → Z' }, + { value: 'name-desc', label: 'Z → A' }, +]; + +const ACTIVITY_STATUS_OPTIONS = [ + { value: 'all', label: 'All' }, + { value: 'processed', label: 'Done' }, + { value: 'processing', label: 'Active' }, + { value: 'error', label: 'Failed' }, + { value: 'pending', label: 'Pending' }, +]; + +function FilterSortBar({ + search, + onSearchChange, + sort, + onSortChange, + extra, +}: { + search: string; + onSearchChange: (v: string) => void; + sort: string; + onSortChange: (v: string) => void; + extra?: React.ReactNode; +}) { + return ( + + onSearchChange(e.currentTarget.value)} + leftSection={} + style={{ flex: 1 }} + styles={{ input: { fontSize: '0.75rem' } }} + /> + {extra} +