From 7105506aa941d3fb9949e1c51cc85cda3844bd08 Mon Sep 17 00:00:00 2001 From: Prajwal D C Date: Thu, 3 Apr 2025 21:20:17 +0530 Subject: [PATCH 01/45] feat: Added API for deletion of processed files --- .../app/libs/cosmos_db/helper.py | 5 +-- .../app/libs/storage_blob/helper.py | 21 +++++++++++ .../app/routers/contentprocessor.py | 31 +++++++++++++++- .../contentprocessor/content_process.py | 35 +++++++++++++++++++ .../routers/models/contentprocessor/model.py | 5 +++ 5 files changed, 94 insertions(+), 3 deletions(-) diff --git a/src/ContentProcessorAPI/app/libs/cosmos_db/helper.py b/src/ContentProcessorAPI/app/libs/cosmos_db/helper.py index efba5aab..57d6b302 100644 --- a/src/ContentProcessorAPI/app/libs/cosmos_db/helper.py +++ b/src/ContentProcessorAPI/app/libs/cosmos_db/helper.py @@ -102,6 +102,7 @@ def update_document_by_query(self, query: Dict[str, Any], update: Dict[str, Any] result = self.container.update_one(query, {"$set": update}) return result - def delete_document(self, item_id: str): - result = self.container.delete_one({"Id": item_id}) + def delete_document(self, item_id: str, field_name: str = None): + field_name = field_name or "Id" # Use "Id" if field_name is empty or None + result = self.container.delete_one({field_name: item_id}) return result diff --git a/src/ContentProcessorAPI/app/libs/storage_blob/helper.py b/src/ContentProcessorAPI/app/libs/storage_blob/helper.py index a1bf4737..3b76cdd5 100644 --- a/src/ContentProcessorAPI/app/libs/storage_blob/helper.py +++ b/src/ContentProcessorAPI/app/libs/storage_blob/helper.py @@ -90,3 +90,24 @@ def delete_blob_and_cleanup(self, blob_name, container_name=None): # Delete the (virtual) folder in the Container blob_client = container_client.get_blob_client(container_name) blob_client.delete_blob() + + def delete_folder(self, folder_name, container_name=None): + container_client = self._get_container_client(container_name) + + # List all blobs inside the folder + blobs_to_delete = container_client.list_blobs(name_starts_with=folder_name + "/") + + # Delete each blob + for blob in blobs_to_delete: + blob_client = container_client.get_blob_client(blob.name) + blob_client.delete_blob() + + blobs_to_delete = container_client.list_blobs() + if not blobs_to_delete: + + # Get Parent Container + container_client = self._get_container_client() + + # Delete the (virtual) folder in the Container + blob_client = container_client.get_blob_client(folder_name) + blob_client.delete_blob() \ No newline at end of file diff --git a/src/ContentProcessorAPI/app/routers/contentprocessor.py b/src/ContentProcessorAPI/app/routers/contentprocessor.py index bac9c823..e9f197bd 100644 --- a/src/ContentProcessorAPI/app/routers/contentprocessor.py +++ b/src/ContentProcessorAPI/app/routers/contentprocessor.py @@ -6,7 +6,7 @@ import urllib.parse import uuid -from fastapi import APIRouter, Body, Depends, File, UploadFile +from fastapi import APIRouter, Body, Depends, File, HTTPException, UploadFile from fastapi.responses import JSONResponse, StreamingResponse from pymongo.results import UpdateResult @@ -28,6 +28,7 @@ ContentProcess, ContentProcessorRequest, ContentResultUpdate, + ContentResultDelete, Paging, ProcessFile, Status, @@ -490,3 +491,31 @@ async def get_original_file( return StreamingResponse( file_stream, media_type=content_type_string, headers=headers ) + +@router.delete( + "/processed/{process_id}", + response_model=ContentResultDelete, + summary="Delete the processed content result", + description=""" + Returns the deleted record for a given process ID. + """, +) +async def delete_processed_file( + process_id: str, app_config: AppConfiguration = Depends(get_app_config) + ) -> ContentResultDelete: + try: + deleted_file = CosmosContentProcess(process_id=process_id).delete_processed_file( + connection_string=app_config.app_cosmos_connstr, + database_name=app_config.app_cosmos_database, + collection_name=app_config.app_cosmos_container_process, + storage_connection_string=app_config.app_storage_blob_url, + container_name=app_config.app_cps_processes, + ) + + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) + return ContentResultDelete( + status="Success" if deleted_file else "Failed", + process_id=deleted_file.process_id if deleted_file else "", + message="" if deleted_file else "This record no longer exists. Please refresh the page." + ) \ No newline at end of file diff --git a/src/ContentProcessorAPI/app/routers/models/contentprocessor/content_process.py b/src/ContentProcessorAPI/app/routers/models/contentprocessor/content_process.py index 4f9b9666..4eb58c12 100644 --- a/src/ContentProcessorAPI/app/routers/models/contentprocessor/content_process.py +++ b/src/ContentProcessorAPI/app/routers/models/contentprocessor/content_process.py @@ -189,6 +189,41 @@ def get_status_from_cosmos( return ContentProcess(**existing_process[0]) else: return None + + def delete_processed_file( + self, + connection_string: str, + database_name: str, + collection_name: str, + storage_connection_string: str, + container_name: str, + ): + """ + Delete the processed file from Cosmos DB & Storage account. + """ + mongo_helper = CosmosMongDBHelper( + connection_string=connection_string, + db_name=database_name, + container_name=collection_name, + indexes=[("process_id", 1)], + ) + + blob_helper = StorageBlobHelper( + account_url=storage_connection_string, container_name=container_name + ) + + # Check if the process_id already exists in the database + existing_process = mongo_helper.find_document( + query={"process_id": self.process_id} + ) + + blob_helper.delete_folder(folder_name=self.process_id) + + if existing_process: + mongo_helper.delete_document(item_id=self.process_id, field_name="process_id") + return ContentProcess(**existing_process[0]) + else: + return None def update_process_result( self, diff --git a/src/ContentProcessorAPI/app/routers/models/contentprocessor/model.py b/src/ContentProcessorAPI/app/routers/models/contentprocessor/model.py index bbe5b124..9cfd45af 100644 --- a/src/ContentProcessorAPI/app/routers/models/contentprocessor/model.py +++ b/src/ContentProcessorAPI/app/routers/models/contentprocessor/model.py @@ -64,6 +64,11 @@ class ContentResultUpdate(BaseModel): process_id: str modified_result: dict +class ContentResultDelete(BaseModel): + process_id: str + status: str + message: str + class ContentCommentUpdate(BaseModel): process_id: str From 62712a94741e9b3701c62927b5a0197b98ec44c1 Mon Sep 17 00:00:00 2001 From: Bangarraju-Microsoft Date: Tue, 8 Apr 2025 17:20:20 +0530 Subject: [PATCH 02/45] CP - US(#16438) story implementation and bug fix(#16072) and code cleanup --- src/ContentProcessorWeb/.dockerignore | 9 + src/ContentProcessorWeb/src/App.tsx | 7 +- .../DialogComponent/DialogComponent.tsx | 64 +++ .../DocumentViewer/DocumentViewer.styles.scss | 8 + .../DocumentViewer/DocumentViewer.tsx | 2 +- .../FluentComponents/Combobox/Combobox.tsx | 96 ---- .../GridComponent/GridComponent.tsx | 497 ------------------ .../src/Components/JSONEditor/JSONEditor.tsx | 19 +- .../src/Components/Panels/PanelLeft.tsx | 34 -- .../src/Components/Panels/PanelRight.tsx | 34 -- .../src/Components/Panels/Panels.css | 170 ------ .../UploadContent/uploadContent.tsx | 121 ----- src/ContentProcessorWeb/src/Hooks/useApi.ts | 126 ----- .../ProcessQueueGrid/CustomCellRender.tsx | 159 ++++++ .../ProcessQueueGrid.styles.scss} | 34 +- .../ProcessQueueGrid/ProcessQueueGrid.tsx | 368 +++++++++++++ .../ProcessQueueGrid/ProcessQueueGridTypes.ts | 29 + .../SchemaDropdown.styles.scss} | 0 .../SchemaDropdown/SchemaDropdown.tsx | 74 +++ .../SchemaDropdown/SchemaDropdownTypes.ts | 17 + ...DefaultPageContent.tsx => PanelCenter.tsx} | 4 +- ...DefaultPagePanelLeft.tsx => PanelLeft.tsx} | 29 +- ...faultPagePanelRight.tsx => PanelRight.tsx} | 2 + .../src/Pages/DefaultPage/Panels.styles.scss | 170 ++++++ .../{DefaultPage.tsx => index.tsx} | 12 +- .../src/Services/apiService.ts | 259 --------- .../src/store/slices/centerPanelSlice.ts | 7 +- .../src/store/slices/leftPanelSlice.ts | 55 +- 28 files changed, 1016 insertions(+), 1390 deletions(-) create mode 100644 src/ContentProcessorWeb/.dockerignore create mode 100644 src/ContentProcessorWeb/src/Components/DialogComponent/DialogComponent.tsx delete mode 100644 src/ContentProcessorWeb/src/Components/FluentComponents/Combobox/Combobox.tsx delete mode 100644 src/ContentProcessorWeb/src/Components/FluentComponents/GridComponent/GridComponent.tsx delete mode 100644 src/ContentProcessorWeb/src/Components/Panels/PanelLeft.tsx delete mode 100644 src/ContentProcessorWeb/src/Components/Panels/PanelRight.tsx delete mode 100644 src/ContentProcessorWeb/src/Components/Panels/Panels.css delete mode 100644 src/ContentProcessorWeb/src/Components/UploadContent/uploadContent.tsx delete mode 100644 src/ContentProcessorWeb/src/Hooks/useApi.ts create mode 100644 src/ContentProcessorWeb/src/Pages/DefaultPage/Components/ProcessQueueGrid/CustomCellRender.tsx rename src/ContentProcessorWeb/src/{Components/FluentComponents/GridComponent/GridComponent.styles.scss => Pages/DefaultPage/Components/ProcessQueueGrid/ProcessQueueGrid.styles.scss} (73%) create mode 100644 src/ContentProcessorWeb/src/Pages/DefaultPage/Components/ProcessQueueGrid/ProcessQueueGrid.tsx create mode 100644 src/ContentProcessorWeb/src/Pages/DefaultPage/Components/ProcessQueueGrid/ProcessQueueGridTypes.ts rename src/ContentProcessorWeb/src/{Components/FluentComponents/Combobox/Combobox.styles.scss => Pages/DefaultPage/Components/SchemaDropdown/SchemaDropdown.styles.scss} (100%) create mode 100644 src/ContentProcessorWeb/src/Pages/DefaultPage/Components/SchemaDropdown/SchemaDropdown.tsx create mode 100644 src/ContentProcessorWeb/src/Pages/DefaultPage/Components/SchemaDropdown/SchemaDropdownTypes.ts rename src/ContentProcessorWeb/src/Pages/DefaultPage/{DefaultPageContent.tsx => PanelCenter.tsx} (98%) rename src/ContentProcessorWeb/src/Pages/DefaultPage/{DefaultPagePanelLeft.tsx => PanelLeft.tsx} (79%) rename src/ContentProcessorWeb/src/Pages/DefaultPage/{DefaultPagePanelRight.tsx => PanelRight.tsx} (97%) create mode 100644 src/ContentProcessorWeb/src/Pages/DefaultPage/Panels.styles.scss rename src/ContentProcessorWeb/src/Pages/DefaultPage/{DefaultPage.tsx => index.tsx} (74%) delete mode 100644 src/ContentProcessorWeb/src/Services/apiService.ts diff --git a/src/ContentProcessorWeb/.dockerignore b/src/ContentProcessorWeb/.dockerignore new file mode 100644 index 00000000..67b465a3 --- /dev/null +++ b/src/ContentProcessorWeb/.dockerignore @@ -0,0 +1,9 @@ +# Include any files or directories that you don't want to be copied to your +# container here (e.g., local build artifacts, temporary files, etc.). +# +# For more help, visit the .dockerignore file reference guide at +# https://docs.docker.com/engine/reference/builder/#dockerignore-file + + +**/node_modules +.env \ No newline at end of file diff --git a/src/ContentProcessorWeb/src/App.tsx b/src/ContentProcessorWeb/src/App.tsx index 03c9b4dd..06462b52 100644 --- a/src/ContentProcessorWeb/src/App.tsx +++ b/src/ContentProcessorWeb/src/App.tsx @@ -2,10 +2,9 @@ import * as React from "react"; import { useEffect } from "react"; import Header from "./Components/Header/Header.tsx"; // Import Header import "./Styles/App.css"; -import "./Components/Panels/Panels.css"; import "./Components/Content/Content.css"; import HomePage from "./Pages/HomePage.tsx"; -import DefaultPage from "./Pages/DefaultPage/DefaultPage.tsx"; +import DefaultPage from "./Pages/DefaultPage"; //import AuxiliaryPage from "./Pages/AuxiliaryPage/AuxiliaryPage.tsx"; import NotFound from "./Pages/NotFound.tsx"; import { ToastContainer } from "react-toastify"; @@ -18,7 +17,7 @@ import { } from "react-router-dom"; import Spinner from "./Components/Spinner/Spinner.tsx"; -import { useDispatch, useSelector,shallowEqual } from 'react-redux'; +import { useDispatch, useSelector, shallowEqual } from 'react-redux'; import { RootState } from './store'; @@ -31,7 +30,7 @@ const App: React.FC = ({ isDarkMode, toggleTheme }) => { const store = useSelector((state: RootState) => ({ loader: state.loader.loadingStack - }),shallowEqual ); + }), shallowEqual); // Apply or remove the "dark-mode" class on the body element based on isDarkMode useEffect(() => { diff --git a/src/ContentProcessorWeb/src/Components/DialogComponent/DialogComponent.tsx b/src/ContentProcessorWeb/src/Components/DialogComponent/DialogComponent.tsx new file mode 100644 index 00000000..be2b439b --- /dev/null +++ b/src/ContentProcessorWeb/src/Components/DialogComponent/DialogComponent.tsx @@ -0,0 +1,64 @@ +import * as React from "react"; +import { + Dialog, + DialogSurface, + DialogTitle, + DialogBody, + DialogContent, + DialogActions, + Button, + useId, +} from "@fluentui/react-components"; + +interface FooterButton { + text: string; + appearance: "primary" | "secondary"; + onClick: () => void; +} + +interface ConfirmationProps { + title: string; + content: string; + isDialogOpen: boolean; // Controlled state for dialog visibility + onDialogClose: () => void; // Function to close the dialog + footerButtons: FooterButton[]; // Array of footer buttons +} + +export const Confirmation: React.FC = ({ + title, + content, + isDialogOpen, + onDialogClose, + footerButtons, +}) => { + const dialogId = useId("dialog-"); + + return ( + + + + {title} + {content} + + {/* Render the footer buttons dynamically */} + {footerButtons.map((button, index) => ( + + ))} + + + + + ); +}; diff --git a/src/ContentProcessorWeb/src/Components/DocumentViewer/DocumentViewer.styles.scss b/src/ContentProcessorWeb/src/Components/DocumentViewer/DocumentViewer.styles.scss index 8a89b6ac..f305e1f9 100644 --- a/src/ContentProcessorWeb/src/Components/DocumentViewer/DocumentViewer.styles.scss +++ b/src/ContentProcessorWeb/src/Components/DocumentViewer/DocumentViewer.styles.scss @@ -43,5 +43,13 @@ } +.noDataDocContainer{ + display: flex; + justify-content: center; + height: 100%; + border: 1px solid rgb(219, 219, 219); + background: rgb(246, 246, 246); +} + diff --git a/src/ContentProcessorWeb/src/Components/DocumentViewer/DocumentViewer.tsx b/src/ContentProcessorWeb/src/Components/DocumentViewer/DocumentViewer.tsx index 40bd4055..9a32ef91 100644 --- a/src/ContentProcessorWeb/src/Components/DocumentViewer/DocumentViewer.tsx +++ b/src/ContentProcessorWeb/src/Components/DocumentViewer/DocumentViewer.tsx @@ -28,7 +28,7 @@ const DocumentViewer = ({ className, metadata, urlWithSasToken, iframeKey }: IIF const getContentComponent = () => { if (!metadata || !urlWithSasToken) { - return
{t("components.document.none", "No document available")}
; + return

{t("components.document.none", "No document available")}

; } switch (metadata.mimeType) { diff --git a/src/ContentProcessorWeb/src/Components/FluentComponents/Combobox/Combobox.tsx b/src/ContentProcessorWeb/src/Components/FluentComponents/Combobox/Combobox.tsx deleted file mode 100644 index 77ea54bb..00000000 --- a/src/ContentProcessorWeb/src/Components/FluentComponents/Combobox/Combobox.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import React, { useState, useEffect } from "react"; -import { - Combobox, - makeStyles, - Option, - useId, -} from "@fluentui/react-components"; -import type { ComboboxProps } from "@fluentui/react-components"; -import './Combobox.styles.scss' -import { useDispatch, useSelector,shallowEqual } from 'react-redux'; -import { RootState } from '../../../store'; -import { setSchemaSelectedOption } from '../../../store/slices/leftPanelSlice'; - - -const useStyles = makeStyles({ - root: { - // Stack the label above the field with a gap - display: "grid", - gridTemplateRows: "repeat(1fr)", - justifyItems: "start", - gap: "2px", - // maxWidth: "250px", - flex: 1 - }, -}); - -interface Option { - key: string; // Assuming `Id` is a string, change to `number` if needed - value: string; -} - -interface SchemaItem { - Id: string; // Adjust type if it's a number - Description: string; -} - -const ComboboxComponent = (props: Partial) => { - const comboId = useId("combo-default"); - const styles = useStyles(); - - const [options, setOptions] = useState([]); - - const [selectedValue, setSelectedValue] = useState([]); - - const dispatch = useDispatch(); - - const store = useSelector((state: RootState) => ({ - schemaData: state.leftPanel.schemaData, - schemaSelectedOption: state.leftPanel.schemaSelectedOption, - schemaLoader: state.leftPanel.schemaLoader, - schemaError: state.leftPanel.schemaError - }),shallowEqual - ); - - React.useEffect(() => { - - // setOptions(store.schemaData.map((item: { ClassName: string; }) => (item as { ClassName: string }).ClassName)); - - setOptions(store.schemaData.map((item: SchemaItem) => { - return { - key: item.Id, - value: item.Description - } - } - )); - - }, [store.schemaData]) - - const handleChange: (typeof props)["onOptionSelect"] = (ev, data) => { - //setSelectedValue(data.selectedOptions); - dispatch(setSchemaSelectedOption(data)) - }; - - return ( -
- - {options.map((option) => ( - - ))} - - {store.schemaError &&
Error: {store.schemaError}
} -
- ); -}; - -export default ComboboxComponent; \ No newline at end of file diff --git a/src/ContentProcessorWeb/src/Components/FluentComponents/GridComponent/GridComponent.tsx b/src/ContentProcessorWeb/src/Components/FluentComponents/GridComponent/GridComponent.tsx deleted file mode 100644 index b5f45c3f..00000000 --- a/src/ContentProcessorWeb/src/Components/FluentComponents/GridComponent/GridComponent.tsx +++ /dev/null @@ -1,497 +0,0 @@ -import React, { useState, useEffect } from "react"; -import { FixedSizeList as List1, ListChildComponentProps } from "react-window"; -import { - FolderRegular, - EditRegular, - OpenRegular, - DocumentRegular, - PeopleRegular, - DocumentPdfRegular, - VideoRegular, - ImageRegular, - EditPersonFilled, -} from "@fluentui/react-icons"; -import { Tooltip } from "@fluentui/react-components"; -import { CaretDown16Filled, CaretUp16Filled, DocumentPdf16Filled ,DocumentQueueAdd20Regular } from "@fluentui/react-icons"; - -import { - PresenceBadgeStatus, - Avatar, - useScrollbarWidth, - useFluent, - TableBody, - TableCell, - TableRow, - Table, - TableHeader, - TableHeaderCell, - TableCellLayout, - TableSelectionCell, - createTableColumn, - useTableFeatures, - useTableSelection, - useTableSort, - TableColumnId, - TableRowData as RowStateBase, - useTableColumnSizing_unstable -} from "@fluentui/react-components"; - -import './GridComponent.styles.scss'; - - -import AutoSizer from "react-virtualized-auto-sizer"; -import { fetchContentTableData /*, fetchTableData*/ } from "../../../Services/apiService"; - - -import { useDispatch, useSelector,shallowEqual } from 'react-redux'; -import { RootState } from '../../../store'; - -import { setSelectedGridRow } from '../../../store/slices/leftPanelSlice'; -import useFileType from "../../../Hooks/useFileType"; - -type Item = { - fileName: { - label: string; - icon: JSX.Element; - }; - imported: { - label: string; - //status: PresenceBadgeStatus; - }; - status: { - label: string; - //timestamp: number; - }; - processTime: { - label: string; - //icon: JSX.Element; - }; - entityScore: { - label: string; - //icon: JSX.Element; - }; - schemaScore: { - label: string; - //icon: JSX.Element; - }; - processId: { - label: string; - //icon: JSX.Element; - }; - lastModifiedBy: { - label: string; - } - file_mime_type: { - label: string; - } -}; - -interface GridComponentProps { - -} - -interface TableRowData extends RowStateBase { - onClick: (e: React.MouseEvent) => void; - onKeyDown: (e: React.KeyboardEvent) => void; - selected: boolean; - appearance: "brand" | "none"; -} - -interface ReactWindowRenderFnProps extends ListChildComponentProps { - data: TableRowData[]; - style: any; - index: any; -} - -const columns = [ - createTableColumn({ - columnId: "fileName", - compare: (a, b) => { - return a.fileName.label.localeCompare(b.fileName.label); - }, - }), - createTableColumn({ - columnId: "imported", - compare: (a, b) => { - const dateA = new Date(a.imported.label).getTime(); - const dateB = new Date(b.imported.label).getTime(); - return dateA - dateB; // Ascending order (oldest to newest) - }, - }), - createTableColumn({ - columnId: "status", - compare: (a, b) => { - return a.status.label.localeCompare(b.status.label); - }, - renderHeaderCell: () => <>
Status
, - }), - createTableColumn({ - columnId: "processTime", - compare: (a, b) => { - return a.processTime.label.localeCompare(b.processTime.label); - }, - }), - createTableColumn({ - columnId: "entityScore", - compare: (a, b) => { - return a.entityScore.label.localeCompare(b.entityScore.label); - }, - }), - createTableColumn({ - columnId: "schemaScore", - compare: (a, b) => { - return a.schemaScore.label.localeCompare(b.schemaScore.label); - }, - }), - createTableColumn({ - columnId: "processId", - compare: (a, b) => { - return a.processId.label.localeCompare(b.processId.label); - }, - }), -]; - - -const GridComponent: React.FC = () => { - - const dispatch = useDispatch(); - - const store = useSelector((state: RootState) => ({ - gridData: state.leftPanel.gridData, - processId: state.leftPanel.processId - }),shallowEqual - ); - - const { targetDocument } = useFluent(); - const scrollbarWidth = useScrollbarWidth({ targetDocument }); - - const [sortState, setSortState] = useState<{ - sortDirection: "ascending" | "descending"; - sortColumn: TableColumnId | undefined; - }>({ - sortDirection: "ascending" as const, - sortColumn: "file", - }); - - const [items, setItems] = useState([]); // State to store fetched items - const [selectedRow, setSelectedRow] = useState(null); // State to store selected row - const { fileType, getMimeType } = useFileType(null); - useEffect(() => { - const getFIleImage = (mimeType:any,file : any)=>{ - if(mimeType ==="application/pdf") return - const mType = getMimeType({name : file}); - switch(mType){ - case 'image/jpeg': - case 'image/png': - case 'image/gif': - case 'image/bmp': - return - - case 'application/pdf': - return - - default : - return - } - } - if (store.gridData.items.length > 0) { - const items = store.gridData.items.map((item: any) => ({ - //fileName: { label: item.processed_file_name, icon: }, - fileName: { - label: item.processed_file_name, - icon: getFIleImage(item.processed_file_mime_type, item.processed_file_name) - }, - imported: { label: item.imported_time }, - status: { label: item.status }, - processTime: { label: item.processed_time ?? "..." }, - entityScore: { label: item.entity_score.toString() }, - schemaScore: { label: item.schema_score.toString() }, - processId: { label: item.process_id }, - lastModifiedBy: { label: item.last_modified_by }, - })); - setItems(items); - //setSelectedRow(items[0].processId.label); - dispatch(setSelectedGridRow({ processId: items[0].processId.label, item: store.gridData.items[0] })) - } - - }, [store.gridData]) - - useEffect(() => { - setSelectedRow(store.processId); - }, [store.processId]) - - const handleRowClick = (processId: string) => { - const selectedItem = store.gridData.items.find((item: any) => item.process_id == processId) - dispatch(setSelectedGridRow({ processId: processId, item: selectedItem })) - } - - const columnSizingOptions = { - fileName: { - idealWidth: 300, - minWidth: 150, - }, - imported: { - minWidth: 110, - defaultWidth: 250, - }, - - }; - - const { - getRows, - sort: { getSortDirection, toggleColumnSort, sort }, - selection: { - allRowsSelected, - someRowsSelected, - toggleAllRows, - toggleRow, - isRowSelected, - }, - } = useTableFeatures( - { - columns, - items, - }, - [ - useTableSelection({ - selectionMode: "multiselect", - defaultSelectedItems: new Set([]), - }), - useTableSort({ - sortState, - onSortChange: (e, nextSortState) => setSortState(nextSortState), - }), - - // useTableColumnSizing_unstable({ - // columnSizingOptions, - // autoFitColumns: false, - // }), - ] - ); - - const renderRoudedButton = (txt: string) => { - return ( - <> -
- - {txt} - -
- - ) - } - - const renderProcessTimeInSeconds = (timeString: string) => { - if (!timeString) { - return
...
; - } - - const parts = timeString.split(":"); - if (parts.length !== 3) { - return
{timeString}
; - } - - const [hours, minutes, seconds] = parts.map(Number); - const totalSeconds = (hours * 3600 + minutes * 60 + seconds).toFixed(2); - - return
{totalSeconds}s
; - }; - - const renderPercentage = (valueText: string, status: string) => { - // Check if the value is a number - const decimalValue = Number(valueText); - if (isNaN(decimalValue) || status !== 'Completed') { - return
...
- }; - - const wholeValue = Math.round(decimalValue * 100); - - let color; - let numberClass = ''; - - // Apply color based on value - if (wholeValue > 80) { - color = '#359B35'; - numberClass = 'gClass' - } else if (wholeValue >= 50 && wholeValue <= 80) { - color = '#C19C00'; - numberClass = 'yClass' - } else if (wholeValue >= 30 && wholeValue < 50) { - color = '#FF5F3DE5'; - numberClass = 'oClass' - } else { - color = '#B10E1C'; - numberClass = 'rClass' - } - - return ( - <> -
- {wholeValue}% - {(wholeValue > 50) ? - - : - - } -
- - ) - } - - const calculateSchemaScore = (valueText: string, lastModifiedBy: string, status: string) => { - if (lastModifiedBy === 'user') { - return ( -
- - Verified - -
- ); - } - return renderPercentage(valueText, status); - } - - const rendertext = (text: any, type = '') => { - if (type === 'date') { - const date = new Date(text); - const formattedDate = `${(date.getMonth() + 1).toString().padStart(2, "0")}/${date.getDate().toString().padStart(2, "0")}/${date.getFullYear()}`; - return
{formattedDate}
; - } - return ( - <>
{text}
- ) - } - - const rows: TableRowData[] = sort(getRows((row) => { - const selected = isRowSelected(row.rowId); - return { - ...row, - onClick: (e: React.MouseEvent) => toggleRow(e, row.rowId), - onKeyDown: (e: React.KeyboardEvent) => { - if (e.key === " ") { - e.preventDefault(); - toggleRow(e, row.rowId); - } - }, - selected, - appearance: selected ? ("brand" as const) : ("none" as const), - }; - })); - - const RenderRow = ({ index, style, data }: ReactWindowRenderFnProps) => { - const { item, selected, appearance, onClick, onKeyDown } = data[index]; - const isSelected = item.processId.label === selectedRow; - return ( - handleRowClick(item.processId.label)} - //onClick={onClick} - //appearance={appearance} - appearance={isSelected ? "brand" : "none"} // Change appearance based on selection - className={isSelected ? "selectedRow" : ""} - > - {/* */} - - - - {item.fileName.label} - - - - - {rendertext(item.imported.label, 'date')} - - - {renderRoudedButton(item.status.label)} - - - {renderProcessTimeInSeconds(item.processTime.label)} - - - - {renderPercentage(item.entityScore.label, item.status.label)} - - - - {calculateSchemaScore(item.schemaScore.label, item.lastModifiedBy.label, item.status.label)} - - - - ); - }; - - const toggleAllKeydown = React.useCallback( - (e: React.KeyboardEvent) => { - if (e.key === " ") { - toggleAllRows(e); - e.preventDefault(); - } - }, - [toggleAllRows] - ); - - const headerSortProps = (columnId: TableColumnId) => ({ - onClick: (e: React.MouseEvent) => toggleColumnSort(e, columnId), - sortDirection: getSortDirection(columnId), - }); - - return ( -
- - - - {/* */} - File name - Imported - Status - Process time - Entity score - Schema score - {/** Scrollbar alignment for the header */} -
- - - -
- - {({ height, width }) => ( - - {RenderRow} - - )} - -
-
-
-
- ); -}; - -export default GridComponent; \ No newline at end of file diff --git a/src/ContentProcessorWeb/src/Components/JSONEditor/JSONEditor.tsx b/src/ContentProcessorWeb/src/Components/JSONEditor/JSONEditor.tsx index 9f93f5d9..2760e7f4 100644 --- a/src/ContentProcessorWeb/src/Components/JSONEditor/JSONEditor.tsx +++ b/src/ContentProcessorWeb/src/Components/JSONEditor/JSONEditor.tsx @@ -24,14 +24,18 @@ const JSONEditor: React.FC = () => { useEffect(() => { - if (Object.keys(store.contentData).length > 0) { - const formattedJson = store.contentData.result; - const data = { - extracted_result: { - ...formattedJson + if(!store.cLoader){ + if (Object.keys(store.contentData).length > 0) { + const formattedJson = store.contentData.result; + const data = { + extracted_result: { + ...formattedJson + } } + setJsonData(data); + }else { + setJsonData({}) } - setJsonData(data); } }, [store.contentData]) @@ -42,7 +46,8 @@ const JSONEditor: React.FC = () => { return ( <>{ - store.cLoader ?

Loading...

: + store.cLoader ?

Loading...

: + Object.keys(jsonData).length == 0 ?

No data available

: { - return ( -
- {/* PanelToolbar */} - -
- ); -}; - -// No need to change the export, even if copied. -export default PanelLeft; diff --git a/src/ContentProcessorWeb/src/Components/Panels/PanelRight.tsx b/src/ContentProcessorWeb/src/Components/Panels/PanelRight.tsx deleted file mode 100644 index 11ec8e5a..00000000 --- a/src/ContentProcessorWeb/src/Components/Panels/PanelRight.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import * as React from "react"; -import { Button } from "@fluentui/react-components"; -import { MoreHorizontalRegular, Sparkle20Filled } from "@fluentui/react-icons"; -import PanelToolbar from "../../Hooks/usePanelHooks.tsx"; - -// Visit https://mochimilk.github.io/cto_coral_docs/index.html#/developers/panels for documentation. - -// It is recommended that you create copies of PanelLeft.tsx and/or PanelRight.tsx and move them into dedicated folders under Pages. -// See file structure in src/Pages/DefaultPage and the imports section in DefaultPage.tsx. - -const PanelRight: React.FC = () => { - return ( -
- {/* PanelHeader */} - } - header="Copilot" - > -
- ); -}; - -// No need to change the export, even if copied. -export default PanelRight; diff --git a/src/ContentProcessorWeb/src/Components/Panels/Panels.css b/src/ContentProcessorWeb/src/Components/Panels/Panels.css deleted file mode 100644 index 5bf4adca..00000000 --- a/src/ContentProcessorWeb/src/Components/Panels/Panels.css +++ /dev/null @@ -1,170 +0,0 @@ -/* Panel container styling */ - -.panelLeft { - /* background-color: var(--colorNeutralBackground4); */ - position: relative; - display: flex; - height: 100%; - box-sizing: border-box; - flex-direction: column; - overflow-y: auto; - background-color: white; - border: 1px solid #D6D6D6; - border-width: 1px 1px 0px 0px; -} - -.panelRight { - /* background-color: var(--colorNeutralBackground4); */ - position: relative; - display: flex; - height: 100%; - box-sizing: border-box; - flex-direction: column; - overflow-y: auto; - background-color: white; - border: 1px solid #D6D6D6; - border-width: 1px 0px 0px 0px; -} - -/* Resize functionality on panels */ - -.resize-handle-left, -.resize-handle-right { - position: absolute; - top: 0; - width: 3px; - height: 100%; - cursor: ew-resize; - background-color: transparent; -} - -.resize-handle-left { - right: 0; -} - -.resize-handle-right { - left: 0; - z-index: 1; -} - -.resize-handle-left:hover, -.resize-handle-right:hover { - background-color: var(--colorNeutralStroke1); -} - -.resize-handle-right:hover { - background-color: var(--colorNeutralStroke1); -} - -.resize-handle-left:active, -.resize-handle-right:active { - background-color: var(--colorNeutralStroke1); -} - -.contentContainer { - display: flex; - flex: 1; - flex-direction: column; - width: 100%; - height: 100%; - align-items: start; - box-sizing: border-box; -} - -.content { - display: flex; - flex-direction: column; - flex: 1 1 auto; - overflow: scroll; - box-sizing: border-box; - padding: 64px 16px 64px 16px; - height: 100%; - width: 100%; - margin: auto; - overflow-x: hidden; - overflow-y: auto; - margin-top: -64px; - -} - -.panelToolbar { - display: flex; - width: 100%; - align-items: center; - padding: 16px 16px; - min-height: 64px; - justify-content: space-between; - box-sizing: border-box; - backdrop-filter: saturate(180%) blur(16px); - transition: all 0.3s cubic-bezier(0.215, 0.61, 0.355, 1); -} - -.buttonGroup { - display: flex; - align-items: center; -} - -.headerTitleGroup { - display: flex; - align-items: center; - gap: 8px; - width: 100%; -} - -.leftcontent { - display: flex; - flex-direction: column; - flex: 1 1 auto; - overflow: scroll; - box-sizing: border-box; - padding: 0px 16px 16px 16px; - height: 100%; - width: 100%; - margin: auto; - overflow-x: hidden; - overflow-y: auto; - -} - -.panelRightContent { - display: flex; - flex-direction: column; - flex: 1 1 auto; - overflow: scroll; - box-sizing: border-box; - padding: 0px 16px 16px 16px; - height: 100%; - width: 100%; - margin: auto; - overflow-x: hidden; - overflow-y: auto; - /* margin-top: -64px; */ -} - -.fullHeight { - height: 100vh; -} - -.brand, -.selectedRow { - background-color: #e0e0e0; - /* Light gray for selected row */ - border: 2px solid #0078d4; - /* Blue border for selected row */ -} - -/* .JSONEditorClass { - max-height: none !important; -} */ - -.right-loader{ - display: flex; - justify-content: center; - align-items: center; - height: 100%; -} - -.custom-test button{ - padding : 0px !important; - padding :5px 12px 12px 10px !important -} \ No newline at end of file diff --git a/src/ContentProcessorWeb/src/Components/UploadContent/uploadContent.tsx b/src/ContentProcessorWeb/src/Components/UploadContent/uploadContent.tsx deleted file mode 100644 index e3792561..00000000 --- a/src/ContentProcessorWeb/src/Components/UploadContent/uploadContent.tsx +++ /dev/null @@ -1,121 +0,0 @@ -// import * as React from "react"; -// import { Button } from "@fluentui/react-components"; -// import { Dialog, DialogSurface, DialogBody, DialogTitle, DialogActions, DialogTrigger } from "@fluentui/react-components"; -// import { Table, TableBody, TableCell, TableHeader, TableRow, TableHeaderCell } from "@fluentui/react-components"; -// import { ProgressBar } from "@fluentui/react-components"; - -// interface FileUploadProps { -// isOpen: boolean; -// onClose: () => void; -// } - -// const API_BASE_URL = process.env.REACT_APP_API_BASE_URL; - -// const FileUpload: React.FC = ({ isOpen, onClose }) => { -// const [selectedFiles, setSelectedFiles] = React.useState([]); -// const [uploading, setUploading] = React.useState(false); -// const [progress, setProgress] = React.useState(0); - -// const handleFileChange = (event: React.ChangeEvent) => { -// if (event.target.files) { -// setSelectedFiles(Array.from(event.target.files)); -// } -// }; - -// const handleUpload = async () => { -// if (selectedFiles.length === 0) { -// alert("Please select files first."); -// return; -// } - -// setUploading(true); -// setProgress(0); - -// // const formData = new FormData(); -// // selectedFiles.forEach((file) => { -// // formData.append("file", file); -// // formData.append("metadata_id", crypto.randomUUID()); -// // formData.append("schema_id", "schema_id"); -// // }); - - -// // update -// const metadata = { -// Metadata_Id: crypto.randomUUID(), -// Schema_Id: "Schema 001", -// }; - -// const formData = new FormData(); - -// selectedFiles.forEach((file) => { -// // Attach the file -// formData.append("file", file); -// // Attach JSON metadata (replace the name if needed by your API) -// formData.append("data", JSON.stringify(metadata)); -// }); - - -// try { -// const response = await fetch(`${API_BASE_URL}/upload`, { -// method: "POST", -// body: formData, -// }); - -// if (response.ok) { -// const responseData = await response.json(); -// alert("Files uploaded successfully!"); -// setSelectedFiles([]); -// } else { -// alert("Upload failed. Try again."); -// } -// } catch (error) { -// console.error("Upload error:", error); -// alert("Error uploading files."); -// } finally { -// setUploading(false); -// onClose(); // Close the dialog after upload -// } -// }; - -// return ( -// !data.open && onClose()}> -// -// -// Upload Documents -// -// - -// {selectedFiles.length > 0 && ( -// -// -// -// File Name -// Size (bytes) -// -// -// -// {selectedFiles.map((file) => ( -// -// {file.name} -// {file.size} -// -// ))} -// -//
-// )} - -// {uploading && } - -// -// -// -// -//
-//
-//
-// ); -// }; - -// export default FileUpload; \ No newline at end of file diff --git a/src/ContentProcessorWeb/src/Hooks/useApi.ts b/src/ContentProcessorWeb/src/Hooks/useApi.ts deleted file mode 100644 index aafb53c8..00000000 --- a/src/ContentProcessorWeb/src/Hooks/useApi.ts +++ /dev/null @@ -1,126 +0,0 @@ -// src/hooks/useApi.ts -import { useState, useEffect } from 'react'; -import useAuth from '../msal-auth/useAuth'; // Importing your custom useAuth hook - -const api = process.env.REACT_APP_API_BASE_URL; // base API URL - -interface RequestOptions { - method: string; - headers: { [key: string]: string }; - body?: string | FormData | null; -} - -interface ApiResponse { - [key: string]: any; -} - -// Custom hook to fetch API with authentication -const useApi = () => { - const { getToken } = useAuth(); // Call useAuth hook to get the token - const [token, setToken] = useState(null); - - // Fetch token on mount - useEffect(() => { - const fetchToken = async () => { - const fetchedToken = await getToken(); // Fetch the token asynchronously - //setToken(fetchedToken || null); // Save the token to state - }; - - fetchToken(); - }, [getToken]); - - // Function to make API requests with JWT token - const fetchWithAuth = async ( - url: string, - method: 'GET' | 'POST' | 'PUT' | 'DELETE' = 'GET', - body: any = null - ): Promise => { - if (!token) { - throw new Error("Token is not available"); - } - - const headers: { [key: string]: string } = { - 'Authorization': `Bearer ${token}`, // Add token to Authorization header - 'Accept': 'application/json', // Add Accept header - 'Content-Type': 'application/json', // Default to application/json - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Methods': 'GET', - 'Access-Control-Allow-Headers': '*' - }; - - if (body instanceof FormData) { - delete headers['Content-Type']; // Don't set Content-Type if the body is FormData - } - - const options: RequestOptions = { - method, - headers, - }; - - if (body) { - options.body = JSON.stringify(body); // Add body for POST/PUT - } - - try { - const response = await fetch(`${api}${url}`, options); - - if (!response.ok) { - const errorText = await response.text(); - throw new Error(errorText || 'Something went wrong'); - } - - const isJson = response.headers.get('content-type')?.includes('application/json'); - return isJson ? await response.json() : null; - } catch (error) { - console.error('API Error:', (error as Error).message); - throw error; - } - }; - - // Function to make API requests without authentication (for login) - const fetchWithoutAuth = async ( - url: string, - method: 'POST' = 'POST', - body: any = null - ): Promise => { - const headers: { [key: string]: string } = { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - }; - - const options: RequestOptions = { - method, - headers, - }; - - if (body) { - options.body = JSON.stringify(body); // Add the body for POST - } - - try { - const response = await fetch(`${api}${url}`, options); - - if (!response.ok) { - const errorText = await response.text(); - throw new Error(errorText || 'Login failed'); - } - - const isJson = response.headers.get('content-type')?.includes('application/json'); - return isJson ? await response.json() : null; - } catch (error) { - console.error('Login Error:', (error as Error).message); - throw error; - } - }; - - return { - get: (url: string): Promise => fetchWithAuth(url, 'GET'), - post: (url: string, body: any): Promise => fetchWithAuth(url, 'POST', body), - put: (url: string, body: any): Promise => fetchWithAuth(url, 'PUT', body), - delete: (url: string): Promise => fetchWithAuth(url, 'DELETE'), - upload: (url: string, formData: FormData): Promise => fetchWithAuth(url, 'POST', formData), - login: (url: string, body: any): Promise => fetchWithoutAuth(url, 'POST', body), // For login without auth - }; -}; - -export default useApi; diff --git a/src/ContentProcessorWeb/src/Pages/DefaultPage/Components/ProcessQueueGrid/CustomCellRender.tsx b/src/ContentProcessorWeb/src/Pages/DefaultPage/Components/ProcessQueueGrid/CustomCellRender.tsx new file mode 100644 index 00000000..794d8c3e --- /dev/null +++ b/src/ContentProcessorWeb/src/Pages/DefaultPage/Components/ProcessQueueGrid/CustomCellRender.tsx @@ -0,0 +1,159 @@ +import React from 'react'; +import { CaretUp16Filled, CaretDown16Filled, EditPersonFilled } from '@fluentui/react-icons'; +import { Button, Menu, MenuTrigger, MenuPopover, MenuList, MenuItem } from '@fluentui/react-components'; +import { MoreVerticalRegular, MoreVerticalFilled, bundleIcon } from '@fluentui/react-icons'; + +type CellRendererProps = { + type: string; + props?: any; +}; + +const MoreVerticallIcon = bundleIcon( + MoreVerticalFilled, + MoreVerticalRegular +); + +const CellRenderer: React.FC = ({ type, props }) => { + // Destructure props based on type + const { + txt, timeString, valueText, status, lastModifiedBy, text, item, deleteBtnStatus, setSelectedDeleteItem, toggleDialog, + } = props || {}; + + // Render for rounded button + const renderRoundedButton = (txt: string) => ( +
+ {txt} +
+ ); + + // Render for processing time + const renderProcessTimeInSeconds = (timeString: string) => { + if (!timeString) { + return
...
; + } + + const parts = timeString.split(":"); + if (parts.length !== 3) { + return
{timeString}
; + } + + const [hours, minutes, seconds] = parts.map(Number); + const totalSeconds = (hours * 3600 + minutes * 60 + seconds).toFixed(2); + + return
{totalSeconds}s
; + }; + + // Render for percentage + const renderPercentage = (valueText: string, status: string) => { + const decimalValue = Number(valueText); + if (isNaN(decimalValue) || status !== 'Completed') { + return
...
; + } + + const wholeValue = Math.round(decimalValue * 100); + let color; + let numberClass = ''; + + // Apply color based on value + if (wholeValue > 80) { + color = '#359B35'; + numberClass = 'gClass'; + } else if (wholeValue >= 50 && wholeValue <= 80) { + color = '#C19C00'; + numberClass = 'yClass'; + } else if (wholeValue >= 30 && wholeValue < 50) { + color = '#FF5F3DE5'; + numberClass = 'oClass'; + } else { + color = '#B10E1C'; + numberClass = 'rClass'; + } + + return ( +
+ {wholeValue}% + {wholeValue > 50 ? ( + + ) : ( + + )} +
+ ); + }; + + // Render for schema score + const calculateSchemaScore = (valueText: string, lastModifiedBy: string, status: string) => { + if (lastModifiedBy === 'user') { + return ( +
+ + Verified + +
+ ); + } + return renderPercentage(valueText, status); + }; + + // Render for text + const renderText = (text: any, type = '') => { + if (type === 'date') { + const date = new Date(text); + const formattedDate = `${(date.getMonth() + 1).toString().padStart(2, "0")}/${date.getDate().toString().padStart(2, "0")}/${date.getFullYear()}`; + return
{formattedDate}
; + } + return
{text}
; + }; + + // Render for delete button + const renderDeleteButton = (item: any, deleteBtnStatus: any) => ( + + + + ); + + // Switch based on type + switch (type) { + case 'roundedButton': + return renderRoundedButton(txt || ''); + case 'processTime': + return renderProcessTimeInSeconds(timeString || ''); + case 'percentage': + return renderPercentage(valueText || '', status || ''); + case 'schemaScore': + return calculateSchemaScore(valueText || '', lastModifiedBy || '', status || ''); + case 'text': + return renderText(text, 'center'); + case 'date': + return renderText(text, 'date'); + case 'deleteButton': + return renderDeleteButton(item, deleteBtnStatus || {}); + default: + return
Invalid Type
; + } +}; + +export default CellRenderer; diff --git a/src/ContentProcessorWeb/src/Components/FluentComponents/GridComponent/GridComponent.styles.scss b/src/ContentProcessorWeb/src/Pages/DefaultPage/Components/ProcessQueueGrid/ProcessQueueGrid.styles.scss similarity index 73% rename from src/ContentProcessorWeb/src/Components/FluentComponents/GridComponent/GridComponent.styles.scss rename to src/ContentProcessorWeb/src/Pages/DefaultPage/Components/ProcessQueueGrid/ProcessQueueGrid.styles.scss index 74c29338..d965cad7 100644 --- a/src/ContentProcessorWeb/src/Components/FluentComponents/GridComponent/GridComponent.styles.scss +++ b/src/ContentProcessorWeb/src/Pages/DefaultPage/Components/ProcessQueueGrid/ProcessQueueGrid.styles.scss @@ -1,30 +1,36 @@ -.col0 { - min-width: 3% !important; -} - .col1 { - min-width: 20% !important; + min-width: 120px !important; } .col2 { - min-width: 10% !important; + min-width: 55px !important; + max-width: 85px !important; } .col3 { - min-width: 12% !important; + min-width: 45px !important; + max-width: 70px !important; } .col4 { - min-width: 12% !important; + min-width: 35px !important; + max-width: 85px !important; } .col5 { - min-width: 12% !important; + min-width: 35px !important; + max-width: 85px !important; } .col6 { - min-width: 12% !important; + min-width: 45px !important; + max-width: 85px !important; +} +.col7 { + min-width: 30px !important; + max-width: 35px !important; + box-sizing: border-box; } .roundedBtn { @@ -38,6 +44,7 @@ white-space: nowrap; font-size: 10px; text-transform: capitalize; + span { color: #2C3C85 } @@ -78,7 +85,7 @@ // max-height: calc(100vh - 235px) !important; // height: calc(100vh - 235px) !important; flex: 1 1 auto; - height : 100%; + height: 100%; //background-color: red; >div { @@ -95,12 +102,13 @@ .centerAlign { text-align: center; } + .percentageContainer { display: flex; align-items: center; justify-content: center; - } - +} + .editPersonIcon { margin-right: 4px; display: flex; diff --git a/src/ContentProcessorWeb/src/Pages/DefaultPage/Components/ProcessQueueGrid/ProcessQueueGrid.tsx b/src/ContentProcessorWeb/src/Pages/DefaultPage/Components/ProcessQueueGrid/ProcessQueueGrid.tsx new file mode 100644 index 00000000..e2cccf18 --- /dev/null +++ b/src/ContentProcessorWeb/src/Pages/DefaultPage/Components/ProcessQueueGrid/ProcessQueueGrid.tsx @@ -0,0 +1,368 @@ +import React, { useState, useEffect } from "react"; +import { FixedSizeList as List, ListChildComponentProps } from "react-window"; +import { DocumentQueueAdd20Regular, DocumentPdfRegular, ImageRegular } from "@fluentui/react-icons"; +import { TableCellActions, Tooltip } from "@fluentui/react-components"; +import { + PresenceBadgeStatus, Avatar, useScrollbarWidth, useFluent, TableBody, TableCell, TableRow, Table, + TableHeader, TableHeaderCell, TableCellLayout, TableSelectionCell, createTableColumn, useTableFeatures, + useTableSelection, useTableSort, TableColumnId, useTableColumnSizing_unstable, + TableRowId +} from "@fluentui/react-components"; +import './ProcessQueueGrid.styles.scss'; +import AutoSizer from "react-virtualized-auto-sizer"; +import CustomCellRender from "./CustomCellRender"; +import { useDispatch, useSelector, shallowEqual } from 'react-redux'; +import { RootState, AppDispatch } from '../../../../store'; +import { setSelectedGridRow, deleteProcessedFile, fetchContentTableData } from '../../../../store/slices/leftPanelSlice'; +import useFileType from "../../../../Hooks/useFileType"; +import { Confirmation } from "../../../../Components/DialogComponent/DialogComponent.tsx"; +import { Item, TableRowData, ReactWindowRenderFnProps, GridComponentProps } from './ProcessQueueGridTypes.ts'; + +const columns = [ + createTableColumn({ + columnId: "fileName", + compare: (a, b) => { + return a.fileName.label.localeCompare(b.fileName.label); + }, + }), + createTableColumn({ + columnId: "imported", + compare: (a, b) => { + const dateA = new Date(a.imported.label).getTime(); + const dateB = new Date(b.imported.label).getTime(); + return dateA - dateB; // Ascending order (oldest to newest) + }, + }), + createTableColumn({ + columnId: "status", + compare: (a, b) => { + return a.status.label.localeCompare(b.status.label); + }, + renderHeaderCell: () => <>
Status
, + }), + createTableColumn({ + columnId: "processTime", + compare: (a, b) => { + return a.processTime.label.localeCompare(b.processTime.label); + }, + }), + createTableColumn({ + columnId: "entityScore", + compare: (a, b) => { + return a.entityScore.label.localeCompare(b.entityScore.label); + }, + }), + createTableColumn({ + columnId: "schemaScore", + compare: (a, b) => { + return a.schemaScore.label.localeCompare(b.schemaScore.label); + }, + }), + createTableColumn({ + columnId: "processId", + compare: (a, b) => { + return a.processId.label.localeCompare(b.processId.label); + }, + }), +]; + + +const ProcessQueueGrid: React.FC = () => { + + const dispatch = useDispatch(); + const store = useSelector((state: RootState) => ({ + gridData: state.leftPanel.gridData, + processId: state.leftPanel.processId, + deleteFilesLoader: state.leftPanel.deleteFilesLoader, + pageSize: state.leftPanel.pageSize, + gridLoader: state.leftPanel.gridLoader + }), shallowEqual + ); + + const { targetDocument } = useFluent(); + const scrollbarWidth = useScrollbarWidth({ targetDocument }); + + const [sortState, setSortState] = useState<{ + sortDirection: "ascending" | "descending"; + sortColumn: TableColumnId | undefined; + }>({ + sortDirection: "ascending" as const, + sortColumn: "file", + }); + + const [items, setItems] = useState([]); // State to store fetched items + const { fileType, getMimeType } = useFileType(null); + + const [selectedRows, setSelectedRows] = React.useState( + () => new Set([0]) + ); + + const [isDialogOpen, setIsDialogOpen] = React.useState(false); + const [selectedDeleteItem, setSelectedDeleteItem] = useState(null); + + useEffect(() => { + const getFIleImage = (mimeType: any, file: any) => { + if (mimeType === "application/pdf") return + const mType = getMimeType({ name: file }); + switch (mType) { + case 'image/jpeg': + case 'image/png': + case 'image/gif': + case 'image/bmp': + return + case 'application/pdf': + return + default: + return + } + } + if (!store.gridLoader) { + if (store.gridData.items.length > 0) { + const items = store.gridData.items.map((item: any) => ({ + fileName: { + label: item.processed_file_name, + icon: getFIleImage(item.processed_file_mime_type, item.processed_file_name) + }, + imported: { label: item.imported_time }, + status: { label: item.status }, + processTime: { label: item.processed_time ?? "..." }, + entityScore: { label: item.entity_score.toString() }, + schemaScore: { label: item.schema_score.toString() }, + processId: { label: item.process_id }, + lastModifiedBy: { label: item.last_modified_by }, + })); + setItems(items); + } else { + setItems([]) + } + } + + }, [store.gridData]) + + useEffect(() => { + console.log("selectedRows", selectedRows, items); + if (items.length > 0 && selectedRows.size > 0) { + const selectedRow = [...selectedRows][0]; + if (typeof selectedRow === 'number') { + let selectedItem = items[selectedRow]; + if (!selectedItem) { + setSelectedRows(new Set([0])); + } else { + console.log("selectedItem", selectedItem); + const findItem = getSelectedItem(selectedItem?.processId.label ?? ''); + dispatch(setSelectedGridRow({ processId: selectedItem?.processId.label, item: findItem })); + } + + } else { + console.error("Selected row is not a valid index", selectedRow); + } + } + }, [selectedRows, items]) + + const getSelectedItem = (processId: string) => { + const findItem = store.gridData.items.find((item: any) => item.process_id == processId) + return findItem; + } + + const { + getRows, + sort: { getSortDirection, toggleColumnSort, sort }, + selection: { + allRowsSelected, + someRowsSelected, + toggleAllRows, + toggleRow, + isRowSelected, + }, + } = useTableFeatures( + { + columns, + items, + }, + [ + useTableSelection({ + selectionMode: "single", + selectedItems: selectedRows, + onSelectionChange: (e, data) => { + setSelectedRows(data.selectedItems) + }, + }), + useTableSort({ + sortState, + onSortChange: (e, nextSortState) => setSortState(nextSortState), + }), + ] + ); + + + + const rows: TableRowData[] = sort(getRows((row) => { + const selected = isRowSelected(row.rowId); + return { + ...row, + onClick: (e: React.MouseEvent) => { + if (!e.defaultPrevented) { + toggleRow(e, row.rowId); + } + }, + onKeyDown: (e: React.KeyboardEvent) => { + if (e.key === " " && !e.defaultPrevented) { + e.preventDefault(); + toggleRow(e, row.rowId); + } + }, + selected, + appearance: selected ? "brand" : "none", + }; + })); + + const onClickCellActions = (e: React.MouseEvent) => + e.preventDefault(); + const onKeyDownCellActions = (e: React.KeyboardEvent) => + e.key === " " && e.preventDefault(); + + const isDeleteDisabled = (processId: string, status: string) => { + if (status != 'Completed' && status != 'Error') return { disabled: true, message: 'Inprogress' } + if (store.deleteFilesLoader.includes(processId)) return { disabled: true, message: 'Deleting' } + return { disabled: false, message: '' } + }; + + const RenderRow = ({ index, style, data }: ReactWindowRenderFnProps) => { + const { item, selected, appearance, onClick, onKeyDown } = data[index]; + const deleteBtnStatus = isDeleteDisabled(item.processId.label, item.status.label); + return ( + + + + + {item.fileName.label} + + + + + + + + + + + + + + + + + + + + + + + + ); + }; + + const headerSortProps = (columnId: TableColumnId) => ({ + onClick: (e: React.MouseEvent) => toggleColumnSort(e, columnId), + sortDirection: getSortDirection(columnId), + }); + + const handleDelete = async () => { + if (selectedDeleteItem) { + try { + toggleDialog(); + await dispatch(deleteProcessedFile({ processId: selectedDeleteItem.processId.label ?? null })); + } catch (error: any) { + + } finally { + dispatch(fetchContentTableData({ pageSize: store.pageSize, pageNumber: 1 })).unwrap(); + } + } + }; + + const toggleDialog = () => { + setIsDialogOpen(!isDialogOpen); + }; + + return ( + <> +
+ + + + File name + Imported + Status + Process time + Entity score + Schema score + + {/*
*/} + + + +
+ + {({ height, width }) => ( + + {RenderRow} + + )} + +
+
+
+
+ + + + ); +}; + +export default ProcessQueueGrid; \ No newline at end of file diff --git a/src/ContentProcessorWeb/src/Pages/DefaultPage/Components/ProcessQueueGrid/ProcessQueueGridTypes.ts b/src/ContentProcessorWeb/src/Pages/DefaultPage/Components/ProcessQueueGrid/ProcessQueueGridTypes.ts new file mode 100644 index 00000000..2cdc6eaf --- /dev/null +++ b/src/ContentProcessorWeb/src/Pages/DefaultPage/Components/ProcessQueueGrid/ProcessQueueGridTypes.ts @@ -0,0 +1,29 @@ +import { TableRowId, TableRowData as RowStateBase, } from "@fluentui/react-components"; +import { ListChildComponentProps } from "react-window"; + +export interface Item { + fileName: { label: string; icon: JSX.Element }; + imported: { label: string }; + status: { label: string }; + processTime: { label: string }; + entityScore: { label: string }; + schemaScore: { label: string }; + processId: { label: string }; + lastModifiedBy: { label: string }; + file_mime_type: { label: string }; +} + +export interface TableRowData extends RowStateBase { + onClick: (e: React.MouseEvent) => void; + onKeyDown: (e: React.KeyboardEvent) => void; + selected: boolean; + appearance: "brand" | "none"; +} + +export interface ReactWindowRenderFnProps extends ListChildComponentProps { + data: TableRowData[]; + style: any; + index: number; +} + +export interface GridComponentProps { } diff --git a/src/ContentProcessorWeb/src/Components/FluentComponents/Combobox/Combobox.styles.scss b/src/ContentProcessorWeb/src/Pages/DefaultPage/Components/SchemaDropdown/SchemaDropdown.styles.scss similarity index 100% rename from src/ContentProcessorWeb/src/Components/FluentComponents/Combobox/Combobox.styles.scss rename to src/ContentProcessorWeb/src/Pages/DefaultPage/Components/SchemaDropdown/SchemaDropdown.styles.scss diff --git a/src/ContentProcessorWeb/src/Pages/DefaultPage/Components/SchemaDropdown/SchemaDropdown.tsx b/src/ContentProcessorWeb/src/Pages/DefaultPage/Components/SchemaDropdown/SchemaDropdown.tsx new file mode 100644 index 00000000..68e15226 --- /dev/null +++ b/src/ContentProcessorWeb/src/Pages/DefaultPage/Components/SchemaDropdown/SchemaDropdown.tsx @@ -0,0 +1,74 @@ +import React, { useState, useEffect } from "react"; +import { Combobox, makeStyles, Option, useId } from "@fluentui/react-components"; +import type { ComboboxProps } from "@fluentui/react-components"; +import './SchemaDropdown.styles.scss'; +import { useDispatch, useSelector, shallowEqual } from 'react-redux'; +import { RootState } from '../../../../store'; +import { setSchemaSelectedOption } from '../../../../store/slices/leftPanelSlice'; +import { OptionList, SchemaItem, StoreState } from './SchemaDropdownTypes'; // Importing types + +const useStyles = makeStyles({ + root: { + display: "grid", + gridTemplateRows: "repeat(1fr)", + justifyItems: "start", + gap: "2px", + flex: 1 + }, +}); + +const ComboboxComponent = (props: Partial) => { + const comboId = useId("combo-default"); + const styles = useStyles(); + + const [options, setOptions] = useState([]); + + const dispatch = useDispatch(); + + const store = useSelector( + (state: RootState) => ({ + schemaData: state.leftPanel.schemaData, + schemaSelectedOption: state.leftPanel.schemaSelectedOption, + schemaError: state.leftPanel.schemaError, + }), + shallowEqual + ); + + useEffect(() => { + setOptions( + store.schemaData.map((item: SchemaItem) => ({ + key: item.Id, + value: item.Description, + })) + ); + }, [store.schemaData]); + + const handleChange: (typeof props)["onOptionSelect"] = (ev, data) => { + dispatch(setSchemaSelectedOption(data)); + }; + + return ( +
+ + {options.map((option) => ( + + ))} + + {store.schemaError &&
Error: {store.schemaError}
} +
+ ); +}; + +export default ComboboxComponent; diff --git a/src/ContentProcessorWeb/src/Pages/DefaultPage/Components/SchemaDropdown/SchemaDropdownTypes.ts b/src/ContentProcessorWeb/src/Pages/DefaultPage/Components/SchemaDropdown/SchemaDropdownTypes.ts new file mode 100644 index 00000000..ba4424b9 --- /dev/null +++ b/src/ContentProcessorWeb/src/Pages/DefaultPage/Components/SchemaDropdown/SchemaDropdownTypes.ts @@ -0,0 +1,17 @@ +// types.ts +export interface OptionList { + key: string; // Assuming `Id` is a string, change to `number` if needed + value: string; +} + +export interface SchemaItem { + Id: string; // Adjust type if it's a number + Description: string; +} + +export interface StoreState { + schemaData: SchemaItem[]; + schemaSelectedOption: { optionText: string } | null; + schemaLoader: boolean; + schemaError: string | null; +} diff --git a/src/ContentProcessorWeb/src/Pages/DefaultPage/DefaultPageContent.tsx b/src/ContentProcessorWeb/src/Pages/DefaultPage/PanelCenter.tsx similarity index 98% rename from src/ContentProcessorWeb/src/Pages/DefaultPage/DefaultPageContent.tsx rename to src/ContentProcessorWeb/src/Pages/DefaultPage/PanelCenter.tsx index c93c9626..5cd861d1 100644 --- a/src/ContentProcessorWeb/src/Pages/DefaultPage/DefaultPageContent.tsx +++ b/src/ContentProcessorWeb/src/Pages/DefaultPage/PanelCenter.tsx @@ -126,7 +126,7 @@ const ContentDevelopers: React.FC = ({ isSavingInProgress: state.centerPanel.isSavingInProgress, processStepsData: state.centerPanel.processStepsData, selectedItem : state.leftPanel.selectedItem, - activeProcessId : state.centerPanel.activeProcessId + activeProcessId : state.centerPanel.activeProcessId, }), shallowEqual ); @@ -184,7 +184,7 @@ const ContentDevelopers: React.FC = ({ /> ) :

