diff --git a/CHANGELOG.md b/CHANGELOG.md index 388fc11e..0a8328bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Option to filter by submission's name in Home page #685 - New navigation bar and new home page #669 #### Added diff --git a/cypress/integration/home.spec.ts b/cypress/integration/home.spec.ts index 3a9355b4..f379702f 100644 --- a/cypress/integration/home.spec.ts +++ b/cypress/integration/home.spec.ts @@ -4,7 +4,8 @@ describe("Home e2e", function () { cy.login() }) - it("show Overview submissions in Home page, create a draft folder, navigate to see folder details, delete object inside folder, navigate back to Overview submissions", () => { + it("show draft submission data table in Home page, be able to edit and delete a draft submission inside the table", () => { + cy.intercept("/folders*").as("fetchFolders") // Create a new Unpublished folder cy.get("button").contains("Create submission").click() @@ -35,29 +36,36 @@ describe("Home e2e", function () { // Save folder and navigate to Home page cy.get("button[type=button]").contains("Save and Exit").click() cy.get('button[aria-label="Save a new folder and move to frontpage"]').contains("Return to homepage").click() + cy.wait("@fetchFolders", { timeout: 10000 }) + cy.contains("My submissions").should("be.visible") cy.get("[data-field='name']").eq(1).should("have.text", "Test unpublished folder") - cy.get("[data-testid='edit-draft-submission']").click() - cy.get("[data-testid='folderName']", { timeout: 1000 }).clear().type("Edited unpublished folder") + cy.get("[data-testid='edit-draft-submission']").scrollIntoView().should("be.visible") + cy.contains("Edit").click({ force: true }) + + cy.get("[data-testid='folderName']", { timeout: 10000 }).clear().type("Edited unpublished folder") cy.get("button[type=button]").contains("Next").click() cy.get("[data-testid='wizard-objects']", { timeout: 10000 }).should("be.visible") cy.get("button[type=button]").contains("Save and Exit").click() cy.get('button[aria-label="Save a new folder and move to frontpage"]').contains("Return to homepage").click() + cy.wait("@fetchFolders", { timeout: 10000 }) // Check that submission's name has been edited - cy.contains("My submissions", { timeout: 1000 }).should("be.visible") + cy.contains("My submissions").should("be.visible").click() cy.contains("Edited unpublished folder").should("be.visible") // Delete the submission and it shouldn't be there anymore - cy.get("[data-testid='delete-draft-submission']").click() + cy.get("[data-testid='delete-draft-submission']").scrollIntoView().should("be.visible") + cy.contains("Delete").click({ force: true }) + cy.contains(".MuiAlert-message", "The submission has been deleted successfully!", { timeout: 1000, }) - cy.contains("Currently there are no draft submissions").should("be.visible") + cy.contains("Currently there are no submissions").should("be.visible") }) - it("create a published folder, navigate to see folder details, delete object inside folder, navigate back to Overview submissions", () => { + it("create a published submission, not be able to edit or delete that submission inside the data table", () => { // Create a new Published folder cy.get("button").contains("Create submission").click() diff --git a/cypress/integration/pagination.spec.ts b/cypress/integration/pagination.spec.ts index 806f2a7c..3f0c7ae0 100644 --- a/cypress/integration/pagination.spec.ts +++ b/cypress/integration/pagination.spec.ts @@ -175,7 +175,7 @@ describe("unpublished folders, published folders, and user's draft templates pag url: "/folders?page=2&per_page=5&published=false", }, unpublishedFoldersResponsePage2 - ) + ).as("unpublishedPage2") // Mock response for Published Folders const publishedFoldersResponse5 = { @@ -197,24 +197,35 @@ describe("unpublished folders, published folders, and user's draft templates pag publishedFoldersResponse5 ) cy.login() + cy.get("[data-testid='table-pagination']", { timeout: 10000 }).should("be.visible") - cy.get("[data-testid='page info']").contains("1 of 20 pages").should("be.visible") - + cy.get("[data-testid='page info']", { timeout: 10000 }).contains("1 of 20 pages").should("be.visible") // Click Next page button - cy.get("[data-testid='NavigateNextIcon']").click() - cy.wait(500) - cy.get("p").contains("6-10 of 100", { timeout: 10000 }).should("be.visible") - cy.get("[data-testid='page info']", { timeout: 10000 }).contains("2 of 20 pages").should("be.visible") + cy.get("[data-testid='NavigateNextIcon']") + .parent() + .should("be.visible") + .then($el => $el.click()) + + cy.wait("@unpublishedPage2") + cy.get("div[aria-haspopup='listbox']").contains(5, { timeout: 10000 }).as("button") + cy.contains("6-10 of 100", { timeout: 10000 }).should("be.visible") + cy.get("[data-testid='page info']").contains("2 of 20 pages", { timeout: 10000 }).should("be.visible") // Check "Items per page" options - cy.get("div[aria-haspopup='listbox']", { timeout: 10000 }).contains(5).click() - cy.get("li[data-value='5']").should("be.visible") + cy.wait(0) + cy.get("@button") + .then($el => { + expect(Cypress.dom.isAttached($el).valueOf()).to.be.true + }) + .click() + + cy.get("li[data-value='5']", { timeout: 10000 }).should("be.visible") cy.get("li[data-value='15']").should("be.visible") cy.get("li[data-value='25']").should("be.visible") cy.get("li[data-value='50']").should("be.visible") // Select 15 items - cy.get("li[data-value='15']").click() + cy.get("li[data-value='15']").click({ force: true }) cy.get("p", { timeout: 10000 }).contains("1-15 of 100").should("be.visible") cy.get("[data-testid='page info']").contains("1 of 7 pages").should("be.visible") @@ -231,7 +242,7 @@ describe("unpublished folders, published folders, and user's draft templates pag cy.get("[data-testid='page info']").contains("1 of 2 pages").should("be.visible") }) - it("should renders pagination for unpublished folders list correctly", () => { + it("should render pagination for unpublished folders list correctly", () => { // Mock responses for Published Folders const publishedFoldersResponse5 = { folders: [ @@ -354,7 +365,7 @@ describe("unpublished folders, published folders, and user's draft templates pag url: "/folders?page=2&per_page=5&published=true", }, publishedFoldersResponsePage2 - ) + ).as("publishedPage2") cy.intercept( { @@ -371,11 +382,15 @@ describe("unpublished folders, published folders, and user's draft templates pag cy.get("[data-testid='page info']").should("be.visible") cy.get("[data-testid='page info']").contains("1 of 30 pages").should("be.visible") // Click Next page button - cy.get("[data-testid='NavigateNextIcon']").click() - cy.get("p", { timeout: 10000 }).contains("6-10 of 150").should("be.visible") + cy.get("[data-testid='NavigateNextIcon']") + .parent() + .should("be.visible") + .then($el => $el.click()) + cy.contains("6-10 of 150", { timeout: 10000 }).should("be.visible") cy.get("[data-testid='page info']").contains("2 of 30 pages").should("be.visible") - + cy.wait("@publishedPage2") cy.get("button[aria-label='Go to page 3']").click() + cy.get("[data-testid='page info']").contains("3 of 30 pages").should("be.visible") // Check "Items per page" options @@ -389,7 +404,244 @@ describe("unpublished folders, published folders, and user's draft templates pag cy.get("li[data-value='50']").click() cy.get("p", { timeout: 10000 }).contains("1-50 of 150").should("be.visible") cy.get("[data-testid='page info']").contains("1 of 3 pages").should("be.visible") - }) + }), + it("should render pagination correctly when filtering the submission name", () => { + const unpublishedPage1 = { + folders: [ + { + descriptioni: "d", + drafts: [], + folderId: "UNPUB1", + metadataObjects: [], + name: "draft-folder1", + published: false, + }, + { descriptioni: "d", drafts: [], folderId: "UNPUB2", metadataObjects: [], name: "test", published: false }, + { descriptioni: "d", drafts: [], folderId: "UNPUB3", metadataObjects: [], name: "Biology", published: false }, + { descriptioni: "d", drafts: [], folderId: "UNPUB4", metadataObjects: [], name: "test2", published: false }, + { descriptioni: "d", drafts: [], folderId: "UNPUB5", metadataObjects: [], name: "Unpub5", published: false }, + ], + page: { page: 1, size: 5, totalPages: 6, totalFolders: 30 }, + } + + const unpublishedPage2 = { + folders: [ + { descriptioni: "d", drafts: [], folderId: "UNPUB6", metadataObjects: [], name: "Unpub6", published: false }, + { descriptioni: "d", drafts: [], folderId: "UNPUB7", metadataObjects: [], name: "Unpub7", published: false }, + { descriptioni: "d", drafts: [], folderId: "UNPUB8", metadataObjects: [], name: "Unpub8", published: false }, + { descriptioni: "d", drafts: [], folderId: "UNPUB9", metadataObjects: [], name: "Unpub9", published: false }, + { + descriptioni: "d", + drafts: [], + folderId: "UNPUB10", + metadataObjects: [], + name: "Unpub10", + published: false, + }, + { descriptioni: "d", drafts: [], folderId: "UNPUB1", metadataObjects: [], name: "Unpub1", published: false }, + ], + page: { page: 2, size: 5, totalPages: 6, totalFolders: 30 }, + } + + const unpublished15 = { + folders: [ + { + descriptioni: "d", + drafts: [], + folderId: "UNPUB1", + metadataObjects: [], + name: "draft-folder1", + published: false, + }, + { descriptioni: "d", drafts: [], folderId: "UNPUB2", metadataObjects: [], name: "test", published: false }, + { descriptioni: "d", drafts: [], folderId: "UNPUB3", metadataObjects: [], name: "Biology", published: false }, + { descriptioni: "d", drafts: [], folderId: "UNPUB4", metadataObjects: [], name: "test2", published: false }, + { descriptioni: "d", drafts: [], folderId: "UNPUB5", metadataObjects: [], name: "Unpub5", published: false }, + { descriptioni: "d", drafts: [], folderId: "UNPUB6", metadataObjects: [], name: "Unpub6", published: false }, + { descriptioni: "d", drafts: [], folderId: "UNPUB7", metadataObjects: [], name: "Unpub7", published: false }, + { descriptioni: "d", drafts: [], folderId: "UNPUB8", metadataObjects: [], name: "Unpub8", published: false }, + { descriptioni: "d", drafts: [], folderId: "UNPUB9", metadataObjects: [], name: "Unpub9", published: false }, + { + descriptioni: "d", + drafts: [], + folderId: "UNPUB10", + metadataObjects: [], + name: "Unpub10", + published: false, + }, + { descriptioni: "d", drafts: [], folderId: "UNPUB1", metadataObjects: [], name: "Unpub1", published: false }, + { descriptioni: "d", drafts: [], folderId: "UNPUB2", metadataObjects: [], name: "Unpub2", published: false }, + { descriptioni: "d", drafts: [], folderId: "UNPUB3", metadataObjects: [], name: "Unpub3", published: false }, + { descriptioni: "d", drafts: [], folderId: "UNPUB4", metadataObjects: [], name: "Unpub4", published: false }, + { descriptioni: "d", drafts: [], folderId: "UNPUB5", metadataObjects: [], name: "Unpub5", published: false }, + { descriptioni: "d", drafts: [], folderId: "UNPUB6", metadataObjects: [], name: "test3", published: false }, + ], + page: { page: 1, size: 15, totalPages: 2, totalFolders: 30 }, + } + + const allUnpublished = { + folders: [ + { + descriptioni: "d", + drafts: [], + folderId: "UNPUB1", + metadataObjects: [], + name: "draft-folder1", + published: false, + }, + { descriptioni: "d", drafts: [], folderId: "UNPUB2", metadataObjects: [], name: "test", published: false }, + { descriptioni: "d", drafts: [], folderId: "UNPUB3", metadataObjects: [], name: "Biology", published: false }, + { descriptioni: "d", drafts: [], folderId: "UNPUB4", metadataObjects: [], name: "test2", published: false }, + { descriptioni: "d", drafts: [], folderId: "UNPUB5", metadataObjects: [], name: "Unpub5", published: false }, + { descriptioni: "d", drafts: [], folderId: "UNPUB6", metadataObjects: [], name: "Unpub6", published: false }, + { descriptioni: "d", drafts: [], folderId: "UNPUB7", metadataObjects: [], name: "Unpub7", published: false }, + { descriptioni: "d", drafts: [], folderId: "UNPUB8", metadataObjects: [], name: "Unpub8", published: false }, + { descriptioni: "d", drafts: [], folderId: "UNPUB9", metadataObjects: [], name: "Unpub9", published: false }, + { + descriptioni: "d", + drafts: [], + folderId: "UNPUB10", + metadataObjects: [], + name: "Unpub10", + published: false, + }, + { descriptioni: "d", drafts: [], folderId: "UNPUB1", metadataObjects: [], name: "Unpub1", published: false }, + { descriptioni: "d", drafts: [], folderId: "UNPUB2", metadataObjects: [], name: "Unpub2", published: false }, + { descriptioni: "d", drafts: [], folderId: "UNPUB3", metadataObjects: [], name: "Unpub3", published: false }, + { descriptioni: "d", drafts: [], folderId: "UNPUB4", metadataObjects: [], name: "Unpub4", published: false }, + { descriptioni: "d", drafts: [], folderId: "UNPUB5", metadataObjects: [], name: "Unpub5", published: false }, + { descriptioni: "d", drafts: [], folderId: "UNPUB6", metadataObjects: [], name: "test3", published: false }, + { descriptioni: "d", drafts: [], folderId: "UNPUB7", metadataObjects: [], name: "test4", published: false }, + { descriptioni: "d", drafts: [], folderId: "UNPUB8", metadataObjects: [], name: "test5", published: false }, + { descriptioni: "d", drafts: [], folderId: "UNPUB9", metadataObjects: [], name: "test6", published: false }, + { + descriptioni: "d", + drafts: [], + folderId: "UNPUB10", + metadataObjects: [], + name: "Unpub10", + published: false, + }, + { descriptioni: "d", drafts: [], folderId: "UNPUB1", metadataObjects: [], name: "test7", published: false }, + { descriptioni: "d", drafts: [], folderId: "UNPUB2", metadataObjects: [], name: "test8", published: false }, + { descriptioni: "d", drafts: [], folderId: "UNPUB3", metadataObjects: [], name: "test9", published: false }, + { descriptioni: "d", drafts: [], folderId: "UNPUB4", metadataObjects: [], name: "test10", published: false }, + { + descriptioni: "d", + drafts: [], + folderId: "UNPUB5", + metadataObjects: [], + name: "this-draft", + published: false, + }, + { + descriptioni: "d", + drafts: [], + folderId: "UNPUB6", + metadataObjects: [], + name: "draft-folder2", + published: false, + }, + { + descriptioni: "d", + drafts: [], + folderId: "UNPUB7", + metadataObjects: [], + name: "draft-folder3", + published: false, + }, + { descriptioni: "d", drafts: [], folderId: "UNPUB8", metadataObjects: [], name: "abc", published: false }, + { descriptioni: "d", drafts: [], folderId: "UNPUB9", metadataObjects: [], name: "Unpub9", published: false }, + { + descriptioni: "d", + drafts: [], + folderId: "UNPUB10", + metadataObjects: [], + name: "Unpub10", + published: false, + }, + ], + page: { page: 1, size: 30, totalPages: 1, totalFolders: 30 }, + } + + cy.intercept( + { + method: "GET", + url: "/folders?page=1&per_page=5&published=false", + }, + unpublishedPage1 + ) + + cy.intercept( + { + method: "GET", + url: "/folders?page=2&per_page=5&published=false", + }, + unpublishedPage2 + ) + + cy.intercept( + { + method: "GET", + url: "/folders?page=1&per_page=30&published=false", + }, + allUnpublished + ) + + cy.intercept("DELETE", "/folders/UNPUB9", { + statusCode: 200, + }).as("deleteSubmission") + + cy.intercept( + { + method: "GET", + url: "/folders?page=1&per_page=15&published=false", + }, + unpublished15 + ).as("unpublished15") + + cy.login() + cy.get("[data-testid='page info']").contains("1 of 6 pages").should("be.visible") + + // Type a filtering text and check if the filtered list renders correctly with pagination + cy.get("input[data-testid='wizard-search-box']").type("test") + cy.contains("1-5 of 10", { timeout: 10000 }).should("be.visible") + cy.get("[data-testid='page info']").contains("1 of 2 pages").should("be.visible") + cy.get("button[aria-label='Go to page 2']").click() + cy.contains("test6", { timeout: 10000 }).should("be.visible") + + // Delete a submission and check that it is deleted from filtered list and the current page stays the same as page 2 + cy.get("[data-testid='delete-draft-submission']").eq(0).click() + cy.wait("@deleteSubmission", { timeout: 6000 }) + cy.contains("test6", { timeout: 10000 }).should("not.exist") + cy.get("button[aria-label='page 2']", { timeout: 10000 }).should("be.visible") + + // Select "Items per page" to check all filtered items exist + cy.wait(0) + cy.get("div[role='button']") + .contains(5, { timeout: 10000 }) + .then($el => { + expect(Cypress.dom.isAttached($el).valueOf()).to.be.true + }) + .click() + + cy.get("li[data-value='15']").click() + cy.wait("@unpublished15") + cy.contains("1-9 of 9").should("be.visible") + + // Clear the filter and check that the pagination is back to normal to render all submissions + cy.get("div[role='button']", { timeout: 10000 }).contains(15).click() + cy.get("li[data-value='5']").click() + cy.get("svg[data-testid='ClearIcon']").click() + cy.get("input[data-testid='wizard-search-box']").should("have.value", "") + + cy.contains("1-5 of 30", { timeout: 10000 }).should("be.visible") + cy.get("[data-testid='page info']").contains("1 of 6 pages").should("be.visible") + + // If the filtering text is not found, the table renders no pagination + cy.get("input[data-testid='wizard-search-box']").type("123456") + cy.get("[data-testid='table-pagination']").should("not.exist") + }) }) export {} diff --git a/src/__tests__/Home.test.tsx b/src/__tests__/Home.test.tsx index 72854987..fabaa273 100644 --- a/src/__tests__/Home.test.tsx +++ b/src/__tests__/Home.test.tsx @@ -22,54 +22,6 @@ jest.mock("react-i18next", () => ({ describe("HomePage", () => { const store = mockStore({ user: { name: "Test User" }, - unpublishedFolders: [ - { descriptioni: "d", drafts: [], folderId: "UNPUB1", metadataObjects: [], name: "Unpub", published: false }, - { descriptioni: "d", drafts: [], folderId: "UNPUB2", metadataObjects: [], name: "Unpub", published: false }, - { descriptioni: "d", drafts: [], folderId: "UNPUB3", metadataObjects: [], name: "Unpub", published: false }, - { descriptioni: "d", drafts: [], folderId: "UNPUB4", metadataObjects: [], name: "Unpub", published: false }, - { descriptioni: "d", drafts: [], folderId: "UNPUB5", metadataObjects: [], name: "Unpub", published: false }, - ], - publishedFolders: [ - { descriptioni: "d", drafts: [], folderId: "PUB1", metadataObjects: [], name: "Pub", published: false }, - { descriptioni: "d", drafts: [], folderId: "PUB2", metadataObjects: [], name: "Pub", published: false }, - { descriptioni: "d", drafts: [], folderId: "PUB3", metadataObjects: [], name: "Pub", published: false }, - { descriptioni: "d", drafts: [], folderId: "PUB4", metadataObjects: [], name: "Pub", published: false }, - { descriptioni: "d", drafts: [], folderId: "PUB5", metadataObjects: [], name: "Pub", published: false }, - ], - totalFolders: { - totalUnpublishedFolders: [ - { descriptioni: "d", drafts: [], folderId: "UNPUB1", metadataObjects: [], name: "Unpub", published: false }, - { descriptioni: "d", drafts: [], folderId: "UNPUB2", metadataObjects: [], name: "Unpub", published: false }, - { descriptioni: "d", drafts: [], folderId: "UNPUB3", metadataObjects: [], name: "Unpub", published: false }, - { descriptioni: "d", drafts: [], folderId: "UNPUB4", metadataObjects: [], name: "Unpub", published: false }, - { descriptioni: "d", drafts: [], folderId: "UNPUB5", metadataObjects: [], name: "Unpub", published: false }, - { descriptioni: "d", drafts: [], folderId: "UNPUB6", metadataObjects: [], name: "Unpub", published: false }, - { descriptioni: "d", drafts: [], folderId: "UNPUB7", metadataObjects: [], name: "Unpub", published: false }, - { descriptioni: "d", drafts: [], folderId: "UNPUB8", metadataObjects: [], name: "Unpub", published: false }, - { descriptioni: "d", drafts: [], folderId: "UNPUB9", metadataObjects: [], name: "Unpub", published: false }, - { descriptioni: "d", drafts: [], folderId: "UNPUB10", metadataObjects: [], name: "Unpub", published: false }, - ], - totalPublishedFolders: [ - { descriptioni: "d", drafts: [], folderId: "PUB1", metadataObjects: [], name: "Pub", published: false }, - { descriptioni: "d", drafts: [], folderId: "PUB2", metadataObjects: [], name: "Pub", published: false }, - { descriptioni: "d", drafts: [], folderId: "PUB3", metadataObjects: [], name: "Pub", published: false }, - { descriptioni: "d", drafts: [], folderId: "PUB4", metadataObjects: [], name: "Pub", published: false }, - { descriptioni: "d", drafts: [], folderId: "PUB5", metadataObjects: [], name: "Pub", published: false }, - { descriptioni: "d", drafts: [], folderId: "PUB6", metadataObjects: [], name: "Pub", published: false }, - { descriptioni: "d", drafts: [], folderId: "PUB7", metadataObjects: [], name: "Pub", published: false }, - { descriptioni: "d", drafts: [], folderId: "PUB8", metadataObjects: [], name: "Pub", published: false }, - { descriptioni: "d", drafts: [], folderId: "PUB9", metadataObjects: [], name: "Pub", published: false }, - { descriptioni: "d", drafts: [], folderId: "PUB10", metadataObjects: [], name: "Pub", published: false }, - ], - }, - selectedFolder: { - folderId: "Test folderId", - name: "Test name", - description: "Test description", - drafts: [], - metadataObjects: [], - allObjects: [], - }, }) beforeEach(() => { render( @@ -92,9 +44,5 @@ describe("HomePage", () => { expect(screen.getByRole("tab", { name: "Published" })).toBeInTheDocument() expect(screen.getByTestId("link-create-submission")).toBeInTheDocument() - - expect(screen.getByPlaceholderText("Filter by Name")).toBeInTheDocument() - - expect(screen.getByRole("grid")).toBeInTheDocument() }) }) diff --git a/src/components/Home/SubmissionDataTable.tsx b/src/components/Home/SubmissionDataTable.tsx index 2cc81349..3ac61d16 100644 --- a/src/components/Home/SubmissionDataTable.tsx +++ b/src/components/Home/SubmissionDataTable.tsx @@ -3,9 +3,8 @@ import React from "react" import DeleteIcon from "@mui/icons-material/Delete" import EditIcon from "@mui/icons-material/Edit" import MuiCard from "@mui/material/Card" -import MuiCardContent from "@mui/material/CardContent" +import Stack from "@mui/material/Stack" import { styled } from "@mui/material/styles" -import Typography from "@mui/material/Typography" import { DataGrid, GridRowParams, @@ -33,11 +32,6 @@ const Card = styled(MuiCard)(() => ({ padding: 0, })) -const CardContent = styled(MuiCardContent)(() => ({ - flexGrow: 1, - padding: 0, -})) - const DataTable = styled(DataGrid)(({ theme }) => ({ color: theme.palette.secondary.main, "&.MuiDataGrid-root .MuiDataGrid-columnHeader:focus, &.MuiDataGrid-root .MuiDataGrid-cell:focus": { @@ -85,9 +79,9 @@ type SubmissionDataTableProps = { page?: number itemsPerPage?: number totalItems?: number - fetchItemsPerPage?: (items: number, submissionType: string) => Promise - fetchPageOnChange?: (page: number, submissionType: string) => Promise - onDeleteSubmission?: (submissionId: string, submissionType: string) => void + fetchItemsPerPage?: (items: number, folderType: string) => Promise + fetchPageOnChange?: (page: number, folderType: string) => Promise + onDeleteSubmission?: (submissionId: string, folderType: string) => void } const SubmissionDataTable: React.FC = props => { @@ -198,6 +192,12 @@ const SubmissionDataTable: React.FC = props => { /> ) : null + const NoRowsOverlay = () => ( + + No results found. + + ) + // Renders when there is folder list const FolderList = () => (
@@ -213,6 +213,7 @@ const SubmissionDataTable: React.FC = props => { hideFooterSelectedRowCount components={{ Pagination: DataGridPagination, + NoRowsOverlay: NoRowsOverlay, }} sortModel={sortModel} onSortModelChange={(newSortModel: GridSortModel) => setSortModel(newSortModel)} @@ -222,16 +223,11 @@ const SubmissionDataTable: React.FC = props => {
) - // Renders when there is no folders in the list - const EmptyList = () => ( - - - Currently there are no {folderType} submissions - - + return ( + + + ) - - return {rows.length > 0 ? : } } export default SubmissionDataTable diff --git a/src/components/NewDraftWizard/WizardComponents/WizardPagination.tsx b/src/components/NewDraftWizard/WizardComponents/WizardPagination.tsx index 69b23fca..d6225b8d 100644 --- a/src/components/NewDraftWizard/WizardComponents/WizardPagination.tsx +++ b/src/components/NewDraftWizard/WizardComponents/WizardPagination.tsx @@ -162,12 +162,12 @@ type WizardPagination = { const DisplayRows = styled("span")(({ theme }) => ({ display: "flex", - "& hr": { + "& span:first-of-type": { height: "auto", margin: 0, }, - "& span": { - color: theme.palette.secondary, + "& span:last-of-type": { + color: theme.palette.secondary.main, fontSize: "1.4rem", marginLeft: "3.25rem", }, @@ -179,7 +179,7 @@ const WizardPagination: React.FC = props => { const labelDisplayedRows = ({ from, to, count }) => ( - {totalNumberOfItems > 5 && } + {totalNumberOfItems > 5 && } {from}-{to} of {count} items @@ -198,7 +198,7 @@ const WizardPagination: React.FC = props => { } return ( - +
void + filteringText: string + handleChangeFilteringText: (e: React.ChangeEvent) => void + handleClearFilteringText: () => void } const TextField = styled(MuiTextField)(({ theme }) => ({ @@ -28,24 +30,19 @@ const TextField = styled(MuiTextField)(({ theme }) => ({ })) const WizardSearchBox: React.FC = props => { - const { placeholder, handleSearchTextChange } = props - - const [searchText, setSearchText] = React.useState("") + const { placeholder, filteringText, handleChangeFilteringText, handleClearFilteringText } = props const handleOnChange = (e: React.ChangeEvent) => { - const text = e.target.value - setSearchText(text) - handleSearchTextChange(text) + handleChangeFilteringText(e) } - const clearSearchText = () => setSearchText("") - return ( , endAdornment: ( @@ -53,8 +50,8 @@ const WizardSearchBox: React.FC = props => { title="Clear" aria-label="Clear" size="medium" - sx={{ visibility: searchText ? "visible" : "hidden" }} - onClick={clearSearchText} + sx={{ visibility: filteringText ? "visible" : "hidden" }} + onClick={handleClearFilteringText} > diff --git a/src/features/publishedFoldersSlice.tsx b/src/features/publishedFoldersSlice.tsx deleted file mode 100644 index a2109603..00000000 --- a/src/features/publishedFoldersSlice.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { createSlice } from "@reduxjs/toolkit" - -import type { FolderDetailsWithId } from "types" - -const initialState: FolderDetailsWithId[] = [] - -const publishedFoldersSlice = createSlice({ - name: "publishedFolders", - initialState, - reducers: { - setPublishedFolders: (_state, action) => action.payload, - resetPublishedFolders: () => initialState, - }, -}) - -export const { setPublishedFolders, resetPublishedFolders } = publishedFoldersSlice.actions -export default publishedFoldersSlice.reducer diff --git a/src/features/unpublishedFoldersSlice.tsx b/src/features/unpublishedFoldersSlice.tsx deleted file mode 100644 index 9986d959..00000000 --- a/src/features/unpublishedFoldersSlice.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { createSlice } from "@reduxjs/toolkit" - -import { ObjectStatus } from "constants/wizardObject" -import type { DispatchReducer, FolderDetailsWithId } from "types" - -const initialState: FolderDetailsWithId[] = [] - -const unpublishedFoldersSlice = createSlice({ - name: "unpublishedFolders", - initialState, - reducers: { - setUnpublishedFolders: (_state, action) => action.payload, - resetUnpublishedFolders: () => initialState, - updateUnpublishedFolders: (state, action) => { - if (state) - (state as FolderDetailsWithId[]).map((folder: { folderId: string }) => - folder.folderId === action.payload.folderId ? action.payload : folder - ) - }, - }, -}) - -export const { setUnpublishedFolders, resetUnpublishedFolders, updateUnpublishedFolders } = - unpublishedFoldersSlice.actions -export default unpublishedFoldersSlice.reducer - -// Remove folder from Unpublished folders when it is deleted -export const deleteFolderFromUnpublishedFolders = - (selectedFolder: FolderDetailsWithId, objectId: string, objectStatus: string) => - (dispatch: (reducer: DispatchReducer) => void) => { - const updatedFolder = - objectStatus === ObjectStatus.draft - ? { - ...selectedFolder, - drafts: selectedFolder.drafts.filter(draft => draft.accessionId !== objectId), - } - : { - ...selectedFolder, - metadataObjects: selectedFolder.metadataObjects.filter(obj => obj.accessionId !== objectId), - } - delete updatedFolder.allObjects - - dispatch(updateUnpublishedFolders(updatedFolder)) - } diff --git a/src/rootReducer.ts b/src/rootReducer.ts index 72454d94..715aa17a 100644 --- a/src/rootReducer.ts +++ b/src/rootReducer.ts @@ -10,11 +10,9 @@ import localeReducer from "features/localeSlice" import objectTypesArrayReducer from "features/objectTypesArraySlice" import openedDoiFormReducer from "features/openedDoiFormSlice" import openedRowsReducer from "features/openedRowsSlice" -import publishedFoldersReducer from "features/publishedFoldersSlice" import selectedFolderReducer from "features/selectedFolderSlice" import statusMessageReducer from "features/statusMessageSlice" import templatesReducer from "features/templatesSlice" -import unpublishedFoldersReducer from "features/unpublishedFoldersSlice" import userReducer from "features/userSlice" import wizardAlertReducer from "features/wizardAlertSlice" import currentObjectReducer from "features/wizardCurrentObjectSlice" @@ -34,8 +32,6 @@ const rootReducer = combineReducers({ draftStatus: draftStatusReducer, currentObject: currentObjectReducer, user: userReducer, - unpublishedFolders: unpublishedFoldersReducer, - publishedFolders: publishedFoldersReducer, selectedFolder: selectedFolderReducer, objectTypesArray: objectTypesArrayReducer, openedRows: openedRowsReducer, diff --git a/src/types/index.ts b/src/types/index.ts index c24357d2..6971b3cc 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -38,10 +38,6 @@ export type ObjectDetails = { export type OldFolderRow = ObjectDetails & { objectData: Record; folderType?: string } -export type FolderId = { - folderId: string -} - export type ObjectInsideFolder = { accessionId: string schema: string @@ -57,6 +53,10 @@ export type ObjectInsideFolderWithTags = ObjectInsideFolder & { tags: ObjectTags export type ObjectInsideFolderWithTagsBySchema = { [schema: string]: ObjectInsideFolderWithTags[] } +export type FolderId = { + folderId: string +} + export type FolderDetails = { name: string description: string diff --git a/src/views/Home.tsx b/src/views/Home.tsx index 627eee29..871342ae 100644 --- a/src/views/Home.tsx +++ b/src/views/Home.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from "react" +import React, { useEffect, useState, useMemo } from "react" import Box from "@mui/material/Box" import Button from "@mui/material/Button" @@ -11,19 +11,19 @@ import { styled } from "@mui/material/styles" import Tab from "@mui/material/Tab" import Tabs from "@mui/material/Tabs" import Typography from "@mui/material/Typography" +import { debounce } from "lodash" import { Link as RouterLink } from "react-router-dom" import SubmissionDataTable from "components/Home/SubmissionDataTable" import WizardSearchBox from "components/NewDraftWizard/WizardComponents/WizardSearchBox" import { ResponseStatus } from "constants/responseStatus" import { FolderSubmissionStatus } from "constants/wizardFolder" -import { setPublishedFolders } from "features/publishedFoldersSlice" import { updateStatus } from "features/statusMessageSlice" -import { setUnpublishedFolders } from "features/unpublishedFoldersSlice" import { resetObjectType } from "features/wizardObjectTypeSlice" import { deleteFolderAndContent, resetFolder } from "features/wizardSubmissionFolderSlice" -import { useAppSelector, useAppDispatch } from "hooks" +import { useAppDispatch } from "hooks" import folderAPIService from "services/folderAPI" +import type { FolderDetailsWithId, FolderRow } from "types" import { pathWithLocale } from "utils" const FrontPageContainer = styled(Container)(() => ({ @@ -55,35 +55,50 @@ const CreateSubmissionButton = styled(Button)(() => ({ bottom: "2rem", })) +const getDisplayRows = (items: Array): Array => { + return items.map(item => ({ + id: item.folderId, + name: item.name, + dateCreated: item.dateCreated, + lastModifiedBy: "TBA", + cscProject: "TBA", + })) +} + const Home: React.FC = () => { const dispatch = useAppDispatch() - const unpublishedFolders = useAppSelector(state => state.unpublishedFolders) - const publishedFolders = useAppSelector(state => state.publishedFolders) - const [isFetchingFolders, setFetchingFolders] = useState(true) + const [isFetchingFolders, setFetchingFolders] = useState(true) - const [totalDraftSubmissions, setTotalDraftSubmissions] = useState(0) - const [totalPublishedSubmissions, setTotalPublishedSubmissions] = useState(0) + // Selected tab value + const [tabValue, setTabValue] = useState(FolderSubmissionStatus.unpublished) - const [draftPage, setDraftPage] = useState(0) - const [draftItemsPerPage, setDraftItemsPerPage] = useState(5) + // Current submissions to be displayed in the data table + const [displaySubmissions, setDisplaySubmissions] = useState | []>([]) - const [publishedPage, setPublishedPage] = useState(0) - const [publishedItemsPerPage, setPublishedItemsPerPage] = useState(5) + // List of all draft and published submissions depending on the selected tab + const [allDraftSubmissions, setAllDraftSubmissions] = useState | []>([]) + const [allPublishedSubmissions, setAllPublishedSubmissions] = useState | []>([]) - const [tabValue, setTabValue] = React.useState(FolderSubmissionStatus.unpublished) + // Filtered submission rows based on filtering text + const [filteringText, setFilteringText] = useState("") + const [filteredSubmissions, setFilteredSubmissions] = useState | []>([]) - // Show folders based on tabValue and convert folders to folderRows for rendering data grid - const displayFolders = tabValue === FolderSubmissionStatus.unpublished ? unpublishedFolders : publishedFolders + // Current page of draft submission table + const [draftPage, setDraftPage] = useState(0) + const [draftItemsPerPage, setDraftItemsPerPage] = useState(5) - const folderRows = displayFolders.map(item => ({ - id: item.folderId, - name: item.name, - dateCreated: item.dateCreated, - lastModifiedBy: "TBA", - cscProject: "TBA", - })) + // Current page of published submission table + const [publishedPage, setPublishedPage] = useState(0) + const [publishedItemsPerPage, setPublishedItemsPerPage] = useState(5) + + // Total number of draft and published submissions + const [numberOfDraftSubmissions, setNumberOfDraftSubmissions] = useState(0) + const [numberOfPublishedSubmissions, setNumberOfPublishedSubmissions] = useState(0) + /* + * Get draft and published submissions for the first page in data table + */ useEffect(() => { let isMounted = true const getFolders = async () => { @@ -97,11 +112,17 @@ const Home: React.FC = () => { if (isMounted) { if (unpublishedResponse.ok && publishedResponse.ok) { - dispatch(setUnpublishedFolders(unpublishedResponse.data?.folders)) - dispatch(setPublishedFolders(publishedResponse.data.folders)) + // Set submissions to be displayed based on tabValue + const displaySubmissions = + tabValue === FolderSubmissionStatus.unpublished + ? unpublishedResponse.data?.folders + : publishedResponse.data?.folders - setTotalDraftSubmissions(unpublishedResponse.data.page?.totalFolders) - setTotalPublishedSubmissions(publishedResponse.data.page?.totalFolders) + setDisplaySubmissions(displaySubmissions) + + // Set total number of submissions + setNumberOfDraftSubmissions(unpublishedResponse.data.page?.totalFolders) + setNumberOfPublishedSubmissions(publishedResponse.data.page?.totalFolders) setFetchingFolders(false) } else { @@ -119,7 +140,62 @@ const Home: React.FC = () => { return () => { isMounted = false } - }, [dispatch]) + }, [dispatch, tabValue]) + + /* + * Get the list of all draft submissions + */ + useEffect(() => { + let isMounted = true + if (isMounted) { + const getAllDraftSubmisisons = async () => { + if (numberOfDraftSubmissions > 0) { + const unpublishedResponse = await folderAPIService.getFolders({ + page: 1, + per_page: numberOfDraftSubmissions, + published: false, + }) + setAllDraftSubmissions(unpublishedResponse.data?.folders) + } + } + getAllDraftSubmisisons() + } + return () => { + isMounted = false + } + }, [numberOfDraftSubmissions]) + + /* + * Get the list of all published submissions + */ + useEffect(() => { + let isMounted = true + if (isMounted) { + const getAllPublishedSubmisisons = async () => { + if (numberOfPublishedSubmissions > 0) { + const publishedResponse = await folderAPIService.getFolders({ + page: 1, + per_page: numberOfPublishedSubmissions, + published: true, + }) + setAllPublishedSubmissions(publishedResponse.data?.folders) + } + } + getAllPublishedSubmisisons() + } + return () => { + isMounted = false + } + }, [numberOfPublishedSubmissions]) + + /* + * Cancel the debouncedChangeFilteringText function when the component unmounts + */ + useEffect(() => { + return () => { + debouncedChangeFilteringText.cancel() + } + }, [allDraftSubmissions, allPublishedSubmissions, tabValue]) const resetWizard = () => { dispatch(resetObjectType()) @@ -128,24 +204,52 @@ const Home: React.FC = () => { const handleChangeTab = (event, newValue) => { setTabValue(newValue) + if (filteringText) getFilter(filteringText, newValue) + } + + const getCurrentRows = (): Array => { + const displaySubmissionRows = getDisplayRows(displaySubmissions) + const filteredSubmissionRows = getDisplayRows(filteredSubmissions) + // Show filteredRows based on tabValue + const displayFilteredSubmissionRows = + tabValue === FolderSubmissionStatus.unpublished + ? filteredSubmissionRows.slice(draftPage * draftItemsPerPage, draftPage * draftItemsPerPage + draftItemsPerPage) + : filteredSubmissionRows.slice( + publishedPage * publishedItemsPerPage, + publishedPage * publishedItemsPerPage + publishedItemsPerPage + ) + return filteringText ? displayFilteredSubmissionRows : displaySubmissionRows } - // Fire when user selects an option from "Items per page" + const getCurrentTotalItems = () => { + if (filteringText) return filteredSubmissions.length + else { + return tabValue === FolderSubmissionStatus.unpublished ? numberOfDraftSubmissions : numberOfPublishedSubmissions + } + } + + /* + * Fire when user selects an option from "Items per page" + */ const handleFetchItemsPerPage = async (numberOfItems: number, folderType: string) => { const response = await folderAPIService.getFolders({ page: 1, per_page: numberOfItems, - published: folderType === FolderSubmissionStatus.unpublished ? false : true, + published: folderType === FolderSubmissionStatus.published, }) - folderType === FolderSubmissionStatus.unpublished ? setDraftPage(0) : setPublishedPage(0) if (response.ok) { - folderType === FolderSubmissionStatus.unpublished - ? dispatch(setUnpublishedFolders(response.data.folders)) - : dispatch(setPublishedFolders(response.data.folders)) - folderType === FolderSubmissionStatus.unpublished - ? setDraftItemsPerPage(numberOfItems) - : setPublishedItemsPerPage(numberOfItems) + // Set new display submissions + const displaySubmissions = response.data?.folders + setDisplaySubmissions(displaySubmissions) + + if (folderType === FolderSubmissionStatus.unpublished) { + setDraftPage(0) + setDraftItemsPerPage(numberOfItems) + } else { + setPublishedPage(0) + setPublishedItemsPerPage(numberOfItems) + } } else { dispatch( updateStatus({ @@ -157,23 +261,32 @@ const Home: React.FC = () => { } } - // Fire when user selects a page - // or selects previous/next arrows - // or deletes a submission - const handleFetchPageOnChange = async (page: number, folderType: string) => { + /* + * Fire when user selects a page + * or selects previous/next arrows + * or deletes a submission + */ + const handleFetchPageOnChange = async (page: number, folderType: string, isDeletingSubmission?: boolean) => { + folderType === FolderSubmissionStatus.unpublished ? setDraftPage(page) : setPublishedPage(page) + + // Fetch new page const response = await folderAPIService.getFolders({ page: page + 1, per_page: folderType === FolderSubmissionStatus.unpublished ? draftItemsPerPage : publishedItemsPerPage, - published: folderType === FolderSubmissionStatus.unpublished ? false : true, + published: folderType === FolderSubmissionStatus.published, }) + if (response.ok) { - folderType === FolderSubmissionStatus.unpublished - ? dispatch(setUnpublishedFolders(response.data.folders)) - : dispatch(setPublishedFolders(response.data.folders)) - folderType === FolderSubmissionStatus.unpublished ? setDraftPage(page) : setPublishedPage(page) - folderType === FolderSubmissionStatus.unpublished - ? setTotalDraftSubmissions(response.data.page?.totalFolders) - : setTotalPublishedSubmissions(response.data.page?.totalFolders) + // Set new display submissions + const displaySubmissions = response.data?.folders + setDisplaySubmissions(displaySubmissions) + + // Only set again a new total number of submissions if a submission is deleted + if (isDeletingSubmission) { + folderType === FolderSubmissionStatus.unpublished + ? setNumberOfDraftSubmissions(response.data.page?.totalFolders) + : setNumberOfPublishedSubmissions(response.data.page?.totalFolders) + } } else { dispatch( updateStatus({ @@ -185,7 +298,8 @@ const Home: React.FC = () => { } } - const handleDeleteSubmission = (submissionId: string, submissionType: string) => { + const handleDeleteSubmission = (submissionId: string, folderType: string) => { + // Dispatch action to delete the submission dispatch(deleteFolderAndContent(submissionId)) .then(() => { dispatch( @@ -194,10 +308,17 @@ const Home: React.FC = () => { helperText: "The submission has been deleted successfully!", }) ) + // Then fetch current page again to get new list of display submissions and a new total number of submissions handleFetchPageOnChange( - submissionType === FolderSubmissionStatus.unpublished ? draftPage : publishedPage, - submissionType + folderType === FolderSubmissionStatus.unpublished ? draftPage : publishedPage, + folderType, + true ) + // If there is a filtering text, get a new filtered list of submissions again + if (filteringText) { + const newFilteredSubmissions = filteredSubmissions.filter(item => item.folderId !== submissionId) + setFilteredSubmissions(newFilteredSubmissions) + } }) .catch(error => { dispatch( @@ -209,6 +330,33 @@ const Home: React.FC = () => { }) } + const getFilter = (textValue: string, currentTab: string) => { + const allSubmissions = + currentTab === FolderSubmissionStatus.unpublished ? allDraftSubmissions : allPublishedSubmissions + + const filteredSubmissions = allSubmissions.filter(item => item && item.name.includes(textValue)) + setFilteredSubmissions(filteredSubmissions) + } + + const handleChangeFilteringText = (e: React.ChangeEvent) => { + // Set the current page in pagination to 0 when starting the filter + tabValue === FolderSubmissionStatus.unpublished ? setDraftPage(0) : setPublishedPage(0) + const textValue = e.target.value + setFilteringText(textValue) + debouncedChangeFilteringText(textValue, tabValue) + } + + const debouncedChangeFilteringText = useMemo( + () => debounce(getFilter, 300), + [allDraftSubmissions, allPublishedSubmissions, tabValue] + ) + + const handleClearFilteringText = () => { + // Set the current page in pagination to 0 when clearing the filter + tabValue === FolderSubmissionStatus.unpublished ? setDraftPage(0) : setPublishedPage(0) + setFilteringText("") + } + // Render either unpublished or published folders based on selected tab return ( @@ -240,38 +388,44 @@ const Home: React.FC = () => { - - {folderRows.length > 0 && ( + {displaySubmissions.length > 0 ? ( + { - return - }} + filteringText={filteringText} + handleChangeFilteringText={handleChangeFilteringText} + handleClearFilteringText={handleClearFilteringText} + /> + + + - )} - - - - {isFetchingFolders && } - + {isFetchingFolders && } + + ) : ( + + + Currently there are no submissions + + + )} )