No data available

} - ), [store.activeProcessId,store.selectedItem]); + ), [store.activeProcessId,store.selectedItem,store.contentData]); const ProcessHistory = useCallback(() => (
diff --git a/src/ContentProcessorWeb/src/Pages/DefaultPage/DefaultPagePanelLeft.tsx b/src/ContentProcessorWeb/src/Pages/DefaultPage/PanelLeft.tsx similarity index 79% rename from src/ContentProcessorWeb/src/Pages/DefaultPage/DefaultPagePanelLeft.tsx rename to src/ContentProcessorWeb/src/Pages/DefaultPage/PanelLeft.tsx index 570ea66f..695ed077 100644 --- a/src/ContentProcessorWeb/src/Pages/DefaultPage/DefaultPagePanelLeft.tsx +++ b/src/ContentProcessorWeb/src/Pages/DefaultPage/PanelLeft.tsx @@ -2,14 +2,14 @@ import React, { useState, useEffect } from "react"; import { Button } from "@fluentui/react-components"; import { ArrowClockwiseRegular, ArrowUploadRegular } from "@fluentui/react-icons"; import PanelToolbar from "../../Hooks/usePanelHooks.tsx"; -import GridComponent from '../../Components/FluentComponents/GridComponent/GridComponent.tsx'; -import ComboboxComponent from '../../Components/FluentComponents/Combobox/Combobox'; -import UploadFilesModal from "../../Components/UploadContent/UploadFilesModal"; +import ProcessQueueGrid from './Components/ProcessQueueGrid/ProcessQueueGrid.tsx'; +import SchemaDropdown from './Components/SchemaDropdown/SchemaDropdown'; +import UploadFilesModal from "../../Components/UploadContent/UploadFilesModal.tsx"; -import { useDispatch, useSelector,shallowEqual } from 'react-redux'; +import { useDispatch, useSelector, shallowEqual } from 'react-redux'; import { fetchSchemaData, fetchContentTableData } from '../../store/slices/leftPanelSlice.ts'; -import { AppDispatch,RootState } from '../../store'; -import { startLoader, stopLoader } from "../../store/slices/loaderSlice"; +import { AppDispatch, RootState } from '../../store/index.ts'; +import { startLoader, stopLoader } from "../../store/slices/loaderSlice.ts"; import { toast } from "react-toastify"; interface PanelLeftProps { @@ -22,9 +22,9 @@ const PanelLeft: React.FC = () => { const store = useSelector((state: RootState) => ({ schemaSelectedOption: state.leftPanel.schemaSelectedOption, - page_size : state.leftPanel.gridData.page_size, + page_size: state.leftPanel.gridData.page_size, pageSize: state.leftPanel.pageSize - }),shallowEqual ); + }), shallowEqual); useEffect(() => { const fetchData = async () => { @@ -51,7 +51,7 @@ const PanelLeft: React.FC = () => { } catch (error) { console.error("Error fetching data:", error); } finally { - dispatch(stopLoader("1")); + dispatch(stopLoader("1")); } } @@ -60,12 +60,13 @@ const PanelLeft: React.FC = () => {
- +
- +
); diff --git a/src/ContentProcessorWeb/src/Pages/DefaultPage/DefaultPagePanelRight.tsx b/src/ContentProcessorWeb/src/Pages/DefaultPage/PanelRight.tsx similarity index 97% rename from src/ContentProcessorWeb/src/Pages/DefaultPage/DefaultPagePanelRight.tsx rename to src/ContentProcessorWeb/src/Pages/DefaultPage/PanelRight.tsx index 29046f7c..798bcbcb 100644 --- a/src/ContentProcessorWeb/src/Pages/DefaultPage/DefaultPagePanelRight.tsx +++ b/src/ContentProcessorWeb/src/Pages/DefaultPage/PanelRight.tsx @@ -43,6 +43,8 @@ const PanelRight: React.FC = () => { const isExists = isBlobExists(); if(store.fileResponse.length > 0 && isExists && isExists.processId == store.processId){ setFileData({ 'urlWithSasToken': isExists.blobURL, 'mimeType': isExists.headers['content-type'] }) + }else { + setFileData({ 'urlWithSasToken': '', 'mimeType': '' }) } },[store.processId, store.fileResponse]) diff --git a/src/ContentProcessorWeb/src/Pages/DefaultPage/Panels.styles.scss b/src/ContentProcessorWeb/src/Pages/DefaultPage/Panels.styles.scss new file mode 100644 index 00000000..94c29e6b --- /dev/null +++ b/src/ContentProcessorWeb/src/Pages/DefaultPage/Panels.styles.scss @@ -0,0 +1,170 @@ +/* Panel container styling */ + +.panelLeft { + /* background-color: var(--colorNeutralBackground4); */ + position: relative; + display: flex; + height: 100%; + box-sizing: border-box; + flex-direction: column; + overflow-y: auto; + background-color: white; + border: 1px solid #D6D6D6; + border-width: 1px 1px 0px 0px; + } + + .panelRight { + /* background-color: var(--colorNeutralBackground4); */ + position: relative; + display: flex; + height: 100%; + box-sizing: border-box; + flex-direction: column; + overflow-y: auto; + background-color: white; + border: 1px solid #D6D6D6; + border-width: 1px 0px 0px 0px; + } + + /* Resize functionality on panels */ + + .resize-handle-left, + .resize-handle-right { + position: absolute; + top: 0; + width: 3px; + height: 100%; + cursor: ew-resize; + background-color: transparent; + } + + .resize-handle-left { + right: 0; + } + + .resize-handle-right { + left: 0; + z-index: 1; + } + + .resize-handle-left:hover, + .resize-handle-right:hover { + background-color: var(--colorNeutralStroke1); + } + + .resize-handle-right:hover { + background-color: var(--colorNeutralStroke1); + } + + .resize-handle-left:active, + .resize-handle-right:active { + background-color: var(--colorNeutralStroke1); + } + + .panelCenter { + display: flex; + flex: 1; + flex-direction: column; + width: 100%; + height: 100%; + align-items: start; + box-sizing: border-box; + } + + .content { + display: flex; + flex-direction: column; + flex: 1 1 auto; + overflow: scroll; + box-sizing: border-box; + padding: 64px 16px 64px 16px; + height: 100%; + width: 100%; + margin: auto; + overflow-x: hidden; + overflow-y: auto; + margin-top: -64px; + + } + + .panelToolbar { + display: flex; + width: 100%; + align-items: center; + padding: 16px 16px; + min-height: 64px; + justify-content: space-between; + box-sizing: border-box; + backdrop-filter: saturate(180%) blur(16px); + transition: all 0.3s cubic-bezier(0.215, 0.61, 0.355, 1); + } + + .buttonGroup { + display: flex; + align-items: center; + } + + .headerTitleGroup { + display: flex; + align-items: center; + gap: 8px; + width: 100%; + } + + .leftcontent { + display: flex; + flex-direction: column; + flex: 1 1 auto; + overflow: scroll; + box-sizing: border-box; + padding: 0px 16px 16px 16px; + height: 100%; + width: 100%; + margin: auto; + overflow-x: hidden; + overflow-y: auto; + + } + + .panelRightContent { + display: flex; + flex-direction: column; + flex: 1 1 auto; + overflow: scroll; + box-sizing: border-box; + padding: 0px 16px 16px 16px; + height: 100%; + width: 100%; + margin: auto; + overflow-x: hidden; + overflow-y: auto; + /* margin-top: -64px; */ + } + + .fullHeight { + height: 100vh; + } + + .brand, + .selectedRow { + background-color: #e0e0e0; + /* Light gray for selected row */ + border: 2px solid #0078d4; + /* Blue border for selected row */ + } + + /* .JSONEditorClass { + max-height: none !important; + } */ + + .right-loader{ + display: flex; + justify-content: center; + align-items: center; + height: 100%; + } + + .custom-test button{ + padding : 0px !important; + padding :5px 12px 12px 10px !important + } \ No newline at end of file diff --git a/src/ContentProcessorWeb/src/Pages/DefaultPage/DefaultPage.tsx b/src/ContentProcessorWeb/src/Pages/DefaultPage/index.tsx similarity index 74% rename from src/ContentProcessorWeb/src/Pages/DefaultPage/DefaultPage.tsx rename to src/ContentProcessorWeb/src/Pages/DefaultPage/index.tsx index 4a1a01b8..1b70f737 100644 --- a/src/ContentProcessorWeb/src/Pages/DefaultPage/DefaultPage.tsx +++ b/src/ContentProcessorWeb/src/Pages/DefaultPage/index.tsx @@ -1,9 +1,10 @@ import * as React from "react"; -import ContentDevelopers from "./DefaultPageContent.tsx"; -import PanelLeft from "./DefaultPagePanelLeft.tsx"; -import PanelRight from "./DefaultPagePanelRight.tsx"; +import PanelCenter from "./PanelCenter.tsx"; +import PanelLeft from "./PanelLeft.tsx"; +import PanelRight from "./PanelRight.tsx"; import { makeStyles } from "@fluentui/react-components"; +import './Panels.styles.scss'; // AppHooks import { useAppHooks } from "../../Hooks/useAppHooks.tsx"; @@ -16,12 +17,11 @@ const Page: React.FC = () => { {isPanelOpen && (
-
)} -
- + { - const apiUrl = process.env.REACT_APP_API_BASE_URL; - const response = await fetch(`${apiUrl}/schemavault/`, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - 'Cache-Control': 'no-cache', - // Add other headers if necessary - }, - }); - if (!response.ok) { - throw new Error(`Error: ${response.status} ${response.statusText}`); - } - const responseData = await response.json(); - - //console.log("response ", response.json()); - return responseData; - }; - - -{ /* Fetch data for Grid Component (Contents Table) */ } -export const fetchContentTableData = async (pageSize = 50): Promise => { - try { - const apiUrl = process.env.REACT_APP_API_BASE_URL; - //console.log("apiUrl fetchContent", apiUrl) - let pageNumber = 1 - let allItems: any[] = []; - let totalPages = 0; - console.log(JSON.stringify({ - page_size: pageSize, - page_number: pageNumber, - })); - - do { - const response = await fetch(`${apiUrl}/contentprocessor/processed`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - 'Cache-Control': 'no-cache', - }, - body: JSON.stringify({ - "page_size" : pageSize, - "page_number" : pageNumber - - - }), - }); - - if (!response.ok) { - throw new Error(`Error: ${response.status} ${response.statusText}`); - } - - const responseData = await response.json(); - totalPages = responseData.total_pages; - //console.log(responseData); - - // Transform each item from the API into the format expected by the table - const transformedItems = responseData.items.map((item: any) => ({ - fileName: { label: item.processed_file_name, icon: "📄" }, - imported: { label: new Date(item.imported_time).toLocaleString() }, - status: { label: item.status }, - processTime: { label: item.processed_time }, - entityScore: { label: item.entity_score.toString() }, - schemaScore: { label: item.schema_score.toString() }, - processId: { label: item.process_id }, - lastModifiedBy: { label: item.last_modified_by }, - })); - - allItems = allItems.concat(transformedItems); - pageNumber++; - } while (pageNumber - <= totalPages); - - return { - data: allItems, - currentPage: 1, - totalPages: totalPages, - pageSize: pageSize, - totalItems: allItems.length, - }; - } catch (error) { - console.error("Error fetching content table data:", error); - throw error; - } -}; - -export const uploadFile = async (file: File, schema: string) => { - //const schemaId = store.dispatch.; - const metadata = { - Metadata_Id: crypto.randomUUID(), - Schema_Id: schema, - }; - - const formData = new FormData(); - - // Attach the file - formData.append("file", file); - - // Attach JSON metadata (replace the name if needed by your API) - formData.append("data", JSON.stringify(metadata)); - - try { - const response = await fetch(`${process.env.REACT_APP_API_BASE_URL}/contentprocessor/submit`, { - method: "POST", - body: formData, - }); - - if (!response.ok) { - throw new Error(`Error: ${response.status} ${response.statusText}`); - } - - const responseData = await response.json(); - //console.log(responseData); - - return responseData; - } catch (error) { - console.error("Error uploading file:", error); - throw error; - } -} - -export const fetchContentJson = async (processId: string) => { - - try { - const resposne = await fetch(`${process.env.REACT_APP_API_BASE_URL}/contentprocessor/processed/${processId}`, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - }, - }); - - if (!resposne.ok) { - throw new Error(`Error: ${resposne.status} ${resposne.statusText}`); - } - - const responseData = await resposne.json(); - - //return responseData; - - return { - extractedResults: responseData.result, - comment: responseData.comment - }; - } catch (error) { - console.error("Error fetching JSON data:", error); - throw error; - } -}; - -export const fetchContentFileData = async (processId: string) => { - - try { - //console.log("document api", `${process.env.REACT_APP_API_BASE_URL}/contentprocessor/processed/files/${processId}`); - const resposne = await fetch(`${process.env.REACT_APP_API_BASE_URL}/contentprocessor/processed/files/${processId}`, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - }, - }); - - if (!resposne.ok) { - throw new Error(`Error: ${resposne.status} ${resposne.statusText}`); - } - - const responseData = await resposne.blob(); - //console.log("docuemnt blob data", responseData); - - return responseData; - - // return { - // extractedResults: responseData.result, - // comment: responseData.comment - // }; - } catch (error) { - console.error("Error fetching document data:", error); - throw error; - } -} - - -export const saveContentJson = async (processId: string, contentJson: string) => { - try { - const response = await fetch(`${process.env.REACT_APP_API_BASE_URL}/contentprocessor/processed/${processId}`, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - 'Cache-Control': 'no-cache', - }, - body: JSON.stringify({ - "process_id": processId, - "modified_result": contentJson, - }), - }); - - if (!response.ok) { - throw new Error(`Error: ${response.status} ${response.statusText}`); - } - - const responseData = await response.json(); - //console.log(responseData); - - return responseData; - - } catch (error) { - console.error("Error saving JSON data:", error); - throw error; - } -} - -export const saveContentComment = async (processId: string, comment: string) => { - try { - const response = await fetch(`${process.env.REACT_APP_API_BASE_URL}/contentprocessor/processed/${processId}`, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - 'Cache-Control': 'no-cache', - }, - body: JSON.stringify({ - "process_id": processId, - "comment": comment, - }), - }); - - if (!response.ok) { - throw new Error(`Error: ${response.status} ${response.statusText}`); - } - - const responseData = await response.json(); - //console.log(responseData); - - return responseData; - - } catch (error) { - console.error("Error saving comment data:", error); - throw error; - } -} \ No newline at end of file diff --git a/src/ContentProcessorWeb/src/store/slices/centerPanelSlice.ts b/src/ContentProcessorWeb/src/store/slices/centerPanelSlice.ts index 6bd44b63..a3d9f795 100644 --- a/src/ContentProcessorWeb/src/store/slices/centerPanelSlice.ts +++ b/src/ContentProcessorWeb/src/store/slices/centerPanelSlice.ts @@ -115,6 +115,8 @@ const centerPanelSlice = createSlice({ .addCase(fetchContentJsonData.rejected, (state, action) => { state.cError = action.error.message || 'An error occurred'; state.cLoader = false; + state.contentData = []; + state.comments = ""; //console.error("Error fetching JSON data:", action.error.message || 'An error occurred'); }); @@ -127,8 +129,9 @@ const centerPanelSlice = createSlice({ toast.success("Data saved successfully!"); // Success toast state.isSavingInProgress = false; }) - .addCase(saveContentJson.rejected, (state, action) => { - toast.error("Date saving failed !"); + .addCase(saveContentJson.rejected, (state, action : any) => { + console.log("action", action) + toast.error(JSON.parse(action.error.message).message); state.isSavingInProgress = false; }); diff --git a/src/ContentProcessorWeb/src/store/slices/leftPanelSlice.ts b/src/ContentProcessorWeb/src/store/slices/leftPanelSlice.ts index e38b5264..e4cdfb0d 100644 --- a/src/ContentProcessorWeb/src/store/slices/leftPanelSlice.ts +++ b/src/ContentProcessorWeb/src/store/slices/leftPanelSlice.ts @@ -10,9 +10,11 @@ interface LeftPanelState { schemaLoader: boolean; schemaSelectedOption: any; gridData: any; + gridLoader: boolean; processId: string | null; selectedItem: any; pageSize : number; + deleteFilesLoader: string[] } interface UploadMetadata { @@ -43,6 +45,21 @@ export const fetchContentTableData = createAsyncThunk('/contentprocessor/deleteProcessedFile/', async ({ processId }, {rejectWithValue}) => { + if (!processId) { + return rejectWithValue("Reset store"); + } + const url = '/contentprocessor/processed/' + processId; + const response = await httpUtility.delete(url); + console.log("response", response); + return response as DeleteApiResponse; ; +}); + export const uploadFile = createAsyncThunk< any, // Type for fulfilled response { file: File; schema: string } // Type for the input payload @@ -89,9 +106,13 @@ const initialState: LeftPanelState = { schemaError: null, gridData: {...gridDefaultVal}, + gridLoader : false, processId: null, selectedItem: {}, pageSize : 500, + + deleteFilesLoader : [], + }; const leftPanelSlice = createSlice({ @@ -126,16 +147,17 @@ const leftPanelSlice = createSlice({ builder .addCase(fetchContentTableData.pending, (state) => { //state.schemaError = null; + state.gridLoader = true; state.gridData = {...gridDefaultVal}; }) .addCase(fetchContentTableData.fulfilled, (state, action: PayloadAction) => { // Adjust `any` to the response data type //state.schemaLoader = false; - state.gridData = action.payload + state.gridData = action.payload; + state.gridLoader = false; }) .addCase(fetchContentTableData.rejected, (state, action) => { - // state.schemaError = action.error.message || 'An error occurred'; - //state.schemaLoader = false; - //console.error("Error fetching content table data : ", action.error.message || 'An error occurred'); + state.gridLoader = false; + toast.error('Something went wrong!') }); @@ -155,6 +177,31 @@ const leftPanelSlice = createSlice({ }); + //Fetch Grid Data + builder + .addCase(deleteProcessedFile.pending, (state, action) => { + const processId = action.meta.arg.processId; + if (processId) { + state.deleteFilesLoader.push(processId); + } + }) + .addCase(deleteProcessedFile.fulfilled, (state, action) => { + const processId = action.meta.arg.processId; + if (processId) { + state.deleteFilesLoader = state.deleteFilesLoader.filter(id => id !== processId); + } + if(action.payload.status === 'Success') + toast.success("Record deleted successfully!") + else + toast.error(action.payload.message) + }) + .addCase(deleteProcessedFile.rejected, (state, action) => { + const processId = action.meta.arg.processId; + if (processId) { + state.deleteFilesLoader = state.deleteFilesLoader.filter(id => id !== processId); + toast.error("Something went wrong!") + } + }); }, }); From 43d24b3ce93bcf928e53642ef0599709e64e9edf Mon Sep 17 00:00:00 2001 From: Bangarraju-Microsoft Date: Wed, 9 Apr 2025 18:17:14 +0530 Subject: [PATCH 03/45] UI - Changes and added flag for logs --- .../deploy_container_app_api_web.bicep | 4 + infra/main.json | 168 ++---------------- src/ContentProcessorWeb/.dockerignore | 3 +- src/ContentProcessorWeb/.env | 3 +- .../src/Hooks/useConsoleSuppression.ts | 2 +- .../ProcessQueueGrid/ProcessQueueGrid.tsx | 6 +- .../src/store/slices/leftPanelSlice.ts | 4 +- 7 files changed, 31 insertions(+), 159 deletions(-) diff --git a/infra/container_app/deploy_container_app_api_web.bicep b/infra/container_app/deploy_container_app_api_web.bicep index fb734e2e..6a377b5f 100644 --- a/infra/container_app/deploy_container_app_api_web.bicep +++ b/infra/container_app/deploy_container_app_api_web.bicep @@ -146,6 +146,10 @@ module containerAppWeb 'deploy_container_app.bicep' = { name: 'APP_MSAL_TOKEN_SCOPE' value: '' } + { + name: 'APP_ISLOGS_ENABLED' + value: 'false' + } ] minReplicas: minReplicaContainerWeb maxReplicas: maxReplicaContainerWeb diff --git a/infra/main.json b/infra/main.json index 98281301..38822f7d 100644 --- a/infra/main.json +++ b/infra/main.json @@ -5,7 +5,7 @@ "_generator": { "name": "bicep", "version": "0.34.44.8038", - "templateHash": "14103938519671400586" + "templateHash": "17985360808056860425" } }, "parameters": { @@ -21,7 +21,7 @@ "type": "string", "defaultValue": "EastUs2", "metadata": { - "description": "Location used for Cosmos DB, Container App deployment" + "description": "Location used for Azure Cosmos DB, Azure Container App deployment" } }, "contentUnderstandingLocation": { @@ -35,7 +35,7 @@ "azd": { "type": "location" }, - "description": "Location for the Content Understanding service deployment:" + "description": "Location for the Azure AI Content Understanding service deployment:" }, "minLength": 1 }, @@ -362,7 +362,8 @@ "solutionPrefix": "[format('cps-{0}', padLeft(take(variables('uniqueId'), 12), 12, '0'))]", "containerImageEndPoint": "cpscontainerreg.azurecr.io", "resourceGroupLocation": "[resourceGroup().location]", - "abbrs": "[variables('$fxv#0')]" + "abbrs": "[variables('$fxv#0')]", + "useLocalBuildLower": "[toLower(parameters('useLocalBuild'))]" }, "resources": [ { @@ -934,7 +935,7 @@ "_generator": { "name": "bicep", "version": "0.34.44.8038", - "templateHash": "13228064894943437182" + "templateHash": "6622167858340258597" } }, "parameters": { @@ -1432,147 +1433,6 @@ "dependsOn": [ "[resourceId('Microsoft.MachineLearningServices/workspaces', variables('aiHubName'))]" ] - }, - { - "type": "Microsoft.KeyVault/vaults/secrets", - "apiVersion": "2021-11-01-preview", - "name": "[format('{0}/{1}', parameters('keyVaultName'), 'TENANT-ID')]", - "properties": { - "value": "[subscription().tenantId]" - } - }, - { - "type": "Microsoft.KeyVault/vaults/secrets", - "apiVersion": "2021-11-01-preview", - "name": "[format('{0}/{1}', parameters('keyVaultName'), 'AZURE-OPENAI-KEY')]", - "properties": { - "value": "[listKeys(resourceId('Microsoft.CognitiveServices/accounts', variables('aiServicesName')), '2021-10-01').key1]" - }, - "dependsOn": [ - "[resourceId('Microsoft.CognitiveServices/accounts', variables('aiServicesName'))]" - ] - }, - { - "type": "Microsoft.KeyVault/vaults/secrets", - "apiVersion": "2021-11-01-preview", - "name": "[format('{0}/{1}', parameters('keyVaultName'), 'AZURE-OPEN-AI-DEPLOYMENT-MODEL')]", - "properties": { - "value": "[parameters('gptModelName')]" - } - }, - { - "type": "Microsoft.KeyVault/vaults/secrets", - "apiVersion": "2021-11-01-preview", - "name": "[format('{0}/{1}', parameters('keyVaultName'), 'AZURE-OPENAI-PREVIEW-API-VERSION')]", - "properties": { - "value": "[parameters('gptModelVersion')]" - } - }, - { - "type": "Microsoft.KeyVault/vaults/secrets", - "apiVersion": "2021-11-01-preview", - "name": "[format('{0}/{1}', parameters('keyVaultName'), 'AZURE-OPENAI-ENDPOINT')]", - "properties": { - "value": "[reference(resourceId('Microsoft.CognitiveServices/accounts', variables('aiServicesName')), '2021-10-01').endpoint]" - }, - "dependsOn": [ - "[resourceId('Microsoft.CognitiveServices/accounts', variables('aiServicesName'))]" - ] - }, - { - "type": "Microsoft.KeyVault/vaults/secrets", - "apiVersion": "2021-11-01-preview", - "name": "[format('{0}/{1}', parameters('keyVaultName'), 'AZURE-AI-PROJECT-CONN-STRING')]", - "properties": { - "value": "[format('{0};{1};{2};{3}', split(reference(resourceId('Microsoft.MachineLearningServices/workspaces', variables('aiProjectName')), '2024-01-01-preview').discoveryUrl, '/')[2], subscription().subscriptionId, resourceGroup().name, variables('aiProjectName'))]" - }, - "dependsOn": [ - "[resourceId('Microsoft.MachineLearningServices/workspaces', variables('aiProjectName'))]" - ] - }, - { - "type": "Microsoft.KeyVault/vaults/secrets", - "apiVersion": "2021-11-01-preview", - "name": "[format('{0}/{1}', parameters('keyVaultName'), 'AZURE-OPENAI-CU-ENDPOINT')]", - "properties": { - "value": "[reference(resourceId('Microsoft.CognitiveServices/accounts', variables('aiServicesName_cu')), '2021-10-01').endpoint]" - }, - "dependsOn": [ - "[resourceId('Microsoft.CognitiveServices/accounts', variables('aiServicesName_cu'))]" - ] - }, - { - "type": "Microsoft.KeyVault/vaults/secrets", - "apiVersion": "2021-11-01-preview", - "name": "[format('{0}/{1}', parameters('keyVaultName'), 'AZURE-OPENAI-CU-KEY')]", - "properties": { - "value": "[listKeys(resourceId('Microsoft.CognitiveServices/accounts', variables('aiServicesName_cu')), '2021-10-01').key1]" - }, - "dependsOn": [ - "[resourceId('Microsoft.CognitiveServices/accounts', variables('aiServicesName_cu'))]" - ] - }, - { - "type": "Microsoft.KeyVault/vaults/secrets", - "apiVersion": "2021-11-01-preview", - "name": "[format('{0}/{1}', parameters('keyVaultName'), 'AZURE-OPENAI-CU-VERSION')]", - "properties": { - "value": "?api-version=2024-12-01-preview" - } - }, - { - "type": "Microsoft.KeyVault/vaults/secrets", - "apiVersion": "2021-11-01-preview", - "name": "[format('{0}/{1}', parameters('keyVaultName'), 'COG-SERVICES-ENDPOINT')]", - "properties": { - "value": "[reference(resourceId('Microsoft.CognitiveServices/accounts', variables('aiServicesName')), '2021-10-01').endpoint]" - }, - "dependsOn": [ - "[resourceId('Microsoft.CognitiveServices/accounts', variables('aiServicesName'))]" - ] - }, - { - "type": "Microsoft.KeyVault/vaults/secrets", - "apiVersion": "2021-11-01-preview", - "name": "[format('{0}/{1}', parameters('keyVaultName'), 'COG-SERVICES-KEY')]", - "properties": { - "value": "[listKeys(resourceId('Microsoft.CognitiveServices/accounts', variables('aiServicesName')), '2021-10-01').key1]" - }, - "dependsOn": [ - "[resourceId('Microsoft.CognitiveServices/accounts', variables('aiServicesName'))]" - ] - }, - { - "type": "Microsoft.KeyVault/vaults/secrets", - "apiVersion": "2021-11-01-preview", - "name": "[format('{0}/{1}', parameters('keyVaultName'), 'COG-SERVICES-NAME')]", - "properties": { - "value": "[variables('aiServicesName')]" - } - }, - { - "type": "Microsoft.KeyVault/vaults/secrets", - "apiVersion": "2021-11-01-preview", - "name": "[format('{0}/{1}', parameters('keyVaultName'), 'AZURE-SUBSCRIPTION-ID')]", - "properties": { - "value": "[subscription().subscriptionId]" - } - }, - { - "type": "Microsoft.KeyVault/vaults/secrets", - "apiVersion": "2021-11-01-preview", - "name": "[format('{0}/{1}', parameters('keyVaultName'), 'AZURE-RESOURCE-GROUP')]", - "properties": { - "value": "[resourceGroup().name]" - } - }, - { - "type": "Microsoft.KeyVault/vaults/secrets", - "apiVersion": "2021-11-01-preview", - "name": "[format('{0}/{1}', parameters('keyVaultName'), 'AZURE-LOCATION')]", - "properties": { - "value": "[parameters('solutionLocation')]" - } } ], "outputs": { @@ -1795,7 +1655,7 @@ "_generator": { "name": "bicep", "version": "0.34.44.8038", - "templateHash": "15388289119319771934" + "templateHash": "15815884747026956332" } }, "parameters": { @@ -2546,6 +2406,10 @@ { "name": "APP_MSAL_TOKEN_SCOPE", "value": "" + }, + { + "name": "APP_ISLOGS_ENABLED", + "value": "false" } ] }, @@ -3269,7 +3133,7 @@ "location": { "value": "[parameters('secondaryLocation')]" }, - "azureContainerRegistry": "[if(equals(parameters('useLocalBuild'), 'true'), createObject('value', reference(resourceId('Microsoft.Resources/deployments', 'deploy_container_registry'), '2022-09-01').outputs.acrEndpoint.value), createObject('value', variables('containerImageEndPoint')))]", + "azureContainerRegistry": "[if(equals(variables('useLocalBuildLower'), 'true'), createObject('value', reference(resourceId('Microsoft.Resources/deployments', 'deploy_container_registry'), '2022-09-01').outputs.acrEndpoint.value), createObject('value', variables('containerImageEndPoint')))]", "appConfigEndPoint": { "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_app_config_service'), '2022-09-01').outputs.appConfigEndpoint.value]" }, @@ -3304,7 +3168,7 @@ "value": "[parameters('maxReplicaContainerWeb')]" }, "useLocalBuild": { - "value": "[parameters('useLocalBuild')]" + "value": "[variables('useLocalBuildLower')]" } }, "template": { @@ -3314,7 +3178,7 @@ "_generator": { "name": "bicep", "version": "0.34.44.8038", - "templateHash": "15388289119319771934" + "templateHash": "15815884747026956332" } }, "parameters": { @@ -4065,6 +3929,10 @@ { "name": "APP_MSAL_TOKEN_SCOPE", "value": "" + }, + { + "name": "APP_ISLOGS_ENABLED", + "value": "false" } ] }, diff --git a/src/ContentProcessorWeb/.dockerignore b/src/ContentProcessorWeb/.dockerignore index 67b465a3..503183c5 100644 --- a/src/ContentProcessorWeb/.dockerignore +++ b/src/ContentProcessorWeb/.dockerignore @@ -5,5 +5,4 @@ # https://docs.docker.com/engine/reference/builder/#dockerignore-file -**/node_modules -.env \ No newline at end of file +**/node_modules \ No newline at end of file diff --git a/src/ContentProcessorWeb/.env b/src/ContentProcessorWeb/.env index 7ef29175..d9bf079d 100644 --- a/src/ContentProcessorWeb/.env +++ b/src/ContentProcessorWeb/.env @@ -9,4 +9,5 @@ REACT_APP_MSAL_POST_REDIRECT_URL = "/" REACT_APP_MSAL_AUTH_SCOPE = APP_MSAL_AUTH_SCOPE -REACT_APP_MSAL_TOKEN_SCOPE = APP_MSAL_TOKEN_SCOPE \ No newline at end of file +REACT_APP_MSAL_TOKEN_SCOPE = APP_MSAL_TOKEN_SCOPE +REACT_APP_ISLOGS_ENABLED = APP_ISLOGS_ENABLED \ No newline at end of file diff --git a/src/ContentProcessorWeb/src/Hooks/useConsoleSuppression.ts b/src/ContentProcessorWeb/src/Hooks/useConsoleSuppression.ts index c9fa7079..2c724db7 100644 --- a/src/ContentProcessorWeb/src/Hooks/useConsoleSuppression.ts +++ b/src/ContentProcessorWeb/src/Hooks/useConsoleSuppression.ts @@ -3,7 +3,7 @@ import { useEffect } from "react"; // Custom hook to suppress console logs, warnings, and errors in localhost const useConsoleSuppression = () => { useEffect(() => { - if (window.location.hostname !== "localhost") { + if (window.location.hostname !== "localhost" && process.env.REACT_APP_ISLOGS_ENABLED?.toLocaleLowerCase() != "true" ) { // Save the original console methods const originalConsoleError = console.error; const originalConsoleWarn = console.warn; diff --git a/src/ContentProcessorWeb/src/Pages/DefaultPage/Components/ProcessQueueGrid/ProcessQueueGrid.tsx b/src/ContentProcessorWeb/src/Pages/DefaultPage/Components/ProcessQueueGrid/ProcessQueueGrid.tsx index e2cccf18..2cf0c51a 100644 --- a/src/ContentProcessorWeb/src/Pages/DefaultPage/Components/ProcessQueueGrid/ProcessQueueGrid.tsx +++ b/src/ContentProcessorWeb/src/Pages/DefaultPage/Components/ProcessQueueGrid/ProcessQueueGrid.tsx @@ -344,13 +344,13 @@ const ProcessQueueGrid: React.FC = () => {
id !== processId); } if(action.payload.status === 'Success') - toast.success("Record deleted successfully!") + toast.success("File deleted successfully.") else toast.error(action.payload.message) }) @@ -199,7 +199,7 @@ const leftPanelSlice = createSlice({ const processId = action.meta.arg.processId; if (processId) { state.deleteFilesLoader = state.deleteFilesLoader.filter(id => id !== processId); - toast.error("Something went wrong!") + toast.error("Failed to delete the file. Please try again.") } }); }, From 76d9e24643405f0635662a02373c55d9828d936b Mon Sep 17 00:00:00 2001 From: Bangarraju-Microsoft Date: Fri, 11 Apr 2025 13:02:40 +0530 Subject: [PATCH 04/45] UI - feedbackpoints fixed (#16858) and code cleanup --- src/ContentProcessorWeb/src/App.tsx | 1 - .../src/Components/Content/Content.css | 19 - .../src/Components/Content/Content.tsx | 52 -- .../DialogComponent/DialogComponent.tsx | 13 +- .../DialogComponent/DialogComponentTypes.ts | 16 + .../DocumentViewer/DocumentViewer.tsx | 3 +- .../DocumentViewer/DocumentViewer.types.ts | 88 -- .../src/Components/Header/Header.tsx | 28 +- .../src/Components/JSONEditor/JSONEditor.tsx | 89 +- .../src/Components/JSONEditor/data.json | 857 ------------------ .../src/Hooks/useAppHooks.tsx | 86 -- .../src/Hooks/useContentHooks.tsx | 38 - .../src/Hooks/useContentToolbarHooks.tsx | 88 -- .../ProcessQueueGrid/CustomCellRender.tsx | 16 +- .../ProcessQueueGrid.styles.scss | 215 +++-- .../ProcessQueueGrid/ProcessQueueGrid.tsx | 14 +- .../Components/ProcessSteps/ProcessSteps.tsx | 110 +++ .../SchemaDropdown/SchemaDropdown.tsx | 2 +- .../src/Pages/DefaultPage/PanelCenter.tsx | 148 +-- .../src/Pages/DefaultPage/PanelLeft.tsx | 35 +- .../src/Pages/DefaultPage/PanelRight.tsx | 28 +- .../src/Pages/DefaultPage/Panels.styles.scss | 231 ++--- .../src/Pages/DefaultPage/index.tsx | 37 +- .../src/store/slices/centerPanelSlice.ts | 2 + .../src/store/slices/leftPanelSlice.ts | 11 +- 25 files changed, 532 insertions(+), 1695 deletions(-) delete mode 100644 src/ContentProcessorWeb/src/Components/Content/Content.css delete mode 100644 src/ContentProcessorWeb/src/Components/Content/Content.tsx create mode 100644 src/ContentProcessorWeb/src/Components/DialogComponent/DialogComponentTypes.ts delete mode 100644 src/ContentProcessorWeb/src/Components/DocumentViewer/DocumentViewer.types.ts delete mode 100644 src/ContentProcessorWeb/src/Components/JSONEditor/data.json delete mode 100644 src/ContentProcessorWeb/src/Hooks/useAppHooks.tsx delete mode 100644 src/ContentProcessorWeb/src/Hooks/useContentHooks.tsx delete mode 100644 src/ContentProcessorWeb/src/Hooks/useContentToolbarHooks.tsx create mode 100644 src/ContentProcessorWeb/src/Pages/DefaultPage/Components/ProcessSteps/ProcessSteps.tsx diff --git a/src/ContentProcessorWeb/src/App.tsx b/src/ContentProcessorWeb/src/App.tsx index 06462b52..11b53f8e 100644 --- a/src/ContentProcessorWeb/src/App.tsx +++ b/src/ContentProcessorWeb/src/App.tsx @@ -2,7 +2,6 @@ import * as React from "react"; import { useEffect } from "react"; import Header from "./Components/Header/Header.tsx"; // Import Header import "./Styles/App.css"; -import "./Components/Content/Content.css"; import HomePage from "./Pages/HomePage.tsx"; import DefaultPage from "./Pages/DefaultPage"; //import AuxiliaryPage from "./Pages/AuxiliaryPage/AuxiliaryPage.tsx"; diff --git a/src/ContentProcessorWeb/src/Components/Content/Content.css b/src/ContentProcessorWeb/src/Components/Content/Content.css deleted file mode 100644 index 125fd66d..00000000 --- a/src/ContentProcessorWeb/src/Components/Content/Content.css +++ /dev/null @@ -1,19 +0,0 @@ -.contentToolbar { - width: calc(100% - 6px); - box-sizing: border-box; - display: flex; - flex: 1; - justify-content: space-between; - align-items: center; - padding: 16px; - height: 64px; - backdrop-filter: saturate(180%) blur(16px); - transition: all 0.3s cubic-bezier(0.215, 0.61, 0.355, 1); - z-index: 1; -} - -.contentToolbarTitleGroupLeft, -.contentToolbarTitleGroupRight { - display: flex; - align-items: center; -} diff --git a/src/ContentProcessorWeb/src/Components/Content/Content.tsx b/src/ContentProcessorWeb/src/Components/Content/Content.tsx deleted file mode 100644 index 6be16822..00000000 --- a/src/ContentProcessorWeb/src/Components/Content/Content.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import React from "react"; -import "../../Styles/App.css"; -import { Toolbar, ToolbarButton } from "@fluentui/react-components"; -import { Link } from "../../Imports/bundleIcons.tsx"; -import { useContentHooks } from "../../Hooks/useContentHooks.tsx"; -import ContentToolbar from "../../Hooks/useContentToolbarHooks.tsx"; - -// Visit https://mochimilk.github.io/cto_coral_docs/index.html#/developers/content for documentation - -interface ContentProps { - isPanelOpen: boolean; - togglePanel?: () => void; // Optional to conditionally render left toggle - isRightPanelOpen: boolean; - toggleRightPanel?: () => void; // Optional to conditionally render left toggle -} - -const ContentDevelopers: React.FC = ({ - isPanelOpen, - togglePanel, - isRightPanelOpen, - toggleRightPanel, -}) => { - const { commandKey } = useContentHooks({ togglePanel, toggleRightPanel }); - - return ( -
- {/*📌 Below is the setup for the content toolbar. - ***You may remove this if your app doesn't need a toolbar. */} - - - - }> - - - - - -
- {/* Populate content */} -
-
- ); -}; - -export default ContentDevelopers; diff --git a/src/ContentProcessorWeb/src/Components/DialogComponent/DialogComponent.tsx b/src/ContentProcessorWeb/src/Components/DialogComponent/DialogComponent.tsx index be2b439b..8a63e342 100644 --- a/src/ContentProcessorWeb/src/Components/DialogComponent/DialogComponent.tsx +++ b/src/ContentProcessorWeb/src/Components/DialogComponent/DialogComponent.tsx @@ -10,19 +10,8 @@ import { useId, } from "@fluentui/react-components"; -interface FooterButton { - text: string; - appearance: "primary" | "secondary"; - onClick: () => void; -} +import { ConfirmationProps } from './DialogComponentTypes' -interface ConfirmationProps { - title: string; - content: string; - isDialogOpen: boolean; // Controlled state for dialog visibility - onDialogClose: () => void; // Function to close the dialog - footerButtons: FooterButton[]; // Array of footer buttons -} export const Confirmation: React.FC = ({ title, diff --git a/src/ContentProcessorWeb/src/Components/DialogComponent/DialogComponentTypes.ts b/src/ContentProcessorWeb/src/Components/DialogComponent/DialogComponentTypes.ts new file mode 100644 index 00000000..02b4349e --- /dev/null +++ b/src/ContentProcessorWeb/src/Components/DialogComponent/DialogComponentTypes.ts @@ -0,0 +1,16 @@ + +import { ReactNode } from 'react'; + +interface FooterButton { + text: string; + appearance: "primary" | "secondary"; + onClick: () => void; +} + +export interface ConfirmationProps { + title: string; + content: string | ReactNode; + isDialogOpen: boolean; // Controlled state for dialog visibility + onDialogClose: () => void; // Function to close the dialog + footerButtons: FooterButton[]; // Array of footer buttons +} \ No newline at end of file diff --git a/src/ContentProcessorWeb/src/Components/DocumentViewer/DocumentViewer.tsx b/src/ContentProcessorWeb/src/Components/DocumentViewer/DocumentViewer.tsx index 9a32ef91..89e60a9b 100644 --- a/src/ContentProcessorWeb/src/Components/DocumentViewer/DocumentViewer.tsx +++ b/src/ContentProcessorWeb/src/Components/DocumentViewer/DocumentViewer.tsx @@ -1,6 +1,5 @@ import React, { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; -import { Document } from "./DocumentViewer.types"; import { TIFFViewer } from 'react-tiff'; import './DocumentViewer.styles.scss'; @@ -107,7 +106,7 @@ const DocumentViewer = ({ className, metadata, urlWithSasToken, iframeKey }: IIF
We can't open this file

Something went wrong.

-
+ : getContentComponent() } diff --git a/src/ContentProcessorWeb/src/Components/DocumentViewer/DocumentViewer.types.ts b/src/ContentProcessorWeb/src/Components/DocumentViewer/DocumentViewer.types.ts deleted file mode 100644 index c5d94910..00000000 --- a/src/ContentProcessorWeb/src/Components/DocumentViewer/DocumentViewer.types.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { SlotRenderFunction } from "@fluentui/react-components" -import { DetailedHTMLProps, HTMLAttributes, JSXElementConstructor, ReactElement, ReactNode, ReactPortal, RefObject } from "react" -// Removed import for ReactI18NextChildren as it is not exported by react-i18next - - -export interface Document { - page_number: ((string | number | boolean | ReactPortal | ReactElement> | Iterable) & (string | number | boolean | ReactPortal | ReactElement> | Iterable | SlotRenderFunction, HTMLSpanElement>, "ref"> & { ref?: ((instance: HTMLSpanElement | null) => void) | RefObject | null | undefined }>)) | null | undefined - processId: string; // Unique document identifier - fileName: string; // Name of the file - keywords: { // Keywords object with dynamic keys and comma-separated string values - [key: string]: string; - }; - importedTime: string; // ISO timestamp for when the document was imported - processingTime: string; // Time taken to process the document - mimeType: string; // MIME type of the document (e.g., PDF, DOCX) - summary: string; // Summary of the document's contents - id: string; // Additional identifier - __partitionkey: string; // Partition key (specific to your data structure) - // index_key: string - // metadata_storage_path: string - // metadata_storage_name: string - // metadata_storage_size: number - // metadata_storage_content_md5: string - // content_size: number - // content_encoding: any - // description: any - // creation_date: string - // last_modified: string - // processing_date: any - // source_processing_date: string - // source_last_modified: string - // key_phrases: string[] - // topics: any[] - // organizations: string[] - // persons: string[] - // locations: string[] - // cities: string[] - // countries: string[] - // language: string - // translated_language: string - // paragraphs_count: number - // summary: string[] - // categories: any[] - // captions: any[] - // title: string - // translated_title: string - // author: string - // content_type: string - // content_group: string - // page_number: number - // page_count: any - // slide_count: any - // links: any[] - // emails: any[] - // // Index_key -> document Id - // // imageUrl - // // Filename - // // Filelocation - // // Tags - // // Uploadtime - // // LatestProcessTime - // // Status - // // Chat history id per document? - // document_id: string - // document_filename: string - document_url: string - // document_segments: string[] - // markets: any[] - // competitions: any[] - // technologies: any[] - // user_keywords: any[] - // user_categories: any[] - // user_tags: any[] - // strategies: any[] - // tables: string[] - // tables_count: number - // kvs: string - // kvs_count: number - // geolocation: any - // restricted: boolean - // parent_id: any - // chunk: any - // vector: any[] - // parent: Parent - // image: Image - // email: any - // document: Document2 - } \ No newline at end of file diff --git a/src/ContentProcessorWeb/src/Components/Header/Header.tsx b/src/ContentProcessorWeb/src/Components/Header/Header.tsx index ef59d6e7..162d7ab6 100644 --- a/src/ContentProcessorWeb/src/Components/Header/Header.tsx +++ b/src/ContentProcessorWeb/src/Components/Header/Header.tsx @@ -26,7 +26,7 @@ import { } from "../../Imports/bundleIcons.tsx"; import MainLogo from "../../Imports/MainLogo.svg"; import "./Header.css"; -import { DocumentBulletListCubeRegular, InfoRegular} from "@fluentui/react-icons" +import { DocumentBulletListCubeRegular, InfoRegular, DocumentData16Regular } from "@fluentui/react-icons" import useAuth from "../../msal-auth/useAuth.ts"; @@ -41,6 +41,12 @@ const tabConfigs = [ value: "default", // Route path defined in App.tsx label: "Content", // Visible label on UI }, + + { + icon: , // Import bundle icon + value: "api", // Route path defined in App.tsx + label: "API Documentation", // Visible label on UI + }, // Add more ]; @@ -69,12 +75,18 @@ const HeaderPage: React.FC = ({ toggleTheme, isDarkMode }) => { _: React.SyntheticEvent, data: { value: TabValue } ) => { - const newRoute = Object.keys(tabRoutes).find( - (key) => tabRoutes[key] === data.value - ); - if (newRoute) { - navigate(newRoute); + if (data.value == 'api') { + _.preventDefault(); // Prevents the default anchor click behavior + window.open(process.env.REACT_APP_API_BASE_URL + "/docs", '_blank', 'noopener noreferrer'); + } else { + const newRoute = Object.keys(tabRoutes).find( + (key) => tabRoutes[key] === data.value + ); + if (newRoute) { + navigate(newRoute); + } } + }; @@ -100,8 +112,8 @@ const HeaderPage: React.FC = ({ toggleTheme, isDarkMode }) => {
- - AI-generated content may be incorrect + + AI-generated content may be incorrect
{/* Tools Section */} diff --git a/src/ContentProcessorWeb/src/Components/JSONEditor/JSONEditor.tsx b/src/ContentProcessorWeb/src/Components/JSONEditor/JSONEditor.tsx index 2760e7f4..cc61e2da 100644 --- a/src/ContentProcessorWeb/src/Components/JSONEditor/JSONEditor.tsx +++ b/src/ContentProcessorWeb/src/Components/JSONEditor/JSONEditor.tsx @@ -2,29 +2,33 @@ import React, { useEffect, useState } from 'react' import { JsonEditor, JsonEditorProps, githubDarkTheme } from 'json-edit-react' import './JSONEditor.styles.scss' -import { useDispatch, useSelector,shallowEqual } from 'react-redux'; +import { useDispatch, useSelector, shallowEqual } from 'react-redux'; import { AppDispatch, RootState } from '../../store'; import { fetchContentJsonData, setModifiedResult } from '../../store/slices/centerPanelSlice'; +import { SearchBox } from "@fluentui/react-components"; + interface JSONEditorProps { processId?: string | null; } const JSONEditor: React.FC = () => { const [jsonData, setJsonData] = React.useState({}) - + const [isFocused, setIsFocused] = useState(false); const dispatch = useDispatch(); + const [searchText, setSearchText] = useState(''); const store = useSelector((state: RootState) => ({ processId: state.leftPanel.processId, contentData: state.centerPanel.contentData, cLoader: state.centerPanel.cLoader, cError: state.centerPanel.cError, - }),shallowEqual ); + isJSONEditorSearchEnabled: state.centerPanel.isJSONEditorSearchEnabled + }), shallowEqual); useEffect(() => { - if(!store.cLoader){ + if (!store.cLoader) { if (Object.keys(store.contentData).length > 0) { const formattedJson = store.contentData.result; const data = { @@ -33,7 +37,7 @@ const JSONEditor: React.FC = () => { } } setJsonData(data); - }else { + } else { setJsonData({}) } } @@ -44,32 +48,59 @@ const JSONEditor: React.FC = () => { dispatch(setModifiedResult(newData)); } + const handleFocus = () => setIsFocused(true); + const handleBlur = () => setIsFocused(false); + return ( <>{ - store.cLoader ?

Loading...

: - Object.keys(jsonData).length == 0 ?

No data available

: - { - onUpdateHandle(newData) - }} - //setData={ setJsonData } // optional - restrictEdit={({ key, path, level, index, value, size, parentData, fullData, collapsed }) => { - return !path.includes('extracted_result') - } - } - restrictDelete={true} - /> + store.cLoader ?

Loading...

: + Object.keys(jsonData).length == 0 ?

No data available

: + <> + {store.isJSONEditorSearchEnabled && +
+ { setSearchText(data.value) }} + style={{ + width: isFocused ? '200px' : '100px', + transition: 'width 0.3s ease', + }} + /> +
} + { + onUpdateHandle(newData) + }} + //setData={ setJsonData } // optional + restrictEdit={({ key, path, level, index, value, size, parentData, fullData, collapsed }) => { + return !path.includes('extracted_result') + } + } + restrictDelete={true} + /> + } ) } diff --git a/src/ContentProcessorWeb/src/Components/JSONEditor/data.json b/src/ContentProcessorWeb/src/Components/JSONEditor/data.json deleted file mode 100644 index b5d0bc47..00000000 --- a/src/ContentProcessorWeb/src/Components/JSONEditor/data.json +++ /dev/null @@ -1,857 +0,0 @@ -{ - "extracted_result": { - "customer_name": "ALD AUTOMOTIVE P LTD.", - "customer_address": { - "street": "Workafella Business Centre 150/1, Infantry Road, Opp. Commissioner Office, Vasanth Nagar", - "city": "Bangalore", - "state": "Karnataka", - "postal_code": "560001", - "country": "India" - }, - "customer_tax_id": "29AAFCA0924K1ZN", - "shipping_address": null, - "purchase_order": null, - "invoice_id": "IATIEN1819013536", - "invoice_date": "2019-02-15", - "payable_by": null, - "vendor_name": "ADISHAKTI CARS PVT. LTD.", - "vendor_address": { - "street": "#56, OPP. LUMBINI GARDEN MAIN GATE, SERVICE RING ROAD, VEERANAPALYA MAIN ROAD", - "city": "BENGALURU", - "state": "Karnataka", - "postal_code": "560045", - "country": "India" - }, - "vendor_tax_id": "29AAHCS6672E1ZZ", - "remittance_address": null, - "subtotal": 350.0, - "total_discount": 0.0, - "total_tax": 63.0, - "invoice_total": 413.0, - "payment_terms": null, - "items": [ - { - "product_code": "FREE2", - "description": "SECOND FREE SERVICE", - "quantity": 1, - "tax": 0.0, - "tax_rate": "9%", - "unit_price": 500.0, - "total": 0.0, - "reason": null - }, - { - "product_code": "423030", - "description": "BRAKE BLEEDING", - "quantity": 1, - "tax": 0.0, - "tax_rate": "9%", - "unit_price": 0.0, - "total": 0.0, - "reason": null - }, - { - "product_code": "183090", - "description": "BREATHER REPLACEMENT", - "quantity": 1, - "tax": 0.0, - "tax_rate": "9%", - "unit_price": 0.0, - "total": 0.0, - "reason": null - }, - { - "product_code": "AKSPROPLUS", - "description": "SERVICE PLUS - AUTOKROM", - "quantity": 1, - "tax": 63.0, - "tax_rate": "9%", - "unit_price": 350.0, - "total": 413.0, - "reason": null - } - ], - "total_item_quantity": 4.0, - "items_customer_signature": { - "signatory": "ANIL DB", - "is_signed": true - }, - "items_vendor_signature": { - "signatory": "Authorized signatory", - "is_signed": true - }, - "returns": [], - "total_return_quantity": null, - "returns_customer_signature": null, - "returns_vendor_signature": null - }, - "confidence": { - "customer_name": { - "confidence": 0.988, - "value": "ALD AUTOMOTIVE P LTD." - }, - "customer_address": { - "street": { - "confidence": 0.993, - "value": "Workafella Business Centre 150/1, Infantry Road, Opp. Commissioner Office, Vasanth Nagar" - }, - "city": { - "confidence": 0.693, - "value": "Bangalore" - }, - "state": { - "confidence": 0.992, - "value": "Karnataka" - }, - "postal_code": { - "confidence": 0.679, - "value": "560001" - }, - "country": { - "confidence": 0.953, - "value": "India" - } - }, - "customer_tax_id": { - "confidence": 0.989, - "value": "29AAFCA0924K1ZN" - }, - "shipping_address": { - "confidence": 0.0, - "value": null - }, - "purchase_order": { - "confidence": 0.0, - "value": null - }, - "invoice_id": { - "confidence": 0.963, - "value": "IATIEN1819013536" - }, - "invoice_date": { - "confidence": 1.0, - "value": "2019-02-15" - }, - "payable_by": { - "confidence": 0.0, - "value": null - }, - "vendor_name": { - "confidence": 0.985, - "value": "ADISHAKTI CARS PVT. LTD." - }, - "vendor_address": { - "street": { - "confidence": 0.889, - "value": "#56, OPP. LUMBINI GARDEN MAIN GATE, SERVICE RING ROAD, VEERANAPALYA MAIN ROAD" - }, - "city": { - "confidence": 0.679, - "value": "BENGALURU" - }, - "state": { - "confidence": 0.965, - "value": "Karnataka" - }, - "postal_code": { - "confidence": 0.889, - "value": "560045" - }, - "country": { - "confidence": 0.953, - "value": "India" - } - }, - "vendor_tax_id": { - "confidence": 0.992, - "value": "29AAHCS6672E1ZZ" - }, - "remittance_address": { - "confidence": 0.0, - "value": null - }, - "subtotal": { - "confidence": 0.807, - "value": 350.0 - }, - "total_discount": { - "confidence": 0.938, - "value": 0.0 - }, - "total_tax": { - "confidence": 0.995, - "value": 63.0 - }, - "invoice_total": { - "confidence": 0.995, - "value": 413.0 - }, - "payment_terms": { - "confidence": 0.0, - "value": null - }, - "items": [ - { - "product_code": { - "confidence": 0.995, - "value": "FREE2" - }, - "description": { - "confidence": 0.962, - "value": "SECOND FREE SERVICE" - }, - "quantity": { - "confidence": 0.989, - "value": 1 - }, - "tax": { - "confidence": 0.98, - "value": 0.0 - }, - "tax_rate": { - "confidence": 0.94, - "value": "9%" - }, - "unit_price": { - "confidence": 0.945, - "value": 500.0 - }, - "total": { - "confidence": 0.793, - "value": 0.0 - }, - "reason": { - "confidence": 0.0, - "value": null - } - }, - { - "product_code": { - "confidence": 0.995, - "value": "423030" - }, - "description": { - "confidence": 1.0, - "value": "BRAKE BLEEDING" - }, - "quantity": { - "confidence": 0.989, - "value": 1 - }, - "tax": { - "confidence": 1.0, - "value": 0.0 - }, - "tax_rate": { - "confidence": 0.943, - "value": "9%" - }, - "unit_price": { - "confidence": 0.999, - "value": 0.0 - }, - "total": { - "confidence": 1.0, - "value": 0.0 - }, - "reason": { - "confidence": 0.0, - "value": null - } - }, - { - "product_code": { - "confidence": 0.996, - "value": "183090" - }, - "description": { - "confidence": 0.998, - "value": "BREATHER REPLACEMENT" - }, - "quantity": { - "confidence": 0.989, - "value": 1 - }, - "tax": { - "confidence": 1.0, - "value": 0.0 - }, - "tax_rate": { - "confidence": 0.943, - "value": "9%" - }, - "unit_price": { - "confidence": 1.0, - "value": 0.0 - }, - "total": { - "confidence": 1.0, - "value": 0.0 - }, - "reason": { - "confidence": 0.0, - "value": null - } - }, - { - "product_code": { - "confidence": 0.999, - "value": "AKSPROPLUS" - }, - "description": { - "confidence": 0.999, - "value": "SERVICE PLUS - AUTOKROM" - }, - "quantity": { - "confidence": 0.989, - "value": 1 - }, - "tax": { - "confidence": 0.947, - "value": 63.0 - }, - "tax_rate": { - "confidence": 0.855, - "value": "9%" - }, - "unit_price": { - "confidence": 0.998, - "value": 350.0 - }, - "total": { - "confidence": 0.986, - "value": 413.0 - }, - "reason": { - "confidence": 0.0, - "value": null - } - } - ], - "total_item_quantity": { - "confidence": 0.0, - "value": 4.0 - }, - "items_customer_signature": { - "signatory": { - "confidence": 0.723, - "value": "ANIL DB" - }, - "is_signed": { - "confidence": 0.0, - "value": true - } - }, - "items_vendor_signature": { - "signatory": { - "confidence": 0.724, - "value": "Authorized signatory" - }, - "is_signed": { - "confidence": 0.0, - "value": true - } - }, - "returns": [], - "total_return_quantity": { - "confidence": 0.0, - "value": null - }, - "returns_customer_signature": { - "confidence": 0.0, - "value": null - }, - "returns_vendor_signature": { - "confidence": 0.0, - "value": null - }, - "total_evaluated_fields_count": 50, - "overall_confidence": 0.94, - "min_extracted_field_confidence": 0.679, - "min_extracted_field_confidence_field": [ - "customer_address.postal_code", - "vendor_address.city" - ], - "missed_fields": [ - "shipping_address", - "purchase_order", - "payable_by", - "remittance_address", - "payment_terms", - "items[0].reason", - "items[1].reason", - "items[2].reason", - "items[3].reason", - "total_item_quantity", - "items_customer_signature.is_signed", - "items_vendor_signature.is_signed", - "total_return_quantity", - "returns_customer_signature", - "returns_vendor_signature" - ], - "missed_fields_count": 15, - "overall_hit_rate": 49.7 - }, - "comparison_result": { - "items": [ - { - "Field": "customer_address_city", - "Extracted": "Bangalore", - "Confidence": "69.30%", - "IsAboveThreshold": false, - "Hited": true - }, - { - "Field": "customer_address_country", - "Extracted": "India", - "Confidence": "95.30%", - "IsAboveThreshold": true, - "Hited": true - }, - { - "Field": "customer_address_postal_code", - "Extracted": "560001", - "Confidence": "67.90%", - "IsAboveThreshold": false, - "Hited": true - }, - { - "Field": "customer_address_state", - "Extracted": "Karnataka", - "Confidence": "99.20%", - "IsAboveThreshold": true, - "Hited": true - }, - { - "Field": "customer_address_street", - "Extracted": "Workafella Business Centre 150/1, Infantry Road, Opp. Commissioner Office, Vasanth Nagar", - "Confidence": "99.30%", - "IsAboveThreshold": true, - "Hited": true - }, - { - "Field": "customer_name", - "Extracted": "ALD AUTOMOTIVE P LTD.", - "Confidence": "98.80%", - "IsAboveThreshold": true, - "Hited": true - }, - { - "Field": "customer_tax_id", - "Extracted": "29AAFCA0924K1ZN", - "Confidence": "98.90%", - "IsAboveThreshold": true, - "Hited": true - }, - { - "Field": "invoice_date", - "Extracted": "2019-02-15", - "Confidence": "100.00%", - "IsAboveThreshold": true, - "Hited": true - }, - { - "Field": "invoice_id", - "Extracted": "IATIEN1819013536", - "Confidence": "96.30%", - "IsAboveThreshold": true, - "Hited": true - }, - { - "Field": "invoice_total", - "Extracted": 413.0, - "Confidence": "99.50%", - "IsAboveThreshold": true, - "Hited": true - }, - { - "Field": "items_0_description", - "Extracted": "SECOND FREE SERVICE", - "Confidence": "96.20%", - "IsAboveThreshold": true, - "Hited": true - }, - { - "Field": "items_0_product_code", - "Extracted": "FREE2", - "Confidence": "99.50%", - "IsAboveThreshold": true, - "Hited": true - }, - { - "Field": "items_0_quantity", - "Extracted": 1, - "Confidence": "98.90%", - "IsAboveThreshold": true, - "Hited": true - }, - { - "Field": "items_0_reason", - "Extracted": null, - "Confidence": "0.00%", - "IsAboveThreshold": false, - "Hited": false - }, - { - "Field": "items_0_tax", - "Extracted": 0.0, - "Confidence": "98.00%", - "IsAboveThreshold": true, - "Hited": true - }, - { - "Field": "items_0_tax_rate", - "Extracted": "9%", - "Confidence": "94.00%", - "IsAboveThreshold": true, - "Hited": true - }, - { - "Field": "items_0_total", - "Extracted": 0.0, - "Confidence": "79.30%", - "IsAboveThreshold": false, - "Hited": true - }, - { - "Field": "items_0_unit_price", - "Extracted": 500.0, - "Confidence": "94.50%", - "IsAboveThreshold": true, - "Hited": true - }, - { - "Field": "items_1_description", - "Extracted": "BRAKE BLEEDING", - "Confidence": "100.00%", - "IsAboveThreshold": true, - "Hited": true - }, - { - "Field": "items_1_product_code", - "Extracted": "423030", - "Confidence": "99.50%", - "IsAboveThreshold": true, - "Hited": true - }, - { - "Field": "items_1_quantity", - "Extracted": 1, - "Confidence": "98.90%", - "IsAboveThreshold": true, - "Hited": true - }, - { - "Field": "items_1_reason", - "Extracted": null, - "Confidence": "0.00%", - "IsAboveThreshold": false, - "Hited": false - }, - { - "Field": "items_1_tax", - "Extracted": 0.0, - "Confidence": "100.00%", - "IsAboveThreshold": true, - "Hited": true - }, - { - "Field": "items_1_tax_rate", - "Extracted": "9%", - "Confidence": "94.30%", - "IsAboveThreshold": true, - "Hited": true - }, - { - "Field": "items_1_total", - "Extracted": 0.0, - "Confidence": "100.00%", - "IsAboveThreshold": true, - "Hited": true - }, - { - "Field": "items_1_unit_price", - "Extracted": 0.0, - "Confidence": "99.90%", - "IsAboveThreshold": true, - "Hited": true - }, - { - "Field": "items_2_description", - "Extracted": "BREATHER REPLACEMENT", - "Confidence": "99.80%", - "IsAboveThreshold": true, - "Hited": true - }, - { - "Field": "items_2_product_code", - "Extracted": "183090", - "Confidence": "99.60%", - "IsAboveThreshold": true, - "Hited": true - }, - { - "Field": "items_2_quantity", - "Extracted": 1, - "Confidence": "98.90%", - "IsAboveThreshold": true, - "Hited": true - }, - { - "Field": "items_2_reason", - "Extracted": null, - "Confidence": "0.00%", - "IsAboveThreshold": false, - "Hited": false - }, - { - "Field": "items_2_tax", - "Extracted": 0.0, - "Confidence": "100.00%", - "IsAboveThreshold": true, - "Hited": true - }, - { - "Field": "items_2_tax_rate", - "Extracted": "9%", - "Confidence": "94.30%", - "IsAboveThreshold": true, - "Hited": true - }, - { - "Field": "items_2_total", - "Extracted": 0.0, - "Confidence": "100.00%", - "IsAboveThreshold": true, - "Hited": true - }, - { - "Field": "items_2_unit_price", - "Extracted": 0.0, - "Confidence": "100.00%", - "IsAboveThreshold": true, - "Hited": true - }, - { - "Field": "items_3_description", - "Extracted": "SERVICE PLUS - AUTOKROM", - "Confidence": "99.90%", - "IsAboveThreshold": true, - "Hited": true - }, - { - "Field": "items_3_product_code", - "Extracted": "AKSPROPLUS", - "Confidence": "99.90%", - "IsAboveThreshold": true, - "Hited": true - }, - { - "Field": "items_3_quantity", - "Extracted": 1, - "Confidence": "98.90%", - "IsAboveThreshold": true, - "Hited": true - }, - { - "Field": "items_3_reason", - "Extracted": null, - "Confidence": "0.00%", - "IsAboveThreshold": false, - "Hited": false - }, - { - "Field": "items_3_tax", - "Extracted": 63.0, - "Confidence": "94.70%", - "IsAboveThreshold": true, - "Hited": true - }, - { - "Field": "items_3_tax_rate", - "Extracted": "9%", - "Confidence": "85.50%", - "IsAboveThreshold": true, - "Hited": true - }, - { - "Field": "items_3_total", - "Extracted": 413.0, - "Confidence": "98.60%", - "IsAboveThreshold": true, - "Hited": true - }, - { - "Field": "items_3_unit_price", - "Extracted": 350.0, - "Confidence": "99.80%", - "IsAboveThreshold": true, - "Hited": true - }, - { - "Field": "items_customer_signature_is_signed", - "Extracted": true, - "Confidence": "0.00%", - "IsAboveThreshold": false, - "Hited": false - }, - { - "Field": "items_customer_signature_signatory", - "Extracted": "ANIL DB", - "Confidence": "72.30%", - "IsAboveThreshold": false, - "Hited": true - }, - { - "Field": "items_vendor_signature_is_signed", - "Extracted": true, - "Confidence": "0.00%", - "IsAboveThreshold": false, - "Hited": false - }, - { - "Field": "items_vendor_signature_signatory", - "Extracted": "Authorized signatory", - "Confidence": "72.40%", - "IsAboveThreshold": false, - "Hited": true - }, - { - "Field": "payable_by", - "Extracted": null, - "Confidence": "0.00%", - "IsAboveThreshold": false, - "Hited": false - }, - { - "Field": "payment_terms", - "Extracted": null, - "Confidence": "0.00%", - "IsAboveThreshold": false, - "Hited": false - }, - { - "Field": "purchase_order", - "Extracted": null, - "Confidence": "0.00%", - "IsAboveThreshold": false, - "Hited": false - }, - { - "Field": "remittance_address", - "Extracted": null, - "Confidence": "0.00%", - "IsAboveThreshold": false, - "Hited": false - }, - { - "Field": "returns_customer_signature", - "Extracted": null, - "Confidence": "0.00%", - "IsAboveThreshold": false, - "Hited": false - }, - { - "Field": "returns_vendor_signature", - "Extracted": null, - "Confidence": "0.00%", - "IsAboveThreshold": false, - "Hited": false - }, - { - "Field": "shipping_address", - "Extracted": null, - "Confidence": "0.00%", - "IsAboveThreshold": false, - "Hited": false - }, - { - "Field": "subtotal", - "Extracted": 350.0, - "Confidence": "80.70%", - "IsAboveThreshold": true, - "Hited": true - }, - { - "Field": "total_discount", - "Extracted": 0.0, - "Confidence": "93.80%", - "IsAboveThreshold": true, - "Hited": true - }, - { - "Field": "total_item_quantity", - "Extracted": 4.0, - "Confidence": "0.00%", - "IsAboveThreshold": false, - "Hited": false - }, - { - "Field": "total_return_quantity", - "Extracted": null, - "Confidence": "0.00%", - "IsAboveThreshold": false, - "Hited": false - }, - { - "Field": "total_tax", - "Extracted": 63.0, - "Confidence": "99.50%", - "IsAboveThreshold": true, - "Hited": true - }, - { - "Field": "vendor_address_city", - "Extracted": "BENGALURU", - "Confidence": "67.90%", - "IsAboveThreshold": false, - "Hited": true - }, - { - "Field": "vendor_address_country", - "Extracted": "India", - "Confidence": "95.30%", - "IsAboveThreshold": true, - "Hited": true - }, - { - "Field": "vendor_address_postal_code", - "Extracted": "560045", - "Confidence": "88.90%", - "IsAboveThreshold": true, - "Hited": true - }, - { - "Field": "vendor_address_state", - "Extracted": "Karnataka", - "Confidence": "96.50%", - "IsAboveThreshold": true, - "Hited": true - }, - { - "Field": "vendor_address_street", - "Extracted": "#56, OPP. LUMBINI GARDEN MAIN GATE, SERVICE RING ROAD, VEERANAPALYA MAIN ROAD", - "Confidence": "88.90%", - "IsAboveThreshold": true, - "Hited": true - }, - { - "Field": "vendor_name", - "Extracted": "ADISHAKTI CARS PVT. LTD.", - "Confidence": "98.50%", - "IsAboveThreshold": true, - "Hited": true - }, - { - "Field": "vendor_tax_id", - "Extracted": "29AAHCS6672E1ZZ", - "Confidence": "99.20%", - "IsAboveThreshold": true, - "Hited": true - } - ] - }, - "prompt_tokens": 5865, - "completion_tokens": 491, - "execution_time": 0 -} \ No newline at end of file diff --git a/src/ContentProcessorWeb/src/Hooks/useAppHooks.tsx b/src/ContentProcessorWeb/src/Hooks/useAppHooks.tsx deleted file mode 100644 index 185b8af2..00000000 --- a/src/ContentProcessorWeb/src/Hooks/useAppHooks.tsx +++ /dev/null @@ -1,86 +0,0 @@ -// useAppHandlers.tsx -import { useState, useEffect } from "react"; - -export const useAppHooks = () => { - // State for Left Panel - const [isPanelOpen, setIsPanelOpen] = useState(true); - const [panelWidth, setPanelWidth] = useState(260); - const [isResizingLeft, setIsResizingLeft] = useState(false); - - // State for Right Panel - const [isRightPanelOpen, setIsRightPanelOpen] = useState(true); - const [rightPanelWidth, setRightPanelWidth] = useState(500); - const [isResizingRight, setIsResizingRight] = useState(false); - - // Left Panel Toggle - const togglePanel = () => setIsPanelOpen(!isPanelOpen); - - // Right Panel Toggle - const toggleRightPanel = () => setIsRightPanelOpen(!isRightPanelOpen); - - // Left Panel Resize Handlers - const handleMouseDownLeft = (e: React.MouseEvent) => { - setIsResizingLeft(true); - e.preventDefault(); - }; - - const handleMouseMoveLeft = (e: MouseEvent) => { - if (isResizingLeft) { - const newWidth = Math.min(Math.max(e.clientX, 192), 400); - setPanelWidth(newWidth); - } - }; - - const handleMouseUpLeft = () => setIsResizingLeft(false); - - // Right Panel Resize Handlers - const handleMouseDownRight = (e: React.MouseEvent) => { - setIsResizingRight(true); - e.preventDefault(); - }; - - const handleMouseMoveRight = (e: MouseEvent) => { - if (isResizingRight) { - const newWidth = Math.min( - Math.max(window.innerWidth - e.clientX, 260), - 500 - ); - setRightPanelWidth(newWidth); - } - }; - - const handleMouseUpRight = () => setIsResizingRight(false); - - useEffect(() => { - if (isResizingLeft) { - window.addEventListener("mousemove", handleMouseMoveLeft); - window.addEventListener("mouseup", handleMouseUpLeft); - } else if (isResizingRight) { - window.addEventListener("mousemove", handleMouseMoveRight); - window.addEventListener("mouseup", handleMouseUpRight); - } else { - window.removeEventListener("mousemove", handleMouseMoveLeft); - window.removeEventListener("mouseup", handleMouseUpLeft); - window.removeEventListener("mousemove", handleMouseMoveRight); - window.removeEventListener("mouseup", handleMouseUpRight); - } - - return () => { - window.removeEventListener("mousemove", handleMouseMoveLeft); - window.removeEventListener("mouseup", handleMouseUpLeft); - window.removeEventListener("mousemove", handleMouseMoveRight); - window.removeEventListener("mouseup", handleMouseUpRight); - }; - }, [isResizingLeft, isResizingRight]); - - return { - isPanelOpen, - panelWidth, - togglePanel, - handleMouseDownLeft, - isRightPanelOpen, - rightPanelWidth, - toggleRightPanel, - handleMouseDownRight, - }; -}; diff --git a/src/ContentProcessorWeb/src/Hooks/useContentHooks.tsx b/src/ContentProcessorWeb/src/Hooks/useContentHooks.tsx deleted file mode 100644 index a7e60042..00000000 --- a/src/ContentProcessorWeb/src/Hooks/useContentHooks.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { useEffect } from "react"; - -interface ContentHooksProps { - togglePanel?: () => void; // Optional for layouts without left panel - toggleRightPanel?: () => void; // Optional for layouts without right panel -} - -export const useContentHooks = ({ - togglePanel, - toggleRightPanel, -}: ContentHooksProps) => { - const isMac = /Mac|iPod|iPhone|iPad/.test(navigator.platform); - const commandKey = isMac ? "⌘ + Shift" : "Ctrl + Shift"; - - useEffect(() => { - const handleKeydown = (event: KeyboardEvent) => { - // Check for Ctrl (or Command on Mac) + Shift + Arrow Keys - if ((event.ctrlKey || (isMac && event.metaKey)) && event.shiftKey) { - if (event.key === "ArrowLeft" && togglePanel) { - // Call togglePanel only if it exists - event.preventDefault(); // Prevent browser's default action - togglePanel(); - } else if (event.key === "ArrowRight" && toggleRightPanel) { - // Call toggleRightPanel only if it exists - event.preventDefault(); // Prevent browser's default action - toggleRightPanel(); - } - } - }; - - window.addEventListener("keydown", handleKeydown, { passive: false }); // Use non-passive listener - return () => { - window.removeEventListener("keydown", handleKeydown); - }; - }, [togglePanel, toggleRightPanel, isMac]); - - return { commandKey }; -}; diff --git a/src/ContentProcessorWeb/src/Hooks/useContentToolbarHooks.tsx b/src/ContentProcessorWeb/src/Hooks/useContentToolbarHooks.tsx deleted file mode 100644 index 125b2b95..00000000 --- a/src/ContentProcessorWeb/src/Hooks/useContentToolbarHooks.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import React from "react"; -import "../Styles/App.css"; -import { Button, Tooltip, Toolbar, ToolbarDivider } from "@fluentui/react-components"; -import { - PanelLeftContract, - PanelLeftExpand, - PanelRightContract, - PanelRightExpand, -} from "../Imports/bundleIcons.tsx"; - -interface ContentToolbarProps { - isPanelOpen: boolean; - togglePanel?: () => void; - isRightPanelOpen: boolean; - toggleRightPanel?: () => void; - commandKey?: string; // Optional for shortcut hints - children?: React.ReactNode; // All nested components - panelConfig?: "left" | "right" | "both"; // Control which panel buttons to show -} - -const ContentToolbar: React.FC = ({ - isPanelOpen, - togglePanel, - isRightPanelOpen, - toggleRightPanel, - commandKey = "Ctrl", - children, - panelConfig = "both", // Default is to show both -}) => { - // Separate the first and the rest - const [firstToolbar, ...rest] = React.Children.toArray(children).filter( - (child) => React.isValidElement(child) && child.type === Toolbar - ); - - // Check if the first Toolbar has valid children - const firstToolbarHasChildren = React.isValidElement(firstToolbar) && - React.Children.toArray(firstToolbar.props.children).length > 0; - - // Check if any of the rest of the Toolbars have valid children - const restHasChildren = rest.some( - (child) => React.isValidElement(child) && - React.Children.toArray(child.props.children).length > 0 - ); - - return ( -
-
- {/* Show Left Panel Toggle and Divider if panelConfig is 'left' or 'both' */} - {togglePanel && (panelConfig === "left" || panelConfig === "both") && ( - <> - -
- -
- {rest} - {/* Show Divider and Right Panel Toggle if panelConfig is 'right' or 'both' */} - {toggleRightPanel && (panelConfig === "right" || panelConfig === "both") && ( - <> - {restHasChildren && ( - - )} - -
-
- ); -}; - -export default ContentToolbar; diff --git a/src/ContentProcessorWeb/src/Pages/DefaultPage/Components/ProcessQueueGrid/CustomCellRender.tsx b/src/ContentProcessorWeb/src/Pages/DefaultPage/Components/ProcessQueueGrid/CustomCellRender.tsx index 794d8c3e..841c58b7 100644 --- a/src/ContentProcessorWeb/src/Pages/DefaultPage/Components/ProcessQueueGrid/CustomCellRender.tsx +++ b/src/ContentProcessorWeb/src/Pages/DefaultPage/Components/ProcessQueueGrid/CustomCellRender.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { CaretUp16Filled, CaretDown16Filled, EditPersonFilled } from '@fluentui/react-icons'; import { Button, Menu, MenuTrigger, MenuPopover, MenuList, MenuItem } from '@fluentui/react-components'; -import { MoreVerticalRegular, MoreVerticalFilled, bundleIcon } from '@fluentui/react-icons'; +import { MoreVerticalRegular, MoreVerticalFilled, bundleIcon , Delete20Filled , Delete20Regular} from '@fluentui/react-icons'; type CellRendererProps = { type: string; @@ -13,6 +13,11 @@ const MoreVerticallIcon = bundleIcon( MoreVerticalRegular ); +const DeleteIcon = bundleIcon( + Delete20Filled, + Delete20Regular +); + const CellRenderer: React.FC = ({ type, props }) => { // Destructure props based on type const { @@ -51,21 +56,16 @@ const CellRenderer: React.FC = ({ type, props }) => { } const wholeValue = Math.round(decimalValue * 100); - let color; let numberClass = ''; // Apply color based on value if (wholeValue > 80) { - color = '#359B35'; numberClass = 'gClass'; } else if (wholeValue >= 50 && wholeValue <= 80) { - color = '#C19C00'; numberClass = 'yClass'; } else if (wholeValue >= 30 && wholeValue < 50) { - color = '#FF5F3DE5'; numberClass = 'oClass'; } else { - color = '#B10E1C'; numberClass = 'rClass'; } @@ -86,8 +86,9 @@ const CellRenderer: React.FC = ({ type, props }) => { if (lastModifiedBy === 'user') { return (
+ - Verified + Verified
); @@ -122,6 +123,7 @@ const CellRenderer: React.FC = ({ type, props }) => { } onClick={() => { setSelectedDeleteItem(item); toggleDialog(); diff --git a/src/ContentProcessorWeb/src/Pages/DefaultPage/Components/ProcessQueueGrid/ProcessQueueGrid.styles.scss b/src/ContentProcessorWeb/src/Pages/DefaultPage/Components/ProcessQueueGrid/ProcessQueueGrid.styles.scss index d965cad7..14b02376 100644 --- a/src/ContentProcessorWeb/src/Pages/DefaultPage/Components/ProcessQueueGrid/ProcessQueueGrid.styles.scss +++ b/src/ContentProcessorWeb/src/Pages/DefaultPage/Components/ProcessQueueGrid/ProcessQueueGrid.styles.scss @@ -1,117 +1,132 @@ -.col1 { - min-width: 120px !important; -} - -.col2 { - min-width: 55px !important; - max-width: 85px !important; -} - -.col3 { - min-width: 45px !important; - max-width: 70px !important; -} - -.col4 { - min-width: 35px !important; - max-width: 85px !important; -} - -.col5 { - min-width: 35px !important; - max-width: 85px !important; -} - -.col6 { - min-width: 45px !important; - max-width: 85px !important; -} - -.col7 { - min-width: 30px !important; - max-width: 35px !important; - box-sizing: border-box; -} - -.roundedBtn { - border: 1px solid #BDBDBD; - border-radius: 20px; - text-align: center; - padding: 1px 10px; - overflow: hidden; - text-overflow: ellipsis; - max-width: 75%; - white-space: nowrap; - font-size: 10px; - text-transform: capitalize; - - span { - color: #2C3C85 +.gridContainer { + height: 100vh; + + .gridTable { + min-width: 100%; + height: 100%; + display: flex; + flex-direction: column; + + .gridTableBody { + height: 100%; + } } - .ProcessedCls { - color: #00666D + .col1 { + min-width: 100px !important; } -} + .col2 { + min-width: 55px !important; + max-width: 85px !important; + } -.percentageContainer { - width: 100%; - text-align: center; + .col3 { + min-width: 45px !important; + max-width: 70px !important; + } - .gClass { - color: #359B35; + .col4 { + min-width: 35px !important; + max-width: 85px !important; } - .yClass { - color: #C19C00; + .col5 { + min-width: 35px !important; + max-width: 85px !important; } - .oClass { - color: #FF5F3DE5; + .col6 { + min-width: 55px !important; + max-width: 105px !important; + box-sizing: border-box; } - .rClass { - color: #B10E1C; + .col7 { + min-width: 30px !important; + max-width: 35px !important; + box-sizing: border-box; } - .textClass { - color: #0F6CBD + .roundedBtn { + border: 1px solid #BDBDBD; + border-radius: 20px; + text-align: center; + padding: 1px 10px; + overflow: hidden; + text-overflow: ellipsis; + max-width: 75%; + white-space: nowrap; + font-size: 10px; + text-transform: capitalize; + + span { + color: #2C3C85 + } + + .ProcessedCls { + color: #00666D + } + + } + + .percentageContainer { + width: 100%; + text-align: center; + + .gClass { + color: #359B35; + } + + .yClass { + color: #C19C00; + } + + .oClass { + color: #FF5F3DE5; + } + + .rClass { + color: #B10E1C; + } + + .textClass { + color: #0F6CBD + } } -} - -.GridList { - width: 100%; - // max-height: calc(100vh - 235px) !important; - // height: calc(100vh - 235px) !important; - flex: 1 1 auto; - height: 100%; - - //background-color: red; - >div { - width: 100% !important; + + .GridList { + width: 100%; + flex: 1 1 auto; + height: 100%; + + >div { + width: 100% !important; + } + } + + .columnCotainer { + font-size: 12px; + color: #323130; + width: 100%; + } + + .centerAlign { + text-align: center; + } + + .percentageContainer { + display: flex; + align-items: center; + justify-content: center; + flex-wrap: wrap; + } + + .editPersonIcon { + margin-right: 4px; + display: flex; + align-items: center; + justify-content: center; + color: #0F6CBD } -} - -.columnCotainer { - font-size: 12px; - color: #323130; - width: 100%; -} - -.centerAlign { - text-align: center; -} - -.percentageContainer { - display: flex; - align-items: center; - justify-content: center; -} - -.editPersonIcon { - margin-right: 4px; - display: flex; - align-items: center; - justify-content: center; } \ No newline at end of file diff --git a/src/ContentProcessorWeb/src/Pages/DefaultPage/Components/ProcessQueueGrid/ProcessQueueGrid.tsx b/src/ContentProcessorWeb/src/Pages/DefaultPage/Components/ProcessQueueGrid/ProcessQueueGrid.tsx index 2cf0c51a..a9ed6cfa 100644 --- a/src/ContentProcessorWeb/src/Pages/DefaultPage/Components/ProcessQueueGrid/ProcessQueueGrid.tsx +++ b/src/ContentProcessorWeb/src/Pages/DefaultPage/Components/ProcessQueueGrid/ProcessQueueGrid.tsx @@ -300,16 +300,22 @@ const ProcessQueueGrid: React.FC = () => { setIsDialogOpen(!isDialogOpen); }; + const dialogContnet = () => { + return ( +

Are you sure you want to delete this file?

+ ) + } + return ( <> -
+
@@ -323,7 +329,7 @@ const ProcessQueueGrid: React.FC = () => { {/*
*/} - +
{({ height, width }) => ( @@ -345,7 +351,7 @@ const ProcessQueueGrid: React.FC = () => { { + const status = ['extract', 'processing', 'map', 'evaluate']; + const [loadingStates, setLoadingStates] = useState({}); + const childRefs = useRef<{ [key: string]: HTMLDivElement | null }>({}); + + + const store = useSelector((state: RootState) => ({ + processStepsData: state.centerPanel.processStepsData, + selectedItem: state.leftPanel.selectedItem, + }), shallowEqual + ); + + const renderProcessTimeInSeconds = (timeString: string) => { + if (!timeString) { + return timeString; + } + const parts = timeString.split(":"); + if (parts.length !== 3) { + return timeString; + } + const [hours, minutes, seconds] = parts.map(Number); + const totalSeconds = (hours * 3600 + minutes * 60 + seconds).toFixed(2); + return `${totalSeconds}s`; + }; + + const handleExpand = (itemId: any) => { + setLoadingStates((prevState) => ({ ...prevState, [itemId]: true })); + setTimeout(() => { + const childDiv = childRefs.current[itemId]; + if (childDiv) { + childDiv.classList.add('loaded'); + } + }, 500); + + }; + + useEffect(() => { + const observers: MutationObserver[] = []; + Object.keys(childRefs.current).forEach((itemId) => { + const childDiv = childRefs.current[itemId]; + if (childDiv) { + const observer = new MutationObserver(() => { + if (childDiv.classList.contains('loaded')) { + setLoadingStates((prevState) => ({ ...prevState, [itemId]: false })); + } + }); + observer.observe(childDiv, { attributes: true, attributeFilter: ['class'] }); + observers.push(observer); + } + }); + return () => { + observers.forEach((observer) => observer.disconnect()); + }; + }, []); + + return ( + + {!status.includes(store.selectedItem.status) && store.processStepsData?.map((step, index) => ( + + handleExpand(index)}> {loadingStates[index] && } + {step.step_name} + + {renderProcessTimeInSeconds(step.processed_time)} + + +
(childRefs.current[index] = el)}> + + + +
+
+ ))} +
+ + ); +}; + +export default ProcessSteps; diff --git a/src/ContentProcessorWeb/src/Pages/DefaultPage/Components/SchemaDropdown/SchemaDropdown.tsx b/src/ContentProcessorWeb/src/Pages/DefaultPage/Components/SchemaDropdown/SchemaDropdown.tsx index 68e15226..3ba64164 100644 --- a/src/ContentProcessorWeb/src/Pages/DefaultPage/Components/SchemaDropdown/SchemaDropdown.tsx +++ b/src/ContentProcessorWeb/src/Pages/DefaultPage/Components/SchemaDropdown/SchemaDropdown.tsx @@ -5,7 +5,7 @@ import './SchemaDropdown.styles.scss'; import { useDispatch, useSelector, shallowEqual } from 'react-redux'; import { RootState } from '../../../../store'; import { setSchemaSelectedOption } from '../../../../store/slices/leftPanelSlice'; -import { OptionList, SchemaItem, StoreState } from './SchemaDropdownTypes'; // Importing types +import { OptionList, SchemaItem, StoreState } from './SchemaDropdownTypes'; const useStyles = makeStyles({ root: { diff --git a/src/ContentProcessorWeb/src/Pages/DefaultPage/PanelCenter.tsx b/src/ContentProcessorWeb/src/Pages/DefaultPage/PanelCenter.tsx index 5cd861d1..5cc18d1a 100644 --- a/src/ContentProcessorWeb/src/Pages/DefaultPage/PanelCenter.tsx +++ b/src/ContentProcessorWeb/src/Pages/DefaultPage/PanelCenter.tsx @@ -1,39 +1,19 @@ -import React, { useCallback, useEffect,useState } from "react"; +import React, { useCallback, useEffect, useState } from "react"; import "../../Styles/App.css"; -import { - makeStyles, SelectTabData, SelectTabEvent, Tab, TabList, TabValue, Textarea, Divider, - Button, - Dialog, - DialogTitle, - DialogSurface, - DialogContent, - DialogActions, - Accordion, - AccordionItem, - AccordionHeader, - AccordionPanel -} from "@fluentui/react-components"; -import { useContentHooks } from "../../Hooks/useContentHooks.tsx"; +import { makeStyles, SelectTabData, SelectTabEvent, Tab, TabList, TabValue, Textarea, Divider, Button } from "@fluentui/react-components"; import { Field } from "@fluentui/react-components"; import PanelToolbar from "../../Hooks/usePanelHooks.tsx"; import JSONEditor from "../../Components/JSONEditor/JSONEditor" - import { useDispatch, useSelector, shallowEqual } from 'react-redux'; -import { saveContentJson, fetchProcessSteps ,setUpdateComments } from '../../store/slices/centerPanelSlice'; +import { saveContentJson, fetchProcessSteps, setUpdateComments } from '../../store/slices/centerPanelSlice'; import { RootState, AppDispatch } from '../../store'; import { startLoader, stopLoader } from "../../store/slices/loaderSlice.ts"; -import { createAsyncThunk } from "@reduxjs/toolkit"; -import httpUtility from "../../Services/httpUtility.ts"; -import { JsonEditor } from "json-edit-react"; -import { CheckmarkCircleFilled } from "@fluentui/react-icons"; -import { fetchContentJsonData , setActiveProcessId } from '../../store/slices/centerPanelSlice'; - +import { fetchContentJsonData, setActiveProcessId } from '../../store/slices/centerPanelSlice'; +import ProcessSteps from './Components/ProcessSteps/ProcessSteps'; +import { setRefreshGrid } from "../../store/slices/leftPanelSlice.ts"; -interface ContentProps { - isPanelOpen: boolean; - togglePanel?: () => void; // Optional to conditionally render left toggle - isRightPanelOpen: boolean; - toggleRightPanel?: () => void; // Optional to conditionally render left toggle +interface PanelCenterProps { + } const useStyles = makeStyles({ @@ -70,14 +50,18 @@ const useStyles = makeStyles({ height: 'calc(100vh - 383px)', border: '1px solid #DBDBDB', overflow: 'auto', - background: '#f6f6f6' + background: '#f6f6f6', + padding: '10px 5px', + boxSizing:'border-box' }, processTabItemCotnent: { height: 'calc(100vh - 200px)', border: '1px solid #DBDBDB', overflow: 'auto', - background: '#f6f6f6' + background: '#f6f6f6', + padding: '5px', + boxSizing:'border-box' }, fieldLabel: { fontWeight: 'bold', @@ -94,7 +78,7 @@ const useStyles = makeStyles({ saveButton: { marginTop: '10px', }, - apiLoader : { + apiLoader: { display: 'flex', justifyContent: 'center', alignItems: 'center', @@ -102,21 +86,14 @@ const useStyles = makeStyles({ } }) -const ContentDevelopers: React.FC = ({ - isPanelOpen, - togglePanel, - isRightPanelOpen, - toggleRightPanel, -}) => { - const { commandKey } = useContentHooks({ togglePanel, toggleRightPanel }); +const PanelCenter: React.FC = () => { const styles = useStyles(); const dispatch = useDispatch(); const [comment, setComment] = React.useState(""); - //const [selectedProcessId, setSelectedProcessId] = React.useState(null); const [selectedTab, setSelectedTab] = React.useState("extracted-results"); - const [ApiLoader ,setApiLoader] = useState(false); - const status = ['extract','processing','map','evaluate']; + const [ApiLoader, setApiLoader] = useState(false); + const status = ['extract', 'processing', 'map', 'evaluate']; const store = useSelector((state: RootState) => ({ processId: state.leftPanel.processId, @@ -125,13 +102,12 @@ const ContentDevelopers: React.FC = ({ modified_result: state.centerPanel.modified_result, isSavingInProgress: state.centerPanel.isSavingInProgress, processStepsData: state.centerPanel.processStepsData, - selectedItem : state.leftPanel.selectedItem, - activeProcessId : state.centerPanel.activeProcessId, + selectedItem: state.leftPanel.selectedItem, + activeProcessId: state.centerPanel.activeProcessId, }), shallowEqual ); useEffect(() => { - //setSelectedProcessId(store.processId); dispatch(setActiveProcessId(store.processId)) setComment(''); }, [store.processId]) @@ -142,39 +118,24 @@ const ContentDevelopers: React.FC = ({ useEffect(() => { - const fetchContent = async() =>{ + const fetchContent = async () => { try { setApiLoader(true); await Promise.allSettled([ - dispatch(fetchContentJsonData({ processId: store.activeProcessId })), - dispatch(fetchProcessSteps({ processId: store.activeProcessId })) - ]); + dispatch(fetchContentJsonData({ processId: store.activeProcessId })), + dispatch(fetchProcessSteps({ processId: store.activeProcessId })) + ]); } catch (error) { console.error("Error fetching data:", error); } finally { setApiLoader(false); } - } - if ((store.activeProcessId != null || store.activeProcessId != '') && !status.includes(store.selectedItem.status) && store.selectedItem?.process_id === store.activeProcessId ) { + } + if ((store.activeProcessId != null || store.activeProcessId != '') && !status.includes(store.selectedItem.status) && store.selectedItem?.process_id === store.activeProcessId) { fetchContent(); } }, [store.activeProcessId, store.selectedItem]) - const renderProcessTimeInSeconds = (timeString: string) => { - if (!timeString) { - return timeString; - } - - const parts = timeString.split(":"); - if (parts.length !== 3) { - return timeString; - } - - const [hours, minutes, seconds] = parts.map(Number); - const totalSeconds = (hours * 3600 + minutes * 60 + seconds).toFixed(2); - - return `${totalSeconds}s`; - }; const ExtractedResults = React.useCallback(() => (
@@ -182,45 +143,16 @@ const ContentDevelopers: React.FC = ({ - ) :

No data available

} + ) :

No data available

}
- ), [store.activeProcessId,store.selectedItem,store.contentData]); + ), [store.activeProcessId, store.selectedItem, store.contentData]); const ProcessHistory = useCallback(() => (
- - {!status.includes(store.selectedItem.status) && store.processStepsData?.map((step, index) => ( - - - {step.step_name} - - {renderProcessTimeInSeconds(step.processed_time)} - - - - - - - - ))} - - - {ApiLoader ?

Loading...

- : (store.processStepsData?.length == 0 || status.includes(store.selectedItem.status)) &&

No data available

} + {ApiLoader ?

Loading...

+ : (store.processStepsData?.length == 0 || status.includes(store.selectedItem.status)) ?

No data available

+ : + }
), [store.processStepsData, store.activeProcessId, styles.tabItemCotnent, ApiLoader]); @@ -232,19 +164,20 @@ const ContentDevelopers: React.FC = ({ try { dispatch(startLoader("1")); dispatch(setUpdateComments(comment)) - await dispatch(saveContentJson({ 'processId': store.activeProcessId, 'contentJson': store.modified_result.extracted_result, 'comments': comment , 'savedComments': store.comments })) + await dispatch(saveContentJson({ 'processId': store.activeProcessId, 'contentJson': store.modified_result.extracted_result, 'comments': comment, 'savedComments': store.comments })) } catch (error) { console.error('API Error:', error); } finally { + dispatch(setRefreshGrid(true)); dispatch(stopLoader("1")); } } const IsButtonSaveDisalbedCheck = () => { - if(status.includes(store.selectedItem.status)) return true; + if (status.includes(store.selectedItem.status)) return true; if (Object.keys(store.modified_result).length > 0) return false; if (comment.trim() !== store.comments && comment.trim() !== '') return false; - if (store.comments !=='' && comment.trim() === '') return false; + if (store.comments !== '' && comment.trim() === '') return false; return true; } @@ -253,9 +186,6 @@ const ContentDevelopers: React.FC = ({
- {/*
- -
*/} Extracted Results Process Steps @@ -274,8 +204,8 @@ const ContentDevelopers: React.FC = ({