diff --git a/.coderabbit.yaml b/.coderabbit.yaml index 46e2d25b8c..08e1985ae7 100644 --- a/.coderabbit.yaml +++ b/.coderabbit.yaml @@ -13,5 +13,6 @@ reviews: drafts: false base_branches: - develop + - main chat: - auto_reply: true \ No newline at end of file + auto_reply: true diff --git a/.github/workflows/compare_translations.py b/.github/workflows/compare_translations.py index fd4f772605..ef65b6c52b 100644 --- a/.github/workflows/compare_translations.py +++ b/.github/workflows/compare_translations.py @@ -88,6 +88,31 @@ def compare_translations(default_translation, errors.append(error_msg) return errors +def flatten_json(nested_json, parent_key=""): + """ + Flattens a nested JSON, concatenating keys to represent the hierarchy. + + Args: + nested_json (dict): The JSON object to flatten. + parent_key (str): The base key for recursion (used to track key hierarchy). + + Returns: + dict: A flattened dictionary with concatenated keys. + """ + flat_dict = {} + + for key, value in nested_json.items(): + # Create the new key by concatenating parent and current key + new_key = f"{parent_key}.{key}" if parent_key else key + + if isinstance(value, dict): + # Recursively flatten the nested dictionary + flat_dict.update(flatten_json(value, new_key)) + else: + # Assign the value to the flattened key + flat_dict[new_key] = value + + return flat_dict def load_translation(filepath): """Load translation from a file. @@ -104,7 +129,8 @@ def load_translation(filepath): if not content.strip(): raise ValueError(f"File {filepath} is empty.") translation = json.loads(content) - return translation + flattened_translation = flatten_json(translation) + return flattened_translation except json.JSONDecodeError as e: raise ValueError(f"Error decoding JSON from file {filepath}: {e}") @@ -170,7 +196,7 @@ def main(): "--directory", type=str, nargs="?", - default=os.path.join(os.getcwd(), "locales"), + default=os.path.join(os.getcwd(), "public/locales"), help="Directory containing translation files(relative to the root directory).", ) args = parser.parse_args() diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 32f9cee912..3846371ed5 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -30,7 +30,7 @@ jobs: - name: Set up Node.js uses: actions/setup-node@v4 with: - node-version: '20.x' + node-version: '22.x' - name: Install Dependencies run: npm install @@ -176,7 +176,7 @@ jobs: - name: Set up Node.js uses: actions/setup-node@v4 with: - node-version: '20.x' + node-version: '22.x' - name: Install Dependencies run: npm install @@ -221,7 +221,7 @@ jobs: - name: Set up Node.js uses: actions/setup-node@v4 with: - node-version: '20.x' + node-version: '22.x' - name: resolve dependency run: npm install -g @graphql-inspector/cli diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index bc86d1b7d8..67e49556b4 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -24,7 +24,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node-version: [20.x] + node-version: [22.x] steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 diff --git a/.node-version b/.node-version index 790e1105f2..751f4c9f38 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -v20.10.0 +v22.7.0 diff --git a/config/babel.config.cjs b/config/babel.config.cjs new file mode 100644 index 0000000000..10cfe1914a --- /dev/null +++ b/config/babel.config.cjs @@ -0,0 +1,8 @@ +module.exports = { + presets: [ + '@babel/preset-env', // Transforms modern JavaScript + '@babel/preset-typescript', // Transforms TypeScript + '@babel/preset-react', // Transforms JSX + ], + plugins: ['babel-plugin-transform-import-meta'], +}; diff --git a/config/vite.config.ts b/config/vite.config.ts new file mode 100644 index 0000000000..71ce6c6f47 --- /dev/null +++ b/config/vite.config.ts @@ -0,0 +1,30 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import viteTsconfigPaths from 'vite-tsconfig-paths'; +import svgrPlugin from 'vite-plugin-svgr'; +import EnvironmentPlugin from 'vite-plugin-environment'; + +export default defineConfig({ + // depending on your application, base can also be "/" + build: { + outDir: 'build', + }, + base: '', + plugins: [ + react(), + viteTsconfigPaths(), + EnvironmentPlugin('all'), + svgrPlugin({ + svgrOptions: { + icon: true, + // ...svgr options (https://react-svgr.com/docs/options/) + }, + }), + ], + server: { + // this ensures that the browser opens upon server start + open: true, + // this sets a default port to 3000 + port: 4321, + }, +}); diff --git a/public/index.html b/index.html similarity index 64% rename from public/index.html rename to index.html index 3bd6e258e7..4375f88592 100644 --- a/public/index.html +++ b/index.html @@ -1,12 +1,12 @@ - + - + - - + + - + Talawa Admin
+ diff --git a/jest.config.js b/jest.config.js index 34f219ba06..276c44dbbc 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,7 +1,8 @@ export default { roots: ['/src'], collectCoverageFrom: ['src/**/*.{ts,tsx}', '!src/index.tsx'], - setupFiles: ['react-app-polyfill/jsdom'], + // setupFiles: ['react-app-polyfill/jsdom'], + setupFiles: ['whatwg-fetch'], setupFilesAfterEnv: ['/src/setupTests.ts'], testMatch: [ '/src/**/__tests__/**/*.{js,jsx,ts,tsx}', @@ -9,11 +10,9 @@ export default { ], testEnvironment: 'jsdom', transform: { - '^.+\\.(js|jsx|mjs|cjs|ts|tsx)$': - 'react-scripts/config/jest/babelTransform.js', - '^.+\\.(css|scss|sass|less)$': 'jest-preview/transforms/css', - '^(?!.*\\.(js|jsx|mjs|cjs|ts|tsx|css|json)$)': - 'jest-preview/transforms/file', + '^.+\\.(js|jsx|ts|tsx)$': ['babel-jest', { configFile: "./config/babel.config.cjs" }], // Use babel-jest for JavaScript and TypeScript files + '^.+\\.(css|scss|sass|less)$': 'jest-preview/transforms/css', // CSS transformations + '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': 'jest-preview/transforms/file', // File transformations }, transformIgnorePatterns: [ '[/\\\\]node_modules[/\\\\].+\\.(js|jsx|mjs|cjs|ts|tsx)$', @@ -41,10 +40,10 @@ export default { 'jsx', 'node', ], - watchPlugins: [ - 'jest-watch-typeahead/filename', - 'jest-watch-typeahead/testname', - ], + // watchPlugins: [ + // 'jest-watch-typeahead/filename', + // 'jest-watch-typeahead/testname', + // ], resetMocks: false, coveragePathIgnorePatterns: [ 'src/state/index.ts', diff --git a/package.json b/package.json index f9d29778a5..ecaa1ae2bc 100644 --- a/package.json +++ b/package.json @@ -10,19 +10,22 @@ "@apollo/react-testing": "^4.0.0", "@dicebear/collection": "^8.0.1", "@dicebear/core": "^8.0.2", - "@emotion/react": "^11.13.0", + "@emotion/react": "^11.13.3", "@emotion/styled": "^11.13.0", - "@mui/icons-material": "^5.16.1", + "@mui/icons-material": "^5.16.7", "@mui/material": "^5.16.7", "@mui/private-theming": "^5.15.12", "@mui/system": "^5.14.12", - "@mui/x-charts": "^7.8.0", - "@mui/x-data-grid": "^7.11.0", + "@mui/x-charts": "^7.17.0", + "@mui/x-data-grid": "^7.16.0", "@mui/x-date-pickers": "^7.11.1", - "@pdfme/generator": "^1.2.6", + "@pdfme/generator": "^4.5.2", + "@vitejs/plugin-react": "^4.3.1", + "babel-plugin-transform-import-meta": "^2.2.1", "bootstrap": "^5.3.3", "customize-cra": "^1.0.0", "dayjs": "^1.11.12", + "dotenv": "^16.4.5", "flag-icons": "^6.6.6", "graphql": "^16.9.0", "graphql-tag": "^2.12.6", @@ -30,17 +33,16 @@ "history": "^5.3.0", "i18next": "^23.11.5", "i18next-browser-languagedetector": "^8.0.0", - "i18next-http-backend": "^2.5.2", + "i18next-http-backend": "^2.6.1", "inquirer": "^8.0.0", "js-cookie": "^3.0.1", "markdown-toc": "^1.2.0", "prettier": "^3.3.2", - "react": "^17.0.2", - "react-app-rewired": "^2.2.1", + "react": "^18.3.1", "react-beautiful-dnd": "^13.1.1", "react-bootstrap": "^2.10.4", "react-datepicker": "^7.3.0", - "react-dom": "^17.0.2", + "react-dom": "^18.3.1", "react-google-recaptcha": "^3.1.0", "react-i18next": "^12.3.1", "react-icons": "^5.2.1", @@ -48,20 +50,25 @@ "react-multi-carousel": "^2.8.5", "react-redux": "^7.2.5", "react-router-dom": "^6.26.0", - "react-scripts": "5.0.1", - "react-toastify": "^9.0.3", + "react-toastify": "^10.0.5", "react-tooltip": "^5.27.1", "redux": "^4.1.1", "redux-thunk": "^2.3.0", "sanitize-html": "^2.13.0", + "typedoc": "^0.26.7", "typedoc-plugin-markdown": "^4.2.1", - "typescript": "^4.3.5", + "typescript": "^5.6.2", + "vite": "^4.5.3", + "vite-plugin-environment": "^1.1.3", + "vite-plugin-svgr": "^3.2.0", + "vite-tsconfig-paths": "^5.0.1", "web-vitals": "^4.2.3" }, "scripts": { - "serve": "cross-env ESLINT_NO_DEV_ERRORS=true node ./scripts/config-overrides/custom_start.js", - "build": "node ./scripts/config-overrides/custom_build.js", - "test": "cross-env NODE_ENV=test node scripts/test.js --env=./scripts/custom-test-env.js --watchAll --coverage", + "serve": "cross-env ESLINT_NO_DEV_ERRORS=true vite --config config/vite.config.ts", + "build": "tsc && vite build --config config/vite.config.ts", + "preview": "vite preview --config config/vite.config.ts", + "test": "cross-env NODE_ENV=test jest --env=./scripts/custom-test-env.js --watchAll --coverage", "eject": "react-scripts eject", "lint:check": "eslint \"**/*.{ts,tsx}\" --max-warnings=0", "lint:fix": "eslint --fix \"**/*.{ts,tsx}\"", @@ -96,29 +103,33 @@ }, "devDependencies": { "@babel/plugin-proposal-private-property-in-object": "^7.21.11", - "@testing-library/jest-dom": "^6.4.2", - "@testing-library/react": "^11.1.0", + "@babel/preset-env": "^7.25.4", + "@babel/preset-react": "^7.24.7", + "@babel/preset-typescript": "^7.24.7", + "@testing-library/jest-dom": "^6.5.0", + "@testing-library/react": "^16.0.0", "@testing-library/user-event": "^12.1.10", "@types/inquirer": "^9.0.7", "@types/jest": "^26.0.24", "@types/js-cookie": "^3.0.6", - "@types/node": "^20.12.12", + "@types/node": "^22.5.4", "@types/node-fetch": "^2.6.10", - "@types/react": "^17.0.14", + "@types/react": "^18.3.3", "@types/react-beautiful-dnd": "^13.1.8", "@types/react-bootstrap": "^0.32.32", - "@types/react-datepicker": "^4.1.4", - "@types/react-dom": "^17.0.9", + "@types/react-datepicker": "^7.0.0", + "@types/react-dom": "^18.3.0", "@types/react-google-recaptcha": "^2.1.5", "@types/react-router-dom": "^5.1.8", "@types/sanitize-html": "^2.13.0", - "@typescript-eslint/eslint-plugin": "^5.9.0", - "@typescript-eslint/parser": "^5.9.0", + "@typescript-eslint/eslint-plugin": "^8.5.0", + "@typescript-eslint/parser": "^8.5.0", + "babel-jest": "^29.7.0", "cross-env": "^7.0.3", "eslint-config-prettier": "^9.1.0", "eslint-plugin-import": "^2.29.1", - "eslint-plugin-jest": "^25.3.4", - "eslint-plugin-prettier": "^5.1.3", + "eslint-plugin-jest": "^28.8.0", + "eslint-plugin-prettier": "^5.2.1", "eslint-plugin-react": "^7.35.0", "eslint-plugin-tsdoc": "^0.3.0", "husky": "^8.0.3", @@ -130,7 +141,8 @@ "lint-staged": "^15.2.8", "postcss-modules": "^6.0.0", "sass": "^1.77.8", - "tsx": "^4.16.2" + "tsx": "^4.16.2", + "whatwg-fetch": "^3.6.20" }, "resolutions": { "@apollo/client": "^3.4.0-beta.19", diff --git a/public/locales/en/common.json b/public/locales/en/common.json index f938663e5f..3cf8df2010 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -82,5 +82,14 @@ "updatedSuccessfully": "{{item}} updated Successfully", "removedSuccessfully": "{{item}} removed Successfully", "successfullyUpdated": "Successfully Updated", - "sort": "Sort" + "sort": "Sort", + "all": "All", + "active": "Active", + "disabled": "Disabled", + "pending": "Pending", + "completed": "Completed", + "late": "Late", + "createdLatest": "Created Latest", + "createdEarliest": "Created Earliest", + "searchBy": "Search by {{item}}" } diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index 3b7b875821..e366764af2 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -22,7 +22,25 @@ "numeric_value_check": "Atleaset one numeric value", "special_char_check": "Atleast one special character", "selectOrg": "Select an organization", - "afterRegister": "Successfully registered. Please wait for admin to approve your request." + "afterRegister": "Successfully registered. Please wait for admin to approve your request.", + "talawa_portal": "talawa_portal", + "login": "login", + "register": "register", + "firstName": "firstName", + "lastName": "lastName", + "email": "email", + "password": "password", + "confirmPassword": "confirmPassword", + "forgotPassword": "forgotPassword", + "enterEmail": "enterEmail", + "enterPassword": "enterPassword", + "talawaApiUnavailable": "talawaApiUnavailable", + "notAuthorised": "notAuthorised", + "notFound": "notFound", + "OR": "OR", + "admin": "admin", + "user": "user", + "loading": "loading" }, "userLoginPage": { "title": "Talawa Admin", @@ -38,7 +56,23 @@ "successfullyRegistered": "Successfully Registered. Please wait until you will be approved.", "userLogin": "User Login", "afterRegister": "Successfully registered. Please wait for admin to approve your request.", - "selectOrg": "Select an organization" + "selectOrg": "Select an organization", + "talawa_portal": "talawa_portal", + "login": "login", + "register": "register", + "firstName": "firstName", + "lastName": "lastName", + "email": "email", + "password": "password", + "confirmPassword": "confirmPassword", + "forgotPassword": "forgotPassword", + "enterEmail": "enterEmail", + "enterPassword": "enterPassword", + "talawaApiUnavailable": "talawaApiUnavailable", + "notAuthorised": "notAuthorised", + "notFound": "notFound", + "OR": "OR", + "loading": "loading" }, "latestEvents": { "eventCardTitle": "Upcoming Events", @@ -51,12 +85,19 @@ "noPostsCreated": "No Posts Created" }, "listNavbar": { - "roles": "Roles" + "roles": "Roles", + "talawa_portal": "talawa_portal", + "requests": "requests", + "logout": "logout" }, "leftDrawer": { "my organizations": "My Organizations", "requests": "Membership Requests", - "communityProfile": "Community Profile" + "communityProfile": "Community Profile", + "talawaAdminPortal": "talawaAdminPortal", + "menu": "menu", + "users": "users", + "logout": "logout" }, "leftDrawerOrg": { "Dashboard": "Dashboard", @@ -75,7 +116,13 @@ "notifications": "Notifications", "spamsThe": "spams the", "group": "group", - "noNotifications": "No Notifications" + "noNotifications": "No Notifications", + "talawaAdminPortal": "talawaAdminPortal", + "menu": "menu", + "talawa_portal": "talawa_portal", + "settings": "settings", + "logout": "logout", + "close": "close" }, "orgList": { "title": "Talawa Organizations", @@ -105,11 +152,25 @@ "manageFeaturesInfo": "Creation Successful ! Please select features that you want to enale for this organization from the plugin store.", "goToStore": "Go to Plugin Store", "enableEverything": "Enable Everything", - "sampleOrgSuccess": "Sample Organization Successfully Created" + "sampleOrgSuccess": "Sample Organization Successfully Created", + "name": "name", + "email": "email", + "searchByName": "searchByName", + "description": "description", + "location": "location", + "address": "address", + "displayImage": "displayImage", + "filter": "filter", + "cancel": "cancel", + "endOfResults": "endOfResults", + "noResultsFoundFor": "noResultsFoundFor", + "OR": "OR" }, "orgListCard": { "manage": "Manage", - "sampleOrganization": "Sample Organization" + "sampleOrganization": "Sample Organization", + "admins": "admins", + "members": "members" }, "paginationList": { "rowsPerPage": "rows per page", @@ -126,7 +187,11 @@ "acceptedSuccessfully": "Request accepted successfully", "rejectedSuccessfully": "Request rejected successfully", "noOrgErrorTitle": "Organizations Not Found", - "noOrgErrorDescription": "Please create an organization through dashboard" + "noOrgErrorDescription": "Please create an organization through dashboard", + "name": "name", + "email": "email", + "endOfResults": "endOfResults", + "noResultsFoundFor": "noResultsFoundFor" }, "users": { "title": "Talawa Roles", @@ -150,7 +215,25 @@ "visit": "Visit", "withdraw": "Widthdraw", "removeUserFrom": "Remove User from {{org}}", - "removeConfirmation": "Are you sure you want to remove '{{name}}' from organization '{{org}}'?" + "removeConfirmation": "Are you sure you want to remove '{{name}}' from organization '{{org}}'?", + "searchByName": "searchByName", + "users": "users", + "name": "name", + "email": "email", + "endOfResults": "endOfResults", + "admin": "admin", + "superAdmin": "superAdmin", + "user": "user", + "filter": "filter", + "noResultsFoundFor": "noResultsFoundFor", + "talawaApiUnavailable": "talawaApiUnavailable", + "cancel": "cancel", + "admins": "admins", + "members": "members", + "orgJoined": "orgJoined", + "MembershipRequestSent": "MembershipRequestSent", + "AlreadyJoined": "AlreadyJoined", + "errorOccured": "errorOccured" }, "communityProfile": { "title": "Community Profile", @@ -178,7 +261,12 @@ "latestPosts": "Latest Posts", "noPostsPresent": "No Posts Present", "membershipRequests": "Membership requests", - "noMembershipRequests": "No Membership requests present" + "noMembershipRequests": "No Membership requests present", + "location": "location", + "members": "members", + "admins": "admins", + "requests": "requests", + "talawaApiUnavailable": "talawaApiUnavailable" }, "organizationPeople": { "title": "Talawa Members", @@ -198,7 +286,23 @@ "enterLastName": "Enter your last name", "enterConfirmPassword": "Enter Password to confirm", "organization": "Organization", - "invalidDetailsMessage": "Please enter valid details." + "invalidDetailsMessage": "Please enter valid details.", + "members": "members", + "admins": "admins", + "users": "users", + "searchFirstName": "searchFirstName", + "searchLastName": "searchLastName", + "firstName": "firstName", + "lastName": "lastName", + "emailAddress": "emailAddress", + "enterEmail": "enterEmail", + "password": "password", + "enterPassword": "enterPassword", + "confirmPassword": "confirmPassword", + "create": "create", + "cancel": "cancel", + "user": "user", + "profile": "profile" }, "organizationTags": { "title": "Organization Tags", @@ -232,19 +336,29 @@ }, "userListCard": { "addAdmin": "Add Admin", - "addedAsAdmin": "User is added as admin." + "addedAsAdmin": "User is added as admin.", + "joined": "joined", + "talawaApiUnavailable": "talawaApiUnavailable" }, "orgAdminListCard": { "remove": "Remove", "removeAdmin": "Remove Admin", "removeAdminMsg": "Do you want to remove this admin?", - "adminRemoved": "The admin is removed." + "adminRemoved": "The admin is removed.", + "joined": "joined", + "no": "no", + "yes": "yes", + "talawaApiUnavailable": "talawaApiUnavailable" }, "orgPeopleListCard": { "remove": "Remove", "removeMember": "Remove Member", "removeMemberMsg": "Do you want to remove this member?", - "memberRemoved": "The Member is removed" + "memberRemoved": "The Member is removed", + "joined": "joined", + "no": "no", + "yes": "yes", + "talawaApiUnavailable": "talawaApiUnavailable" }, "organizationEvents": { "title": "Events", @@ -272,7 +386,17 @@ "never": "Never", "on": "On", "after": "After", - "occurences": "occurences" + "occurences": "occurences", + "startTime": "startTime", + "endTime": "endTime", + "eventLocation": "eventLocation", + "events": "events", + "description": "description", + "location": "location", + "startDate": "startDate", + "endDate": "endDate", + "talawaApiUnavailable": "talawaApiUnavailable", + "done": "done" }, "organizationActionItems": { "actionItemCategory": "Action Item Category", @@ -283,9 +407,8 @@ "assignmentDate": "Assignment Date", "active": "Active", "clearFilters": "Clear Filters", - "completed": "Completed", "completionDate": "Completion Date", - "createActionItem": "Create Action Items", + "createActionItem": "Create Action Item", "deleteActionItem": "Delete Action Item", "deleteActionItemMsg": "Do you want to remove this action item?", "details": "Details", @@ -308,7 +431,18 @@ "successfulCreation": "Action Item created successfully", "successfulUpdation": "Action Item updated successfully", "successfulDeletion": "Action Item deleted successfully", - "title": "Action Items" + "title": "Action Items", + "category": "Category", + "allotedHours": "Alloted Hours", + "latestDueDate": "Latest Due Date", + "earliestDueDate": "Earliest Due Date", + "updateActionItem": "Update Action Item", + "noneUpdated": "None of the fields were updated", + "updateStatusMsg": "Are you sure you want to mark this action item as pending?", + "close": "close", + "eventActionItems": "eventActionItems", + "no": "no", + "yes": "yes" }, "organizationAgendaCategory": { "agendaCategoryDetails": "Agenda Category Details", @@ -385,7 +519,19 @@ "never": "Never", "on": "On", "after": "After", - "occurences": "occurences" + "occurences": "occurences", + "startTime": "startTime", + "endTime": "endTime", + "location": "location", + "no": "no", + "yes": "yes", + "description": "description", + "startDate": "startDate", + "endDate": "endDate", + "registerEvent": "registerEvent", + "close": "close", + "talawaApiUnavailable": "talawaApiUnavailable", + "done": "done" }, "funds": { "title": "Funds", @@ -408,7 +554,8 @@ "deleteFundMsg": "Are you sure you want to delete this fund?", "createdLatest": "Created Latest", "createdEarliest": "Created Earliest", - "viewCampaigns": "View Campaigns" + "viewCampaigns": "View Campaigns", + "searchByName": "searchByName" }, "fundCampaign": { "title": "Fundraising Campaigns", @@ -460,7 +607,9 @@ "pledges": "Pledges", "endsOn": "Ends on", "raisedAmount": "Raised amount ", - "pledgedAmount": "Pledged amount" + "pledgedAmount": "Pledged amount", + "startDate": "startDate", + "endDate": "endDate" }, "orgPost": { "title": "Posts", @@ -488,7 +637,8 @@ "postCreatedSuccess": "Congratulations! You have Posted Something.", "pinPost": "Pin post", "Next": "Next Page", - "Previous": "Previous Page" + "Previous": "Previous Page", + "cancel": "cancel" }, "postNotFound": { "post": "Post", @@ -503,7 +653,8 @@ "user not found!": "User Not Found!", "member not found!": "Member Not Found!", "admin not found!": "Admin Not Found!", - "roles not found!": "Roles Not Found!" + "roles not found!": "Roles Not Found!", + "user": "user" }, "orgPostCard": { "author": "Author", @@ -522,7 +673,12 @@ "postDeleted": "Post deleted successfully.", "postUpdated": "Post Updated successfully.", "tag": " Your browser does not support the video tag", - "pin": "Pin Post" + "pin": "Pin Post", + "edit": "edit", + "no": "no", + "yes": "yes", + "close": "close", + "talawaApiUnavailable": "talawaApiUnavailable" }, "blockUnblockUser": { "title": "Block/Unblock User", @@ -538,7 +694,12 @@ "blockedUsers": "Blocked Users", "searchByFirstName": "Search By First Name", "searchByLastName": "Search By Last Name", - "noSpammerFound": "No spammer found" + "noSpammerFound": "No spammer found", + "searchByName": "searchByName", + "name": "name", + "email": "email", + "talawaApiUnavailable": "talawaApiUnavailable", + "noResultsFoundFor": "noResultsFoundFor" }, "eventManagement": { "title": "Event Management", @@ -563,7 +724,10 @@ "errorSendingMail": "Error in sending mail.", "passwordMismatches": "Password and Confirm password mismatches.", "passwordChanges": "Password changes successfully.", - "OTPsent": "OTP is sent to your registered email." + "OTPsent": "OTP is sent to your registered email.", + "forgotPassword": "forgotPassword", + "password": "password", + "talawaApiUnavailable": "talawaApiUnavailable" }, "pageNotFound": { "title": "404 Not Found", @@ -601,7 +765,8 @@ "noData": "No data", "otherSettings": "Other Settings", "changeLanguage": "Change Language", - "manageCustomFields": "Manage Custom Fields" + "manageCustomFields": "Manage Custom Fields", + "agendaItemCategories": "Agenda Item Categories" }, "deleteOrg": { "deleteOrganization": "Delete Organization", @@ -609,18 +774,30 @@ "deleteMsg": "Do you want to delete this organization?", "confirmDelete": "Confirm Delete", "longDelOrgMsg": "By clicking on Delete Organization button the organization will be permanently deleted along with its events, tags and all related data.", - "successfullyDeletedSampleOrganization": "Successfully deleted sample Organization" + "successfullyDeletedSampleOrganization": "Successfully deleted sample Organization", + "cancel": "cancel" }, "userUpdate": { "appLanguageCode": "Default Language", - "userType": "User Type" + "userType": "User Type", + "firstName": "firstName", + "lastName": "lastName", + "email": "email", + "password": "password", + "admin": "admin", + "superAdmin": "superAdmin", + "displayImage": "displayImage", + "saveChanges": "saveChanges", + "cancel": "cancel" }, "userPasswordUpdate": { "previousPassword": "Previous Password", "newPassword": "New Password", "confirmNewPassword": "Confirm New Password", "passCantBeEmpty": "Password can't be empty", - "passNoMatch": "New and Confirm password do not match." + "passNoMatch": "New and Confirm password do not match.", + "saveChanges": "saveChanges", + "cancel": "cancel" }, "orgDelete": { "deleteOrg": "Delete Org" @@ -628,7 +805,9 @@ "membershipRequest": { "accept": "Accept", "reject": "Reject", - "memberAdded": "it is accepted" + "memberAdded": "it is accepted", + "joined": "joined", + "talawaApiUnavailable": "talawaApiUnavailable" }, "orgUpdate": { "city": "City", @@ -642,7 +821,15 @@ "userRegistrationRequired": "User Registration Required", "isVisibleInSearch": "Visible in Search", "enterNameOrganization": "Enter Organization Name", - "successfulUpdated": "Organization updated successfully" + "successfulUpdated": "Organization updated successfully", + "name": "name", + "description": "description", + "location": "location", + "address": "address", + "displayImage": "displayImage", + "saveChanges": "saveChanges", + "cancel": "cancel", + "talawaApiUnavailable": "talawaApiUnavailable" }, "addOnRegister": { "addNew": "Add New", @@ -652,7 +839,9 @@ "pluginDesc": "Plugin Description", "pName": "Ex: Donations", "cName": "Ex: john Doe", - "pDesc": "This Plugin enables UI for" + "pDesc": "This Plugin enables UI for", + "close": "close", + "register": "register" }, "addOnStore": { "title": "Add On Store", @@ -706,7 +895,14 @@ "membershipRequests": "Membership requests", "adminForEvents": "Admin for events", "addedAsAdmin": "User is added as admin.", - "userType": "User Type" + "userType": "User Type", + "email": "email", + "displayImage": "displayImage", + "address": "address", + "delete": "delete", + "saveChanges": "saveChanges", + "joined": "joined", + "talawaApiUnavailable": "talawaApiUnavailable" }, "people": { "title": "People", @@ -717,7 +913,14 @@ "loginIntoYourAccount": "Login into your account", "invalidDetailsMessage": "Please enter a valid email and password.", "notAuthorised": "Sorry! you are not Authorised!", - "invalidCredentials": "Entered credentials are incorrect. Please enter valid credentials." + "invalidCredentials": "Entered credentials are incorrect. Please enter valid credentials.", + "forgotPassword": "forgotPassword", + "emailAddress": "emailAddress", + "enterEmail": "enterEmail", + "password": "password", + "enterPassword": "enterPassword", + "register": "register", + "talawaApiUnavailable": "talawaApiUnavailable" }, "userRegister": { "enterFirstName": "Enter your first name", @@ -727,7 +930,16 @@ "login": "Login", "afterRegister": "Successfully registered. Please wait for admin to approve your request.", "passwordNotMatch": "Password doesn't match. Confirm Password and try again.", - "invalidDetailsMessage": "Please enter valid details." + "invalidDetailsMessage": "Please enter valid details.", + "register": "register", + "firstName": "firstName", + "lastName": "lastName", + "emailAddress": "emailAddress", + "enterEmail": "enterEmail", + "password": "password", + "enterPassword": "enterPassword", + "confirmPassword": "confirmPassword", + "talawaApiUnavailable": "talawaApiUnavailable" }, "userNavbar": { "talawa": "Talawa", @@ -736,7 +948,10 @@ "events": "Events", "chat": "Chat", "donate": "Donate", - "language": "Language" + "language": "Language", + "settings": "settings", + "logout": "logout", + "close": "close" }, "userOrganizations": { "allOrganizations": "All Organizations", @@ -745,7 +960,10 @@ "selectOrganization": "Select an organization", "searchUsers": "Search users", "nothingToShow": "Nothing to show here.", - "organizations": "Organizations" + "organizations": "Organizations", + "search": "search", + "filter": "filter", + "searchByName": "searchByName" }, "userSidebarOrg": { "yourOrganizations": "Your Organizations", @@ -758,13 +976,15 @@ "communityProfile": "Community Profile", "logout": "Logout", "settings": "Settings", - "chat": "Chat" + "chat": "Chat", + "menu": "menu" }, "organizationSidebar": { "viewAll": "View all", "events": "Events", "noEvents": "No Events to show", - "noMembers": "No Members to show" + "noMembers": "No Members to show", + "members": "members" }, "postCard": { "likes": "Likes", @@ -843,7 +1063,15 @@ "fullTime": "Full Time", "partTime": "Part Time", "selectCountry": "Select a country", - "enterState": "Enter City or State" + "enterState": "Enter City or State", + "settings": "settings", + "firstName": "firstName", + "lastName": "lastName", + "emailAddress": "emailAddress", + "displayImage": "displayImage", + "address": "address", + "saveChanges": "saveChanges", + "joined": "joined" }, "donate": { "title": "Donations", @@ -854,7 +1082,11 @@ "yourPreviousDonations": "Your Previous Donations", "donate": "Donate", "nothingToShow": "Nothing to show here.", - "success": "Donation Successful" + "success": "Donation Successful", + "invalidAmount": "Please enter a numerical value for the donation amount.", + "donationAmountDescription": "Please enter the numerical value for the donation amount.", + "donationOutOfRange": "Donation amount must be between {{min}} and {{max}}.", + "donateTo": "donateTo" }, "userEvents": { "title": "Events", @@ -872,13 +1104,25 @@ "publicEvent": "Is Public", "registerable": "Is Registerable", "monthlyCalendarView": "Monthly Calendar", - "yearlyCalendarView": "Yearly Calender" + "yearlyCalendarView": "Yearly Calender", + "startTime": "startTime", + "endTime": "endTime", + "enterLocation": "enterLocation", + "search": "search", + "cancel": "cancel", + "create": "create", + "eventDescription": "eventDescription", + "eventLocation": "eventLocation", + "startDate": "startDate", + "endDate": "endDate" }, "userEventCard": { "starts": "Starts", "ends": "Ends", "creator": "Creator", - "alreadyRegistered": "Already registered" + "alreadyRegistered": "Already registered", + "location": "location", + "register": "register" }, "advertisement": { "title": "Advertisements", @@ -903,7 +1147,15 @@ "editAdvertisement": "Edit Advertisement", "advertisementDeleted": "Advertisement deleted successfully.", "endDateGreaterOrEqual": "End Date should be greater than or equal to Start Date", - "advertisementCreated": "Advertisement created successfully." + "advertisementCreated": "Advertisement created successfully.", + "pHeading": "pHeading", + "delete": "delete", + "close": "close", + "no": "no", + "yes": "yes", + "edit": "edit", + "saveChanges": "saveChanges", + "endOfResults": "endOfResults" }, "userChat": { "chat": "Chat", @@ -927,20 +1179,28 @@ "String": "String", "Boolean": "Boolean", "Date": "Date", - "Number": "Number" + "Number": "Number", + "saveChanges": "saveChanges" }, "orgActionItemCategories": { "enableButton": "Enable", "disableButton": "Disable", "updateActionItemCategory": "Update", "actionItemCategoryName": "Name", - "actionItemCategoryDetails": "Action Item Category Details", + "categoryDetails": "Category Details", "enterName": "Enter Name", "successfulCreation": "Action Item Category created successfully", "successfulUpdation": "Action Item Category updated successfully", "sameNameConflict": "Please change the name to make an update", "categoryEnabled": "Action Item Category Enabled", - "categoryDisabled": "Action Item Category Disabled" + "categoryDisabled": "Action Item Category Disabled", + "noActionItemCategories": "No Action Item Categories", + "status": "Status", + "categoryDeleted": "Action Item Category deleted successfully", + "deleteCategory": "Delete Category", + "deleteCategoryMsg": "Are you sure you want to delete this Action Item Category?", + "createButton": "createButton", + "editButton": "editButton" }, "organizationVenues": { "title": "Venues", @@ -964,7 +1224,12 @@ "view": "View", "venueTitleError": "Venue title cannot be empty!", "venueCapacityError": "Capacity must be a positive number!", - "searchBy": "Search By" + "searchBy": "Search By", + "description": "description", + "edit": "edit", + "delete": "delete", + "name": "name", + "desc": "desc" }, "addMember": { "title": "Add Member", @@ -978,7 +1243,18 @@ "organization": "Organization", "invalidDetailsMessage": "Please provide all required details.", "passwordNotMatch": "Passwords do not match.", - "addMember": "Add Member" + "addMember": "Add Member", + "firstName": "firstName", + "lastName": "lastName", + "emailAddress": "emailAddress", + "enterEmail": "enterEmail", + "password": "password", + "enterPassword": "enterPassword", + "confirmPassword": "confirmPassword", + "cancel": "cancel", + "create": "create", + "user": "user", + "profile": "profile" }, "eventActionItems": { "title": "Action Items", @@ -1006,7 +1282,9 @@ "successfulCreation": "Action Item created successfully", "successfulUpdation": "Action Item updated successfully", "notes": "Notes", - "save": "Save" + "save": "Save", + "yes": "yes", + "no": "no" }, "checkIn": { "errorCheckingIn": "Error checking in", diff --git a/public/locales/fr/common.json b/public/locales/fr/common.json index 250584da70..6796237d38 100644 --- a/public/locales/fr/common.json +++ b/public/locales/fr/common.json @@ -82,5 +82,14 @@ "addedSuccessfully": "{{item}} ajouté avec succès", "updatedSuccessfully": "{{item}} mis à jour avec succès", "removedSuccessfully": "{{item}} supprimé avec succès", - "successfullyUpdated": "Mis à jour avec succès" + "successfullyUpdated": "Mis à jour avec succès", + "all": "Tous", + "active": "Actif", + "disabled": "Désactivé", + "pending": "En attente", + "completed": "Complété", + "late": "En retard", + "createdLatest": "Créé le plus récemment", + "createdEarliest": "Créé le plus tôt", + "searchBy": "Rechercher par {{item}}" } diff --git a/public/locales/fr/translation.json b/public/locales/fr/translation.json index ac72be3def..4b6d38527c 100644 --- a/public/locales/fr/translation.json +++ b/public/locales/fr/translation.json @@ -22,7 +22,25 @@ "numeric_value_check": "Au moins une valeur numérique", "special_char_check": "Au moins un caractère spécial", "selectOrg": "Sélectionnez une organisation", - "afterRegister": "Enregistré avec succès. " + "afterRegister": "Enregistré avec succès. ", + "talawa_portal": "Portail Talawa", + "login": "Connexion", + "register": "S'inscrire", + "firstName": "Prénom", + "lastName": "Nom de famille", + "email": "E-mail", + "password": "Mot de passe", + "confirmPassword": "Confirmer le mot de passe", + "forgotPassword": "Mot de passe oublié", + "enterEmail": "Entrer l'e-mail", + "enterPassword": "Entrer le mot de passe", + "talawaApiUnavailable": "API Talawa indisponible", + "notAuthorised": "Non autorisé", + "notFound": "Non trouvé", + "OR": "OU", + "admin": "Administrateur", + "user": "Utilisateur", + "loading": "Chargement" }, "userLoginPage": { "title": "Administrateur Talawa", @@ -38,7 +56,23 @@ "successfullyRegistered": "Enregistré avec succès. ", "userLogin": "Utilisateur en ligne", "afterRegister": "Enregistré avec succès. ", - "selectOrg": "Sélectionnez une organisation" + "selectOrg": "Sélectionnez une organisation", + "talawa_portal": "Portail Talawa", + "login": "Connexion", + "register": "S'inscrire", + "firstName": "Prénom", + "lastName": "Nom de famille", + "email": "E-mail", + "password": "Mot de passe", + "confirmPassword": "Confirmer le mot de passe", + "forgotPassword": "Mot de passe oublié", + "enterEmail": "Entrer l'e-mail", + "enterPassword": "Entrer le mot de passe", + "talawaApiUnavailable": "API Talawa indisponible", + "notAuthorised": "Non autorisé", + "notFound": "Non trouvé", + "OR": "OU", + "loading": "Chargement" }, "latestEvents": { "eventCardTitle": "évènements à venir", @@ -51,12 +85,19 @@ "noPostsCreated": "Aucun message créé" }, "listNavbar": { - "roles": "Les rôles" + "roles": "Les rôles", + "talawa_portal": "Portail Talawa", + "requests": "Demandes", + "logout": "Déconnexion" }, "leftDrawer": { "my organizations": "Mes organisations", "requests": "Demandes d'adhésion", - "communityProfile": "Profil de la communauté" + "communityProfile": "Profil de la communauté", + "talawaAdminPortal": "Portail Administrateur Talawa", + "menu": "Menu", + "users": "Utilisateurs", + "logout": "Déconnexion" }, "leftDrawerOrg": { "Dashboard": "Tableau de bord", @@ -75,7 +116,13 @@ "notifications": "Notifications", "spamsThe": "spamme le", "group": "groupe", - "noNotifications": "Aucune notification" + "noNotifications": "Aucune notification", + "talawaAdminPortal": "Portail Administrateur Talawa", + "menu": "Menu", + "talawa_portal": "Portail Talawa", + "settings": "Paramètres", + "logout": "Déconnexion", + "close": "Fermer" }, "orgList": { "title": "Organisations Talawa", @@ -105,11 +152,25 @@ "manageFeaturesInfo": "Création réussie ! ", "goToStore": "Accédez à la boutique de plugins", "enableEverything": "Activer tout", - "sampleOrgSuccess": "Exemple d'organisation créée avec succès" + "sampleOrgSuccess": "Exemple d'organisation créée avec succès", + "name": "Nom", + "email": "E-mail", + "searchByName": "Rechercher par nom", + "description": "Description", + "location": "Emplacement", + "address": "Adresse", + "displayImage": "Image d'affichage", + "filter": "Filtrer", + "cancel": "Annuler", + "endOfResults": "Fin des résultats", + "noResultsFoundFor": "Aucun résultat trouvé pour", + "OR": "OU" }, "orgListCard": { "manage": "Gérer", - "sampleOrganization": "Exemple d'organisation" + "sampleOrganization": "Exemple d'organisation", + "admins": "Administrateurs", + "members": "Membres" }, "paginationList": { "rowsPerPage": "lignes par page", @@ -126,7 +187,11 @@ "acceptedSuccessfully": "Demande acceptée avec succès", "rejectedSuccessfully": "Demande rejetée avec succès", "noOrgErrorTitle": "Organisations introuvables", - "noOrgErrorDescription": "Veuillez créer une organisation via le tableau de bord" + "noOrgErrorDescription": "Veuillez créer une organisation via le tableau de bord", + "name": "Nom", + "email": "E-mail", + "endOfResults": "Fin des résultats", + "noResultsFoundFor": "Aucun résultat trouvé pour" }, "users": { "title": "Rôles Talawa", @@ -150,7 +215,25 @@ "visit": "Visite", "withdraw": "Largeur de tirage", "removeUserFrom": "Supprimer l'Utilisateur de {{org}}", - "removeConfirmation": "Êtes-vous sûr de vouloir supprimer '{{name}}' de l'organisation '{{org}}' ?" + "removeConfirmation": "Êtes-vous sûr de vouloir supprimer '{{name}}' de l'organisation '{{org}}' ?", + "searchByName": "Rechercher par nom", + "users": "Utilisateurs", + "name": "Nom", + "email": "E-mail", + "endOfResults": "Fin des résultats", + "admin": "Administrateur", + "superAdmin": "Super Administrateur", + "user": "Utilisateur", + "filter": "Filtrer", + "noResultsFoundFor": "Aucun résultat trouvé pour", + "talawaApiUnavailable": "API Talawa indisponible", + "cancel": "Annuler", + "admins": "Administrateurs", + "members": "Membres", + "orgJoined": "Organisation Rejointe", + "MembershipRequestSent": "Demande d'adhésion envoyée", + "AlreadyJoined": "Déjà rejoint", + "errorOccured": "Une erreur s'est produite" }, "communityProfile": { "title": "Profil de la communauté", @@ -178,7 +261,12 @@ "latestPosts": "Derniers messages", "noPostsPresent": "Aucun message présent", "membershipRequests": "Demandes d'adhésion", - "noMembershipRequests": "Aucune demande d'adhésion présente" + "noMembershipRequests": "Aucune demande d'adhésion présente", + "location": "Emplacement", + "members": "Membres", + "admins": "Administrateurs", + "requests": "Demandes", + "talawaApiUnavailable": "API Talawa indisponible" }, "organizationPeople": { "title": "Membres Talawa", @@ -198,7 +286,23 @@ "enterLastName": "Entrez votre nom de famille", "enterConfirmPassword": "Entrez votre mot de passe pour confirmer", "organization": "Organisation", - "invalidDetailsMessage": "Veuillez saisir des informations valides." + "user": "Utilisateur", + "profile": "Profil", + "invalidDetailsMessage": "Veuillez saisir des informations valides.", + "members": "Membres", + "admins": "Administrateurs", + "users": "Utilisateurs", + "searchFirstName": "Rechercher par prénom", + "searchLastName": "Rechercher par nom de famille", + "firstName": "Prénom", + "lastName": "Nom de famille", + "emailAddress": "Adresse e-mail", + "enterEmail": "Entrer l'e-mail", + "password": "Mot de passe", + "enterPassword": "Entrer le mot de passe", + "confirmPassword": "Confirmer le mot de passe", + "create": "Créer", + "cancel": "Annuler" }, "organizationTags": { "title": "Étiquettes d'Organisation", @@ -232,19 +336,29 @@ }, "userListCard": { "addAdmin": "Ajouter un administrateur", - "addedAsAdmin": "L'utilisateur est ajouté en tant qu'administrateur." + "addedAsAdmin": "L'utilisateur est ajouté en tant qu'administrateur.", + "joined": "Rejoint", + "talawaApiUnavailable": "API Talawa indisponible" }, "orgAdminListCard": { "remove": "Retirer", "removeAdmin": "Supprimer l'administrateur", "removeAdminMsg": "Voulez-vous supprimer cet administrateur ?", - "adminRemoved": "L'administrateur est supprimé." + "adminRemoved": "L'administrateur est supprimé.", + "joined": "Rejoint", + "no": "Non", + "yes": "Oui", + "talawaApiUnavailable": "API Talawa indisponible" }, "orgPeopleListCard": { "remove": "Retirer", "removeMember": "Supprimer un membre", "removeMemberMsg": "Voulez-vous supprimer ce membre ?", - "memberRemoved": "Le membre est supprimé" + "memberRemoved": "Le membre est supprimé", + "joined": "Rejoint", + "no": "Non", + "yes": "Oui", + "talawaApiUnavailable": "API Talawa indisponible" }, "organizationEvents": { "title": "Événements", @@ -275,7 +389,14 @@ "never": "Jamais", "on": "Sur", "after": "Après", - "occurences": "événements" + "occurences": "événements", + "events": "Événements", + "description": "Description", + "location": "Emplacement", + "startDate": "Date de début", + "endDate": "Date de fin", + "talawaApiUnavailable": "API Talawa indisponible", + "done": "Fait" }, "organizationActionItems": { "actionItemCategory": "Catégorie d'élément d'action", @@ -286,7 +407,6 @@ "assignmentDate": "Date d'affectation", "active": "Actif", "clearFilters": "Effacer les filtres", - "completed": "Complété", "completionDate": "Date d'achèvement", "createActionItem": "Créer un élément d'action", "deleteActionItem": "Supprimer l'élément d'action", @@ -311,7 +431,18 @@ "successfulCreation": "Élément d'action créé avec succès", "successfulUpdation": "Élément d'action mis à jour avec succès", "successfulDeletion": "Élément d'action supprimé avec succès", - "title": "Éléments d'action" + "title": "Éléments d'action", + "category": "Catégorie", + "allotedHours": "Heures allouées", + "latestDueDate": "Date d'échéance la plus récente", + "earliestDueDate": "Date d'échéance la plus ancienne", + "updateActionItem": "Mettre à jour l'élément d'action", + "noneUpdated": "Aucun des champs n'a été mis à jour", + "updateStatusMsg": "Êtes-vous sûr de vouloir marquer cet élément d'action comme en attente?", + "close": "Fermer", + "eventActionItems": "Éléments d'action d'événement", + "no": "Non", + "yes": "Oui" }, "organizationAgendaCategory": { "agendaCategoryDetails": "Détails de la catégorie d'ordre du jour", @@ -390,7 +521,17 @@ "never": "Jamais", "on": "Sur", "after": "Après", - "occurences": "événements" + "occurences": "événements", + "location": "Lieu", + "no": "Non", + "yes": "Oui", + "description": "Description", + "startDate": "Date de début", + "endDate": "Date de fin", + "registerEvent": "Inscrire à l'événement", + "close": "Fermer", + "talawaApiUnavailable": "API Talawa non disponible", + "done": "Terminé" }, "funds": { "title": "Fonds", @@ -466,7 +607,9 @@ "pledges": "Promesses de dons", "endsOn": "Se termine le", "raisedAmount": "Montant collecté", - "pledgedAmount": "Montant promis" + "pledgedAmount": "Montant promis", + "startDate": "Date de début", + "endDate": "Date de fin" }, "orgPost": { "title": "Des postes", @@ -494,7 +637,8 @@ "postCreatedSuccess": "Toutes nos félicitations! ", "pinPost": "Épingler le message", "Next": "Page suivante", - "Previous": "Page précédente" + "Previous": "Page précédente", + "cancel": "Annuler" }, "postNotFound": { "post": "Poste", @@ -509,7 +653,8 @@ "user not found!": "Utilisateur non trouvé!", "member not found!": "Membre introuvable!", "admin not found!": "Administrateur introuvable !", - "roles not found!": "Rôles introuvables !" + "roles not found!": "Rôles introuvables !", + "user": "Utilisateur" }, "orgPostCard": { "author": "Auteur", @@ -528,7 +673,12 @@ "postDeleted": "Message supprimé avec succès.", "postUpdated": "Post mis à jour avec succès.", "tag": " Votre navigateur ne prend pas en charge la balise vidéo", - "pin": "Épingler le message" + "pin": "Épingler le message", + "edit": "Modifier", + "no": "Non", + "yes": "Oui", + "close": "Fermer", + "talawaApiUnavailable": "API Talawa non disponible" }, "blockUnblockUser": { "title": "Bloquer/débloquer un utilisateur", @@ -544,7 +694,12 @@ "blockedUsers": "Utilisateurs bloqués", "searchByFirstName": "Recherche par prénom", "searchByLastName": "Rechercher par nom de famille", - "noSpammerFound": "Aucun spammeur trouvé" + "noSpammerFound": "Aucun spammeur trouvé", + "searchByName": "Rechercher par nom", + "name": "Nom", + "email": "Email", + "talawaApiUnavailable": "API Talawa non disponible", + "noResultsFoundFor": "Aucun résultat trouvé pour" }, "eventManagement": { "title": "Gestion d'événements", @@ -569,7 +724,10 @@ "errorSendingMail": "Erreur lors de l'envoi du courrier.", "passwordMismatches": "Mot de passe et Confirmer les incompatibilités de mot de passe.", "passwordChanges": "Le mot de passe a été modifié avec succès.", - "OTPsent": "OTP est envoyé à votre adresse e-mail enregistrée." + "OTPsent": "OTP est envoyé à votre adresse e-mail enregistrée.", + "forgotPassword": "Mot de passe oublié", + "password": "Mot de passe", + "talawaApiUnavailable": "API Talawa non disponible" }, "pageNotFound": { "404": "404", @@ -604,10 +762,11 @@ "actionItemCategories": "Catégories d'éléments d'action", "updateOrganization": "Mettre à jour l'organisation", "seeRequest": "Voir la demande", - "noData": "Pas de données", - "otherSettings": "Autres réglages", + "noData": "Aucune donnée", + "otherSettings": "Autres paramètres", "changeLanguage": "Changer de langue", - "manageCustomFields": "Gérer les champs personnalisés" + "manageCustomFields": "Gérer les champs personnalisés", + "agendaItemCategories": "Catégories d'éléments d'agenda" }, "deleteOrg": { "deleteOrganization": "Supprimer l'organisation", @@ -615,18 +774,30 @@ "deleteMsg": "Voulez-vous supprimer cette organisation ?", "confirmDelete": "Confirmation de la suppression", "longDelOrgMsg": "En cliquant sur le bouton Supprimer l'organisation, l'organisation sera définitivement supprimée ainsi que ses événements, balises et toutes les données associées.", - "successfullyDeletedSampleOrganization": "Exemple d'organisation supprimé avec succès" + "successfullyDeletedSampleOrganization": "Exemple d'organisation supprimé avec succès", + "cancel": "Annuler" }, "userUpdate": { "appLanguageCode": "Langage par défaut", - "userType": "Type d'utilisateur" + "userType": "Type d'utilisateur", + "firstName": "Prénom", + "lastName": "Nom de famille", + "email": "Email", + "password": "Mot de passe", + "admin": "Admin", + "superAdmin": "Super Admin", + "displayImage": "Image d'affichage", + "saveChanges": "Enregistrer les modifications", + "cancel": "Annuler" }, "userPasswordUpdate": { "previousPassword": "Mot de passe précédent", "newPassword": "nouveau mot de passe", "confirmNewPassword": "Confirmer le nouveau mot de passe", "passCantBeEmpty": "Le mot de passe ne peut pas être vide", - "passNoMatch": "Le nouveau mot de passe et la confirmation du mot de passe ne correspondent pas." + "passNoMatch": "Le nouveau mot de passe et la confirmation du mot de passe ne correspondent pas.", + "saveChanges": "Enregistrer les modifications", + "cancel": "Annuler" }, "orgDelete": { "deleteOrg": "Supprimer l'organisation" @@ -634,7 +805,9 @@ "membershipRequest": { "accept": "Accepter", "reject": "Rejeter", - "memberAdded": "c'est accepté" + "memberAdded": "c'est accepté", + "joined": "Rejoint", + "talawaApiUnavailable": "API Talawa non disponible" }, "orgUpdate": { "city": "Ville", @@ -648,7 +821,15 @@ "userRegistrationRequired": "Inscription de l'utilisateur requise", "isVisibleInSearch": "Visible dans la recherche", "enterNameOrganization": "Entrez le nom de l'organisation", - "successfulUpdated": "Organisation mise à jour avec succès" + "successfulUpdated": "Organisation mise à jour avec succès", + "name": "Nom", + "description": "Description", + "location": "Lieu", + "address": "Adresse", + "displayImage": "Image d'affichage", + "saveChanges": "Enregistrer les modifications", + "cancel": "Annuler", + "talawaApiUnavailable": "API Talawa non disponible" }, "addOnRegister": { "addNew": "Ajouter un nouveau", @@ -658,7 +839,9 @@ "pluginDesc": "Description du plugin", "pName": "Ex : Dons", "cName": "Ex : John Doe", - "pDesc": "Ce plugin active l'interface utilisateur pour" + "pDesc": "Ce plugin active l'interface utilisateur pour", + "close": "Fermer", + "register": "Inscription" }, "addOnStore": { "title": "Ajouter sur la boutique", @@ -669,7 +852,8 @@ "pHeading": "Plugins", "install": "installée", "available": "Disponible", - "pMessage": "Le plugin n'existe pas" + "pMessage": "Le plugin n'existe pas", + "filter": "filtrer" }, "addOnEntry": { "enable": "Activé", @@ -711,7 +895,14 @@ "membershipRequests": "Demandes d'adhésion", "adminForEvents": "Administrateur pour les événements", "addedAsAdmin": "L'utilisateur est ajouté en tant qu'administrateur.", - "userType": "Type d'utilisateur" + "userType": "Type d'utilisateur", + "email": "Email", + "displayImage": "Image d'affichage", + "address": "Adresse", + "delete": "Supprimer", + "saveChanges": "Enregistrer les modifications", + "joined": "Rejoint", + "talawaApiUnavailable": "API Talawa non disponible" }, "people": { "title": "Personnes", @@ -722,7 +913,14 @@ "loginIntoYourAccount": "Connectez-vous à votre compte", "invalidDetailsMessage": "Veuillez entrer un email et un mot de passe valides.", "notAuthorised": "Désolé! ", - "invalidCredentials": "Les informations d'identification saisies sont incorrectes. " + "invalidCredentials": "Les informations d'identification saisies sont incorrectes. ", + "forgotPassword": "Mot de passe oublié", + "emailAddress": "Adresse email", + "enterEmail": "Entrer l'email", + "password": "Mot de passe", + "enterPassword": "Entrer le mot de passe", + "register": "Inscription", + "talawaApiUnavailable": "API Talawa non disponible" }, "userRegister": { "enterFirstName": "Entrez votre prénom", @@ -732,7 +930,16 @@ "login": "Se connecter", "afterRegister": "Enregistré avec succès. ", "passwordNotMatch": "Le mot de passe ne correspond pas. ", - "invalidDetailsMessage": "Veuillez saisir des informations valides." + "invalidDetailsMessage": "Veuillez saisir des informations valides.", + "register": "Inscription", + "firstName": "Prénom", + "lastName": "Nom de famille", + "emailAddress": "Adresse email", + "enterEmail": "Entrer l'email", + "password": "Mot de passe", + "enterPassword": "Entrer le mot de passe", + "confirmPassword": "Confirmer le mot de passe", + "talawaApiUnavailable": "API Talawa non disponible" }, "userNavbar": { "talawa": "Talawa", @@ -741,7 +948,10 @@ "events": "Événements", "chat": "Chat", "donate": "Faire un don", - "language": "Langue" + "language": "Langue", + "settings": "Paramètres", + "logout": "Déconnexion", + "close": "Fermer" }, "userOrganizations": { "allOrganizations": "Toutes les organisations", @@ -750,7 +960,10 @@ "selectOrganization": "Sélectionnez une organisation", "searchUsers": "Rechercher des utilisateurs", "nothingToShow": "Rien à montrer ici.", - "organizations": "Organisations" + "organizations": "Organisations", + "search": "Rechercher", + "filter": "Filtrer", + "searchByName": "Rechercher par nom" }, "userSidebarOrg": { "yourOrganizations": "Vos organisations", @@ -758,13 +971,20 @@ "viewAll": "Voir tout", "talawaUserPortal": "Portail utilisateur Talawa", "my organizations": "Mes organisations", - "communityProfile": "Profil de la communauté" + "communityProfile": "Profil de la communauté", + "users": "utilisateurs", + "requests": "demandes", + "logout": "se déconnecter", + "settings": "paramètres", + "chat": "discussion", + "menu": "Menu" }, "organizationSidebar": { "viewAll": "Voir tout", "events": "Événements", "noEvents": "Aucun événement à afficher", - "noMembers": "Aucun membre à afficher" + "noMembers": "Aucun membre à afficher", + "members": "Membres" }, "postCard": { "likes": "Aime", @@ -843,7 +1063,15 @@ "fullTime": "À temps plein", "partTime": "À temps partiel", "selectCountry": "Choisissez un pays", - "enterState": "Entrez la ville ou l'état" + "enterState": "Entrez la ville ou l'état", + "settings": "Paramètres", + "firstName": "Prénom", + "lastName": "Nom de famille", + "emailAddress": "Adresse email", + "displayImage": "Image d'affichage", + "address": "Adresse", + "saveChanges": "Enregistrer les modifications", + "joined": "Rejoint" }, "donate": { "title": "Des dons", @@ -854,7 +1082,11 @@ "yourPreviousDonations": "Vos dons précédents", "donate": "Faire un don", "nothingToShow": "Rien à montrer ici.", - "success": "Don réussi" + "success": "Don réussi", + "invalidAmount": "Veuillez saisir une valeur numérique pour le montant du don.", + "donationAmountDescription": "Veuillez saisir la valeur numérique du montant du don.", + "donationOutOfRange": "Le montant du don doit être compris entre {{min}} et {{max}}.", + "donateTo": "Faire un don à" }, "userEvents": { "title": "Événements", @@ -875,13 +1107,22 @@ "publicEvent": "est public", "registerable": "Est enregistrable", "monthlyCalendarView": "Calendrier mensuel", - "yearlyCalendarView": "Calendrier annuel" + "yearlyCalendarView": "Calendrier annuel", + "search": "Rechercher", + "cancel": "Annuler", + "create": "Créer", + "eventDescription": "Description de l'événement", + "eventLocation": "Lieu de l'événement", + "startDate": "Date de début", + "endDate": "Date de fin" }, "userEventCard": { "starts": "Départs", "ends": "Prend fin", "creator": "Créateur", - "alreadyRegistered": "Déjà enregistré" + "alreadyRegistered": "Déjà enregistré", + "location": "Lieu", + "register": "Inscription" }, "advertisement": { "title": "Annonces", @@ -906,11 +1147,21 @@ "editAdvertisement": "Modifier l'annonce", "advertisementDeleted": "Publicité supprimée avec succès.", "endDateGreaterOrEqual": "La date de fin doit être supérieure ou égale à la date de début", - "advertisementCreated": "Publicité créée avec succès." + "advertisementCreated": "Publicité créée avec succès.", + "pHeading": "Titre principal", + "delete": "Supprimer", + "close": "Fermer", + "no": "Non", + "yes": "Oui", + "edit": "Modifier", + "saveChanges": "Enregistrer les modifications", + "endOfResults": "Fin des résultats" }, "userChat": { "chat": "Chat", - "contacts": "Contacts" + "contacts": "Contacts", + "search": "rechercher", + "messages": "messages" }, "userChatRoom": { "selectContact": "Sélectionnez un contact pour démarrer la conversation", @@ -928,20 +1179,28 @@ "String": "Chaîne", "Boolean": "Booléen", "Date": "Date", - "Number": "Nombre" + "Number": "Nombre", + "saveChanges": "Enregistrer les modifications" }, "orgActionItemCategories": { "enableButton": "Activer", "disableButton": "Désactiver", "updateActionItemCategory": "Mise à jour", "actionItemCategoryName": "Nom", - "actionItemCategoryDetails": "Détails de la catégorie d'élément d'action", + "categoryDetails": "Détails de la catégorie", "enterName": "Entrez le nom", "successfulCreation": "Catégorie d'élément d'action créée avec succès", "successfulUpdation": "Catégorie d'élément d'action mise à jour avec succès", "sameNameConflict": "Veuillez changer le nom pour effectuer une mise à jour", "categoryEnabled": "Catégorie d'élément d'action activée", - "categoryDisabled": "Catégorie d'élément d'action désactivée" + "categoryDisabled": "Catégorie d'élément d'action désactivée", + "noActionItemCategories": "Aucune catégorie d'élément d'action", + "status": "Statut", + "categoryDeleted": "Catégorie d'élément d'action supprimée avec succès", + "deleteCategory": "Supprimer la catégorie", + "deleteCategoryMsg": "Êtes-vous sûr de vouloir supprimer cette catégorie d'élément d'action ?", + "createButton": "Créer", + "editButton": "Modifier" }, "organizationVenues": { "title": "Lieux", @@ -965,7 +1224,12 @@ "view": "Voir", "venueTitleError": "Le titre du lieu ne peut pas être vide !", "venueCapacityError": "La capacité doit être un nombre positif !", - "searchBy": "Recherché par" + "searchBy": "Recherché par", + "description": "Description", + "edit": "Modifier", + "delete": "Supprimer", + "name": "Nom", + "desc": "Description" }, "addMember": { "title": "Ajouter un membre", @@ -979,7 +1243,18 @@ "organization": "Organisation", "invalidDetailsMessage": "Veuillez fournir tous les détails requis.", "passwordNotMatch": "Les mots de passe ne correspondent pas.", - "addMember": "Ajouter un membre" + "addMember": "Ajouter un membre", + "firstName": "Prénom", + "lastName": "Nom de famille", + "emailAddress": "Adresse email", + "enterEmail": "Entrer l'email", + "password": "Mot de passe", + "enterPassword": "Entrer le mot de passe", + "confirmPassword": "Confirmer le mot de passe", + "cancel": "Annuler", + "create": "Créer", + "user": "Utilisateur", + "profile": "Profil" }, "eventActionItems": { "title": "Éléments d'action", @@ -1007,7 +1282,9 @@ "actionItemStatus": "Statut de l'action", "actionItemCompleted": "Élément d'action terminé", "markCompletion": "Marquer l'achèvement", - "save": "Sauvegarder" + "save": "Sauvegarder", + "yes": "Oui", + "no": "Non" }, "checkIn": { "errorCheckingIn": "Erreur lors de l'enregistrement", diff --git a/public/locales/hi/common.json b/public/locales/hi/common.json index 3889f73e59..c044033db5 100644 --- a/public/locales/hi/common.json +++ b/public/locales/hi/common.json @@ -82,5 +82,14 @@ "addedSuccessfully": "{{item}} सफलतापूर्वक जोड़ा गया", "updatedSuccessfully": "{{item}} सफलतापूर्वक अपडेट किया गया", "removedSuccessfully": "{{item}} सफलतापूर्वक हटाया गया", - "successfullyUpdated": "सफलतापूर्वक अपडेट किया गया" + "successfullyUpdated": "सफलतापूर्वक अपडेट किया गया", + "all": "सभी", + "active": "सक्रिय", + "disabled": "अक्षम", + "pending": "लंबित", + "completed": "पूरा हुआ", + "late": "देर से", + "createdLatest": "नवीनतम बनाया गया", + "createdEarliest": "सबसे पहले बनाया गया", + "searchBy": "के द्वारा खोजें {{item}}" } diff --git a/public/locales/hi/translation.json b/public/locales/hi/translation.json index 0f74a5978e..057980a68b 100644 --- a/public/locales/hi/translation.json +++ b/public/locales/hi/translation.json @@ -1,49 +1,83 @@ { "loginPage": { - "title": "तलावा प्रशासन", - "fromPalisadoes": "पैलिसाडोज़ फाउंडेशन के स्वयंसेवकों द्वारा एक खुला स्रोत एप्लिकेशन", + "title": "तालावा व्यवस्थापक", + "fromPalisadoes": "Palisadoes फाउंडेशन स्वयंसेवकों द्वारा विकसित एक ओपन-सोर्स एप्लिकेशन", "userLogin": "उपयोगकर्ता लॉगिन", - "atleast_8_char_long": "कम से कम 8 अक्षर लंबा", - "atleast_6_char_long": "कम से कम 6 अक्षर लंबा", - "firstName_invalid": "प्रथम नाम में केवल छोटे और बड़े अक्षर होने चाहिए", - "lastName_invalid": "अंतिम नाम में केवल छोटे और बड़े अक्षर होने चाहिए", - "password_invalid": "पासवर्ड में कम से कम एक लोअरकेस अक्षर, एक अपरकेस अक्षर, एक संख्यात्मक मान और एक विशेष अक्षर होना चाहिए", + "atleast_8_char_long": "कम से कम 8 अक्षर लंबे", + "atleast_6_char_long": "कम से कम 6 अक्षर लंबे", + "firstName_invalid": "पहला नाम केवल छोटे और बड़े अक्षरों को शामिल कर सकता है", + "lastName_invalid": "अंतिम नाम केवल छोटे और बड़े अक्षरों को शामिल कर सकता है", + "password_invalid": "पासवर्ड में कम से कम 1 छोटा अक्षर, 1 बड़ा अक्षर, 1 संख्या और 1 विशेष अक्षर होना चाहिए", "email_invalid": "ईमेल में कम से कम 8 अक्षर होने चाहिए", - "Password_and_Confirm_password_mismatches.": "पासवर्ड और पासवर्ड बेमेल होने की पुष्टि करें।", - "doNotOwnAnAccount": "क्या आपके पास कोई खाता नहीं है?", + "Password_and_Confirm_password_mismatches.": "पासवर्ड और पुष्टि पासवर्ड मेल नहीं खाते।", + "doNotOwnAnAccount": "कोई खाता नहीं है?", "captchaError": "कैप्चा त्रुटि!", - "Please_check_the_captcha": "कृपया, कैप्चा जांचें।", - "Something_went_wrong": "कुछ ग़लत हो गया, कृपया कुछ देर बाद प्रयास करें।", - "passwordMismatches": "पासवर्ड और पासवर्ड बेमेल होने की पुष्टि करें।", - "fillCorrectly": "सभी विवरण सही-सही भरें।", - "successfullyRegistered": "पंजीकरण सफलतापूर्वक हो गया है। ", + "Please_check_the_captcha": "कृपया कैप्चा जांचें।", + "Something_went_wrong": "कुछ गलत हो गया, कृपया बाद में पुनः प्रयास करें।", + "passwordMismatches": "पासवर्ड और पुष्टि पासवर्ड मेल नहीं खाते।", + "fillCorrectly": "सभी विवरण सही ढंग से भरें।", + "successfullyRegistered": "सफलतापूर्वक पंजीकृत।", "lowercase_check": "कम से कम एक छोटा अक्षर", "uppercase_check": "कम से कम एक बड़ा अक्षर", - "numeric_value_check": "कम से कम एक संख्यात्मक मान निर्धारित करें", - "special_char_check": "कम से कम एक विशेष पात्र", + "numeric_value_check": "कम से कम एक संख्या", + "special_char_check": "कम से कम एक विशेष अक्षर", "selectOrg": "एक संगठन चुनें", - "afterRegister": "पंजीकरण सफलतापूर्वक हो गया है। " + "afterRegister": "सफलतापूर्वक पंजीकृत।", + "talawa_portal": "तालावा पोर्टल", + "login": "लॉगिन", + "register": "पंजीकरण", + "firstName": "पहला नाम", + "lastName": "अंतिम नाम", + "email": "ईमेल", + "password": "पासवर्ड", + "confirmPassword": "पुष्टि पासवर्ड", + "forgotPassword": "पासवर्ड भूल गए", + "enterEmail": "ईमेल दर्ज करें", + "enterPassword": "पासवर्ड दर्ज करें", + "talawaApiUnavailable": "तालावा एपीआई अनुपलब्ध", + "notAuthorised": "अनधिकृत", + "notFound": "नहीं मिला", + "OR": "या", + "admin": "व्यवस्थापक", + "user": "उपयोगकर्ता", + "loading": "लोड हो रहा है" }, "userLoginPage": { - "title": "तलावा प्रशासन", - "fromPalisadoes": "पैलिसाडोज़ फाउंडेशन के स्वयंसेवकों द्वारा एक खुला स्रोत एप्लिकेशन", - "atleast_8_char_long": "कम से कम 8 अक्षर लंबा", - "Password_and_Confirm_password_mismatches.": "पासवर्ड और पासवर्ड बेमेल होने की पुष्टि करें।", - "doNotOwnAnAccount": "क्या आपके पास कोई खाता नहीं है?", + "title": "तालावा व्यवस्थापक", + "fromPalisadoes": "Palisadoes फाउंडेशन स्वयंसेवकों द्वारा विकसित एक ओपन-सोर्स एप्लिकेशन", + "atleast_8_char_long": "कम से कम 8 अक्षर लंबे", + "Password_and_Confirm_password_mismatches.": "पासवर्ड और पुष्टि पासवर्ड मेल नहीं खाते।", + "doNotOwnAnAccount": "कोई खाता नहीं है?", "captchaError": "कैप्चा त्रुटि!", - "Please_check_the_captcha": "कृपया, कैप्चा जांचें।", - "Something_went_wrong": "कुछ ग़लत हो गया, कृपया कुछ देर बाद प्रयास करें।", - "passwordMismatches": "पासवर्ड और पासवर्ड बेमेल होने की पुष्टि करें।", - "fillCorrectly": "सभी विवरण सही-सही भरें।", - "successfullyRegistered": "पंजीकरण सफलतापूर्वक हो गया है। ", + "Please_check_the_captcha": "कृपया कैप्चा जांचें।", + "Something_went_wrong": "कुछ गलत हो गया, कृपया बाद में पुनः प्रयास करें।", + "passwordMismatches": "पासवर्ड और पुष्टि पासवर्ड मेल नहीं खाते।", + "fillCorrectly": "सभी विवरण सही ढंग से भरें।", + "successfullyRegistered": "सफलतापूर्वक पंजीकृत।", "userLogin": "उपयोगकर्ता लॉगिन", - "afterRegister": "पंजीकरण सफलतापूर्वक हो गया है। ", - "selectOrg": "एक संगठन चुनें" + "afterRegister": "सफलतापूर्वक पंजीकृत।", + "selectOrg": "एक संगठन चुनें", + "talawa_portal": "तालावा पोर्टल", + "login": "लॉगिन", + "register": "पंजीकरण", + "firstName": "पहला नाम", + "lastName": "अंतिम नाम", + "email": "ईमेल", + "password": "पासवर्ड", + "confirmPassword": "पुष्टि पासवर्ड", + "forgotPassword": "पासवर्ड भूल गए", + "enterEmail": "ईमेल दर्ज करें", + "enterPassword": "पासवर्ड दर्ज करें", + "talawaApiUnavailable": "तालावा एपीआई अनुपलब्ध", + "notAuthorised": "अनधिकृत", + "notFound": "नहीं मिला", + "OR": "या", + "loading": "लोड हो रहा है" }, "latestEvents": { - "eventCardTitle": "आगामी कार्यक्रम", + "eventCardTitle": "आगामी घटनाएँ", "eventCardSeeAll": "सभी देखें", - "noEvents": "कोई आगामी ईवेंट नहीं" + "noEvents": "कोई आगामी घटनाएँ नहीं हैं" }, "latestPosts": { "latestPostsTitle": "नवीनतम पोस्ट", @@ -51,65 +85,92 @@ "noPostsCreated": "कोई पोस्ट नहीं बनाई गई" }, "listNavbar": { - "roles": "भूमिकाएँ" + "roles": "भूमिकाएँ", + "talawa_portal": "तालावा पोर्टल", + "requests": "अनुरोध", + "logout": "लॉगआउट" }, "leftDrawer": { "my organizations": "मेरे संगठन", "requests": "सदस्यता अनुरोध", - "communityProfile": "सामुदायिक प्रोफ़ाइल" + "communityProfile": "समुदाय प्रोफ़ाइल", + "talawaAdminPortal": "तालावा व्यवस्थापक पोर्टल", + "menu": "मेनू", + "users": "उपयोगकर्ता", + "logout": "लॉगआउट" }, "leftDrawerOrg": { "Dashboard": "डैशबोर्ड", "People": "लोग", - "Events": "आयोजन", + "Events": "घटनाएँ", "Contributions": "योगदान", - "Posts": "पदों", - "Block/Unblock": "ब्लॉक/अनब्लॉक करें", - "Plugins": "प्लग-इन", + "Posts": "पोस्ट", + "Block/Unblock": "ब्लॉक/अनब्लॉक", + "Plugins": "प्लगइन्स", "Plugin Store": "प्लगइन स्टोर", - "Advertisement": "विज्ञापनों", + "Advertisement": "विज्ञापन", "allOrganizations": "सभी संगठन", - "yourOrganization": "आपकी संगठन", - "notification": "अधिसूचना", + "yourOrganization": "आपका संगठन", + "notification": "सूचना", "language": "भाषा", - "notifications": "सूचनाएं", - "spamsThe": "स्पैम करता है", + "notifications": "सूचनाएँ", + "spamsThe": "स्पैम", "group": "समूह", - "noNotifications": "कोई सूचनाएं नहीं" + "noNotifications": "कोई सूचनाएँ नहीं", + "talawaAdminPortal": "तालावा व्यवस्थापक पोर्टल", + "menu": "मेनू", + "talawa_portal": "तालावा पोर्टल", + "settings": "सेटिंग्स", + "logout": "लॉगआउट", + "close": "बंद करें" }, "orgList": { - "title": "तलावा संगठन", + "title": "तालावा संगठन", "you": "आप", - "designation": "पद का नाम", + "designation": "पदनाम", "my organizations": "मेरे संगठन", "createOrganization": "संगठन बनाएं", "createSampleOrganization": "नमूना संगठन बनाएं", "city": "शहर", - "countryCode": "कंट्री कोड", - "dependentLocality": "आश्रित इलाका", + "countryCode": "देश कोड", + "dependentLocality": "निर्भर स्थान", "line1": "लाइन 1", "line2": "लाइन 2", - "postalCode": "डाक कोड", - "sortingCode": "कोड क्रमबद्ध करना", - "state": "राज्य/प्रान्त", + "postalCode": "पोस्टल कोड", + "sortingCode": "सॉर्टिंग कोड", + "state": "राज्य", "userRegistrationRequired": "उपयोगकर्ता पंजीकरण आवश्यक", - "visibleInSearch": "खोज में दृश्यमान", + "visibleInSearch": "खोज में दिखाई दे", "enterName": "नाम दर्ज करें", - "sort": "क्रम से लगाना", + "sort": "प्रकार", "Latest": "नवीनतम", - "Earliest": "जल्द से जल्द", - "noOrgErrorTitle": "संगठन नहीं मिले", - "sampleOrgDuplicate": "केवल एक नमूना संगठन को अनुमति दी गई", - "noOrgErrorDescription": "कृपया डैशबोर्ड के माध्यम से एक संगठन बनाएं", - "manageFeatures": "सुविधाओं को प्रबंधित करें", - "manageFeaturesInfo": "सृजन सफल! ", - "goToStore": "प्लगइन स्टोर पर जाएँ", + "Earliest": "सबसे पहले", + "noOrgErrorTitle": "संगठन नहीं मिला", + "sampleOrgDuplicate": "केवल एक नमूना संगठन की अनुमति है", + "noOrgErrorDescription": "कृपया डैशबोर्ड के माध्यम से संगठन बनाएं", + "manageFeatures": "विशेषताएं प्रबंधित करें", + "manageFeaturesInfo": "सफलतापूर्वक बनाया गया!", + "goToStore": "प्लगइन स्टोर पर जाएं", "enableEverything": "सब कुछ सक्षम करें", - "sampleOrgSuccess": "नमूना संगठन सफलतापूर्वक बनाया गया" + "sampleOrgSuccess": "नमूना संगठन सफलतापूर्वक बनाया गया", + "name": "नाम", + "email": "ईमेल", + "searchByName": "नाम से खोजें", + "description": "विवरण", + "location": "स्थान", + "address": "पता", + "displayImage": "छवि प्रदर्शित करें", + "filter": "फ़िल्टर", + "cancel": "रद्द करें", + "endOfResults": "परिणाम समाप्त", + "noResultsFoundFor": "कोई परिणाम नहीं मिला", + "OR": "या" }, "orgListCard": { - "manage": "प्रबंधित करना", - "sampleOrganization": "नमूना संगठन" + "manage": "प्रबंधित करें", + "sampleOrganization": "संगठन नमूना", + "admins": "प्रशासक", + "members": "सदस्य" }, "paginationList": { "rowsPerPage": "प्रति पृष्ठ पंक्तियाँ", @@ -117,88 +178,131 @@ }, "requests": { "title": "सदस्यता अनुरोध", - "sl_no": "क्र.सं. ", - "accept": "स्वीकार करना", - "reject": "अस्वीकार करना", + "sl_no": "क्रम संख्या", + "accept": "स्वीकार करें", + "reject": "अस्वीकार करें", "searchRequests": "सदस्यता अनुरोध खोजें", - "noOrgError": "संगठन नहीं मिला, कृपया डैशबोर्ड के माध्यम से एक संगठन बनाएं", + "noOrgError": "संगठन नहीं मिला, कृपया डैशबोर्ड के माध्यम से संगठन बनाएं", "noRequestsFound": "कोई सदस्यता अनुरोध नहीं मिला", "acceptedSuccessfully": "अनुरोध सफलतापूर्वक स्वीकार किया गया", - "rejectedSuccessfully": "अनुरोध सफलतापूर्वक अस्वीकार कर दिया गया", - "noOrgErrorTitle": "संगठन नहीं मिले", - "noOrgErrorDescription": "कृपया डैशबोर्ड के माध्यम से एक संगठन बनाएं" + "rejectedSuccessfully": "अनुरोध सफलतापूर्वक अस्वीकार किया गया", + "noOrgErrorTitle": "संगठन नहीं मिला", + "noOrgErrorDescription": "कृपया डैशबोर्ड के माध्यम से संगठन बनाएं", + "name": "नाम", + "email": "ईमेल", + "endOfResults": "परिणाम समाप्त", + "noResultsFoundFor": "कोई परिणाम नहीं मिला" }, "users": { - "title": "तलावा भूमिकाएँ", - "joined_organizations": "संगठनों से जुड़े", + "title": "तालावा भूमिकाएँ", + "joined_organizations": "संगठनों में शामिल हुए", "blocked_organizations": "अवरुद्ध संगठन", - "orgJoinedBy": "संगठनों से जुड़े", - "orgThatBlocked": "वे संगठन जिन्होंने अवरुद्ध किया", - "hasNotJoinedAnyOrg": "किसी भी संगठन में शामिल नहीं हुआ है", - "isNotBlockedByAnyOrg": "किसी भी संगठन द्वारा अवरुद्ध नहीं किया गया है", + "orgJoinedBy": "द्वारा शामिल संगठन", + "orgThatBlocked": "अवरुद्ध संगठन", + "hasNotJoinedAnyOrg": "किसी भी संगठन में शामिल नहीं हुआ", + "isNotBlockedByAnyOrg": "किसी भी संगठन द्वारा अवरुद्ध नहीं किया गया", "searchByOrgName": "संगठन के नाम से खोजें", - "view": "देखना", + "view": "दृश्य", "enterName": "नाम दर्ज करें", - "loadingUsers": "उपयोगकर्ता लोड हो रहे हैं...", + "loadingUsers": "उपयोगकर्ताओं को लोड कर रहा है...", "noUserFound": "कोई उपयोगकर्ता नहीं मिला", - "sort": "क्रम से लगाना", - "Newest": "नवीनतम पहले", - "Oldest": "सबसे पुराना पहले", - "noOrgError": "संगठन नहीं मिला, कृपया डैशबोर्ड के माध्यम से एक संगठन बनाएं", - "roleUpdated": "भूमिका अद्यतन की गई.", - "joinNow": "अब शामिल हों", - "visit": "मिलने जाना", - "withdraw": "चौड़ाई निकालना", - "removeUserFrom": "{{org}} से उपयोगकर्ता हटाएं", - "removeConfirmation": "क्या आप वाकई '{{org}}' संगठन से '{{name}}' को हटाना चाहते हैं?" + "sort": "प्रकार", + "Newest": "नवीनतम", + "Oldest": "सबसे पुराना", + "noOrgError": "संगठन नहीं मिला, कृपया डैशबोर्ड के माध्यम से संगठन बनाएं", + "roleUpdated": "भूमिका अपडेट की गई।", + "joinNow": "अभी शामिल हों", + "visit": "यात्रा", + "withdraw": "वापस लें", + "removeUserFrom": "{{org}} से उपयोगकर्ता को हटाएं", + "removeConfirmation": "क्या आप वाकई '{{name}}' को संगठन '{{org}}' से हटाना चाहते हैं?", + "searchByName": "नाम से खोजें", + "users": "उपयोगकर्ता", + "name": "नाम", + "email": "ईमेल", + "endOfResults": "परिणाम समाप्त", + "admin": "प्रशासक", + "superAdmin": "सुपर प्रशासक", + "user": "उपयोगकर्ता", + "filter": "फ़िल्टर", + "noResultsFoundFor": "कोई परिणाम नहीं मिला", + "talawaApiUnavailable": "तालावा एपीआई अनुपलब्ध", + "cancel": "रद्द करें", + "admins": "प्रशासक", + "members": "सदस्य", + "orgJoined": "संगठन में शामिल", + "MembershipRequestSent": "सदस्यता अनुरोध भेजा गया", + "AlreadyJoined": "पहले से शामिल", + "errorOccured": "त्रुटि हुई" }, "communityProfile": { - "title": "सामुदायिक प्रोफ़ाइल", + "title": "समुदाय प्रोफ़ाइल", "editProfile": "प्रोफ़ाइल संपादित करें", - "communityProfileInfo": "ये विवरण आपके और आपके समुदाय के सदस्यों के लिए लॉगिन/साइनअप स्क्रीन पर दिखाई देंगे", + "communityProfileInfo": "ये विवरण आपके और आपके समुदाय के सदस्यों के लॉगिन/पंजीकरण स्क्रीन पर दिखाई देंगे", "communityName": "समुदाय का नाम", - "wesiteLink": "वेबसाइट की लिंक", - "logo": "प्रतीक चिन्ह", + "wesiteLink": "वेबसाइट लिंक", + "logo": "लोगो", "social": "सोशल मीडिया लिंक", - "url": "यू आर एल दर्ज करो", - "profileChangedMsg": "प्रोफ़ाइल विवरण सफलतापूर्वक अपडेट किया गया.", - "resetData": "प्रोफ़ाइल विवरण सफलतापूर्वक रीसेट करें।" + "url": "यूआरएल दर्ज करें", + "profileChangedMsg": "प्रोफ़ाइल विवरण सफलतापूर्वक अपडेट किया गया।", + "resetData": "प्रोफ़ाइल विवरण सफलतापूर्वक रीसेट किया गया।" }, "dashboard": { "title": "डैशबोर्ड", "about": "के बारे में", - "deleteThisOrganization": "इस संगठन को हटाएँ", + "deleteThisOrganization": "इस संगठन को हटाएं", "statistics": "आंकड़े", - "posts": "पदों", - "events": "आयोजन", - "blockedUsers": "रोके गए उपयोगकर्ता", - "viewAll": "सभी को देखें", - "upcomingEvents": "आगामी कार्यक्रम", - "noUpcomingEvents": "कोई आगामी ईवेंट नहीं", + "posts": "पोस्ट", + "events": "घटनाएँ", + "blockedUsers": "अवरुद्ध उपयोगकर्ता", + "viewAll": "सभी देखें", + "upcomingEvents": "आगामी घटनाएँ", + "noUpcomingEvents": "कोई आगामी घटनाएँ नहीं हैं", "latestPosts": "नवीनतम पोस्ट", - "noPostsPresent": "कोई पोस्ट मौजूद नहीं", + "noPostsPresent": "कोई पोस्ट नहीं हैं", "membershipRequests": "सदस्यता अनुरोध", - "noMembershipRequests": "कोई सदस्यता अनुरोध मौजूद नहीं है" + "noMembershipRequests": "कोई सदस्यता अनुरोध नहीं हैं", + "location": "स्थान", + "members": "सदस्य", + "admins": "प्रशासक", + "requests": "अनुरोध", + "talawaApiUnavailable": "तालावा एपीआई अनुपलब्ध" }, "organizationPeople": { - "title": "तलवा सदस्य", + "title": "तालावा सदस्य", "filterByName": "नाम से फ़िल्टर करें", - "filterByLocation": "स्थान के अनुसार फ़िल्टर करें", - "filterByEvent": "इवेंट के अनुसार फ़िल्टर करें", + "filterByLocation": "स्थान से फ़िल्टर करें", + "filterByEvent": "घटना से फ़िल्टर करें", "searchName": "नाम दर्ज करें", - "searchevent": "इवेंट दर्ज करें", + "searchevent": "घटना दर्ज करें", "searchFullName": "पूरा नाम दर्ज करें", "people": "लोग", - "sort": "भूमिका के आधार पर खोजें", - "actions": "कार्रवाई", + "sort": "भूमिका से खोजें", + "actions": "क्रियाएँ", "addMembers": "सदस्य जोड़ें", "existingUser": "मौजूदा उपयोगकर्ता", - "newUser": "नए उपयोगकर्ता", + "newUser": "नया उपयोगकर्ता", "enterFirstName": "अपना पहला नाम दर्ज करें", "enterLastName": "अपना अंतिम नाम दर्ज करें", - "enterConfirmPassword": "पुष्टि करने के लिए अपना पासवर्ड दर्ज करें", + "enterConfirmPassword": "अपना पासवर्ड पुष्टि के लिए दर्ज करें", "organization": "संगठन", - "invalidDetailsMessage": "कृपया वैध विवरण दर्ज करें." + "invalidDetailsMessage": "कृपया मान्य विवरण दर्ज करें।", + "members": "सदस्य", + "admins": "प्रशासक", + "users": "उपयोगकर्ता", + "searchFirstName": "प्रथम नाम खोजें", + "searchLastName": "अंतिम नाम खोजें", + "firstName": "प्रथम नाम", + "lastName": "अंतिम नाम", + "emailAddress": "ईमेल पता", + "enterEmail": "ईमेल दर्ज करें", + "password": "पासवर्ड", + "enterPassword": "पासवर्ड दर्ज करें", + "confirmPassword": "पासवर्ड की पुष्टि करें", + "user": "उपयोगकर्ता", + "profile": "प्रोफ़ाइल", + "create": "बनाएं", + "cancel": "रद्द करें" }, "organizationTags": { "title": "संस्थान टैग", @@ -232,19 +336,29 @@ }, "userListCard": { "addAdmin": "व्यवस्थापक जोड़ें", - "addedAsAdmin": "उपयोगकर्ता को व्यवस्थापक के रूप में जोड़ा गया है." + "addedAsAdmin": "उपयोगकर्ता को व्यवस्थापक के रूप में जोड़ा गया है.", + "joined": "शामिल हुए", + "talawaApiUnavailable": "Talawa API अनुपलब्ध" }, "orgAdminListCard": { "remove": "निकालना", "removeAdmin": "व्यवस्थापक हटाएँ", "removeAdminMsg": "क्या आप इस व्यवस्थापक को हटाना चाहते हैं?", - "adminRemoved": "व्यवस्थापक को हटा दिया गया है." + "adminRemoved": "व्यवस्थापक को हटा दिया गया है.", + "joined": "शामिल हुए", + "no": "नहीं", + "yes": "हाँ", + "talawaApiUnavailable": "Talawa API अनुपलब्ध" }, "orgPeopleListCard": { "remove": "निकालना", "removeMember": "सदस्य हटाएँ", "removeMemberMsg": "क्या आप इस सदस्य को हटाना चाहते हैं?", - "memberRemoved": "सदस्य को हटा दिया गया है" + "memberRemoved": "सदस्य को हटा दिया गया है", + "joined": "शामिल हुए", + "no": "नहीं", + "yes": "हाँ", + "talawaApiUnavailable": "Talawa API अनुपलब्ध" }, "organizationEvents": { "title": "आयोजन", @@ -275,7 +389,14 @@ "never": "कभी नहीं", "on": "पर", "after": "बाद", - "occurences": "घटनाओं" + "occurences": "घटनाओं", + "events": "कार्यक्रम", + "description": "विवरण", + "location": "स्थान", + "startDate": "प्रारंभ तिथि", + "endDate": "समाप्ति तिथि", + "talawaApiUnavailable": "Talawa API अनुपलब्ध", + "done": "पूर्ण" }, "organizationActionItems": { "actionItemCategory": "कार्य आइटम श्रेणी", @@ -286,7 +407,6 @@ "assignmentDate": "असाइनमेंट दिनांक", "active": "सक्रिय", "clearFilters": "फ़िल्टर साफ़ करें", - "completed": "पुरा होना।", "completionDate": "पूरा करने की तिथि", "createActionItem": "कार्रवाई आइटम बनाएं", "deleteActionItem": "क्रिया आइटम हटाएँ", @@ -311,7 +431,18 @@ "successfulCreation": "कार्रवाई आइटम सफलतापूर्वक बनाया गया", "successfulUpdation": "कार्रवाई आइटम सफलतापूर्वक अपडेट किया गया", "successfulDeletion": "कार्रवाई आइटम सफलतापूर्वक हटा दिया गया", - "title": "एक्शन आइटम्स" + "title": "एक्शन आइटम्स", + "category": "श्रेणी", + "allotedHours": "आवंटित घंटे", + "latestDueDate": "सबसे अधिक नियत तिथि", + "earliestDueDate": "सबसे पहले की नियत तिथि", + "updateActionItem": "कार्य आइटम अपडेट करें", + "noneUpdated": "कोई फ़ील्ड अपडेट नहीं किया गया", + "updateStatusMsg": "क्या आप वाकई इस कार्य आइटम को लंबित के रूप में चिह्नित करना चाहते हैं?", + "close": "बंद करें", + "eventActionItems": "कार्यक्रम कार्रवाई आइटम", + "no": "नहीं", + "yes": "हाँ" }, "organizationAgendaCategory": { "agendaCategoryDetails": "एजेंडा श्रेणी विवरण", @@ -390,7 +521,17 @@ "never": "कभी नहीं", "on": "पर", "after": "बाद", - "occurences": "घटनाओं" + "occurences": "घटनाओं", + "location": "स्थान", + "no": "नहीं", + "yes": "हाँ", + "description": "विवरण", + "startDate": "प्रारंभ तिथि", + "endDate": "समाप्ति तिथि", + "registerEvent": "कार्यक्रम के लिए पंजीकरण करें", + "close": "बंद करें", + "talawaApiUnavailable": "Talawa API अनुपलब्ध", + "done": "समाप्त" }, "funds": { "title": "फंड", @@ -466,7 +607,9 @@ "pledges": "प्रतिज्ञाएँ", "endsOn": "पर समाप्त होता है", "raisedAmount": "उठाया गया राशि", - "pledgedAmount": "प्रतिबद्ध राशि" + "pledgedAmount": "प्रतिबद्ध राशि", + "startDate": "प्रारंभ तिथि", + "endDate": "समाप्ति तिथि" }, "orgPost": { "title": "पदों", @@ -494,7 +637,8 @@ "postCreatedSuccess": "बधाई हो! ", "pinPost": "पिन पद", "Next": "अगला पृष्ठ", - "Previous": "पिछला पृष्ठ" + "Previous": "पिछला पृष्ठ", + "cancel": "रद्द करें" }, "postNotFound": { "post": "डाक", @@ -509,7 +653,8 @@ "user not found!": "उपयोगकर्ता नहीं मिला!", "member not found!": "सदस्य अनुपस्थित!", "admin not found!": "व्यवस्थापक नहीं मिला!", - "roles not found!": "भूमिकाएँ नहीं मिलीं!" + "roles not found!": "भूमिकाएँ नहीं मिलीं!", + "user": "उपयोगकर्ता" }, "orgPostCard": { "author": "लेखक", @@ -528,7 +673,12 @@ "postDeleted": "पोस्ट सफलतापूर्वक हटा दी गई.", "postUpdated": "पोस्ट सफलतापूर्वक अपडेट किया गया.", "tag": " आपका ब्राउज़र में वीडियो टैग समर्थित नहीं है", - "pin": "पिन पद" + "pin": "पिन पद", + "edit": "संपादित करें", + "no": "नहीं", + "yes": "हाँ", + "close": "बंद करें", + "talawaApiUnavailable": "Talawa API अनुपलब्ध" }, "blockUnblockUser": { "title": "उपयोगकर्ता को ब्लॉक/अनब्लॉक करें", @@ -544,7 +694,12 @@ "blockedUsers": "रोके गए उपयोगकर्ता", "searchByFirstName": "प्रथम नाम से खोजें", "searchByLastName": "अंतिम नाम से खोजें", - "noSpammerFound": "कोई स्पैमर नहीं मिला" + "noSpammerFound": "कोई स्पैमर नहीं मिला", + "searchByName": "नाम से खोजें", + "name": "नाम", + "email": "ईमेल", + "talawaApiUnavailable": "Talawa API अनुपलब्ध", + "noResultsFoundFor": "के लिए कोई परिणाम नहीं मिला" }, "eventManagement": { "title": "इवेंट मैनेजमेंट", @@ -569,7 +724,10 @@ "errorSendingMail": "मेल भेजने में त्रुटि.", "passwordMismatches": "पासवर्ड और पासवर्ड बेमेल होने की पुष्टि करें।", "passwordChanges": "पासवर्ड सफलतापूर्वक बदल गया.", - "OTPsent": "ओटीपी आपके पंजीकृत ईमेल पर भेजा जाता है।" + "OTPsent": "ओटीपी आपके पंजीकृत ईमेल पर भेजा जाता है।", + "forgotPassword": "पासवर्ड भूल गए", + "password": "पासवर्ड", + "talawaApiUnavailable": "Talawa API अनुपलब्ध" }, "pageNotFound": { "404": "404", @@ -599,15 +757,16 @@ "amount": "मात्रा" }, "orgSettings": { - "title": "समायोजन", + "title": "सेटिंग्स", "general": "सामान्य", - "actionItemCategories": "कार्रवाई आइटम श्रेणियाँ", - "updateOrganization": "संगठन अद्यतन करें", + "actionItemCategories": "कार्य आइटम श्रेणियाँ", + "updateOrganization": "संगठन अपडेट करें", "seeRequest": "अनुरोध देखें", "noData": "कोई डेटा नहीं", - "otherSettings": "अन्य सेटिंग", + "otherSettings": "अन्य सेटिंग्स", "changeLanguage": "भाषा बदलें", - "manageCustomFields": "कस्टम फ़ील्ड प्रबंधित करें" + "manageCustomFields": "कस्टम फ़ील्ड प्रबंधित करें", + "agendaItemCategories": "एजेंडा आइटम श्रेणियाँ" }, "deleteOrg": { "deleteOrganization": "संगठन हटाएँ", @@ -615,18 +774,30 @@ "deleteMsg": "क्या आप इस संगठन को हटाना चाहते हैं?", "confirmDelete": "हटाने की पुष्टि करें", "longDelOrgMsg": "संगठन हटाएं बटन पर क्लिक करने से संगठन अपने ईवेंट, टैग और सभी संबंधित डेटा के साथ स्थायी रूप से हटा दिया जाएगा।", - "successfullyDeletedSampleOrganization": "नमूना संगठन सफलतापूर्वक हटा दिया गया" + "successfullyDeletedSampleOrganization": "नमूना संगठन सफलतापूर्वक हटा दिया गया", + "cancel": "रद्द करें" }, "userUpdate": { "appLanguageCode": "डिफ़ॉल्ट भाषा", - "userType": "उपयोगकर्ता का प्रकार" + "userType": "उपयोगकर्ता का प्रकार", + "firstName": "प्रथम नाम", + "lastName": "अंतिम नाम", + "email": "ईमेल", + "password": "पासवर्ड", + "admin": "प्रशासक", + "superAdmin": "सुपर प्रशासक", + "displayImage": "प्रदर्शन छवि", + "saveChanges": "परिवर्तन सहेजें", + "cancel": "रद्द करें" }, "userPasswordUpdate": { "previousPassword": "पिछला पासवर्ड", "newPassword": "नया पासवर्ड", "confirmNewPassword": "नए पासवर्ड की पुष्टि करें", "passCantBeEmpty": "पासवर्ड खाली नहीं हो सकता", - "passNoMatch": "नया पासवर्ड और पुष्टि पासवर्ड मेल नहीं खाते।" + "passNoMatch": "नया पासवर्ड और पुष्टि पासवर्ड मेल नहीं खाते।", + "saveChanges": "परिवर्तन सहेजें", + "cancel": "रद्द करें" }, "orgDelete": { "deleteOrg": "संगठन हटाएं" @@ -634,7 +805,9 @@ "membershipRequest": { "accept": "स्वीकार करना", "reject": "अस्वीकार करना", - "memberAdded": "यह स्वीकृत है" + "memberAdded": "यह स्वीकृत है", + "joined": "शामिल हुए", + "talawaApiUnavailable": "Talawa API अनुपलब्ध" }, "orgUpdate": { "city": "शहर", @@ -648,7 +821,15 @@ "userRegistrationRequired": "उपयोगकर्ता पंजीकरण आवश्यक", "isVisibleInSearch": "खोज में दृश्यमान", "enterNameOrganization": "संगठन का नाम दर्ज करें", - "successfulUpdated": "संगठन सफलतापूर्वक अद्यतन किया गया" + "successfulUpdated": "संगठन सफलतापूर्वक अद्यतन किया गया", + "name": "नाम", + "description": "विवरण", + "location": "स्थान", + "address": "पता", + "displayImage": "प्रदर्शन छवि", + "saveChanges": "परिवर्तन सहेजें", + "cancel": "रद्द करें", + "talawaApiUnavailable": "Talawa API अनुपलब्ध" }, "addOnRegister": { "addNew": "नया जोड़ो", @@ -658,7 +839,9 @@ "pluginDesc": "प्लगइन विवरण", "pName": "उदाहरणार्थ: दान", "cName": "उदाहरण: जॉन डो", - "pDesc": "यह प्लगइन यूआई को सक्षम बनाता है" + "pDesc": "यह प्लगइन यूआई को सक्षम बनाता है", + "close": "बंद करें", + "register": "पंजीकरण करें" }, "addOnStore": { "title": "स्टोर पर जोड़ें", @@ -669,7 +852,8 @@ "pHeading": "प्लग-इन", "install": "स्थापित", "available": "उपलब्ध", - "pMessage": "प्लगइन मौजूद नहीं है" + "pMessage": "प्लगइन मौजूद नहीं है", + "filter": "फ़िल्टर" }, "addOnEntry": { "enable": "सक्रिय", @@ -711,7 +895,14 @@ "membershipRequests": "सदस्यता अनुरोध", "adminForEvents": "घटनाओं के लिए व्यवस्थापक", "addedAsAdmin": "उपयोगकर्ता को व्यवस्थापक के रूप में जोड़ा गया है.", - "userType": "उपयोगकर्ता का प्रकार" + "userType": "उपयोगकर्ता का प्रकार", + "email": "ईमेल", + "displayImage": "प्रदर्शन छवि", + "address": "पता", + "delete": "हटाएं", + "saveChanges": "परिवर्तन सहेजें", + "joined": "शामिल हुए", + "talawaApiUnavailable": "Talawa API अनुपलब्ध" }, "people": { "title": "लोग", @@ -722,7 +913,14 @@ "loginIntoYourAccount": "अपने खाते में लॉगिन करें", "invalidDetailsMessage": "कृपया एक वैध ईमेल और पासवर्ड दर्ज करें।", "notAuthorised": "क्षमा मांगना! ", - "invalidCredentials": "दर्ज किए गए क्रेडेंशियल ग़लत हैं. " + "invalidCredentials": "दर्ज किए गए क्रेडेंशियल ग़लत हैं. ", + "forgotPassword": "पासवर्ड भूल गए", + "emailAddress": "ईमेल पता", + "enterEmail": "ईमेल दर्ज करें", + "password": "पासवर्ड", + "enterPassword": "पासवर्ड दर्ज करें", + "register": "पंजीकरण करें", + "talawaApiUnavailable": "Talawa API अनुपलब्ध" }, "userRegister": { "enterFirstName": "अपना पहला नाम दर्ज करें", @@ -732,7 +930,16 @@ "login": "लॉग इन करें", "afterRegister": "पंजीकरण सफलतापूर्वक हो गया है। ", "passwordNotMatch": "पासवर्ड मेल नहीं खाता. ", - "invalidDetailsMessage": "कृपया वैध विवरण दर्ज करें." + "invalidDetailsMessage": "कृपया वैध विवरण दर्ज करें.", + "register": "पंजीकरण करें", + "firstName": "प्रथम नाम", + "lastName": "अंतिम नाम", + "emailAddress": "ईमेल पता", + "enterEmail": "ईमेल दर्ज करें", + "password": "पासवर्ड", + "enterPassword": "पासवर्ड दर्ज करें", + "confirmPassword": "पासवर्ड की पुष्टि करें", + "talawaApiUnavailable": "Talawa API अनुपलब्ध" }, "userNavbar": { "talawa": "तलावा", @@ -741,7 +948,10 @@ "events": "आयोजन", "chat": "बात करना", "donate": "दान करें", - "language": "भाषा" + "language": "भाषा", + "settings": "समायोजन", + "logout": "लॉग आउट करें", + "close": "बंद करें" }, "userOrganizations": { "allOrganizations": "सभी संगठन", @@ -750,7 +960,10 @@ "selectOrganization": "एक संगठन चुनें", "searchUsers": "उपयोगकर्ता खोजें", "nothingToShow": "यहां दिखाने के लिए कुछ भी नहीं है.", - "organizations": "संगठनों" + "organizations": "संगठनों", + "search": "खोजें", + "filter": "फ़िल्टर", + "searchByName": "नाम से खोजें" }, "userSidebarOrg": { "yourOrganizations": "आपके संगठन", @@ -758,13 +971,20 @@ "viewAll": "सभी को देखें", "talawaUserPortal": "तलावा उपयोगकर्ता पोर्टल", "my organizations": "मेरे संगठन", - "communityProfile": "सामुदायिक प्रोफ़ाइल" + "communityProfile": "सामुदायिक प्रोफ़ाइल", + "users": "उपयोगकर्ता", + "requests": "अनुरोध", + "logout": "लॉग आउट", + "settings": "सेटिंग्स", + "chat": "चैट", + "menu": "मेनू" }, "organizationSidebar": { "viewAll": "सभी को देखें", "events": "आयोजन", "noEvents": "दिखाने के लिए कोई ईवेंट नहीं", - "noMembers": "दिखाने के लिए कोई सदस्य नहीं" + "noMembers": "दिखाने के लिए कोई सदस्य नहीं", + "members": "सदस्य" }, "postCard": { "likes": "पसंद है", @@ -843,7 +1063,15 @@ "fullTime": "पूरा समय", "partTime": "पार्ट टाईम", "selectCountry": "कोई देश चुनें", - "enterState": "शहर या राज्य दर्ज करें" + "enterState": "शहर या राज्य दर्ज करें", + "settings": "समायोजन", + "firstName": "प्रथम नाम", + "lastName": "अंतिम नाम", + "emailAddress": "ईमेल पता", + "displayImage": "प्रदर्शन छवि", + "address": "पता", + "saveChanges": "परिवर्तन सहेजें", + "joined": "शामिल हुए" }, "donate": { "title": "दान", @@ -854,7 +1082,11 @@ "yourPreviousDonations": "आपका पिछला दान", "donate": "दान करें", "nothingToShow": "यहां दिखाने के लिए कुछ भी नहीं है.", - "success": "दान सफल" + "success": "दान सफल", + "invalidAmount": "कृपया दान राशि के लिए संख्यात्मक मान दर्ज करें.", + "donationAmountDescription": "कृपया दान राशि के लिए संख्यात्मक मान दर्ज करें.", + "donationOutOfRange": "दान राशि {{min}} और {{max}} के बीच होनी चाहिए.", + "donateTo": "दान करें" }, "userEvents": { "title": "ईवेंट", @@ -875,13 +1107,22 @@ "publicEvent": "सार्वजनिक है", "registerable": "पंजीकरण योग्य है", "monthlyCalendarView": "मासिक कैलेंडर", - "yearlyCalendarView": "वार्षिक कैलेंडर" + "yearlyCalendarView": "वार्षिक कैलेंडर", + "search": "खोजें", + "cancel": "रद्द करें", + "create": "बनाएं", + "eventDescription": "कार्यक्रम विवरण", + "eventLocation": "कार्यक्रम स्थान", + "startDate": "प्रारंभ तिथि", + "endDate": "समाप्ति तिथि" }, "userEventCard": { "starts": "प्रारंभ होगा", "ends": "समाप्त होता है", "creator": "निर्माता", - "alreadyRegistered": "पहले से ही पंजीकृत" + "alreadyRegistered": "पहले से ही पंजीकृत", + "location": "स्थान", + "register": "पंजीकरण करें" }, "advertisement": { "title": "विज्ञापनों", @@ -906,11 +1147,21 @@ "editAdvertisement": "विज्ञापन संपादित करें", "advertisementDeleted": "विज्ञापन सफलतापूर्वक हटाया गया।", "endDateGreaterOrEqual": "समाप्ति तिथि प्रारंभ तिथि से अधिक या उसके बराबर होनी चाहिए", - "advertisementCreated": "विज्ञापन सफलतापूर्वक बनाया गया।" + "advertisementCreated": "विज्ञापन सफलतापूर्वक बनाया गया।", + "pHeading": "विज्ञापन शीर्षक", + "delete": "हटाएं", + "close": "बंद करें", + "no": "नहीं", + "yes": "हाँ", + "edit": "संपादित करें", + "saveChanges": "परिवर्तन सहेजें", + "endOfResults": "परिणाम समाप्त" }, "userChat": { "chat": "बात करना", - "contacts": "संपर्क" + "contacts": "संपर्क", + "search": "खोज", + "messages": "संदेश" }, "userChatRoom": { "selectContact": "बातचीत शुरू करने के लिए एक संपर्क चुनें", @@ -928,20 +1179,28 @@ "String": "स्ट्रिंग", "Boolean": "बूलियन", "Date": "तारीख", - "Number": "संख्या" + "Number": "संख्या", + "saveChanges": "परिवर्तन सहेजें" }, "orgActionItemCategories": { "enableButton": "सक्षम", "disableButton": "अक्षम करना", "updateActionItemCategory": "अद्यतन", "actionItemCategoryName": "नाम", - "actionItemCategoryDetails": "कार्य आइटम श्रेणी विवरण", + "categoryDetails": "श्रेणी विवरण", "enterName": "नाम दर्ज करें", "successfulCreation": "कार्रवाई आइटम श्रेणी सफलतापूर्वक बनाई गई", "successfulUpdation": "कार्रवाई आइटम श्रेणी सफलतापूर्वक अपडेट की गई", "sameNameConflict": "अपडेट करने के लिए कृपया नाम बदलें", "categoryEnabled": "कार्य आइटम श्रेणी सक्षम", - "categoryDisabled": "कार्रवाई आइटम श्रेणी अक्षम" + "categoryDisabled": "कार्रवाई आइटम श्रेणी अक्षम", + "noActionItemCategories": "कोई कार्रवाई आइटम श्रेणी नहीं", + "status": "स्थिति", + "categoryDeleted": "कार्रवाई आइटम श्रेणी सफलतापूर्वक हटा दी गई", + "deleteCategory": "श्रेणी हटाएं", + "deleteCategoryMsg": "क्या आप वाकई इस कार्रवाई आइटम श्रेणी को हटाना चाहते हैं?", + "createButton": "बटन बनाएं", + "editButton": "संपादित बटन" }, "organizationVenues": { "title": "स्थानों", @@ -965,7 +1224,12 @@ "view": "देखना", "venueTitleError": "स्थान का शीर्षक खाली नहीं हो सकता!", "venueCapacityError": "क्षमता एक धनात्मक संख्या होनी चाहिए!", - "searchBy": "खोज से" + "searchBy": "खोज से", + "description": "विवरण", + "edit": "संपादित करें", + "delete": "हटाएं", + "name": "नाम", + "desc": "विवरण" }, "addMember": { "title": "सदस्य जोड़ें", @@ -979,7 +1243,18 @@ "organization": "संगठन", "invalidDetailsMessage": "कृपया सभी आवश्यक विवरण प्रदान करें।", "passwordNotMatch": "सांकेतिक शब्द मेल नहीं खाते।", - "addMember": "सदस्य जोड़ें" + "addMember": "सदस्य जोड़ें", + "firstName": "प्रथम नाम", + "lastName": "अंतिम नाम", + "emailAddress": "ईमेल पता", + "enterEmail": "ईमेल दर्ज करें", + "password": "पासवर्ड", + "enterPassword": "पासवर्ड दर्ज करें", + "confirmPassword": "पासवर्ड की पुष्टि करें", + "cancel": "रद्द करें", + "create": "बनाएं", + "user": "उपयोगकर्ता", + "profile": "प्रोफ़ाइल" }, "eventActionItems": { "title": "एक्शन आइटम्स", @@ -1007,7 +1282,9 @@ "actionItemStatus": "कार्रवाई आइटम स्थिति", "actionItemCompleted": "कार्रवाई आइटम पूर्ण हुआ", "markCompletion": "पूर्णता को चिह्नित करें", - "save": "बचाना" + "save": "बचाना", + "yes": "हाँ", + "no": "नहीं" }, "checkIn": { "errorCheckingIn": "चेक-इन में त्रुटि", diff --git a/public/locales/sp/common.json b/public/locales/sp/common.json index d784652da5..7e5871c914 100644 --- a/public/locales/sp/common.json +++ b/public/locales/sp/common.json @@ -82,5 +82,14 @@ "addedSuccessfully": "{{item}} agregado con éxito", "updatedSuccessfully": "{{item}} actualizado con éxito", "removedSuccessfully": "{{item}} eliminado con éxito", - "successfullyUpdated": "Actualizado con éxito" + "successfullyUpdated": "Actualizado con éxito", + "all": "Todos", + "active": "Activo", + "disabled": "Deshabilitado", + "pending": "Pendiente", + "completed": "Completado", + "late": "Tarde", + "createdLatest": "Creado más reciente", + "createdEarliest": "Creado más temprano", + "searchBy": "Buscar por {{item}}" } diff --git a/public/locales/sp/translation.json b/public/locales/sp/translation.json index 5d32d79368..f97ef36d58 100644 --- a/public/locales/sp/translation.json +++ b/public/locales/sp/translation.json @@ -121,7 +121,8 @@ "spamsThe": "envía correo no deseado", "group": "grupo", "noNotifications": "No Notificaciones", - "close": "Cerca" + "close": "Cerca", + "Advertisement": "Publicidad" }, "orgList": { "title": "Organizaciones Talawa", @@ -159,7 +160,11 @@ "noOrgErrorDescription": "Por favor, crea una organización a través del panel de control", "noResultsFoundFor": "No se encontraron resultados para ", "OR": "O", - "sampleOrgSuccess": "Organización de ejemplo creada exitosamente" + "sampleOrgSuccess": "Organización de ejemplo creada exitosamente", + "manageFeatures": "Gestionar funciones", + "manageFeaturesInfo": "Información de gestión de funciones", + "goToStore": "Ir a la tienda", + "enableEverything": "Habilitar todo" }, "orgListCard": { "admins": "Administradores", @@ -227,7 +232,8 @@ "AlreadyJoined": "Ya eres miembro de esta organización.", "errorOccured": "Se produjo un error. Por favor, inténtalo de nuevo más tarde.", "removeUserFrom": "Eliminar Usuario de {{org}}", - "removeConfirmation": "¿Está seguro de que desea eliminar a '{{name}}' de la organización '{{org}}'?" + "removeConfirmation": "¿Está seguro de que desea eliminar a '{{name}}' de la organización '{{org}}'?", + "noOrgError": "Error sin organización" }, "communityProfile": { "title": "Perfil de la comunidad", @@ -292,7 +298,11 @@ "organization": "Organización", "create": "Crear", "cancel": "Cancelar", - "invalidDetailsMessage": "Ingrese detalles válidos." + "invalidDetailsMessage": "Ingrese detalles válidos.", + "addMembers": "Agregar miembros", + "searchFullName": "Ingrese el nombre completo", + "user": "Usuario", + "profile": "Perfil" }, "organizationTags": { "title": "Etiquetas de Organización", @@ -400,7 +410,6 @@ "active": "Activo", "clearFilters": "Borrar filtros", "close": "Cerrar", - "completed": "Completado", "completionDate": "Fecha de finalización", "createActionItem": "Crear ítem de acción", "deleteActionItem": "Eliminar ítem de acción", @@ -426,7 +435,14 @@ "successfulUpdation": "Ítem de acción actualizado con éxito", "successfulDeletion": "Ítem de acción eliminado con éxito", "title": "Ítems de acción", - "yes": "Sí" + "yes": "Sí", + "category": "Categoría", + "allotedHours": "Horas asignadas", + "latestDueDate": "Fecha de vencimiento más reciente", + "earliestDueDate": "Fecha de vencimiento más antigua", + "updateActionItem": "Actualizar elemento de acción", + "noneUpdated": "Ninguno de los campos se actualizó", + "updateStatusMsg": "¿Está seguro de que desea marcar este elemento de acción como pendiente?" }, "organizationAgendaCategory": { "agendaCategoryDetails": "Detalles de la categoría de la agenda", @@ -662,7 +678,8 @@ "postDeleted": "Publicación eliminada exitosamente.", "postUpdated": "Publicación actualizada exitosamente.", "tag": "Su navegador no admite la etiqueta de video", - "talawaApiUnavailable": "El servicio Talawa-API no está disponible. ¿Está en funcionamiento? Compruebe también su conectividad de red." + "talawaApiUnavailable": "El servicio Talawa-API no está disponible. ¿Está en funcionamiento? Compruebe también su conectividad de red.", + "pin": "Fijar" }, "blockUnblockUser": { "title": "Usuario de bloqueo/desbloqueo de Talawa", @@ -741,19 +758,16 @@ "amount": "Monto" }, "orgSettings": { - "title": "Configuración Talawa", - "pageName": "Configuración", + "title": "Configuración", "general": "General", "actionItemCategories": "Categorías de elementos de acción", - "updateYourDetails": "Actualiza tus datos", - "updateYourPassword": "Actualice su contraseña", - "updateOrganization": "Actualizar Organización", - "seeRequest": "Ver Solicitud", - "settings": "Ajustes", + "updateOrganization": "Actualizar organización", + "seeRequest": "Ver solicitud", "noData": "Sin datos", - "otherSettings": "Otras Configuraciones", - "changeLanguage": "Cambiar Idioma", - "manageCustomFields": "Gestionar Campos Personalizados" + "otherSettings": "Otras configuraciones", + "changeLanguage": "Cambiar idioma", + "manageCustomFields": "Administrar campos personalizados", + "agendaItemCategories": "Categorías de elementos de agenda" }, "deleteOrg": { "deleteOrganization": "Eliminar organización", @@ -845,7 +859,9 @@ "addOnEntry": { "enable": "Activada", "install": "Instalar", - "uninstall": "Desinstalar" + "uninstall": "Desinstalar", + "uninstallMsg": "Mensaje de desinstalación", + "installMsg": "Mensaje de instalación" }, "memberDetail": { "title": "Detalles del usuario", @@ -885,7 +901,9 @@ "membershipRequests": "Solicitudes de membresía", "adminForEvents": "Administrador de eventos", "addedAsAdmin": "El usuario se agrega como administrador.", - "talawaApiUnavailable": "El servicio Talawa-API no está disponible. Compruebe amablemente su conexión de red y espere un momento." + "talawaApiUnavailable": "El servicio Talawa-API no está disponible. Compruebe amablemente su conexión de red y espere un momento.", + "deleteUser": "Eliminar usuario", + "userType": "Tipo de usuario" }, "userLogin": { "login": "Acceso", @@ -945,7 +963,8 @@ "selectOrganization": "Seleccionar organización", "filter": "Filtrar", "organizations": "Organizaciones", - "searchByName": "Buscar por nombre" + "searchByName": "Buscar por nombre", + "searchUsers": "Buscar usuarios" }, "userSidebarOrg": { "yourOrganizations": "Tus Organizaciones", @@ -958,7 +977,8 @@ "requests": "Solicitudes", "communityProfile": "Perfil de la comunidad", "logout": "Cerrar sesión", - "settings": "Ajustes" + "settings": "Ajustes", + "chat": "Chat" }, "organizationSidebar": { "viewAll": "Ver todo", @@ -989,7 +1009,8 @@ "startPost": "Comenzar una publicación", "media": "Medios", "event": "Evento", - "article": "Artículo" + "article": "Artículo", + "postNowVisibleInFeed": "Publicar ahora visible en el feed" }, "settings": { "settings": "Ajustes", @@ -1063,7 +1084,10 @@ "yourPreviousDonations": "Tus donaciones anteriores", "donate": "Donar", "nothingToShow": "Nada que mostrar aquí.", - "success": "Donación exitosa" + "success": "Donación exitosa", + "invalidAmount": "Ingrese un valor numérico para el monto de la donación.", + "donationAmountDescription": "Ingrese el valor numérico del monto de la donación.", + "donationOutOfRange": "El monto de la donación debe estar entre {{min}} y {{max}}." }, "userEvents": { "title": "Eventos", @@ -1123,12 +1147,22 @@ "endDateGreaterOrEqual": "La fecha de finalización debe ser mayor o igual a la fecha de inicio", "advertisementCreated": "Anuncio creado con éxito.", "saveChanges": "Guardar Cambios", - "endOfResults": "Fin de los resultados" + "endOfResults": "Fin de los resultados", + "Rname": "Nombre", + "Rtype": "Tipo", + "RstartDate": "Fecha de inicio", + "RendDate": "Fecha de finalización", + "RClose": "Cerrar", + "addNew": "Agregar nuevo", + "EXname": "Nombre", + "EXlink": "Enlace", + "createAdvertisement": "Crear publicidad" }, "userChat": { "chat": "Charlar", "search": "Buscar", - "contacts": "Contactos" + "contacts": "Contactos", + "messages": "Mensajes" }, "userChatRoom": { "selectContact": "Seleccione un contacto para iniciar una conversación", @@ -1156,13 +1190,18 @@ "disableButton": "Inhabilitar", "updateActionItemCategory": "Actualizar", "actionItemCategoryName": "Nombre", - "actionItemCategoryDetails": "Detalles de la categoría de elemento de acción", + "categoryDetails": "Detalles de la categoría", "enterName": "Introduzca el nombre", "successfulCreation": "Categoría de elemento de acción creada correctamente", "successfulUpdation": "Categoría de elemento de acción actualizada correctamente", "sameNameConflict": "Cambie el nombre para realizar una actualización", "categoryEnabled": "Categoría de elemento de acción habilitada", - "categoryDisabled": "Categoría de elemento de acción deshabilitada" + "categoryDisabled": "Categoría de elemento de acción deshabilitada", + "noActionItemCategories": "No hay categorías de elementos de acción", + "status": "Estado", + "categoryDeleted": "Categoría de elemento de acción eliminada con éxito", + "deleteCategory": "Eliminar categoría", + "deleteCategoryMsg": "¿Está seguro de que desea eliminar esta categoría de elemento de acción?" }, "organizationVenues": { "title": "Lugares", @@ -1215,6 +1254,7 @@ "invalidDetailsMessage": "Por favor proporcione todos los detalles requeridos.", "passwordNotMatch": "Las contraseñas no coinciden.", "user": "Usuario", + "profile": "Perfil", "addMember": "Agregar miembro" }, "eventActionItems": { diff --git a/public/locales/zh/common.json b/public/locales/zh/common.json index 71b697b354..93659c644c 100644 --- a/public/locales/zh/common.json +++ b/public/locales/zh/common.json @@ -82,5 +82,14 @@ "addedSuccessfully": "{{item}} 添加成功", "updatedSuccessfully": "{{item}} 更新成功", "removedSuccessfully": "{{item}} 删除成功", - "successfullyUpdated": "更新成功" + "successfullyUpdated": "更新成功", + "all": "全部", + "active": "活跃", + "disabled": "禁用", + "pending": "待处理", + "completed": "已完成", + "late": "迟到", + "createdLatest": "最近创建", + "createdEarliest": "最早创建", + "searchBy": "搜索依据 {{item}}" } diff --git a/public/locales/zh/translation.json b/public/locales/zh/translation.json index 56d93b13da..adc4403f25 100644 --- a/public/locales/zh/translation.json +++ b/public/locales/zh/translation.json @@ -22,7 +22,25 @@ "numeric_value_check": "至少设定一个数值", "special_char_check": "至少一个特殊字符", "selectOrg": "选择一个组织", - "afterRegister": "注册成功。" + "afterRegister": "注册成功。", + "talawa_portal": "塔拉瓦门户", + "login": "登录", + "register": "注册", + "firstName": "名字", + "lastName": "姓氏", + "email": "电子邮件", + "password": "密码", + "confirmPassword": "确认密码", + "forgotPassword": "忘记密码", + "enterEmail": "输入电子邮件", + "enterPassword": "输入密码", + "talawaApiUnavailable": "塔拉瓦 API 不可用", + "notAuthorised": "未授权", + "notFound": "未找到", + "OR": "或", + "admin": "管理员", + "user": "用户", + "loading": "加载中" }, "userLoginPage": { "title": "塔拉瓦管理员", @@ -38,7 +56,23 @@ "successfullyRegistered": "注册成功。", "userLogin": "用户登录", "afterRegister": "注册成功。", - "selectOrg": "选择一个组织" + "selectOrg": "选择一个组织", + "talawa_portal": "塔拉瓦门户", + "login": "登录", + "register": "注册", + "firstName": "名字", + "lastName": "姓氏", + "email": "电子邮件", + "password": "密码", + "confirmPassword": "确认密码", + "forgotPassword": "忘记密码", + "enterEmail": "输入电子邮件", + "enterPassword": "输入密码", + "talawaApiUnavailable": "塔拉瓦 API 不可用", + "notAuthorised": "未授权", + "notFound": "未找到", + "OR": "或", + "loading": "加载中" }, "latestEvents": { "eventCardTitle": "即将举行的活动", @@ -51,12 +85,19 @@ "noPostsCreated": "没有创建帖子" }, "listNavbar": { - "roles": "角色" + "roles": "角色", + "talawa_portal": "塔拉瓦门户", + "requests": "请求", + "logout": "退出登录" }, "leftDrawer": { "my organizations": "我的组织", "requests": "会员申请", - "communityProfile": "社区简介" + "communityProfile": "社区简介", + "talawaAdminPortal": "塔拉瓦管理员门户", + "menu": "菜单", + "users": "用户", + "logout": "退出登录" }, "leftDrawerOrg": { "Dashboard": "仪表板", @@ -75,7 +116,13 @@ "notifications": "通知", "spamsThe": "垃圾邮件", "group": "团体", - "noNotifications": "无通知" + "noNotifications": "无通知", + "talawaAdminPortal": "塔拉瓦管理员门户", + "menu": "菜单", + "talawa_portal": "塔拉瓦门户", + "settings": "设置", + "logout": "退出登录", + "close": "关闭" }, "orgList": { "title": "塔拉瓦组织", @@ -105,11 +152,25 @@ "manageFeaturesInfo": "创建成功!", "goToStore": "前往插件商店", "enableEverything": "启用一切", - "sampleOrgSuccess": "样本组织已成功创建" + "sampleOrgSuccess": "样本组织已成功创建", + "name": "名称", + "email": "电子邮件", + "searchByName": "按名称搜索", + "description": "描述", + "location": "位置", + "address": "地址", + "displayImage": "显示图像", + "filter": "筛选", + "cancel": "取消", + "endOfResults": "结果结束", + "noResultsFoundFor": "未找到结果", + "OR": "或" }, "orgListCard": { "manage": "管理", - "sampleOrganization": "组织样本" + "sampleOrganization": "组织样本", + "admins": "管理员", + "members": "成员" }, "paginationList": { "rowsPerPage": "每页行数", @@ -126,7 +187,11 @@ "acceptedSuccessfully": "请求已成功接受", "rejectedSuccessfully": "请求被成功拒绝", "noOrgErrorTitle": "未找到组织", - "noOrgErrorDescription": "请通过仪表板创建组织" + "noOrgErrorDescription": "请通过仪表板创建组织", + "name": "名称", + "email": "电子邮件", + "endOfResults": "结果结束", + "noResultsFoundFor": "未找到结果" }, "users": { "title": "塔拉瓦角色", @@ -150,7 +215,25 @@ "visit": "访问", "withdraw": "拉幅", "removeUserFrom": "从{{org}}中删除用户", - "removeConfirmation": "您确定要将'{{name}}'从组织'{{org}}'中删除吗?" + "removeConfirmation": "您确定要将'{{name}}'从组织'{{org}}'中删除吗?", + "searchByName": "按名称搜索", + "users": "用户", + "name": "名称", + "email": "电子邮件", + "endOfResults": "结果结束", + "admin": "管理员", + "superAdmin": "超级管理员", + "user": "用户", + "filter": "筛选", + "noResultsFoundFor": "未找到结果", + "talawaApiUnavailable": "塔拉瓦 API 不可用", + "cancel": "取消", + "admins": "管理员", + "members": "成员", + "orgJoined": "已加入组织", + "MembershipRequestSent": "会员请求已发送", + "AlreadyJoined": "已加入", + "errorOccured": "发生错误" }, "communityProfile": { "title": "社区简介", @@ -178,7 +261,12 @@ "latestPosts": "最新帖子", "noPostsPresent": "没有帖子", "membershipRequests": "会员请求", - "noMembershipRequests": "没有会员请求" + "noMembershipRequests": "没有会员请求", + "location": "位置", + "members": "成员", + "admins": "管理员", + "requests": "请求", + "talawaApiUnavailable": "塔拉瓦 API 不可用" }, "organizationPeople": { "title": "塔拉瓦会员", @@ -198,7 +286,23 @@ "enterLastName": "输入您的姓氏", "enterConfirmPassword": "输入您的密码进行确认", "organization": "组织", - "invalidDetailsMessage": "请输入有效的详细信息。" + "invalidDetailsMessage": "请输入有效的详细信息。", + "members": "成员", + "admins": "管理员", + "users": "用户", + "searchFirstName": "按名字搜索", + "searchLastName": "按姓氏搜索", + "firstName": "名字", + "lastName": "姓氏", + "emailAddress": "电子邮件地址", + "enterEmail": "输入你的电子邮件地址", + "password": "密码", + "enterPassword": "输入你的密码", + "confirmPassword": "确认密码", + "cancel": "取消", + "create": "创建", + "user": "用户", + "profile": "个人资料" }, "organizationTags": { "title": "组织标签", @@ -232,19 +336,29 @@ }, "userListCard": { "addAdmin": "添加管理员", - "addedAsAdmin": "用户被添加为管理员。" + "addedAsAdmin": "用户被添加为管理员。", + "joined": "已加入", + "talawaApiUnavailable": "塔拉瓦 API 不可用" }, "orgAdminListCard": { "remove": "消除", "removeAdmin": "删除管理员", "removeAdminMsg": "您想删除该管理员吗?", - "adminRemoved": "管理员被删除。" + "adminRemoved": "管理员被删除。", + "joined": "已加入", + "no": "否", + "yes": "是", + "talawaApiUnavailable": "塔拉瓦 API 不可用" }, "orgPeopleListCard": { "remove": "消除", "removeMember": "删除会员", "removeMemberMsg": "您想删除该成员吗?", - "memberRemoved": "该会员已被删除" + "memberRemoved": "该会员已被删除", + "joined": "已加入", + "no": "否", + "yes": "是", + "talawaApiUnavailable": "塔拉瓦 API 不可用" }, "organizationEvents": { "title": "活动", @@ -275,7 +389,14 @@ "never": "绝不", "on": "在", "after": "后", - "occurences": "事件" + "occurences": "事件", + "events": "活动", + "description": "描述", + "location": "位置", + "startDate": "开始日期", + "endDate": "结束日期", + "talawaApiUnavailable": "塔拉瓦 API 不可用", + "done": "完成" }, "organizationActionItems": { "actionItemCategory": "行动项目类别", @@ -286,7 +407,6 @@ "assignmentDate": "分配日期", "active": "积极的", "clearFilters": "清除过滤器", - "completed": "完全的", "completionDate": "完成日期", "createActionItem": "创建操作项", "deleteActionItem": "删除操作项", @@ -311,7 +431,18 @@ "successfulCreation": "操作项创建成功", "successfulUpdation": "操作项已成功更新", "successfulDeletion": "操作项已成功删除", - "title": "行动项目" + "title": "行动项目", + "category": "类别", + "allotedHours": "分配的小时", + "latestDueDate": "最晚到期日", + "earliestDueDate": "最早到期日", + "updateActionItem": "更新操作项", + "noneUpdated": "没有更新任何字段", + "updateStatusMsg": "您确定要将此操作项标记为待处理吗?", + "close": "关闭", + "eventActionItems": "活动行动项目", + "no": "否", + "yes": "是" }, "organizationAgendaCategory": { "agendaCategoryDetails": "议程类别详情", @@ -390,7 +521,17 @@ "never": "绝不", "on": "在", "after": "后", - "occurences": "事件" + "occurences": "事件", + "location": "位置", + "no": "否", + "yes": "是", + "description": "描述", + "startDate": "开始日期", + "endDate": "结束日期", + "registerEvent": "注册活动", + "close": "关闭", + "talawaApiUnavailable": "塔拉瓦 API 不可用", + "done": "完成" }, "funds": { "title": "基金", @@ -466,7 +607,9 @@ "pledges": "承诺", "endsOn": "结束于", "raisedAmount": "募集金額", - "pledgedAmount": "承诺金額" + "pledgedAmount": "承诺金額", + "startDate": "开始日期", + "endDate": "结束日期" }, "orgPost": { "title": "帖子", @@ -494,7 +637,8 @@ "postCreatedSuccess": "恭喜!", "pinPost": "针柱", "Next": "下一页", - "Previous": "上一页" + "Previous": "上一页", + "cancel": "取消" }, "postNotFound": { "post": "邮政", @@ -509,7 +653,8 @@ "user not found!": "未找到用户!", "member not found!": "未找到会员!", "admin not found!": "找不到管理员!", - "roles not found!": "未找到角色!" + "roles not found!": "未找到角色!", + "user": "用户" }, "orgPostCard": { "author": "作者", @@ -528,7 +673,12 @@ "postDeleted": "帖子删除成功。", "postUpdated": "帖子更新成功。", "tag": " 您的浏览器不支持video标签", - "pin": "针柱" + "pin": "针柱", + "edit": "编辑", + "no": "否", + "yes": "是", + "close": "关闭", + "talawaApiUnavailable": "塔拉瓦 API 不可用" }, "blockUnblockUser": { "title": "阻止/取消阻止用户", @@ -544,7 +694,12 @@ "blockedUsers": "被阻止的用户", "searchByFirstName": "按名字搜索", "searchByLastName": "按姓氏搜索", - "noSpammerFound": "未发现垃圾邮件发送者" + "noSpammerFound": "未发现垃圾邮件发送者", + "searchByName": "按名称搜索", + "name": "名称", + "email": "电子邮件", + "talawaApiUnavailable": "塔拉瓦 API 不可用", + "noResultsFoundFor": "未找到结果" }, "eventManagement": { "title": "事件管理", @@ -569,7 +724,10 @@ "errorSendingMail": "发送邮件时出错。", "passwordMismatches": "密码和确认密码不匹配。", "passwordChanges": "密码修改成功。", - "OTPsent": "OTP 已发送至您的注册邮箱。" + "OTPsent": "OTP 已发送至您的注册邮箱。", + "forgotPassword": "忘记密码", + "password": "密码", + "talawaApiUnavailable": "塔拉瓦 API 不可用" }, "pageNotFound": { "404": "404", @@ -600,14 +758,15 @@ }, "orgSettings": { "title": "设置", - "general": "一般的", - "actionItemCategories": "行动项目类别", + "general": "一般", + "actionItemCategories": "操作项目类别", "updateOrganization": "更新组织", "seeRequest": "查看请求", "noData": "没有数据", "otherSettings": "其他设置", - "changeLanguage": "改变语言", - "manageCustomFields": "管理自定义字段" + "changeLanguage": "更改语言", + "manageCustomFields": "管理自定义字段", + "agendaItemCategories": "议程项目类别" }, "deleteOrg": { "deleteOrganization": "删除组织", @@ -615,18 +774,30 @@ "deleteMsg": "您想删除该组织吗?", "confirmDelete": "确认删除", "longDelOrgMsg": "通过单击“删除组织”按钮,该组织及其事件、标签和所有相关数据将被永久删除。", - "successfullyDeletedSampleOrganization": "已成功删除样本组织" + "successfullyDeletedSampleOrganization": "已成功删除样本组织", + "cancel": "取消" }, "userUpdate": { "appLanguageCode": "默认语言", - "userType": "用户类型" + "userType": "用户类型", + "firstName": "名字", + "lastName": "姓氏", + "email": "电子邮件", + "password": "密码", + "admin": "管理员", + "superAdmin": "超级管理员", + "displayImage": "显示图像", + "saveChanges": "保存更改", + "cancel": "取消" }, "userPasswordUpdate": { "previousPassword": "旧密码", "newPassword": "新密码", "confirmNewPassword": "确认新密码", "passCantBeEmpty": "密码不能为空", - "passNoMatch": "新密码和确认密码不匹配。" + "passNoMatch": "新密码和确认密码不匹配。", + "saveChanges": "保存更改", + "cancel": "取消" }, "orgDelete": { "deleteOrg": "删除组织" @@ -634,7 +805,9 @@ "membershipRequest": { "accept": "接受", "reject": "拒绝", - "memberAdded": "它被接受" + "memberAdded": "它被接受", + "joined": "已加入", + "talawaApiUnavailable": "塔拉瓦 API 不可用" }, "orgUpdate": { "city": "城市", @@ -648,7 +821,15 @@ "userRegistrationRequired": "需要用户注册", "isVisibleInSearch": "在搜索中可见", "enterNameOrganization": "输入组织名称", - "successfulUpdated": "组织更新成功" + "successfulUpdated": "组织更新成功", + "name": "名称", + "description": "描述", + "location": "位置", + "address": "地址", + "displayImage": "显示图像", + "saveChanges": "保存更改", + "cancel": "取消", + "talawaApiUnavailable": "塔拉瓦 API 不可用" }, "addOnRegister": { "addNew": "添新", @@ -658,7 +839,9 @@ "pluginDesc": "插件说明", "pName": "例如:捐款", "cName": "例如:约翰·多伊", - "pDesc": "该插件启用 UI" + "pDesc": "该插件启用 UI", + "close": "关闭", + "register": "注册" }, "addOnStore": { "title": "添加商店", @@ -669,7 +852,8 @@ "pHeading": "插件", "install": "已安装", "available": "可用的", - "pMessage": "插件不存在" + "pMessage": "插件不存在", + "filter": "筛选" }, "addOnEntry": { "enable": "启用", @@ -711,14 +895,28 @@ "membershipRequests": "会员请求", "adminForEvents": "活动管理员", "addedAsAdmin": "用户被添加为管理员。", - "userType": "用户类型" + "userType": "用户类型", + "email": "电子邮件", + "displayImage": "显示图像", + "address": "地址", + "delete": "删除", + "saveChanges": "保存更改", + "joined": "已加入", + "talawaApiUnavailable": "塔拉瓦 API 不可用" }, "userLogin": { "login": "登录", "loginIntoYourAccount": "登录您的帐户", "invalidDetailsMessage": "请输入有效的电子邮件和密码。", "notAuthorised": "对不起!", - "invalidCredentials": "输入的凭据不正确。" + "invalidCredentials": "输入的凭据不正确。", + "forgotPassword": "忘记密码", + "emailAddress": "电子邮件地址", + "enterEmail": "输入电子邮件", + "password": "密码", + "enterPassword": "输入密码", + "register": "注册", + "talawaApiUnavailable": "塔拉瓦 API 不可用" }, "people": { "title": "人们", @@ -732,7 +930,16 @@ "login": "登录", "afterRegister": "注册成功。", "passwordNotMatch": "密码不匹配。", - "invalidDetailsMessage": "请输入有效的详细信息。" + "invalidDetailsMessage": "请输入有效的详细信息。", + "register": "注册", + "firstName": "名字", + "lastName": "姓氏", + "emailAddress": "电子邮件地址", + "enterEmail": "输入电子邮件", + "password": "密码", + "enterPassword": "输入密码", + "confirmPassword": "确认密码", + "talawaApiUnavailable": "塔拉瓦 API 不可用" }, "userNavbar": { "talawa": "塔拉瓦", @@ -741,7 +948,10 @@ "events": "活动", "chat": "聊天", "donate": "捐", - "language": "语言" + "language": "语言", + "settings": "设置", + "logout": "退出登录", + "close": "关闭" }, "userOrganizations": { "allOrganizations": "所有组织", @@ -750,7 +960,10 @@ "selectOrganization": "选择一个组织", "searchUsers": "搜索用户", "nothingToShow": "这里没有什么可显示的。", - "organizations": "组织机构" + "organizations": "组织机构", + "search": "搜索", + "filter": "筛选", + "searchByName": "按名称搜索" }, "userSidebarOrg": { "yourOrganizations": "您的组织", @@ -758,13 +971,20 @@ "viewAll": "查看全部", "talawaUserPortal": "塔拉瓦用户门户", "my organizations": "我的组织", - "communityProfile": "社区简介" + "communityProfile": "社区简介", + "users": "用户", + "requests": "请求", + "logout": "登出", + "settings": "设置", + "chat": "聊天", + "menu": "菜单" }, "organizationSidebar": { "viewAll": "查看全部", "events": "活动", "noEvents": "没有可显示的活动", - "noMembers": "没有可显示的会员" + "noMembers": "没有可显示的会员", + "members": "成员" }, "postCard": { "likes": "喜欢", @@ -843,7 +1063,15 @@ "fullTime": "全职", "partTime": "兼职", "selectCountry": "选择一个国家", - "enterState": "输入城市或州" + "enterState": "输入城市或州", + "settings": "设置", + "firstName": "名字", + "lastName": "姓氏", + "emailAddress": "电子邮件地址", + "displayImage": "显示图像", + "address": "地址", + "saveChanges": "保存更改", + "joined": "已加入" }, "donate": { "title": "捐款", @@ -854,7 +1082,11 @@ "yourPreviousDonations": "您之前的捐款", "donate": "捐", "nothingToShow": "这里没有什么可显示的。", - "success": "捐赠成功" + "success": "捐赠成功", + "invalidAmount": "请输入捐赠金额的数值。", + "donationAmountDescription": "请输入捐款金额的数值。", + "donationOutOfRange": "捐款金额必须在 {{min}} 和 {{max}} 之间。", + "donateTo": "捐赠给" }, "userEvents": { "title": "活动", @@ -875,13 +1107,22 @@ "publicEvent": "是公开的", "registerable": "可注册", "monthlyCalendarView": "月历", - "yearlyCalendarView": "年历" + "yearlyCalendarView": "年历", + "search": "搜索", + "cancel": "取消", + "create": "创建", + "eventDescription": "活动描述", + "eventLocation": "活动位置", + "startDate": "开始日期", + "endDate": "结束日期" }, "userEventCard": { "starts": "开始", "ends": "结束", "creator": "创作者", - "alreadyRegistered": "已经注册" + "alreadyRegistered": "已经注册", + "location": "位置", + "register": "注册" }, "advertisement": { "title": "广告", @@ -906,11 +1147,21 @@ "editAdvertisement": "编辑广告", "advertisementDeleted": "广告已成功删除。", "endDateGreaterOrEqual": "结束日期应大于或等于开始日期", - "advertisementCreated": "广告已成功创建。" + "advertisementCreated": "广告已成功创建。", + "pHeading": "广告标题", + "delete": "删除", + "close": "关闭", + "no": "否", + "yes": "是", + "edit": "编辑", + "saveChanges": "保存更改", + "endOfResults": "结果结束" }, "userChat": { "chat": "聊天", - "contacts": "联系方式" + "contacts": "联系方式", + "search": "搜索", + "messages": "消息" }, "userChatRoom": { "selectContact": "选择联系人开始对话", @@ -928,20 +1179,28 @@ "String": "字符串", "Boolean": "布尔值", "Date": "日期", - "Number": "数字" + "Number": "数字", + "saveChanges": "保存更改" }, "orgActionItemCategories": { "enableButton": "使能够", "disableButton": "禁用", "updateActionItemCategory": "更新", "actionItemCategoryName": "姓名", - "actionItemCategoryDetails": "行动项目类别详细信息", + "categoryDetails": "类别详情", "enterName": "输入名字", "successfulCreation": "操作项类别创建成功", "successfulUpdation": "行动项目类别已成功更新", "sameNameConflict": "请更改名称以进行更新", "categoryEnabled": "已启用操作项类别", - "categoryDisabled": "操作项类别已禁用" + "categoryDisabled": "操作项类别已禁用", + "noActionItemCategories": "没有操作项目类别", + "status": "地位", + "categoryDeleted": "操作项目类别已成功删除", + "deleteCategory": "删除类别", + "deleteCategoryMsg": "您确定要删除此操作项目类别吗?", + "createButton": "创建按钮", + "editButton": "编辑按钮" }, "organizationVenues": { "title": "场地", @@ -965,7 +1224,12 @@ "view": "看法", "venueTitleError": "场地名称不能为空!", "venueCapacityError": "容量必须是正数!", - "searchBy": "搜索依据" + "searchBy": "搜索依据", + "description": "描述", + "edit": "编辑", + "delete": "删除", + "name": "名称", + "desc": "描述" }, "addMember": { "title": "添加会员", @@ -979,7 +1243,18 @@ "organization": "组织", "invalidDetailsMessage": "请提供所有必需的详细信息。", "passwordNotMatch": "密码不匹配。", - "addMember": "添加会员" + "firstName": "名字", + "lastName": "姓氏", + "emailAddress": "电子邮件地址", + "enterEmail": "输入电子邮件", + "password": "密码", + "enterPassword": "输入密码", + "confirmPassword": "确认密码", + "cancel": "取消", + "create": "创建", + "addMember": "添加会员", + "user": "用户", + "profile": "个人资料" }, "eventActionItems": { "title": "行动项目", @@ -1007,7 +1282,9 @@ "actionItemStatus": "行动项目状态", "actionItemCompleted": "行动项目已完成", "markCompletion": "标记完成", - "save": "节省" + "save": "节省", + "yes": "是", + "no": "否" }, "checkIn": { "errorCheckingIn": "签到错误", diff --git a/scripts/config-overrides/custom_build.js b/scripts/config-overrides/custom_build.js index d5c0ce93a2..d8f41150e6 100644 --- a/scripts/config-overrides/custom_build.js +++ b/scripts/config-overrides/custom_build.js @@ -14,4 +14,3 @@ if (process.env.ALLOW_LOGS === "YES") { else { spawn(react_script_build, { stdio: 'inherit', shell: true }); } - diff --git a/scripts/config-overrides/custom_start.js b/scripts/config-overrides/custom_start.js index a784f1bb48..b30dfb600a 100644 --- a/scripts/config-overrides/custom_start.js +++ b/scripts/config-overrides/custom_start.js @@ -16,4 +16,3 @@ else { // Execute the npm command spawn(react_script_start, { stdio: 'inherit', shell: true }); } - diff --git a/src/App.test.tsx b/src/App.test.tsx index 3146066e26..f4fba2ebf8 100644 --- a/src/App.test.tsx +++ b/src/App.test.tsx @@ -1,5 +1,5 @@ -import React from 'react'; -import { act, render, screen } from '@testing-library/react'; +import React, { act } from 'react'; +import { render, screen } from '@testing-library/react'; import { Provider } from 'react-redux'; import { MockedProvider } from '@apollo/react-testing'; import { BrowserRouter } from 'react-router-dom'; diff --git a/src/App.tsx b/src/App.tsx index f73055f4c8..d6e825006f 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -14,7 +14,6 @@ import OrgList from 'screens/OrgList/OrgList'; import OrgPost from 'screens/OrgPost/OrgPost'; import OrgSettings from 'screens/OrgSettings/OrgSettings'; import OrganizationActionItems from 'screens/OrganizationActionItems/OrganizationActionItems'; -import OrganizationAgendaCategory from 'screens/OrganizationAgendaCategory/OrganizationAgendaCategory'; import OrganizationDashboard from 'screens/OrganizationDashboard/OrganizationDashboard'; import OrganizationEvents from 'screens/OrganizationEvents/OrganizationEvents'; import OrganizaitionFundCampiagn from 'screens/OrganizationFundCampaign/OrganizationFundCampagins'; @@ -37,7 +36,6 @@ import Posts from 'screens/UserPortal/Posts/Posts'; import Organizations from 'screens/UserPortal/Organizations/Organizations'; import People from 'screens/UserPortal/People/People'; import Settings from 'screens/UserPortal/Settings/Settings'; -// import UserLoginPage from 'screens/UserPortal/UserLoginPage/UserLoginPage'; import Chat from 'screens/UserPortal/Chat/Chat'; import { useQuery } from '@apollo/client'; import { CHECK_AUTH } from 'GraphQl/Queries/Queries'; @@ -122,12 +120,12 @@ function app(): JSX.Element { ], index: number, ) => { - const extraComponent = plugin[1]; + const ExtraComponent = plugin[1]; return ( } /> ); }, @@ -164,10 +162,6 @@ function app(): JSX.Element { path="/orgactionitems/:orgId" element={} /> - } - /> } /> - -angle-left - - \ No newline at end of file diff --git a/src/assets/svgs/eventDashboard.svg b/src/assets/svgs/eventDashboard.svg deleted file mode 100644 index 769c57d315..0000000000 --- a/src/assets/svgs/eventDashboard.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/src/assets/svgs/eventStats.svg b/src/assets/svgs/eventStats.svg deleted file mode 100644 index ffa43fdc4a..0000000000 --- a/src/assets/svgs/eventStats.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/src/components/ActionItems/ActionItemsContainer.module.css b/src/components/ActionItems/ActionItemsContainer.module.css deleted file mode 100644 index b55328c563..0000000000 --- a/src/components/ActionItems/ActionItemsContainer.module.css +++ /dev/null @@ -1,25 +0,0 @@ -.actionItemStatusBadge { - width: 5.5rem; - margin-left: 1.1rem; -} - -.createModal { - margin-top: 20vh; - margin-left: 13vw; - max-width: 80vw; -} - -.titlemodal { - color: var(--bs-gray-600); - font-weight: 600; - font-size: 20px; - margin-bottom: 20px; - padding-bottom: 5px; - border-bottom: 3px solid var(--bs-primary); - width: 65%; -} - -.actionItemsOptionsButton { - width: 24px; - height: 24px; -} diff --git a/src/components/ActionItems/ActionItemsContainer.test.tsx b/src/components/ActionItems/ActionItemsContainer.test.tsx deleted file mode 100644 index 7cb9c9e9bb..0000000000 --- a/src/components/ActionItems/ActionItemsContainer.test.tsx +++ /dev/null @@ -1,777 +0,0 @@ -import React from 'react'; -import { - render, - screen, - fireEvent, - waitFor, - act, - waitForElementToBeRemoved, -} from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import 'jest-localstorage-mock'; -import { MockedProvider } from '@apollo/client/testing'; -import 'jest-location-mock'; -import { I18nextProvider } from 'react-i18next'; -import { Provider } from 'react-redux'; -import { BrowserRouter } from 'react-router-dom'; -import i18nForTest from 'utils/i18nForTest'; -import { toast } from 'react-toastify'; -import { LocalizationProvider } from '@mui/x-date-pickers'; -import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; - -import { store } from 'state/store'; -import { StaticMockLink } from 'utils/StaticMockLink'; - -import ActionItemsContainer from './ActionItemsContainer'; -import { props, props2 } from './ActionItemsContainerProps'; -import { MOCKS, MOCKS_ERROR_MUTATIONS } from './ActionItemsContainerMocks'; - -const link = new StaticMockLink(MOCKS, true); -const link2 = new StaticMockLink(MOCKS_ERROR_MUTATIONS, true); - -jest.mock('react-toastify', () => ({ - toast: { - success: jest.fn(), - error: jest.fn(), - }, -})); - -jest.mock('@mui/x-date-pickers/DateTimePicker', () => { - return { - DateTimePicker: jest.requireActual( - '@mui/x-date-pickers/DesktopDateTimePicker', - ).DesktopDateTimePicker, - }; -}); - -async function wait(ms = 100): Promise { - await act(() => { - return new Promise((resolve) => { - setTimeout(resolve, ms); - }); - }); -} - -const translations = JSON.parse( - JSON.stringify( - i18nForTest.getDataByLanguage('en')?.translation.organizationActionItems, - ), -); - -describe('Testing Action Item Categories Component', () => { - const formData = { - assignee: 'Scott Norris', - preCompletionNotes: 'pre completion notes edited', - dueDate: '02/14/2024', - completionDate: '02/21/2024', - }; - - test('component loads correctly with action items', async () => { - render( - - - - - - - - - , - ); - - await wait(); - - await waitFor(() => { - expect( - screen.queryByText(translations.noActionItems), - ).not.toBeInTheDocument(); - }); - - expect(screen.getByText('#')).toBeInTheDocument(); - expect(screen.getByText(translations.assignee)).toBeInTheDocument(); - expect( - screen.getByText(translations.actionItemCategory), - ).toBeInTheDocument(); - expect( - screen.getByText(translations.preCompletionNotes), - ).toBeInTheDocument(); - expect( - screen.getByText(translations.postCompletionNotes), - ).toBeInTheDocument(); - - await wait(); - expect(screen.getAllByText('Harve Lance')[0]).toBeInTheDocument(); - - const asigneeAnchorElement = screen.getAllByText('Harve Lance')[0]; - expect(asigneeAnchorElement.tagName).toBe('A'); - expect(asigneeAnchorElement).toHaveAttribute('href', '/member/event1'); - - expect(screen.getAllByText('ActionItemCategory 1')[0]).toBeInTheDocument(); - const updateButtons = screen.getAllByTestId('editActionItemModalBtn'); - const previewButtons = screen.getAllByTestId('previewActionItemModalBtn'); - const updateStatusButtons = screen.getAllByTestId( - 'actionItemStatusChangeCheckbox', - ); - expect(updateButtons[0]).toBeInTheDocument(); - expect(previewButtons[0]).toBeInTheDocument(); - expect(updateStatusButtons[0]).toBeInTheDocument(); - }); - - test('component loads correctly with no action items', async () => { - render( - - - - - - - - - , - ); - - await wait(); - - await waitFor(() => { - expect( - screen.queryByText(translations.noActionItems), - ).toBeInTheDocument(); - }); - }); - - test('opens and closes the update modal correctly', async () => { - render( - - - - - - - - - - - , - ); - - await wait(); - - await waitFor(() => { - expect( - screen.getAllByTestId('editActionItemModalBtn')[0], - ).toBeInTheDocument(); - }); - userEvent.click(screen.getAllByTestId('editActionItemModalBtn')[0]); - - await waitFor(() => { - return expect( - screen.findByTestId('updateActionItemModalCloseBtn'), - ).resolves.toBeInTheDocument(); - }); - userEvent.click(screen.getByTestId('updateActionItemModalCloseBtn')); - - await waitForElementToBeRemoved(() => - screen.queryByTestId('updateActionItemModalCloseBtn'), - ); - }); - - test('opens and closes the action item status change modal correctly', async () => { - render( - - - - - - - - - - - , - ); - - await wait(); - - await waitFor(() => { - expect( - screen.getAllByTestId('actionItemStatusChangeCheckbox')[0], - ).toBeInTheDocument(); - }); - userEvent.click(screen.getAllByTestId('actionItemStatusChangeCheckbox')[0]); - - await waitFor(() => { - return expect( - screen.findByTestId('actionItemStatusChangeModalCloseBtn'), - ).resolves.toBeInTheDocument(); - }); - userEvent.click(screen.getByTestId('actionItemStatusChangeModalCloseBtn')); - - await waitForElementToBeRemoved(() => - screen.queryByTestId('actionItemStatusChangeModalCloseBtn'), - ); - }); - - test('completed action item status change modal loads correctly', async () => { - render( - - - - - - - - - - - , - ); - - await wait(); - - await waitFor(() => { - expect( - screen.getAllByTestId('actionItemStatusChangeCheckbox')[1], - ).toBeInTheDocument(); - }); - userEvent.click(screen.getAllByTestId('actionItemStatusChangeCheckbox')[1]); - - await waitFor(() => { - return expect( - screen.findByTestId('actionItemStatusChangeModalCloseBtn'), - ).resolves.toBeInTheDocument(); - }); - expect(screen.getByText(translations.actionItemStatus)).toBeInTheDocument(); - - expect( - screen.getByTestId('actionItemsStatusChangeNotes'), - ).toBeInTheDocument(); - expect( - screen.getByPlaceholderText(translations.actionItemCompleted), - ).toBeInTheDocument(); - expect( - screen.getByRole('button', { name: translations.makeActive }), - ).toBeInTheDocument(); - }); - - test('opens and closes the preview modal correctly', async () => { - render( - - - - - - - - - - - , - ); - - await wait(); - - await waitFor(() => { - expect( - screen.getAllByTestId('previewActionItemModalBtn')[0], - ).toBeInTheDocument(); - }); - userEvent.click(screen.getAllByTestId('previewActionItemModalBtn')[0]); - - await waitFor(() => { - return expect( - screen.findByTestId('previewActionItemModalCloseBtn'), - ).resolves.toBeInTheDocument(); - }); - userEvent.click(screen.getByTestId('previewActionItemModalCloseBtn')); - - await waitForElementToBeRemoved(() => - screen.queryByTestId('previewActionItemModalCloseBtn'), - ); - }); - - test('opens and closes the update and delete modals through the preview modal', async () => { - render( - - - - - - - - - - - , - ); - - await wait(); - - await waitFor(() => { - expect( - screen.getAllByTestId('previewActionItemModalBtn')[0], - ).toBeInTheDocument(); - }); - userEvent.click(screen.getAllByTestId('previewActionItemModalBtn')[0]); - - await waitFor(() => { - return expect( - screen.findByTestId('previewActionItemModalCloseBtn'), - ).resolves.toBeInTheDocument(); - }); - - await waitFor(() => { - expect( - screen.getByTestId('deleteActionItemPreviewModalBtn'), - ).toBeInTheDocument(); - }); - userEvent.click(screen.getByTestId('deleteActionItemPreviewModalBtn')); - - await waitFor(() => { - return expect( - screen.findByTestId('actionItemDeleteModalCloseBtn'), - ).resolves.toBeInTheDocument(); - }); - userEvent.click(screen.getByTestId('actionItemDeleteModalCloseBtn')); - - await waitForElementToBeRemoved(() => - screen.queryByTestId('actionItemDeleteModalCloseBtn'), - ); - - await waitFor(() => { - expect( - screen.getByTestId('editActionItemPreviewModalBtn'), - ).toBeInTheDocument(); - }); - userEvent.click(screen.getByTestId('editActionItemPreviewModalBtn')); - - await waitFor(() => { - return expect( - screen.findByTestId('updateActionItemModalCloseBtn'), - ).resolves.toBeInTheDocument(); - }); - userEvent.click(screen.getByTestId('updateActionItemModalCloseBtn')); - - await waitForElementToBeRemoved(() => - screen.queryByTestId('updateActionItemModalCloseBtn'), - ); - }); - - test('updates an action item and toasts success', async () => { - render( - - - - - - - - - - - , - ); - - await wait(); - - await waitFor(() => { - expect( - screen.getAllByTestId('editActionItemModalBtn')[0], - ).toBeInTheDocument(); - }); - userEvent.click(screen.getAllByTestId('editActionItemModalBtn')[0]); - - await waitFor(() => { - expect(screen.getByTestId('formUpdateAssignee')).toBeInTheDocument(); - }); - - userEvent.selectOptions( - screen.getByTestId('formUpdateAssignee'), - formData.assignee, - ); - - const preCompletionNotes = screen.getByPlaceholderText( - translations.preCompletionNotes, - ); - fireEvent.change(preCompletionNotes, { target: { value: '' } }); - userEvent.type(preCompletionNotes, formData.preCompletionNotes); - - // const postCompletionNotes = screen.getByPlaceholderText( - // translations.postCompletionNotes, - // ); - // fireEvent.change(postCompletionNotes, { target: { value: '' } }); - // userEvent.type(postCompletionNotes, formData.postCompletionNotes); - - const dueDatePicker = screen.getByLabelText(translations.dueDate); - fireEvent.change(dueDatePicker, { - target: { value: formData.dueDate }, - }); - - const completionDatePicker = screen.getByLabelText( - translations.completionDate, - ); - fireEvent.change(completionDatePicker, { - target: { value: formData.completionDate }, - }); - - // await waitFor(() => { - // expect(screen.getByTestId('alldayCheck')).toBeInTheDocument(); - // }); - // userEvent.click(screen.getByTestId('alldayCheck')); - - await waitFor(() => { - expect(screen.getByTestId('editActionItemBtn')).toBeInTheDocument(); - }); - userEvent.click(screen.getByTestId('editActionItemBtn')); - - await waitFor(() => { - expect(toast.success).toBeCalledWith(translations.successfulUpdation); - }); - }); - - test('toasts error on unsuccessful updation', async () => { - render( - - - - - - - - - - - , - ); - - await wait(); - - await waitFor(() => { - expect( - screen.getAllByTestId('editActionItemModalBtn')[0], - ).toBeInTheDocument(); - }); - userEvent.click(screen.getAllByTestId('editActionItemModalBtn')[0]); - - await waitFor(() => { - expect(screen.getByTestId('formUpdateAssignee')).toBeInTheDocument(); - }); - - userEvent.selectOptions( - screen.getByTestId('formUpdateAssignee'), - formData.assignee, - ); - - const preCompletionNotes = screen.getByPlaceholderText( - translations.preCompletionNotes, - ); - fireEvent.change(preCompletionNotes, { target: { value: '' } }); - userEvent.type(preCompletionNotes, formData.preCompletionNotes); - - // const postCompletionNotes = screen.getByPlaceholderText( - // translations.postCompletionNotes, - // ); - // fireEvent.change(postCompletionNotes, { target: { value: '' } }); - // userEvent.type(postCompletionNotes, formData.postCompletionNotes); - - const dueDatePicker = screen.getByLabelText(translations.dueDate); - fireEvent.change(dueDatePicker, { - target: { value: formData.dueDate }, - }); - - const completionDatePicker = screen.getByLabelText( - translations.completionDate, - ); - fireEvent.change(completionDatePicker, { - target: { value: formData.completionDate }, - }); - - // await waitFor(() => { - // expect(screen.getByTestId('alldayCheck')).toBeInTheDocument(); - // }); - // userEvent.click(screen.getByTestId('alldayCheck')); - - await waitFor(() => { - expect(screen.getByTestId('editActionItemBtn')).toBeInTheDocument(); - }); - userEvent.click(screen.getByTestId('editActionItemBtn')); - - await waitFor(() => { - expect(toast.error).toHaveBeenCalled(); - }); - }); - - test('updates an action item status through the action item status change modal', async () => { - render( - - - - - - - - - - - , - ); - - await wait(); - - await waitFor(() => { - expect( - screen.getAllByTestId('actionItemStatusChangeCheckbox')[0], - ).toBeInTheDocument(); - }); - userEvent.click(screen.getAllByTestId('actionItemStatusChangeCheckbox')[0]); - - await waitFor(() => { - expect( - screen.getByTestId('actionItemsStatusChangeNotes'), - ).toBeInTheDocument(); - }); - - const postCompletionNotes = screen.getByTestId( - 'actionItemsStatusChangeNotes', - ); - fireEvent.change(postCompletionNotes, { target: { value: '' } }); - userEvent.type( - postCompletionNotes, - 'this action item has been completed successfully', - ); - - await waitFor(() => { - expect( - screen.getByTestId('actionItemStatusChangeSubmitBtn'), - ).toBeInTheDocument(); - }); - expect( - screen.getByTestId('actionItemStatusChangeSubmitBtn'), - ).toHaveTextContent(translations.markCompletion); - userEvent.click(screen.getByTestId('actionItemStatusChangeSubmitBtn')); - - await waitFor(() => { - expect(toast.success).toBeCalledWith(translations.successfulUpdation); - }); - - await waitFor(() => { - expect( - screen.getAllByTestId('actionItemStatusChangeCheckbox')[1], - ).toBeInTheDocument(); - }); - userEvent.click(screen.getAllByTestId('actionItemStatusChangeCheckbox')[1]); - - await waitFor(() => { - expect( - screen.getByTestId('actionItemsStatusChangeNotes'), - ).toBeInTheDocument(); - }); - - const preCompletionNotes = screen.getByTestId( - 'actionItemsStatusChangeNotes', - ); - fireEvent.change(preCompletionNotes, { target: { value: '' } }); - userEvent.type( - preCompletionNotes, - 'this action item has been made active again', - ); - - await waitFor(() => { - expect( - screen.getByTestId('actionItemStatusChangeSubmitBtn'), - ).toBeInTheDocument(); - }); - expect( - screen.getByTestId('actionItemStatusChangeSubmitBtn'), - ).toHaveTextContent(translations.makeActive); - userEvent.click(screen.getByTestId('actionItemStatusChangeSubmitBtn')); - - await waitFor(() => { - expect(toast.success).toBeCalledWith(translations.successfulUpdation); - }); - }); - - test('deletes the action item and toasts success', async () => { - render( - - - - - - - - - - - , - ); - - await wait(); - - await waitFor(() => { - expect( - screen.getAllByTestId('previewActionItemModalBtn')[0], - ).toBeInTheDocument(); - }); - userEvent.click(screen.getAllByTestId('previewActionItemModalBtn')[0]); - - await waitFor(() => { - return expect( - screen.findByTestId('previewActionItemModalCloseBtn'), - ).resolves.toBeInTheDocument(); - }); - - await waitFor(() => { - expect( - screen.getByTestId('deleteActionItemPreviewModalBtn'), - ).toBeInTheDocument(); - }); - userEvent.click(screen.getByTestId('deleteActionItemPreviewModalBtn')); - - await waitFor(() => { - return expect( - screen.findByTestId('actionItemDeleteModalCloseBtn'), - ).resolves.toBeInTheDocument(); - }); - - userEvent.click(screen.getByTestId('deleteActionItemBtn')); - - await waitFor(() => { - expect(toast.success).toBeCalledWith(translations.successfulDeletion); - }); - }); - - test('toasts error on unsuccessful deletion', async () => { - render( - - - - - - - - - - - , - ); - - await wait(); - - await waitFor(() => { - expect( - screen.getAllByTestId('previewActionItemModalBtn')[0], - ).toBeInTheDocument(); - }); - userEvent.click(screen.getAllByTestId('previewActionItemModalBtn')[0]); - - await waitFor(() => { - return expect( - screen.findByTestId('previewActionItemModalCloseBtn'), - ).resolves.toBeInTheDocument(); - }); - - await waitFor(() => { - expect( - screen.getByTestId('deleteActionItemPreviewModalBtn'), - ).toBeInTheDocument(); - }); - userEvent.click(screen.getByTestId('deleteActionItemPreviewModalBtn')); - - await waitFor(() => { - return expect( - screen.findByTestId('actionItemDeleteModalCloseBtn'), - ).resolves.toBeInTheDocument(); - }); - userEvent.click(screen.getByTestId('deleteActionItemBtn')); - - await waitFor(() => { - expect(toast.error).toHaveBeenCalled(); - }); - }); - - test('shows the overlay text on action item notes', async () => { - const { getAllByTestId } = render( - - - - - - - - - - - , - ); - - await wait(); - - await waitFor(() => { - expect( - screen.getAllByTestId('actionItemPreCompletionNotesOverlay')[0], - ).toBeInTheDocument(); - }); - - await waitFor(() => { - fireEvent.mouseEnter( - getAllByTestId('actionItemPreCompletionNotesOverlay')[0], - ); - }); - - await waitFor(() => { - expect(screen.getByTestId('popover-actionItem1')).toBeInTheDocument(); - }); - - await waitFor(() => { - fireEvent.mouseLeave( - getAllByTestId('actionItemPreCompletionNotesOverlay')[0], - ); - }); - - await waitFor(() => { - fireEvent.mouseEnter( - getAllByTestId('actionItemPostCompletionNotesOverlay')[0], - ); - }); - - await waitFor(() => { - expect(screen.getByTestId('popover-actionItem2')).toBeInTheDocument(); - }); - - await waitFor(() => { - fireEvent.mouseLeave( - getAllByTestId('actionItemPostCompletionNotesOverlay')[0], - ); - }); - }); - - test('Action Items loads with correct headers', async () => { - render( - - - - - - - - - , - ); - - await wait(); - - const actionItemHeaders = screen.getByTestId('actionItemsHeader'); - expect(actionItemHeaders).toBeInTheDocument(); - expect(screen.getByText(translations.assignee)).toBeInTheDocument(); - expect( - screen.getByText(translations.actionItemCategory), - ).toBeInTheDocument(); - expect( - screen.getByText(translations.preCompletionNotes), - ).toBeInTheDocument(); - expect( - screen.getByText(translations.postCompletionNotes), - ).toBeInTheDocument(); - expect(screen.getByText(translations.options)).toBeInTheDocument(); - }); -}); diff --git a/src/components/ActionItems/ActionItemsContainer.tsx b/src/components/ActionItems/ActionItemsContainer.tsx deleted file mode 100644 index 4b393514e6..0000000000 --- a/src/components/ActionItems/ActionItemsContainer.tsx +++ /dev/null @@ -1,568 +0,0 @@ -import React, { useState } from 'react'; -import dayjs from 'dayjs'; -import type { ChangeEvent } from 'react'; -import { - Button, - Col, - Form, - Modal, - OverlayTrigger, - Popover, - Row, -} from 'react-bootstrap'; -import { useTranslation } from 'react-i18next'; -import { toast } from 'react-toastify'; - -import { - DELETE_ACTION_ITEM_MUTATION, - UPDATE_ACTION_ITEM_MUTATION, -} from 'GraphQl/Mutations/mutations'; -import { useMutation } from '@apollo/client'; - -import type { - InterfaceActionItemInfo, - InterfaceMemberInfo, -} from 'utils/interfaces'; -import styles from './ActionItemsContainer.module.css'; -import ActionItemUpdateModal from '../../screens/OrganizationActionItems/ActionItemUpdateModal'; -import ActionItemPreviewModal from '../../screens/OrganizationActionItems/ActionItemPreviewModal'; -import ActionItemDeleteModal from '../../screens/OrganizationActionItems/ActionItemDeleteModal'; -import { Link } from 'react-router-dom'; - -/** - * ActionItemsContainer component is responsible for displaying, managing, and updating action items - * related to either an organization or an event. It provides a UI for previewing, updating, and deleting - * action items, as well as changing their status. - * - * @param props - The component props - * @param actionItemsConnection - Specifies the connection type (Organization or Event) to determine the context of the action items. - * @param actionItemsData - Array of action item data to be displayed. - * @param membersData - Array of member data for the organization. - * @param actionItemsRefetch - Function to refetch the action items data. - * - * @example - * ```tsx - * - * ``` - * This example renders the `ActionItemsContainer` component with organization connection, providing the necessary action items and members data along with a refetch function. - */ -function actionItemsContainer({ - actionItemsConnection, - actionItemsData, - membersData, - actionItemsRefetch, -}: { - actionItemsConnection: 'Organization' | 'Event'; - actionItemsData: InterfaceActionItemInfo[] | undefined; - membersData: InterfaceMemberInfo[] | undefined; - actionItemsRefetch: () => void; -}): JSX.Element { - // Translation hooks for localized text - const { t } = useTranslation('translation', { - keyPrefix: 'organizationActionItems', - }); - const { t: tCommon } = useTranslation('common'); - - // State hooks for controlling modals and action item properties - const [actionItemPreviewModalIsOpen, setActionItemPreviewModalIsOpen] = - useState(false); - const [actionItemUpdateModalIsOpen, setActionItemUpdateModalIsOpen] = - useState(false); - const [actionItemDeleteModalIsOpen, setActionItemDeleteModalIsOpen] = - useState(false); - const [actionItemStatusModal, setActionItemStatusModal] = useState(false); - const [isActionItemCompleted, setIsActionItemCompleted] = useState(false); - - const [assignmentDate, setAssignmentDate] = useState(new Date()); - const [dueDate, setDueDate] = useState(new Date()); - const [completionDate, setCompletionDate] = useState(new Date()); - const [actionItemId, setActionItemId] = useState(''); - const [actionItemNotes, setActionItemNotes] = useState(''); - - const [formState, setFormState] = useState({ - assignee: '', - assigner: '', - assigneeId: '', - preCompletionNotes: '', - postCompletionNotes: '', - isCompleted: false, - }); - - /** - * Opens the preview modal for the selected action item. - * - * @param actionItem - The action item to be previewed. - */ - const showPreviewModal = (actionItem: InterfaceActionItemInfo): void => { - setActionItemState(actionItem); - setActionItemPreviewModalIsOpen(true); - }; - - /** - * Toggles the update modal visibility. - */ - const showUpdateModal = (): void => { - setActionItemUpdateModalIsOpen(!actionItemUpdateModalIsOpen); - }; - - /** - * Hides the preview modal. - */ - const hidePreviewModal = (): void => { - setActionItemPreviewModalIsOpen(false); - }; - - /** - * Hides the update modal and resets the action item ID. - */ - const hideUpdateModal = (): void => { - setActionItemId(''); - setActionItemUpdateModalIsOpen(!actionItemUpdateModalIsOpen); - }; - - /** - * Toggles the delete modal visibility. - */ - const toggleDeleteModal = (): void => { - setActionItemDeleteModalIsOpen(!actionItemDeleteModalIsOpen); - }; - - // Apollo Client mutations for updating and deleting action items - const [updateActionItem] = useMutation(UPDATE_ACTION_ITEM_MUTATION); - - /** - * Handles the form submission for updating an action item. - * - * @param e - The form submission event. - */ - const updateActionItemHandler = async ( - e: ChangeEvent, - ): Promise => { - e.preventDefault(); - try { - await updateActionItem({ - variables: { - actionItemId, - assigneeId: formState.assigneeId, - preCompletionNotes: formState.preCompletionNotes, - postCompletionNotes: formState.postCompletionNotes, - dueDate: dayjs(dueDate).format('YYYY-MM-DD'), - completionDate: dayjs(completionDate).format('YYYY-MM-DD'), - isCompleted: formState.isCompleted, - }, - }); - - actionItemsRefetch(); - hideUpdateModal(); - toast.success(t('successfulUpdation')); - } catch (error: unknown) { - if (error instanceof Error) { - toast.error(error.message); - console.log(error.message); - } - } - }; - - const [removeActionItem] = useMutation(DELETE_ACTION_ITEM_MUTATION); - - /** - * Handles the action item deletion. - */ - const deleteActionItemHandler = async (): Promise => { - try { - await removeActionItem({ - variables: { - actionItemId, - }, - }); - - actionItemsRefetch(); - toggleDeleteModal(); - toast.success(t('successfulDeletion')); - } catch (error: unknown) { - if (error instanceof Error) { - toast.error(error.message); - console.log(error.message); - } - } - }; - - /** - * Handles the edit button click and opens the update modal with the action item data. - * - * @param actionItem - The action item to be edited. - */ - const handleEditClick = (actionItem: InterfaceActionItemInfo): void => { - setActionItemState(actionItem); - showUpdateModal(); - }; - - /** - * Handles the action item status change and updates the state accordingly. - * - * @param actionItem - The action item whose status is being changed. - */ - const handleActionItemStatusChange = ( - actionItem: InterfaceActionItemInfo, - ): void => { - actionItem = { ...actionItem, isCompleted: !actionItem.isCompleted }; - setIsActionItemCompleted(!actionItem.isCompleted); - setActionItemState(actionItem); - setActionItemStatusModal(true); - }; - - /** - * Hides the action item status modal. - */ - const hideActionItemStatusModal = (): void => { - setActionItemStatusModal(false); - }; - - /** - * Sets the state with the action item data. - * - * @param actionItem - The action item data. - */ - const setActionItemState = (actionItem: InterfaceActionItemInfo): void => { - setFormState({ - ...formState, - assignee: `${actionItem.assignee.firstName} ${actionItem.assignee.lastName}`, - assigner: `${actionItem.assigner.firstName} ${actionItem.assigner.lastName}`, - assigneeId: actionItem.assignee._id, - preCompletionNotes: actionItem.preCompletionNotes, - postCompletionNotes: actionItem.postCompletionNotes, - isCompleted: actionItem.isCompleted, - }); - setActionItemId(actionItem._id); - setDueDate(actionItem.dueDate); - setAssignmentDate(actionItem.assignmentDate); - setCompletionDate(actionItem.completionDate); - }; - - const popover = ( - - {actionItemNotes} - - ); - - return ( - <> -
-
- - -
{'#'}
- - -
{t('assignee')}
- - - {t('actionItemCategory')} - - -
{t('preCompletionNotes')}
- - -
{t('postCompletionNotes')}
- - -
{t('options')}
- -
-
- -
- {actionItemsData?.map((actionItem, index) => ( -
- - - {index + 1} - - - - {`${actionItem.assignee.firstName} ${actionItem.assignee.lastName}`} - - - - {actionItem.actionItemCategory.name} - - -
- - { - setActionItemId(actionItem._id); - setActionItemNotes(actionItem.preCompletionNotes); - }} - > - {actionItem.preCompletionNotes.length > 25 - ? `${actionItem.preCompletionNotes.substring(0, 25)}...` - : actionItem.preCompletionNotes} - - -
- - -
- {actionItem.isCompleted ? ( - - { - setActionItemId(actionItem._id); - setActionItemNotes(actionItem.postCompletionNotes); - }} - className="ms-3 " - > - {actionItem.postCompletionNotes?.length > 25 - ? `${actionItem.postCompletionNotes.substring(0, 25)}...` - : actionItem.postCompletionNotes} - - - ) : ( - - {t('actionItemActive')} - - )} -
- - -
- handleActionItemStatusChange(actionItem)} - /> - - -
- -
- - {index !== actionItemsData.length - 1 &&
} -
- ))} - - {actionItemsData?.length === 0 && ( -
- {t('noActionItems')} -
- )} -
-
- - {/* action item status change modal */} - - -

{t('actionItemStatus')}

- -
- -
- - {isActionItemCompleted - ? t('preCompletionNotes') - : t('postCompletionNotes')} - - { - if (isActionItemCompleted) { - setFormState({ - ...formState, - preCompletionNotes: e.target.value, - }); - } else { - setFormState({ - ...formState, - postCompletionNotes: e.target.value, - }); - } - }} - /> - - -
-
- - {/* preview modal */} - - - {/* Update Modal */} - - - {/* Delete Modal */} - - - ); -} - -export default actionItemsContainer; diff --git a/src/components/ActionItems/ActionItemsContainerMocks.ts b/src/components/ActionItems/ActionItemsContainerMocks.ts deleted file mode 100644 index a2dc20c5eb..0000000000 --- a/src/components/ActionItems/ActionItemsContainerMocks.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { - UPDATE_ACTION_ITEM_MUTATION, - DELETE_ACTION_ITEM_MUTATION, -} from 'GraphQl/Mutations/mutations'; - -export const MOCKS = [ - { - request: { - query: UPDATE_ACTION_ITEM_MUTATION, - variables: { - actionItemId: 'actionItem1', - assigneeId: 'user2', - preCompletionNotes: 'pre completion notes edited', - postCompletionNotes: 'Post Completion Notes', - dueDate: '2024-02-14', - completionDate: '2024-02-21', - isCompleted: false, - }, - }, - result: { - data: { - updateActionItem: { - _id: 'actionItem1', - }, - }, - }, - }, - { - request: { - query: UPDATE_ACTION_ITEM_MUTATION, - variables: { - actionItemId: 'actionItem1', - assigneeId: 'user1', - preCompletionNotes: 'Pre Completion Notes', - postCompletionNotes: 'this action item has been completed successfully', - dueDate: '2024-02-21', - completionDate: '2024-02-21', - isCompleted: true, - }, - }, - result: { - data: { - updateActionItem: { - _id: 'actionItem1', - }, - }, - }, - }, - { - request: { - query: UPDATE_ACTION_ITEM_MUTATION, - variables: { - actionItemId: 'actionItem2', - assigneeId: 'user1', - preCompletionNotes: 'this action item has been made active again', - postCompletionNotes: 'Post Completion Notes', - dueDate: '2024-02-21', - completionDate: '2024-02-21', - isCompleted: false, - }, - }, - result: { - data: { - updateActionItem: { - _id: 'actionItem1', - }, - }, - }, - }, - { - request: { - query: DELETE_ACTION_ITEM_MUTATION, - variables: { - actionItemId: 'actionItem1', - }, - }, - result: { - data: { - removeActionItem: { - _id: 'actionItem1', - }, - }, - }, - }, -]; - -export const MOCKS_ERROR_MUTATIONS = [ - { - request: { - query: UPDATE_ACTION_ITEM_MUTATION, - variables: { - actionItemId: 'actionItem1', - assigneeId: 'user2', - preCompletionNotes: 'pre completion notes edited', - postCompletionNotes: '', - dueDate: '2024-02-14', - completionDate: '2024-02-21', - isCompleted: false, - }, - }, - error: new Error('Mock Graphql Error'), - }, - { - request: { - query: DELETE_ACTION_ITEM_MUTATION, - variables: { - actionItemId: 'actionItem1', - }, - }, - error: new Error('Mock Graphql Error'), - }, -]; diff --git a/src/components/ActionItems/ActionItemsContainerProps.ts b/src/components/ActionItems/ActionItemsContainerProps.ts deleted file mode 100644 index 19ba6f6369..0000000000 --- a/src/components/ActionItems/ActionItemsContainerProps.ts +++ /dev/null @@ -1,131 +0,0 @@ -type ActionItemsConnectionType = 'Organization' | 'Event'; - -export const props = { - actionItemsConnection: 'Organization' as ActionItemsConnectionType, - actionItemsData: [ - { - _id: 'actionItem1', - assignee: { - _id: 'user1', - firstName: 'Harve', - lastName: 'Lance', - }, - actionItemCategory: { - _id: 'actionItemCategory1', - name: 'ActionItemCategory 1', - }, - preCompletionNotes: 'Pre Completion Notes', - postCompletionNotes: 'Post Completion Notes', - assignmentDate: new Date('2024-02-14'), - dueDate: new Date('2024-02-21'), - completionDate: new Date('2024-02-21'), - isCompleted: false, - assigner: { - _id: 'user0', - firstName: 'Wilt', - lastName: 'Shepherd', - }, - event: { - _id: 'event1', - title: 'event 1', - }, - creator: { - _id: 'user0', - firstName: 'Wilt', - lastName: 'Shepherd', - }, - }, - { - _id: 'actionItem2', - assignee: { - _id: 'user1', - firstName: 'Harve', - lastName: 'Lance', - }, - actionItemCategory: { - _id: 'actionItemCategory1', - name: 'ActionItemCategory 1', - }, - preCompletionNotes: 'Pre Completion Notes', - postCompletionNotes: 'Post Completion Notes', - assignmentDate: new Date('2024-02-14'), - dueDate: new Date('2024-02-21'), - completionDate: new Date('2024-02-21'), - isCompleted: true, - assigner: { - _id: 'user0', - firstName: 'Wilt', - lastName: 'Shepherd', - }, - event: { - _id: 'event1', - title: 'event 1', - }, - creator: { - _id: 'user0', - firstName: 'Wilt', - lastName: 'Shepherd', - }, - }, - { - _id: 'actionItem3', - assignee: { - _id: 'user1', - firstName: 'Harve', - lastName: 'Lance', - }, - actionItemCategory: { - _id: 'actionItemCategory1', - name: 'ActionItemCategory 1', - }, - preCompletionNotes: 'Pre Completion Notes more than 25 characters', - postCompletionNotes: 'Post Completion Notes more than 25 characters', - assignmentDate: new Date('2024-02-14'), - dueDate: new Date('2024-02-21'), - completionDate: new Date('2024-02-21'), - isCompleted: true, - assigner: { - _id: 'user0', - firstName: 'Wilt', - lastName: 'Shepherd', - }, - event: { - _id: 'event1', - title: 'event 1', - }, - creator: { - _id: 'user0', - firstName: 'Wilt', - lastName: 'Shepherd', - }, - }, - ], - membersData: [ - { - _id: 'user1', - firstName: 'Harve', - lastName: 'Lance', - email: 'harve@example.com', - image: '', - organizationsBlockedBy: [], - createdAt: '2024-02-14', - }, - { - _id: 'user2', - firstName: 'Scott', - lastName: 'Norris', - email: 'scott@example.com', - image: '', - organizationsBlockedBy: [], - createdAt: '2024-02-14', - }, - ], - actionItemsRefetch: jest.fn(), -}; - -export const props2 = { - actionItemsConnection: 'Organization' as ActionItemsConnectionType, - actionItemsData: [], - membersData: [], - actionItemsRefetch: jest.fn(), -}; diff --git a/src/components/ActionItems/ActionItemsModal.test.tsx b/src/components/ActionItems/ActionItemsModal.test.tsx deleted file mode 100644 index 01a15ee6aa..0000000000 --- a/src/components/ActionItems/ActionItemsModal.test.tsx +++ /dev/null @@ -1,294 +0,0 @@ -import React from 'react'; -import { - act, - fireEvent, - render, - screen, - waitFor, -} from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import { MockedProvider } from '@apollo/react-testing'; -import { ActionItemsModal } from './ActionItemsModal'; -import { BrowserRouter } from 'react-router-dom'; -import { Provider } from 'react-redux'; -import { store } from 'state/store'; -import { I18nextProvider } from 'react-i18next'; -import i18nForTest from 'utils/i18nForTest'; -import { LocalizationProvider } from '@mui/x-date-pickers'; -import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; -import { toast } from 'react-toastify'; - -import { - MOCKS_ERROR_ACTION_ITEM_CATEGORY_LIST_QUERY, - MOCKS_ERROR_ACTION_ITEM_LIST_QUERY, - MOCKS_ERROR_MEMBERS_LIST_QUERY, - MOCKS_ERROR_MUTATIONS, -} from '../../screens/OrganizationActionItems/OrganizationActionItemsErrorMocks'; -import { MOCKS } from '../../screens/OrganizationActionItems/OrganizationActionItemMocks'; -import { StaticMockLink } from 'utils/StaticMockLink'; - -jest.mock('react-toastify', () => ({ - toast: { - success: jest.fn(), - error: jest.fn(), - }, -})); - -jest.mock('@mui/x-date-pickers/DateTimePicker', () => { - return { - DateTimePicker: jest.requireActual( - '@mui/x-date-pickers/DesktopDateTimePicker', - ).DesktopDateTimePicker, - }; -}); - -async function wait(ms = 100): Promise { - await act(() => { - return new Promise((resolve) => { - setTimeout(resolve, ms); - }); - }); -} - -const link = new StaticMockLink(MOCKS, true); -const link2 = new StaticMockLink( - MOCKS_ERROR_ACTION_ITEM_CATEGORY_LIST_QUERY, - true, -); -const link3 = new StaticMockLink(MOCKS_ERROR_MEMBERS_LIST_QUERY, true); -const link4 = new StaticMockLink(MOCKS_ERROR_ACTION_ITEM_LIST_QUERY, true); -const link5 = new StaticMockLink(MOCKS_ERROR_MUTATIONS, true); - -const translations = JSON.parse( - JSON.stringify( - i18nForTest.getDataByLanguage('en')?.translation.organizationActionItems, - ), -); - -describe('Testing Check In Attendees Modal', () => { - const formData = { - actionItemCategory: 'ActionItemCategory 1', - assignee: 'Harve Lance', - preCompletionNotes: 'pre completion notes', - dueDate: '02/14/2024', - }; - - const props = { - show: true, - eventId: 'event1', - orgId: '123', - handleClose: jest.fn(), - }; - - test('The modal should be rendered properly', async () => { - render( - - - - - - - - - - - , - ); - - await waitFor(() => - expect(screen.queryByTestId('modal-title')).toBeInTheDocument(), - ); - - await waitFor(() => { - return expect( - screen.findByTestId('createEventActionItemBtn'), - ).resolves.toBeInTheDocument(); - }); - }); - - test('render error component on unsuccessful action item category list query', async () => { - const { queryByText } = render( - - - - - - - - - , - ); - - await wait(); - - await waitFor(() => { - expect(queryByText('createEventActionItemBtn')).not.toBeInTheDocument(); - }); - }); - - test('render error component on unsuccessful member list query', async () => { - const { queryByText } = render( - - - - - - - - - , - ); - - await wait(); - - await waitFor(() => { - expect(queryByText('createEventActionItemBtn')).not.toBeInTheDocument(); - }); - }); - - test('render error component on unsuccessful action items list query', async () => { - const { queryByText } = render( - - - - - - - - - , - ); - - await wait(); - - await waitFor(() => { - expect(queryByText('createEventActionItemBtn')).not.toBeInTheDocument(); - }); - }); - - test('creates new action item associated with the event', async () => { - render( - - - - - - - - - - - , - ); - - await wait(); - - await waitFor(() => { - expect( - screen.getByTestId('createEventActionItemBtn'), - ).toBeInTheDocument(); - }); - userEvent.click(screen.getByTestId('createEventActionItemBtn')); - - await waitFor(() => { - return expect( - screen.findByTestId('createActionItemModalCloseBtn'), - ).resolves.toBeInTheDocument(); - }); - - await waitFor(() => { - expect( - screen.getByTestId('formSelectActionItemCategory'), - ).toBeInTheDocument(); - }); - - userEvent.selectOptions( - screen.getByTestId('formSelectActionItemCategory'), - formData.actionItemCategory, - ); - - userEvent.selectOptions( - screen.getByTestId('formSelectAssignee'), - formData.assignee, - ); - - userEvent.type( - screen.getByPlaceholderText(translations.preCompletionNotes), - formData.preCompletionNotes, - ); - - const dueDatePicker = screen.getByLabelText(translations.dueDate); - fireEvent.change(dueDatePicker, { - target: { value: formData.dueDate }, - }); - - userEvent.click(screen.getByTestId('createActionItemFormSubmitBtn')); - - await waitFor(() => { - expect(toast.success).toBeCalledWith(translations.successfulCreation); - }); - }); - - test('toasts error on unsuccessful creation', async () => { - render( - - - - - - - - - - - , - ); - - await wait(); - - await waitFor(() => { - expect( - screen.getByTestId('createEventActionItemBtn'), - ).toBeInTheDocument(); - }); - userEvent.click(screen.getByTestId('createEventActionItemBtn')); - - await waitFor(() => { - return expect( - screen.findByTestId('createActionItemModalCloseBtn'), - ).resolves.toBeInTheDocument(); - }); - - await waitFor(() => { - expect( - screen.getByTestId('formSelectActionItemCategory'), - ).toBeInTheDocument(); - }); - - userEvent.selectOptions( - screen.getByTestId('formSelectActionItemCategory'), - formData.actionItemCategory, - ); - - userEvent.selectOptions( - screen.getByTestId('formSelectAssignee'), - formData.assignee, - ); - - userEvent.type( - screen.getByPlaceholderText(translations.preCompletionNotes), - formData.preCompletionNotes, - ); - - const dueDatePicker = screen.getByLabelText(translations.dueDate); - fireEvent.change(dueDatePicker, { - target: { value: formData.dueDate }, - }); - - userEvent.click(screen.getByTestId('createActionItemFormSubmitBtn')); - - await waitFor(() => { - expect(toast.error).toHaveBeenCalled(); - }); - }); -}); diff --git a/src/components/ActionItems/ActionItemsModal.tsx b/src/components/ActionItems/ActionItemsModal.tsx deleted file mode 100644 index f2e7c362a7..0000000000 --- a/src/components/ActionItems/ActionItemsModal.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import React from 'react'; -import { Modal } from 'react-bootstrap'; -import styles from 'components/ActionItems/ActionItemsWrapper.module.css'; -import { ActionItemsModalBody } from './ActionItemsModalBody'; -import { useTranslation } from 'react-i18next'; - -/** - * Interface defining the props for the ActionItemsModal component. - */ -export interface InterfaceModalProp { - show: boolean; - eventId: string; - orgId: string; - handleClose: () => void; -} - -/** - * ActionItemsModal component displays a modal containing action items for a specific event within an organization. - * It includes a header with a title and a body that renders the ActionItemsModalBody component. - * - * @param props - The props for the ActionItemsModal component. - * @param show - Indicates whether the modal is visible. - * @param eventId - Event ID related to the action items. - * @param orgId - Organization ID related to the action items. - * @param handleClose - Function to handle closing the modal. - * - * - * @example - * ```tsx - * setShowModal(false)} - * /> - * ``` - * This example renders the `ActionItemsModal` component with the modal shown, using specific event and organization IDs, and a function to handle closing the modal. - */ -export const ActionItemsModal = (props: InterfaceModalProp): JSX.Element => { - const { show, eventId, orgId, handleClose } = props; - const { t } = useTranslation('translation', { - keyPrefix: 'organizationActionItems', - }); - - return ( - <> - - - - {t('eventActionItems')} - - - - - - - - ); -}; diff --git a/src/components/ActionItems/ActionItemsModalBody.tsx b/src/components/ActionItems/ActionItemsModalBody.tsx deleted file mode 100644 index a94901d808..0000000000 --- a/src/components/ActionItems/ActionItemsModalBody.tsx +++ /dev/null @@ -1,257 +0,0 @@ -import React, { useState } from 'react'; -import type { ChangeEvent } from 'react'; -import { Button } from 'react-bootstrap'; -import { useMutation, useQuery } from '@apollo/client'; -import { - ACTION_ITEM_CATEGORY_LIST, - ACTION_ITEM_LIST, - MEMBERS_LIST, -} from 'GraphQl/Queries/Queries'; -import styles from 'components/ActionItems/ActionItemsWrapper.module.css'; -import type { - InterfaceActionItemCategoryList, - InterfaceActionItemList, - InterfaceMembersList, -} from 'utils/interfaces'; - -import ActionItemsContainer from 'components/ActionItems/ActionItemsContainer'; -import Loader from 'components/Loader/Loader'; -import { WarningAmberRounded } from '@mui/icons-material'; -import { CREATE_ACTION_ITEM_MUTATION } from 'GraphQl/Mutations/ActionItemMutations'; -import dayjs from 'dayjs'; -import { toast } from 'react-toastify'; -import ActionItemCreateModal from 'screens/OrganizationActionItems/ActionItemCreateModal'; -import { useTranslation } from 'react-i18next'; - -/** - * Component displaying the body of the Action Items modal. - * Fetches and displays action items, members, and action item categories related to a specific event within an organization. - * - * @param organizationId - The ID of the organization. - * @param eventId - The ID of the event. - * - * - * @example - * ```tsx - * - * ``` - * This example renders the `ActionItemsModalBody` component with the provided organization and event IDs. - */ -export const ActionItemsModalBody = ({ - organizationId, - eventId, -}: { - organizationId: string; - eventId: string; -}): JSX.Element => { - // Setting up translation - const { t } = useTranslation('translation', { - keyPrefix: 'organizationActionItems', - }); - const { t: tCommon } = useTranslation('common'); - - // State to manage due date - const [dueDate, setDueDate] = useState(new Date()); - // State to manage the visibility of the action item creation modal - const [actionItemCreateModalIsOpen, setActionItemCreateModalIsOpen] = - useState(false); - - // State to manage form inputs - const [formState, setFormState] = useState({ - actionItemCategoryId: '', - assigneeId: '', - preCompletionNotes: '', - }); - - // Query to fetch action item categories - const { - data: actionItemCategoriesData, - loading: actionItemCategoriesLoading, - error: actionItemCategoriesError, - }: { - data: InterfaceActionItemCategoryList | undefined; - loading: boolean; - error?: Error | undefined; - } = useQuery(ACTION_ITEM_CATEGORY_LIST, { - variables: { - organizationId, - }, - notifyOnNetworkStatusChange: true, - }); - - // Query to fetch members list - const { - data: membersData, - loading: membersLoading, - error: membersError, - }: { - data: InterfaceMembersList | undefined; - loading: boolean; - error?: Error | undefined; - } = useQuery(MEMBERS_LIST, { - variables: { id: organizationId }, - }); - - // Query to fetch action items list - const { - data: actionItemsData, - loading: actionItemsLoading, - error: actionItemsError, - refetch: actionItemsRefetch, - }: { - data: InterfaceActionItemList | undefined; - loading: boolean; - error?: Error | undefined; - refetch: () => void; - } = useQuery(ACTION_ITEM_LIST, { - variables: { - organizationId, - eventId, - orderBy: 'createdAt_DESC', - }, - notifyOnNetworkStatusChange: true, - }); - - // Mutation to create a new action item - const [createActionItem] = useMutation(CREATE_ACTION_ITEM_MUTATION); - - /** - * Handles the creation of a new action item. - * - * @param e - The change event from the form submission. - */ - const createActionItemHandler = async ( - e: ChangeEvent, - ): Promise => { - e.preventDefault(); - try { - await createActionItem({ - variables: { - assigneeId: formState.assigneeId, - actionItemCategoryId: formState.actionItemCategoryId, - eventId, - preCompletionNotes: formState.preCompletionNotes, - dueDate: dayjs(dueDate).format('YYYY-MM-DD'), - }, - }); - - // Resetting form state and due date after successful creation - setFormState({ - assigneeId: '', - actionItemCategoryId: '', - preCompletionNotes: '', - }); - - setDueDate(new Date()); - - // Refetching the action items list to update the UI - actionItemsRefetch(); - hideCreateModal(); - toast.success(t('successfulCreation')); - } catch (error: unknown) { - if (error instanceof Error) { - toast.error(error.message); - console.log(error.message); - } - } - }; - - /** - * Shows the create action item modal. - */ - const showCreateModal = (): void => { - setActionItemCreateModalIsOpen(!actionItemCreateModalIsOpen); - }; - - /** - * Hides the create action item modal. - */ - const hideCreateModal = (): void => { - setActionItemCreateModalIsOpen(!actionItemCreateModalIsOpen); - }; - - // Showing loader while data is being fetched - if (actionItemCategoriesLoading || membersLoading || actionItemsLoading) { - return ; - } - - // Showing error message if any of the queries fail - if (actionItemCategoriesError || membersError || actionItemsError) { - return ( -
- -
- Error occured while loading{' '} - {actionItemCategoriesError - ? 'Action Item Categories' - : membersError - ? 'Members List' - : 'Action Items List'}{' '} - Data -
- {actionItemCategoriesError - ? actionItemCategoriesError.message - : membersError - ? membersError.message - : actionItemsError?.message} -
-
- ); - } - - // Filtering out disabled action item categories - const actionItemCategories = - actionItemCategoriesData?.actionItemCategoriesByOrganization.filter( - (category) => !category.isDisabled, - ); - - // Counting the number of completed action items - const completedActionItemsCount = - actionItemsData?.actionItemsByOrganization.reduce( - (acc, item) => (item.isCompleted === true ? acc + 1 : acc), - 0, - ); - - return ( - <> -
- - Status: - {actionItemsData?.actionItemsByOrganization.length} action items - assigned, {completedActionItemsCount} completed - - - -
- - - - {/* Create Modal */} - - - ); -}; diff --git a/src/components/ActionItems/ActionItemsWrapper.module.css b/src/components/ActionItems/ActionItemsWrapper.module.css deleted file mode 100644 index 125db8a125..0000000000 --- a/src/components/ActionItems/ActionItemsWrapper.module.css +++ /dev/null @@ -1,53 +0,0 @@ -.actionItemsModal { - margin: auto; - max-width: 85%; -} - -button .iconWrapper { - width: 32px; - padding-right: 4px; - margin-right: 4px; -} - -button .iconWrapperSm { - width: 32px; - display: flex; - justify-content: center; - align-items: center; -} - -.errorIcon { - transform: scale(1.5); - color: var(--bs-danger); - margin-bottom: 1rem; -} - -.greenregbtn { - margin: 1rem 0 0; - margin-top: 0; - margin-right: 4px; - border: 1px solid var(--bs-gray-300); - box-shadow: 0 2px 2px var(--bs-gray-300); - padding: 10px 10px; - border-radius: 5px; - background-color: var(--bs-primary); - width: 100%; - font-size: 16px; - color: var(--bs-white); - outline: none; - font-weight: 600; - cursor: pointer; - transition: - transform 0.2s, - box-shadow 0.2s; - max-width: 100px; -} - -.message { - margin-top: 15%; - margin-bottom: 15%; - display: flex; - justify-content: center; - align-items: center; - flex-direction: column; -} diff --git a/src/components/ActionItems/ActionItemsWrapper.test.tsx b/src/components/ActionItems/ActionItemsWrapper.test.tsx deleted file mode 100644 index 7b27e7ccd3..0000000000 --- a/src/components/ActionItems/ActionItemsWrapper.test.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import React from 'react'; -import { act, render, screen, waitFor } from '@testing-library/react'; -import { MockedProvider } from '@apollo/react-testing'; -import { ActionItemsWrapper } from './ActionItemsWrapper'; -import { BrowserRouter } from 'react-router-dom'; -import { Provider } from 'react-redux'; -import { store } from 'state/store'; -import { I18nextProvider } from 'react-i18next'; -import i18nForTest from 'utils/i18nForTest'; -import { ToastContainer } from 'react-toastify'; -import { LocalizationProvider } from '@mui/x-date-pickers'; -import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; -import { StaticMockLink } from 'utils/StaticMockLink'; -import userEvent from '@testing-library/user-event'; -import { MOCKS } from '../../screens/OrganizationActionItems/OrganizationActionItemMocks'; - -async function wait(ms = 100): Promise { - await act(() => { - return new Promise((resolve) => { - setTimeout(resolve, ms); - }); - }); -} - -const link = new StaticMockLink(MOCKS, true); - -describe('Testing Action Items Wrapper', () => { - const props = { - eventId: 'event1', - orgId: '123', - }; - - test('The button to open and close the modal should work properly', async () => { - render( - - - - - - - - - - - - , - ); - - await wait(); - - await waitFor(() => { - expect( - screen.getByLabelText('eventDashboardActionItems'), - ).toBeInTheDocument(); - }); - - userEvent.click(screen.getByLabelText('eventDashboardActionItems')); - - await waitFor(() => - expect(screen.queryByTestId('modal-title')).toBeInTheDocument(), - ); - - await waitFor(() => { - expect(screen.getByLabelText('Close')).toBeInTheDocument(); - }); - - userEvent.click(screen.getByLabelText('Close')); - - await waitFor(() => - expect(screen.queryByTestId('modal-title')).not.toBeInTheDocument(), - ); - }); -}); diff --git a/src/components/ActionItems/ActionItemsWrapper.tsx b/src/components/ActionItems/ActionItemsWrapper.tsx deleted file mode 100644 index 16634637ad..0000000000 --- a/src/components/ActionItems/ActionItemsWrapper.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import React, { useState } from 'react'; -import { ActionItemsModal } from './ActionItemsModal'; -import { Button } from 'react-bootstrap'; -import IconComponent from 'components/IconComponent/IconComponent'; -import styles from './ActionItemsWrapper.module.css'; -import { useTranslation } from 'react-i18next'; - -type PropType = { - orgId: string; - eventId: string; -}; - -/** - * A React functional component that provides a button to open a modal for viewing and managing action items related to a specific event. - * - * This component displays a button that, when clicked, opens a modal dialog (`ActionItemsModal`). The modal allows users to interact with action items specific to the organization and event IDs passed as props. - * - * @param props - The props that define the organization's and event's context for the action items. - * @param orgId - The unique identifier for the organization. This ID is used to fetch and manage the organization's action items. - * @param eventId - The unique identifier for the event. This ID is used to fetch and manage the action items associated with the specific event. - * - * @returns The JSX element representing the action items button and modal. - * - * @example - * ```tsx - * - * ``` - * This example renders the `ActionItemsWrapper` component for an organization with ID "12345" and an event with ID "67890". The button will open a modal for managing action items related to this event. - */ -export const ActionItemsWrapper = ({ - orgId, - eventId, -}: PropType): JSX.Element => { - // Extract the translation function from the useTranslation hook, specifying the namespace and key prefix for translations - const { t } = useTranslation('translation', { - keyPrefix: 'organizationActionItems', - }); - - // State to control the visibility of the ActionItemsModal - const [showModal, setShowModal] = useState(false); - - return ( - <> - {/* Button to open the ActionItemsModal */} - - - {/* Conditionally render the ActionItemsModal if showModal is true */} - {showModal && ( - setShowModal(false)} // Function to close the modal - orgId={orgId} - eventId={eventId} - /> - )} - - ); -}; diff --git a/src/components/AddOn/AddOn.test.tsx b/src/components/AddOn/AddOn.test.tsx index 8313fefcb1..68475e8196 100644 --- a/src/components/AddOn/AddOn.test.tsx +++ b/src/components/AddOn/AddOn.test.tsx @@ -15,6 +15,14 @@ describe('Testing Addon component', () => { children: 'This is a dummy text', }; + test('should render with default props', () => { + const { getByTestId } = render(); + const container = getByTestId('pluginContainer'); + expect(container).toBeInTheDocument(); + expect(container).toHaveClass('plugin-container'); + expect(container).toHaveTextContent('Default text'); + }); + test('should render props and text elements test for the page component', () => { const { getByTestId, getByText } = render( diff --git a/src/components/AddOn/AddOn.tsx b/src/components/AddOn/AddOn.tsx index 1d617535b1..850cc05ee4 100644 --- a/src/components/AddOn/AddOn.tsx +++ b/src/components/AddOn/AddOn.tsx @@ -2,9 +2,9 @@ import React from 'react'; import PropTypes from 'prop-types'; interface InterfaceAddOnProps { - extras: any; - name: string; - children: any; + extras?: any; + name?: string; + children?: React.ReactNode; } /** @@ -19,31 +19,26 @@ interface InterfaceAddOnProps { * * @returns The JSX element representing the AddOn component. */ -function addOn({ children }: InterfaceAddOnProps): JSX.Element { +function AddOn({ + children = 'Default text', + extras = {}, + name = '', +}: InterfaceAddOnProps): JSX.Element { return ( - <> -
- {children} -
- +
+ {children} +
); } -// Default props for the AddOn component -addOn.defaultProps = { - extras: {}, - name: '', - children: null, -}; - // PropTypes validation for the AddOn component -addOn.propTypes = { +AddOn.propTypes = { extras: PropTypes.shape({ components: PropTypes.shape({}), actions: PropTypes.shape({}), }), name: PropTypes.string, - children: PropTypes.any, + children: PropTypes.node, }; -export default addOn; +export default AddOn; diff --git a/src/components/AddOn/core/AddOnEntry/AddOnEntry.test.tsx b/src/components/AddOn/core/AddOnEntry/AddOnEntry.test.tsx index c09840a8f0..3d800eb59f 100644 --- a/src/components/AddOn/core/AddOnEntry/AddOnEntry.test.tsx +++ b/src/components/AddOn/core/AddOnEntry/AddOnEntry.test.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { render } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import { BrowserRouter } from 'react-router-dom'; import AddOnEntry from './AddOnEntry'; import { @@ -76,6 +76,32 @@ describe('Testing AddOnEntry', () => { expect(getByTestId('AddOnEntry')).toBeInTheDocument(); }); + test('uses default values for title and description when not provided', () => { + // Render the component with only required parameters + const mockGetInstalledPlugins = jest.fn(); + render( + + + + + + + + + , + ); + + const titleElement = screen.getByText('No title provided'); // This will check for the default empty string in the title + const descriptionElement = screen.getByText('Description not available'); // This will check for the default empty string in the description + expect(titleElement).toBeInTheDocument(); // Ensure the title element with default value exists + expect(descriptionElement).toBeInTheDocument(); // Ensure the description element with default value exists + }); + it('renders correctly', () => { const props = { id: '1', @@ -110,6 +136,7 @@ describe('Testing AddOnEntry', () => { expect(getByText('Test addon description')).toBeInTheDocument(); expect(getByText('Test User')).toBeInTheDocument(); }); + it('Uninstall Button works correctly', async () => { const props = { id: '1', diff --git a/src/components/AddOn/core/AddOnEntry/AddOnEntry.tsx b/src/components/AddOn/core/AddOnEntry/AddOnEntry.tsx index cc6feafa6b..257917e2c2 100644 --- a/src/components/AddOn/core/AddOnEntry/AddOnEntry.tsx +++ b/src/components/AddOn/core/AddOnEntry/AddOnEntry.tsx @@ -1,5 +1,4 @@ import React, { useState } from 'react'; -import PropTypes from 'prop-types'; import styles from './AddOnEntry.module.css'; import { Button, Card, Spinner } from 'react-bootstrap'; import { UPDATE_INSTALL_STATUS_PLUGIN_MUTATION } from 'GraphQl/Mutations/mutations'; @@ -13,12 +12,12 @@ import { Navigate, useParams } from 'react-router-dom'; */ interface InterfaceAddOnEntryProps { id: string; - enabled: boolean; - title: string; - description: string; + enabled?: boolean; // Optional props + title?: string; // Optional props + description?: string; // Optional props createdBy: string; - component: string; - modified: any; + component?: string; // Optional props + modified?: any; // Optional props uninstalledOrgs: string[]; getInstalledPlugins: () => any; } @@ -46,11 +45,14 @@ interface InterfaceAddOnEntryProps { */ function addOnEntry({ id, - title, - description, + title = 'No title provided', // Default parameter + description = 'Description not available', // Default parameter createdBy, uninstalledOrgs, getInstalledPlugins, + // enabled = false, // Default parameter + // component = '', // Default parameter + // modified = null, // Default parameter }: InterfaceAddOnEntryProps): JSX.Element { // Translation hook with namespace 'addOnEntry' const { t } = useTranslation('translation', { keyPrefix: 'addOnEntry' }); @@ -147,21 +149,4 @@ function addOnEntry({ ); } -// Default prop values for the component -addOnEntry.defaultProps = { - enabled: false, - configurable: true, - title: '', - description: '', - isInstalled: false, -}; - -addOnEntry.propTypes = { - enabled: PropTypes.bool, - configurable: PropTypes.bool, - title: PropTypes.string, - description: PropTypes.string, - isInstalled: PropTypes.bool, -}; - export default addOnEntry; diff --git a/src/components/AddOn/core/AddOnRegister/AddOnRegister.test.tsx b/src/components/AddOn/core/AddOnRegister/AddOnRegister.test.tsx index 6df580aa76..b04b450977 100644 --- a/src/components/AddOn/core/AddOnRegister/AddOnRegister.test.tsx +++ b/src/components/AddOn/core/AddOnRegister/AddOnRegister.test.tsx @@ -1,5 +1,5 @@ -import React from 'react'; -import { act, render, screen, waitFor } from '@testing-library/react'; +import React, { act } from 'react'; +import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { MockedProvider } from '@apollo/react-testing'; import { ADD_PLUGIN_MUTATION } from 'GraphQl/Mutations/mutations'; @@ -41,7 +41,7 @@ const mocks = [ query: ADD_PLUGIN_MUTATION, variables: { pluginName: 'Test Plugin', - pluginCreatedBy: 'Test Creator', + pluginCreatedBy: 'AdminTest Creator', pluginDesc: 'Test Description', pluginInstallStatus: false, installedOrgs: ['id'], @@ -52,7 +52,7 @@ const mocks = [ createPlugin: { _id: '1', pluginName: 'Test Plugin', - pluginCreatedBy: 'Test Creator', + pluginCreatedBy: 'AdminTest Creator', pluginDesc: 'Test Description', }, }, @@ -100,26 +100,31 @@ describe('Testing AddOnRegister', () => { - {} + , ); - - await wait(100); - - userEvent.click(screen.getByRole('button', { name: /Add New/i })); - userEvent.type(screen.getByPlaceholderText(/Ex: Donations/i), 'myplugin'); - userEvent.type( - screen.getByPlaceholderText(/This Plugin enables UI for/i), - 'test description', - ); - userEvent.type( - screen.getByPlaceholderText(/Ex: john Doe/i), - 'test creator', - ); }); + // Wait for the button to be in the document + await waitFor(() => + expect( + screen.getByRole('button', { name: /Add New/i }), + ).toBeInTheDocument(), + ); + + // Simulate user interactions + userEvent.click(screen.getByRole('button', { name: /Add New/i })); + userEvent.type(screen.getByPlaceholderText(/Ex: Donations/i), 'myplugin'); + userEvent.type( + screen.getByPlaceholderText(/This Plugin enables UI for/i), + 'test description', + ); + userEvent.type( + screen.getByPlaceholderText(/Ex: john Doe/i), + 'test creator', + ); }); test('Expect toast.success to be called on successful plugin addition', async () => { @@ -135,22 +140,22 @@ describe('Testing AddOnRegister', () => {
, ); - await waitFor(() => new Promise((resolve) => setTimeout(resolve, 0))); - - userEvent.click(screen.getByRole('button', { name: /Add New/i })); - await wait(100); - expect(screen.getByTestId('addonregisterBtn')).toBeInTheDocument(); - userEvent.type(screen.getByTestId('pluginName'), pluginData.pluginName); - userEvent.type( - screen.getByTestId('pluginCreatedBy'), - pluginData.pluginCreatedBy, - ); - userEvent.type(screen.getByTestId('pluginDesc'), pluginData.pluginDesc); - userEvent.click(screen.getByTestId('addonregisterBtn')); - - await wait(100); - expect(toast.success).toBeCalledWith('Plugin added Successfully'); }); + await waitFor(() => new Promise((resolve) => setTimeout(resolve, 0))); + + userEvent.click(screen.getByRole('button', { name: /Add New/i })); + await wait(100); + expect(screen.getByTestId('addonregisterBtn')).toBeInTheDocument(); + userEvent.type(screen.getByTestId('pluginName'), pluginData.pluginName); + userEvent.type( + screen.getByTestId('pluginCreatedBy'), + pluginData.pluginCreatedBy, + ); + userEvent.type(screen.getByTestId('pluginDesc'), pluginData.pluginDesc); + userEvent.click(screen.getByTestId('addonregisterBtn')); + + await wait(100); + expect(toast.success).toBeCalledWith('Plugin added Successfully'); }); test('Expect the window to reload after successful plugin addition', async () => { @@ -166,23 +171,24 @@ describe('Testing AddOnRegister', () => { , ); - await waitFor(() => new Promise((resolve) => setTimeout(resolve, 0))); - - userEvent.click(screen.getByRole('button', { name: /Add New/i })); - await wait(100); - expect(screen.getByTestId('addonregisterBtn')).toBeInTheDocument(); - userEvent.type(screen.getByTestId('pluginName'), pluginData.pluginName); - userEvent.type( - screen.getByTestId('pluginCreatedBy'), - pluginData.pluginCreatedBy, - ); - userEvent.type(screen.getByTestId('pluginDesc'), pluginData.pluginDesc); - userEvent.click(screen.getByTestId('addonregisterBtn')); - - await wait(3000); // Waiting for 3 seconds to reload the page as timeout is set to 2 seconds in the component - expect(mockNavigate).toHaveBeenCalledWith(0); }); + await waitFor(() => new Promise((resolve) => setTimeout(resolve, 0))); + + userEvent.click(screen.getByRole('button', { name: /Add New/i })); + await wait(100); + expect(screen.getByTestId('addonregisterBtn')).toBeInTheDocument(); + userEvent.type(screen.getByTestId('pluginName'), pluginData.pluginName); + userEvent.type( + screen.getByTestId('pluginCreatedBy'), + pluginData.pluginCreatedBy, + ); + userEvent.type(screen.getByTestId('pluginDesc'), pluginData.pluginDesc); + userEvent.click(screen.getByTestId('addonregisterBtn')); + + await wait(3000); // Waiting for 3 seconds to reload the page as timeout is set to 2 seconds in the component + expect(mockNavigate).toHaveBeenCalledWith(0); }); + test('should be redirected to /orglist if orgId is undefined', async () => { mockId = undefined; render( @@ -190,7 +196,7 @@ describe('Testing AddOnRegister', () => { - {} + diff --git a/src/components/AddOn/core/AddOnRegister/AddOnRegister.tsx b/src/components/AddOn/core/AddOnRegister/AddOnRegister.tsx index 9c572f06d7..56ed6006c2 100644 --- a/src/components/AddOn/core/AddOnRegister/AddOnRegister.tsx +++ b/src/components/AddOn/core/AddOnRegister/AddOnRegister.tsx @@ -19,6 +19,10 @@ interface InterfaceFormStateTypes { installedOrgs: [string] | []; } +interface AddOnRegisterProps { + createdBy?: string; +} + /** * A React component for registering a new add-on plugin. * @@ -31,7 +35,9 @@ interface InterfaceFormStateTypes { * * @returns A JSX element containing the button and modal for plugin registration. */ -function addOnRegister(): JSX.Element { +function addOnRegister({ + createdBy = 'Admin', +}: AddOnRegisterProps): JSX.Element { // Translation hook for the 'addOnRegister' namespace const { t } = useTranslation('translation', { keyPrefix: 'addOnRegister' }); // Translation hook for the 'common' namespace @@ -59,7 +65,7 @@ function addOnRegister(): JSX.Element { // Initial form state const [formState, setFormState] = useState({ pluginName: '', - pluginCreatedBy: '', + pluginCreatedBy: createdBy, // Using the default value here pluginDesc: '', pluginInstallStatus: false, installedOrgs: [currentUrl], @@ -82,7 +88,7 @@ function addOnRegister(): JSX.Element { if (data) { // Show a success message when the plugin is added - toast.success(tCommon('addedSuccessfully', { item: 'Plugin' })); + toast.success(tCommon('addedSuccessfully', { item: 'Plugin' }) as string); // Refresh the page after 2 seconds setTimeout(() => { navigate(0); @@ -188,11 +194,6 @@ function addOnRegister(): JSX.Element { ); } -// Default values for props if not provided -addOnRegister.defaultProps = { - createdBy: 'Admin', -}; - // Prop types validation for the component addOnRegister.propTypes = { createdBy: PropTypes.string, diff --git a/src/components/AddOn/core/AddOnStore/AddOnStore.test.tsx b/src/components/AddOn/core/AddOnStore/AddOnStore.test.tsx index 600e34dd5d..e76e2a7b73 100644 --- a/src/components/AddOn/core/AddOnStore/AddOnStore.test.tsx +++ b/src/components/AddOn/core/AddOnStore/AddOnStore.test.tsx @@ -1,6 +1,6 @@ -import React from 'react'; +import React, { act } from 'react'; import 'jest-location-mock'; -import { act, fireEvent, render, screen } from '@testing-library/react'; +import { fireEvent, render, screen } from '@testing-library/react'; import { ApolloClient, ApolloProvider, diff --git a/src/components/AddOn/core/AddOnStore/AddOnStore.tsx b/src/components/AddOn/core/AddOnStore/AddOnStore.tsx index eb59b9139e..878ad64e31 100644 --- a/src/components/AddOn/core/AddOnStore/AddOnStore.tsx +++ b/src/components/AddOn/core/AddOnStore/AddOnStore.tsx @@ -314,10 +314,4 @@ function addOnStore(): JSX.Element { ); } -// Default values for props if not provided -addOnStore.defaultProps = {}; - -// Prop types validation for the component -addOnStore.propTypes = {}; - export default addOnStore; diff --git a/src/components/Advertisements/Advertisements.test.tsx b/src/components/Advertisements/Advertisements.test.tsx index b45e1bab38..c0992a1012 100644 --- a/src/components/Advertisements/Advertisements.test.tsx +++ b/src/components/Advertisements/Advertisements.test.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { act } from 'react'; import { ApolloClient, ApolloLink, @@ -7,7 +7,7 @@ import { InMemoryCache, } from '@apollo/client'; import { MockedProvider } from '@apollo/client/testing'; -import { act, fireEvent, render, screen } from '@testing-library/react'; +import { fireEvent, render, screen } from '@testing-library/react'; import 'jest-location-mock'; import type { DocumentNode, NormalizedCacheObject } from '@apollo/client'; diff --git a/src/components/Advertisements/Advertisements.tsx b/src/components/Advertisements/Advertisements.tsx index 97d4fed674..f20c2a7d8e 100644 --- a/src/components/Advertisements/Advertisements.tsx +++ b/src/components/Advertisements/Advertisements.tsx @@ -24,7 +24,7 @@ import InfiniteScroll from 'react-infinite-scroll-component'; * */ -export default function advertisements(): JSX.Element { +export default function Advertisements(): JSX.Element { // Retrieve the organization ID from URL parameters const { orgId: currentOrgId } = useParams(); // Translation hook for internationalization @@ -65,7 +65,7 @@ export default function advertisements(): JSX.Element { }); // State to manage the list of advertisements - const [advertisements, setAdvertisements] = useState( + const [advertisements, setAdvertisements] = useState( orgAdvertisementListData?.organizations[0].advertisements?.edges.map( (edge: { node: Ad }) => edge.node, ) || [], @@ -77,10 +77,8 @@ export default function advertisements(): JSX.Element { const ads: Ad[] = orgAdvertisementListData.organizations[0].advertisements?.edges.map( (edge) => edge.node, - ); - after - ? setAdvertisements([...advertisements, ...ads]) - : setAdvertisements(ads); + ) || []; + setAdvertisements(after ? [...advertisements, ...ads] : ads); } }, [orgAdvertisementListData, after]); @@ -273,7 +271,3 @@ export default function advertisements(): JSX.Element { ); } - -advertisements.defaultProps = {}; - -advertisements.propTypes = {}; diff --git a/src/components/Advertisements/core/AdvertisementEntry/AdvertisementEntry.test.tsx b/src/components/Advertisements/core/AdvertisementEntry/AdvertisementEntry.test.tsx index 0850d1f89f..dbd6f88cc3 100644 --- a/src/components/Advertisements/core/AdvertisementEntry/AdvertisementEntry.test.tsx +++ b/src/components/Advertisements/core/AdvertisementEntry/AdvertisementEntry.test.tsx @@ -130,7 +130,64 @@ describe('Testing Advertisement Entry Component', () => { expect(deletionFailedText).toBeNull(); }); }); + it('should use default props when none are provided', () => { + render( + , + ): void { + throw new Error('Function not implemented.'); + }} + />, + ); + + //Check if component renders with default ''(empty string) + const elements = screen.getAllByText(''); // This will return an array of matching elements + elements.forEach((element) => expect(element).toBeInTheDocument()); + + // Check that the component renders with default `mediaUrl` (empty string) + const mediaElement = screen.getByTestId('media'); + expect(mediaElement).toHaveAttribute('src', ''); + + // Check that the component renders with default `endDate` + const defaultEndDate = new Date().toDateString(); + expect(screen.getByText(`Ends on ${defaultEndDate}`)).toBeInTheDocument(); + // Check that the component renders with default `startDate` + const defaultStartDate = new Date().toDateString(); + console.log(screen.getByText); + expect(screen.getByText(`Ends on ${defaultStartDate}`)).toBeInTheDocument(); //fix text "Ends on"? + }); + it('should correctly override default props when values are provided', () => { + const mockName = 'Test Ad'; + const mockType = 'Banner'; + const mockMediaUrl = 'https://example.com/media.png'; + const mockEndDate = new Date(2025, 11, 31); + const mockStartDate = new Date(2024, 0, 1); + const mockOrganizationId = 'org123'; + + const { getByText } = render( + , + ): void { + throw new Error('Function not implemented.'); + }} + />, + ); + + // Check that the component renders with provided values + expect(getByText(mockName)).toBeInTheDocument(); + // Add more checks based on how each prop affects rendering + }); it('should open and close the dropdown when options button is clicked', () => { const { getByTestId, queryByText, getAllByText } = render( diff --git a/src/components/Advertisements/core/AdvertisementEntry/AdvertisementEntry.tsx b/src/components/Advertisements/core/AdvertisementEntry/AdvertisementEntry.tsx index aa8324dc19..7368ded68e 100644 --- a/src/components/Advertisements/core/AdvertisementEntry/AdvertisementEntry.tsx +++ b/src/components/Advertisements/core/AdvertisementEntry/AdvertisementEntry.tsx @@ -12,12 +12,12 @@ import { toast } from 'react-toastify'; interface InterfaceAddOnEntryProps { id: string; - name: string; - mediaUrl: string; - type: string; - organizationId: string; - startDate: Date; - endDate: Date; + name?: string; + mediaUrl?: string; + type?: string; + organizationId?: string; + startDate?: Date; + endDate?: Date; setAfter: React.Dispatch>; } @@ -28,14 +28,14 @@ interface InterfaceAddOnEntryProps { * @param props - Component properties * @returns The rendered component */ -function advertisementEntry({ +function AdvertisementEntry({ id, - name, - type, - mediaUrl, - endDate, - organizationId, - startDate, + name = '', + type = '', + mediaUrl = '', + endDate = new Date(), + organizationId = '', + startDate = new Date(), setAfter, }: InterfaceAddOnEntryProps): JSX.Element { const { t } = useTranslation('translation', { keyPrefix: 'advertisement' }); @@ -75,9 +75,9 @@ function advertisementEntry({ id: id.toString(), }, }); - toast.success(t('advertisementDeleted')); + toast.success(t('advertisementDeleted') as string); setButtonLoading(false); - setAfter(null); + setAfter?.(null); } catch (error: unknown) { if (error instanceof Error) { toast.error(error.message); @@ -92,6 +92,7 @@ function advertisementEntry({ const handleOptionsClick = (): void => { setDropdown(!dropdown); }; + return ( <> @@ -208,7 +209,7 @@ function advertisementEntry({ ); } -advertisementEntry.propTypes = { +AdvertisementEntry.propTypes = { name: PropTypes.string, type: PropTypes.string, organizationId: PropTypes.string, @@ -217,12 +218,4 @@ advertisementEntry.propTypes = { startDate: PropTypes.instanceOf(Date), }; -advertisementEntry.defaultProps = { - name: '', - type: '', - organizationId: '', - mediaUrl: '', - endDate: new Date(), - startDate: new Date(), -}; -export default advertisementEntry; +export default AdvertisementEntry; diff --git a/src/components/Advertisements/core/AdvertisementRegister/AdvertisementRegister.test.tsx b/src/components/Advertisements/core/AdvertisementRegister/AdvertisementRegister.test.tsx index c3aa536d1f..1bf16ec76b 100644 --- a/src/components/Advertisements/core/AdvertisementRegister/AdvertisementRegister.test.tsx +++ b/src/components/Advertisements/core/AdvertisementRegister/AdvertisementRegister.test.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { act } from 'react'; import { render, fireEvent, waitFor, screen } from '@testing-library/react'; import { @@ -147,12 +147,12 @@ describe('Testing Advertisement Register Component', () => { @@ -166,176 +166,242 @@ describe('Testing Advertisement Register Component', () => { }); test('create advertisement', async () => { + jest.useFakeTimers(); const setTimeoutSpy = jest.spyOn(global, 'setTimeout'); - const { getByText, queryByText, getByLabelText } = render( - - - - - - - - - , - ); - expect(getByText(translations.createAdvertisement)).toBeInTheDocument(); + await act(async () => { + render( + + + + + + + + + , + ); + }); - fireEvent.click(getByText(translations.createAdvertisement)); - expect(queryByText(translations.addNew)).toBeInTheDocument(); + expect( + screen.getByText(translations.createAdvertisement), + ).toBeInTheDocument(); - fireEvent.change(getByLabelText(translations.Rname), { - target: { value: 'Ad1' }, + await act(async () => { + fireEvent.click(screen.getByText(translations.createAdvertisement)); }); - expect(getByLabelText(translations.Rname)).toHaveValue('Ad1'); - const mediaFile = new File(['media content'], 'test.png', { - type: 'image/png', + expect(screen.queryByText(translations.addNew)).toBeInTheDocument(); + + await act(async () => { + fireEvent.change(screen.getByLabelText(translations.Rname), { + target: { value: 'Ad1' }, + }); + + const mediaFile = new File(['media content'], 'test.png', { + type: 'image/png', + }); + + fireEvent.change(screen.getByLabelText(translations.Rmedia), { + target: { + files: [mediaFile], + }, + }); }); - const mediaInput = getByLabelText(translations.Rmedia); - fireEvent.change(mediaInput, { - target: { - files: [mediaFile], - }, + await waitFor(() => { + expect(screen.getByTestId('mediaPreview')).toBeInTheDocument(); }); - const mediaPreview = await screen.findByTestId('mediaPreview'); - expect(mediaPreview).toBeInTheDocument(); + await act(async () => { + fireEvent.change(screen.getByLabelText(translations.Rtype), { + target: { value: 'BANNER' }, + }); - fireEvent.change(getByLabelText(translations.Rtype), { - target: { value: 'BANNER' }, - }); - expect(getByLabelText(translations.Rtype)).toHaveValue('BANNER'); + fireEvent.change(screen.getByLabelText(translations.RstartDate), { + target: { value: '2023-01-01' }, + }); - fireEvent.change(getByLabelText(translations.RstartDate), { - target: { value: '2023-01-01' }, + fireEvent.change(screen.getByLabelText(translations.RendDate), { + target: { value: '2023-02-01' }, + }); }); - expect(getByLabelText(translations.RstartDate)).toHaveValue('2023-01-01'); - fireEvent.change(getByLabelText(translations.RendDate), { - target: { value: '2023-02-01' }, + expect(screen.getByLabelText(translations.Rname)).toHaveValue('Ad1'); + expect(screen.getByLabelText(translations.Rtype)).toHaveValue('BANNER'); + expect(screen.getByLabelText(translations.RstartDate)).toHaveValue( + '2023-01-01', + ); + expect(screen.getByLabelText(translations.RendDate)).toHaveValue( + '2023-02-01', + ); + + await act(async () => { + fireEvent.click(screen.getByText(translations.register)); }); - expect(getByLabelText(translations.RendDate)).toHaveValue('2023-02-01'); await waitFor(() => { - fireEvent.click(getByText(translations.register)); + expect(toast.success).toBeCalledWith( + 'Advertisement created successfully.', + ); + expect(setTimeoutSpy).toHaveBeenCalled(); }); - expect(toast.success).toBeCalledWith('Advertisement created successfully.'); - expect(setTimeoutSpy).toHaveBeenCalled(); + jest.useRealTimers(); }); test('update advertisement', async () => { + jest.useFakeTimers(); const setTimeoutSpy = jest.spyOn(global, 'setTimeout'); - const { getByText, getByLabelText } = render( - - - - - - - - - , - ); - - fireEvent.click(getByText(translations.edit)); - fireEvent.change(getByLabelText(translations.Rname), { - target: { value: 'Ad1' }, + await act(async () => { + render( + + + + + + + + + , + ); }); - expect(getByLabelText(translations.Rname)).toHaveValue('Ad1'); - const mediaFile = new File(['media content'], 'test.png', { - type: 'image/png', + await waitFor(() => { + expect(screen.getByText(translations.edit)).toBeInTheDocument(); }); - const mediaInput = getByLabelText(translations.Rmedia); - fireEvent.change(mediaInput, { - target: { - files: [mediaFile], - }, + await act(async () => { + fireEvent.click(screen.getByText(translations.edit)); }); - const mediaPreview = await screen.findByTestId('mediaPreview'); - expect(mediaPreview).toBeInTheDocument(); + await act(async () => { + fireEvent.change(screen.getByLabelText(translations.Rname), { + target: { value: 'Ad1' }, + }); - fireEvent.change(getByLabelText(translations.Rtype), { - target: { value: 'BANNER' }, + const mediaFile = new File(['media content'], 'test.png', { + type: 'image/png', + }); + + fireEvent.change(screen.getByLabelText(translations.Rmedia), { + target: { + files: [mediaFile], + }, + }); }); - expect(getByLabelText(translations.Rtype)).toHaveValue('BANNER'); - fireEvent.change(getByLabelText(translations.RstartDate), { - target: { value: '2023-01-01' }, + await waitFor(() => { + expect(screen.getByTestId('mediaPreview')).toBeInTheDocument(); }); - expect(getByLabelText(translations.RstartDate)).toHaveValue('2023-01-01'); - fireEvent.change(getByLabelText(translations.RendDate), { - target: { value: '2023-02-01' }, + await act(async () => { + fireEvent.change(screen.getByLabelText(translations.Rtype), { + target: { value: 'BANNER' }, + }); + + fireEvent.change(screen.getByLabelText(translations.RstartDate), { + target: { value: '2023-01-01' }, + }); + + fireEvent.change(screen.getByLabelText(translations.RendDate), { + target: { value: '2023-02-01' }, + }); + }); + + expect(screen.getByLabelText(translations.Rname)).toHaveValue('Ad1'); + expect(screen.getByLabelText(translations.Rtype)).toHaveValue('BANNER'); + expect(screen.getByLabelText(translations.RstartDate)).toHaveValue( + '2023-01-01', + ); + expect(screen.getByLabelText(translations.RendDate)).toHaveValue( + '2023-02-01', + ); + + await act(async () => { + fireEvent.click(screen.getByText(translations.saveChanges)); }); - expect(getByLabelText(translations.RendDate)).toHaveValue('2023-02-01'); await waitFor(() => { - fireEvent.click(getByText(translations.saveChanges)); + expect(toast.success).toBeCalledWith( + 'Advertisement created successfully.', + ); + expect(setTimeoutSpy).toHaveBeenCalled(); }); - expect(toast.success).toBeCalledWith('Advertisement created successfully.'); - expect(setTimeoutSpy).toHaveBeenCalled(); + + jest.useRealTimers(); }); test('Logs error to the console and shows error toast when advertisement creation fails', async () => { + jest.useFakeTimers(); const setTimeoutSpy = jest.spyOn(global, 'setTimeout'); - const { getByText, queryByText } = render( - - - - - - - - - , - ); + const toastErrorSpy = jest.spyOn(toast, 'error'); + + await act(async () => { + render( + + + + + + + + + , + ); + }); - expect(getByText(translations.createAdvertisement)).toBeInTheDocument(); + expect( + screen.getByText(translations.createAdvertisement), + ).toBeInTheDocument(); - fireEvent.click(getByText(translations.createAdvertisement)); - expect(queryByText(translations.addNew)).toBeInTheDocument(); + await act(async () => { + fireEvent.click(screen.getByText(translations.createAdvertisement)); + }); + + expect(screen.queryByText(translations.addNew)).toBeInTheDocument(); + + await act(async () => { + fireEvent.click(screen.getByText(translations.register)); + }); await waitFor(() => { - fireEvent.click(getByText(translations.register)); + expect(toastErrorSpy).toHaveBeenCalledWith( + `An error occurred. Couldn't create advertisement`, + ); }); - expect(toast.error).toBeCalledWith( - `An error occurred. Couldn't create advertisement`, - ); + expect(setTimeoutSpy).toHaveBeenCalled(); + jest.useRealTimers(); }); test('Throws error when the end date is less than the start date', async () => { + jest.useFakeTimers(); const setTimeoutSpy = jest.spyOn(global, 'setTimeout'); const { getByText, queryByText, getByLabelText } = render( @@ -343,12 +409,12 @@ describe('Testing Advertisement Register Component', () => { @@ -403,21 +469,23 @@ describe('Testing Advertisement Register Component', () => { 'End Date should be greater than or equal to Start Date', ); expect(setTimeoutSpy).toHaveBeenCalled(); + jest.useRealTimers(); }); test('AdvertismentRegister component loads correctly in edit mode', async () => { + jest.useFakeTimers(); render( @@ -429,21 +497,23 @@ describe('Testing Advertisement Register Component', () => { await waitFor(() => { expect(screen.getByTestId('editBtn')).toBeInTheDocument(); }); + jest.useRealTimers(); }); test('Opens and closes modals on button click', async () => { + jest.useFakeTimers(); const { getByText, queryByText } = render( @@ -459,9 +529,11 @@ describe('Testing Advertisement Register Component', () => { await waitFor(() => { expect(queryByText(translations.close)).not.toBeInTheDocument(); }); + jest.useRealTimers(); }); test('Throws error when the end date is less than the start date while editing the advertisement', async () => { + jest.useFakeTimers(); const { getByText, getByLabelText, queryByText } = render( @@ -470,12 +542,12 @@ describe('Testing Advertisement Register Component', () => { { } @@ -524,21 +596,23 @@ describe('Testing Advertisement Register Component', () => { 'End Date should be greater than or equal to Start Date', ); }); + jest.useRealTimers(); }); test('Media preview renders correctly', async () => { + jest.useFakeTimers(); render( @@ -563,4 +637,5 @@ describe('Testing Advertisement Register Component', () => { fireEvent.click(closeButton); expect(mediaPreview).not.toBeInTheDocument(); }); + jest.useRealTimers(); }); diff --git a/src/components/Advertisements/core/AdvertisementRegister/AdvertisementRegister.tsx b/src/components/Advertisements/core/AdvertisementRegister/AdvertisementRegister.tsx index cded70fa97..1d5c51ce84 100644 --- a/src/components/Advertisements/core/AdvertisementRegister/AdvertisementRegister.tsx +++ b/src/components/Advertisements/core/AdvertisementRegister/AdvertisementRegister.tsx @@ -58,13 +58,13 @@ interface InterfaceFormStateTypes { * ``` */ function advertisementRegister({ - formStatus, + formStatus = 'register', idEdit, - nameEdit, - typeEdit, - advertisementMediaEdit, - endDateEdit, - startDateEdit, + nameEdit = '', + typeEdit = 'BANNER', + advertisementMediaEdit = '', + endDateEdit = new Date(), + startDateEdit = new Date(), setAfter, }: InterfaceAddOnRegisterProps): JSX.Element { const { t } = useTranslation('translation', { keyPrefix: 'advertisement' }); @@ -135,7 +135,7 @@ function advertisementRegister({ try { console.log('At handle register', formState); if (formState.endDate < formState.startDate) { - toast.error(t('endDateGreaterOrEqual')); + toast.error(t('endDateGreaterOrEqual') as string); return; } const { data } = await create({ @@ -150,7 +150,7 @@ function advertisementRegister({ }); if (data) { - toast.success(t('advertisementCreated')); + toast.success(t('advertisementCreated') as string); setFormState({ name: '', advertisementMedia: '', @@ -164,7 +164,9 @@ function advertisementRegister({ } catch (error: unknown) { if (error instanceof Error) { toast.error( - tErrors('errorOccurredCouldntCreate', { entity: 'advertisement' }), + tErrors('errorOccurredCouldntCreate', { + entity: 'advertisement', + }) as string, ); console.log('error occured', error.message); } @@ -190,7 +192,7 @@ function advertisementRegister({ updatedFields.type = formState.type; } if (formState.endDate < formState.startDate) { - toast.error(t('endDateGreaterOrEqual')); + toast.error(t('endDateGreaterOrEqual') as string); return; } const startDateFormattedString = dayjs(formState.startDate).format( @@ -231,7 +233,7 @@ function advertisementRegister({ if (data) { toast.success( - tCommon('updatedSuccessfully', { item: 'Advertisement' }), + tCommon('updatedSuccessfully', { item: 'Advertisement' }) as string, ); handleClose(); setAfter(null); @@ -430,16 +432,6 @@ function advertisementRegister({ ); } -advertisementRegister.defaultProps = { - name: '', - advertisementMedia: '', - type: 'BANNER', - startDate: new Date(), - endDate: new Date(), - organizationId: '', - formStatus: 'register', -}; - advertisementRegister.propTypes = { name: PropTypes.string, advertisementMedia: PropTypes.string, diff --git a/src/components/AgendaCategory/AgendaCategoryContainer.tsx b/src/components/AgendaCategory/AgendaCategoryContainer.tsx index fa41dd0099..7b4c5cf8f4 100644 --- a/src/components/AgendaCategory/AgendaCategoryContainer.tsx +++ b/src/components/AgendaCategory/AgendaCategoryContainer.tsx @@ -12,9 +12,9 @@ import { import type { InterfaceAgendaItemCategoryInfo } from 'utils/interfaces'; import styles from './AgendaCategoryContainer.module.css'; -import AgendaCategoryDeleteModal from 'screens/OrganizationAgendaCategory/AgendaCategoryDeleteModal'; -import AgendaCategoryPreviewModal from 'screens/OrganizationAgendaCategory/AgendaCategoryPreviewModal'; -import AgendaCategoryUpdateModal from 'screens/OrganizationAgendaCategory/AgendaCategoryUpdateModal'; +import AgendaCategoryDeleteModal from 'components/OrgSettings/AgendaItemCategories/AgendaCategoryDeleteModal'; +import AgendaCategoryPreviewModal from 'components/OrgSettings/AgendaItemCategories/AgendaCategoryPreviewModal'; +import AgendaCategoryUpdateModal from 'components/OrgSettings/AgendaItemCategories/AgendaCategoryUpdateModal'; /** * Component for displaying and managing agenda item categories. @@ -129,7 +129,7 @@ function agendaCategoryContainer({ agendaCategoryRefetch(); hideUpdateModal(); - toast.success(t('agendaCategoryUpdated')); + toast.success(t('agendaCategoryUpdated') as string); } catch (error: unknown) { if (error instanceof Error) { toast.error(`Agenda Category Update Failed ${error.message}`); @@ -153,7 +153,7 @@ function agendaCategoryContainer({ }); agendaCategoryRefetch(); toggleDeleteModal(); - toast.success(t('agendaCategoryDeleted')); + toast.success(t('agendaCategoryDeleted') as string); } catch (error: unknown) { if (error instanceof Error) { toast.error(`Agenda Category Delete Failed, ${error.message}`); diff --git a/src/components/AgendaItems/AgendaItemsContainer.test.tsx b/src/components/AgendaItems/AgendaItemsContainer.test.tsx index cb514bbfb9..7ceb8b4d08 100644 --- a/src/components/AgendaItems/AgendaItemsContainer.test.tsx +++ b/src/components/AgendaItems/AgendaItemsContainer.test.tsx @@ -1,9 +1,8 @@ -import React from 'react'; +import React, { act } from 'react'; import { render, screen, waitFor, - act, waitForElementToBeRemoved, fireEvent, } from '@testing-library/react'; @@ -36,6 +35,15 @@ jest.mock('react-toastify', () => ({ }, })); +//temporarily fixes react-beautiful-dnd droppable method's depreciation error +//needs to be fixed in React 19 +jest.spyOn(console, 'error').mockImplementation((message) => { + if (message.includes('Support for defaultProps will be removed')) { + return; + } + console.error(message); +}); + async function wait(ms = 100): Promise { await act(async () => { return new Promise((resolve) => setTimeout(resolve, ms)); diff --git a/src/components/AgendaItems/AgendaItemsContainer.tsx b/src/components/AgendaItems/AgendaItemsContainer.tsx index 6c7f33cd5e..4e50cb5fe8 100644 --- a/src/components/AgendaItems/AgendaItemsContainer.tsx +++ b/src/components/AgendaItems/AgendaItemsContainer.tsx @@ -145,7 +145,7 @@ function AgendaItemsContainer({ }); agendaItemRefetch(); hideUpdateModal(); - toast.success(t('agendaItemUpdated')); + toast.success(t('agendaItemUpdated') as string); } catch (error) { if (error instanceof Error) { toast.error(`${error.message}`); @@ -167,7 +167,7 @@ function AgendaItemsContainer({ }); agendaItemRefetch(); toggleDeleteModal(); - toast.success(t('agendaItemDeleted')); + toast.success(t('agendaItemDeleted') as string); } catch (error) { if (error instanceof Error) { toast.error(`${error.message}`); diff --git a/src/components/ChangeLanguageDropdown/ChangeLanguageDropdown.test.tsx b/src/components/ChangeLanguageDropdown/ChangeLanguageDropdown.test.tsx index 71f01677b4..dc14f6ce17 100644 --- a/src/components/ChangeLanguageDropdown/ChangeLanguageDropdown.test.tsx +++ b/src/components/ChangeLanguageDropdown/ChangeLanguageDropdown.test.tsx @@ -1,5 +1,5 @@ -import React from 'react'; -import { act, render } from '@testing-library/react'; +import React, { act } from 'react'; +import { render } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { I18nextProvider } from 'react-i18next'; import { BrowserRouter } from 'react-router-dom'; @@ -11,8 +11,8 @@ import { MockedProvider } from '@apollo/react-testing'; import { UPDATE_USER_MUTATION } from 'GraphQl/Mutations/mutations'; import { StaticMockLink } from 'utils/StaticMockLink'; import useLocalStorage from 'utils/useLocalstorage'; -import { Provider } from 'react-redux'; -import { store } from 'state/store'; +// import { Provider } from 'react-redux'; +// import { store } from 'state/store'; const { setItem } = useLocalStorage(); async function wait(ms = 100): Promise { diff --git a/src/components/CheckIn/TableRow.test.tsx b/src/components/CheckIn/TableRow.test.tsx index 9a206047d0..14c3d20c29 100644 --- a/src/components/CheckIn/TableRow.test.tsx +++ b/src/components/CheckIn/TableRow.test.tsx @@ -13,7 +13,11 @@ import { MockedProvider } from '@apollo/react-testing'; import { checkInMutationSuccess, checkInMutationUnsuccess } from './mocks'; describe('Testing Table Row for CheckIn Table', () => { - test('If the user in not checked in, button to check in should be displayed, and the user should be able to check in succesfully', async () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + test('If the user is not checked in, button to check in should be displayed, and the user should be able to check in successfully', async () => { const props = { data: { id: `123`, @@ -25,7 +29,7 @@ describe('Testing Table Row for CheckIn Table', () => { refetch: jest.fn(), }; - const { queryByText } = render( + const { findByText } = render( @@ -40,31 +44,29 @@ describe('Testing Table Row for CheckIn Table', () => { , ); - await waitFor(() => expect(queryByText('Check In')).toBeInTheDocument()); + expect(await findByText('Check In')).toBeInTheDocument(); - fireEvent.click(queryByText('Check In') as Element); + fireEvent.click(await findByText('Check In')); - await waitFor(() => - expect(queryByText('Checked in successfully')).toBeInTheDocument(), - ); + expect(await findByText('Checked in successfully')).toBeInTheDocument(); }); - test('If the user in checked in, option to download tag should be shown', async () => { + test('If the user is checked in, the option to download tag should be shown', async () => { const props = { data: { - id: `123`, - name: `John Doe`, - userId: `user123`, + id: '123', + name: 'John Doe', + userId: 'user123', checkIn: { _id: '123', time: '12:00:00', }, - eventId: `event123`, + eventId: 'event123', }, refetch: jest.fn(), }; - const { queryByText } = render( + const { findByText } = render( @@ -79,26 +81,22 @@ describe('Testing Table Row for CheckIn Table', () => { , ); - // Stubbing functions required by the @pdfme to show pdfs - global.URL.createObjectURL = jest.fn(); + global.URL.createObjectURL = jest.fn(() => 'mockURL'); global.window.open = jest.fn(); - await waitFor(() => expect(queryByText('Checked In')).toBeInTheDocument()); - await waitFor(() => - expect(queryByText('Download Tag')).toBeInTheDocument(), - ); + expect(await findByText('Checked In')).toBeInTheDocument(); + expect(await findByText('Download Tag')).toBeInTheDocument(); - fireEvent.click(queryByText('Download Tag') as Element); + fireEvent.click(await findByText('Download Tag')); - await waitFor(() => - expect(queryByText('Generating pdf...')).toBeInTheDocument(), - ); - await waitFor(() => { - expect(queryByText('PDF generated successfully!')).toBeInTheDocument(); - }); + expect(await findByText('Generating pdf...')).toBeInTheDocument(); + expect(await findByText('PDF generated successfully!')).toBeInTheDocument(); + + // Cleanup mocks + jest.clearAllMocks(); }); - test('Upon failing of check in mutation, the appropiate error message should be shown', async () => { + test('Upon failing of check in mutation, the appropriate error message should be shown', async () => { const props = { data: { id: `123`, @@ -110,7 +108,7 @@ describe('Testing Table Row for CheckIn Table', () => { refetch: jest.fn(), }; - const { queryByText } = render( + const { findByText } = render( @@ -125,13 +123,11 @@ describe('Testing Table Row for CheckIn Table', () => { , ); - await waitFor(() => expect(queryByText('Check In')).toBeInTheDocument()); + expect(await findByText('Check In')).toBeInTheDocument(); - fireEvent.click(queryByText('Check In') as Element); + fireEvent.click(await findByText('Check In')); - await waitFor(() => - expect(queryByText('Error checking in')).toBeInTheDocument(), - ); - await waitFor(() => expect(queryByText('Oops')).toBeInTheDocument()); + expect(await findByText('Error checking in')).toBeInTheDocument(); + expect(await findByText('Oops')).toBeInTheDocument(); }); }); diff --git a/src/components/CheckIn/TableRow.tsx b/src/components/CheckIn/TableRow.tsx index c2c2def815..38d1dda912 100644 --- a/src/components/CheckIn/TableRow.tsx +++ b/src/components/CheckIn/TableRow.tsx @@ -7,7 +7,6 @@ import { toast } from 'react-toastify'; import { generate } from '@pdfme/generator'; import { tagTemplate } from './tagTemplate'; import { useTranslation } from 'react-i18next'; - /** * Component that represents a single row in the check-in table. * Allows users to mark themselves as checked in and download a tag if they are already checked in. @@ -40,15 +39,14 @@ export const TableRow = ({ }, }) .then(() => { - toast.success(t('checkedInSuccessfully')); + toast.success(t('checkedInSuccessfully') as string); refetch(); }) .catch((err) => { - toast.error(t('errorCheckingIn')); + toast.error(t('errorCheckingIn') as string); toast.error(err.message); }); }; - /** * Triggers a notification while generating and downloading a PDF tag. * @@ -67,10 +65,22 @@ export const TableRow = ({ * @returns A promise that resolves when the PDF is successfully generated and opened. */ const generateTag = async (): Promise => { - const inputs = [{ name: data.name }]; - const pdf = await generate({ template: tagTemplate, inputs }); - const blob = new Blob([pdf.buffer], { type: 'application/pdf' }); - window.open(URL.createObjectURL(blob)); + try { + const inputs = [{ name: data.name }]; + const pdf = await generate({ template: tagTemplate, inputs }); + // istanbul ignore next + const blob = new Blob([pdf.buffer], { type: 'application/pdf' }); + // istanbul ignore next + const url = URL.createObjectURL(blob); + // istanbul ignore next + window.open(url); + // istanbul ignore next + toast.success('PDF generated successfully!'); + } catch (error: unknown) { + const errorMessage = + error instanceof Error ? error.message : 'Unknown error'; + toast.error(`Error generating pdf: ${errorMessage}`); + } }; return ( diff --git a/src/components/CheckIn/tagTemplate.ts b/src/components/CheckIn/tagTemplate.ts index 0af9eeeb15..4aa4475e02 100644 --- a/src/components/CheckIn/tagTemplate.ts +++ b/src/components/CheckIn/tagTemplate.ts @@ -1,4 +1,4 @@ -import { Template } from '@pdfme/generator'; +import { Template } from '@pdfme/common'; export const tagTemplate: Template = { schemas: [ diff --git a/src/components/CollapsibleDropdown/CollapsibleDropdown.test.tsx b/src/components/CollapsibleDropdown/CollapsibleDropdown.test.tsx index cfb1001e6b..efee248ffb 100644 --- a/src/components/CollapsibleDropdown/CollapsibleDropdown.test.tsx +++ b/src/components/CollapsibleDropdown/CollapsibleDropdown.test.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { act } from 'react'; import { render, screen, fireEvent } from '@testing-library/react'; import { BrowserRouter } from 'react-router-dom'; @@ -72,7 +72,9 @@ describe('Testing CollapsibleDropdown component', () => { const nonActiveDropdownBtn = screen.getByText('SubCategory 2'); // Check if dropdown is rendered with correct classes - activeDropdownBtn.click(); + act(() => { + fireEvent.click(activeDropdownBtn); + }); expect(parentDropdownBtn).toBeInTheDocument(); expect(parentDropdownBtn).toHaveClass('text-white'); expect(parentDropdownBtn).toHaveClass('btn-success'); @@ -88,15 +90,21 @@ describe('Testing CollapsibleDropdown component', () => { expect(nonActiveDropdownBtn).toHaveClass('btn-light'); // Check if dropdown is collapsed after clicking on it - fireEvent.click(parentDropdownBtn); + act(() => { + fireEvent.click(parentDropdownBtn); + }); expect(props.setShowDropdown).toHaveBeenCalledWith(false); // Check if dropdown is expanded after clicking on it again - fireEvent.click(parentDropdownBtn); + act(() => { + fireEvent.click(parentDropdownBtn); + }); expect(props.setShowDropdown).toHaveBeenCalledWith(true); - // Click on non active dropdown button and check if it navigates to the correct url - nonActiveDropdownBtn.click(); + // Click on non-active dropdown button and check if it navigates to the correct URL + act(() => { + fireEvent.click(nonActiveDropdownBtn); + }); expect(window.location.pathname).toBe('/sub-category-2'); }); }); diff --git a/src/components/ContriStats/ContriStats.test.tsx b/src/components/ContriStats/ContriStats.test.tsx index 3164c880c8..8edb853684 100644 --- a/src/components/ContriStats/ContriStats.test.tsx +++ b/src/components/ContriStats/ContriStats.test.tsx @@ -14,7 +14,6 @@ const client: ApolloClient = new ApolloClient({ describe('Testing Contribution Stats', () => { const props = { - key: '123', id: '234', recentAmount: '200', highestAmount: '500', diff --git a/src/components/ContriStats/ContriStats.tsx b/src/components/ContriStats/ContriStats.tsx index 995a7ab93d..a2db307d91 100644 --- a/src/components/ContriStats/ContriStats.tsx +++ b/src/components/ContriStats/ContriStats.tsx @@ -4,7 +4,6 @@ import { useTranslation } from 'react-i18next'; import styles from './ContriStats.module.css'; interface InterfaceContriStatsProps { - key: string; id: string; recentAmount: string; highestAmount: string; @@ -18,7 +17,7 @@ interface InterfaceContriStatsProps { * * @returns JSX.Element - The rendered component displaying the contribution stats. */ -function contriStats(props: InterfaceContriStatsProps): JSX.Element { +function ContriStats(props: InterfaceContriStatsProps): JSX.Element { const { t } = useTranslation('translation', { keyPrefix: 'contriStats', }); @@ -37,4 +36,4 @@ function contriStats(props: InterfaceContriStatsProps): JSX.Element { ); } -export default contriStats; +export default ContriStats; diff --git a/src/components/DeleteOrg/DeleteOrg.test.tsx b/src/components/DeleteOrg/DeleteOrg.test.tsx deleted file mode 100644 index d9ac99f3ad..0000000000 --- a/src/components/DeleteOrg/DeleteOrg.test.tsx +++ /dev/null @@ -1,248 +0,0 @@ -import React from 'react'; -import { MockedProvider } from '@apollo/react-testing'; -import { render, screen } from '@testing-library/react'; -import 'jest-location-mock'; -import { I18nextProvider } from 'react-i18next'; -import { Provider } from 'react-redux'; -import { BrowserRouter } from 'react-router-dom'; - -import { - DELETE_ORGANIZATION_MUTATION, - REMOVE_SAMPLE_ORGANIZATION_MUTATION, -} from 'GraphQl/Mutations/mutations'; -import { act } from 'react-dom/test-utils'; -import { store } from 'state/store'; -import { StaticMockLink } from 'utils/StaticMockLink'; -import i18nForTest from 'utils/i18nForTest'; -import DeleteOrg from './DeleteOrg'; -import { ToastContainer, toast } from 'react-toastify'; -import { IS_SAMPLE_ORGANIZATION_QUERY } from 'GraphQl/Queries/Queries'; -import useLocalStorage from 'utils/useLocalstorage'; - -const { setItem } = useLocalStorage(); - -async function wait(ms = 1000): Promise { - await act(async () => { - await new Promise((resolve) => setTimeout(resolve, ms)); - }); -} - -const MOCKS = [ - { - request: { - query: IS_SAMPLE_ORGANIZATION_QUERY, - variables: { - isSampleOrganizationId: '123', - }, - }, - result: { - data: { - isSampleOrganization: true, - }, - }, - }, - { - request: { - query: REMOVE_SAMPLE_ORGANIZATION_MUTATION, - }, - result: { - data: { - removeSampleOrganization: true, - }, - }, - }, - { - request: { - query: DELETE_ORGANIZATION_MUTATION, - variables: { - id: '456', - }, - }, - result: { - data: { - removeOrganization: { - _id: '456', - }, - }, - }, - }, -]; - -const MOCKS_WITH_ERROR = [ - { - request: { - query: IS_SAMPLE_ORGANIZATION_QUERY, - variables: { - isSampleOrganizationId: '123', - }, - }, - result: { - data: { - isSampleOrganization: true, - }, - }, - }, - { - request: { - query: DELETE_ORGANIZATION_MUTATION, - variables: { - id: '456', - }, - }, - error: new Error('Failed to delete organization'), - }, - { - request: { - query: REMOVE_SAMPLE_ORGANIZATION_MUTATION, - }, - error: new Error('Failed to delete sample organization'), - }, -]; -const mockNavgatePush = jest.fn(); -let mockURL = '123'; -jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useParams: () => ({ orgId: mockURL }), - useNavigate: () => mockNavgatePush, -})); - -const link = new StaticMockLink(MOCKS, true); -const link2 = new StaticMockLink(MOCKS_WITH_ERROR, true); - -afterEach(() => { - localStorage.clear(); -}); - -describe('Delete Organization Component', () => { - test('should be able to Toggle Delete Organization Modal', async () => { - mockURL = '456'; - setItem('SuperAdmin', true); - render( - - - - - - - - - - , - ); - await wait(); - screen.getByTestId(/openDeleteModalBtn/i).click(); - expect(screen.getByTestId(/orgDeleteModal/i)).toBeInTheDocument(); - screen.getByTestId(/closeDelOrgModalBtn/i).click(); - await act(async () => { - expect(screen.queryByTestId(/orgDeleteModal/i)).not.toHaveFocus(); - }); - }); - - test('should be able to Toggle Delete Organization Modal When Organization is Sample Organization', async () => { - mockURL = '123'; - setItem('SuperAdmin', true); - render( - - - - - - - - - - , - ); - await wait(); - screen.getByTestId(/openDeleteModalBtn/i).click(); - expect(screen.getByTestId(/orgDeleteModal/i)).toBeInTheDocument(); - screen.getByTestId(/closeDelOrgModalBtn/i).click(); - await act(async () => { - expect(screen.queryByTestId(/orgDeleteModal/i)).not.toHaveFocus(); - }); - }); - - test('Delete organization functionality should work properly', async () => { - mockURL = '456'; - setItem('SuperAdmin', true); - render( - - - - - - - - - , - ); - await wait(); - screen.getByTestId(/openDeleteModalBtn/i).click(); - screen.getByTestId(/deleteOrganizationBtn/i).click(); - }); - - test('Delete organization functionality should work properly for sample org', async () => { - mockURL = '123'; - setItem('SuperAdmin', true); - render( - - - - - - - - - , - ); - await wait(); - screen.getByTestId(/openDeleteModalBtn/i).click(); - screen.getByTestId(/deleteOrganizationBtn/i).click(); - await wait(2000); - expect(mockNavgatePush).toHaveBeenCalledWith('/orglist'); - }); - - test('Error handling for IS_SAMPLE_ORGANIZATION_QUERY mock', async () => { - mockURL = '123'; - setItem('SuperAdmin', true); - jest.spyOn(toast, 'error'); - render( - - - - - - - - - , - ); - await wait(); - screen.getByTestId(/openDeleteModalBtn/i).click(); - screen.getByTestId(/deleteOrganizationBtn/i).click(); - await wait(); - expect(toast.error).toHaveBeenCalledWith( - 'Failed to delete sample organization', - ); - }); - - test('Error handling for DELETE_ORGANIZATION_MUTATION mock', async () => { - mockURL = '456'; - setItem('SuperAdmin', true); - render( - - - - - - - - - , - ); - await wait(); - screen.getByTestId(/openDeleteModalBtn/i).click(); - screen.getByTestId(/deleteOrganizationBtn/i).click(); - await wait(); - }); -}); diff --git a/src/components/EditCustomFieldDropDown/EditCustomFieldDropDown.test.tsx b/src/components/EditCustomFieldDropDown/EditCustomFieldDropDown.test.tsx index 2e3c52ad38..19d2249a43 100644 --- a/src/components/EditCustomFieldDropDown/EditCustomFieldDropDown.test.tsx +++ b/src/components/EditCustomFieldDropDown/EditCustomFieldDropDown.test.tsx @@ -1,13 +1,13 @@ import type { Dispatch, SetStateAction } from 'react'; -import React from 'react'; -import { act, render } from '@testing-library/react'; +import React, { act } from 'react'; +import { render } from '@testing-library/react'; import { BrowserRouter } from 'react-router-dom'; import EditOrgCustomFieldDropDown from './EditCustomFieldDropDown'; -import type { InterfaceCustomFieldData } from 'components/OrgProfileFieldSettings/OrgProfileFieldSettings'; import userEvent from '@testing-library/user-event'; import availableFieldTypes from 'utils/fieldTypes'; import { I18nextProvider } from 'react-i18next'; import i18nForTest from 'utils/i18nForTest'; +import type { InterfaceCustomFieldData } from 'utils/interfaces'; async function wait(ms = 100): Promise { await act(() => { diff --git a/src/components/EditCustomFieldDropDown/EditCustomFieldDropDown.tsx b/src/components/EditCustomFieldDropDown/EditCustomFieldDropDown.tsx index 4207e3f259..2350d2c9c4 100644 --- a/src/components/EditCustomFieldDropDown/EditCustomFieldDropDown.tsx +++ b/src/components/EditCustomFieldDropDown/EditCustomFieldDropDown.tsx @@ -2,8 +2,8 @@ import React from 'react'; import type { SetStateAction, Dispatch } from 'react'; import { Dropdown } from 'react-bootstrap'; import availableFieldTypes from 'utils/fieldTypes'; -import type { InterfaceCustomFieldData } from 'components/OrgProfileFieldSettings/OrgProfileFieldSettings'; import { useTranslation } from 'react-i18next'; +import type { InterfaceCustomFieldData } from 'utils/interfaces'; /** * Props for the EditOrgCustomFieldDropDown component. diff --git a/src/components/EventCalendar/EventHeader.test.tsx b/src/components/EventCalendar/EventHeader.test.tsx index 215182af9c..e18d066306 100644 --- a/src/components/EventCalendar/EventHeader.test.tsx +++ b/src/components/EventCalendar/EventHeader.test.tsx @@ -1,10 +1,9 @@ -import React from 'react'; +import React, { act } from 'react'; // Import act for async testing import { render, fireEvent } from '@testing-library/react'; import EventHeader from './EventHeader'; import { ViewType } from '../../screens/OrganizationEvents/OrganizationEvents'; import { I18nextProvider } from 'react-i18next'; import i18nForTest from 'utils/i18nForTest'; -import { act } from 'react-dom/test-utils'; // Import act for async testing describe('EventHeader Component', () => { const viewType = ViewType.MONTH; diff --git a/src/components/EventListCard/EventListCard.test.tsx b/src/components/EventListCard/EventListCard.test.tsx index 22ddccd8d2..b882d5887b 100644 --- a/src/components/EventListCard/EventListCard.test.tsx +++ b/src/components/EventListCard/EventListCard.test.tsx @@ -1,12 +1,6 @@ -import React from 'react'; +import React, { act } from 'react'; import type { RenderResult } from '@testing-library/react'; -import { - act, - render, - screen, - fireEvent, - waitFor, -} from '@testing-library/react'; +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import { MockedProvider } from '@apollo/react-testing'; import userEvent from '@testing-library/user-event'; import { I18nextProvider } from 'react-i18next'; @@ -57,6 +51,8 @@ const translations = { const renderEventListCard = ( props: InterfaceEventListCardProps, ): RenderResult => { + const { key, ...restProps } = props; // Destructure the key and separate other props + return render( @@ -66,11 +62,11 @@ const renderEventListCard = ( } + element={} /> } + element={} /> { }); test('should show an error toast when the delete event mutation fails', async () => { + // Destructure key from props[1] and pass it separately to avoid spreading it + const { key, ...otherProps } = props[1]; render( @@ -887,11 +885,11 @@ describe('Testing Event List Card', () => { } + element={} /> } + element={} /> diff --git a/src/components/EventListCard/EventListCardMocks.ts b/src/components/EventListCard/EventListCardMocks.ts index e5f7ea3227..6312b5af51 100644 --- a/src/components/EventListCard/EventListCardMocks.ts +++ b/src/components/EventListCard/EventListCardMocks.ts @@ -69,8 +69,8 @@ export const MOCKS = [ startDate: '2022-03-18', endDate: '2022-03-20', location: 'New Delhi', - startTime: '09:00:00Z', - endTime: '17:00:00Z', + startTime: '09:00:00', + endTime: '17:00:00', }, }, result: { diff --git a/src/components/EventListCard/EventListCardModals.tsx b/src/components/EventListCard/EventListCardModals.tsx index 878d8796a4..193890941c 100644 --- a/src/components/EventListCard/EventListCardModals.tsx +++ b/src/components/EventListCard/EventListCardModals.tsx @@ -255,8 +255,8 @@ function EventListCardModals({ startDate: dayjs(eventStartDate).format('YYYY-MM-DD'), endDate: dayjs(eventEndDate).format('YYYY-MM-DD'), location: formState.location, - startTime: !alldaychecked ? formState.startTime + 'Z' : undefined, - endTime: !alldaychecked ? formState.endTime + 'Z' : undefined, + startTime: !alldaychecked ? formState.startTime : undefined, + endTime: !alldaychecked ? formState.endTime : undefined, recurrenceStartDate: recurringchecked ? recurringEventUpdateType === thisAndFollowingInstances && (instanceDatesChanged || recurrenceRuleChanged) @@ -284,7 +284,7 @@ function EventListCardModals({ }); if (data) { - toast.success(t('eventUpdated')); + toast.success(t('eventUpdated') as string); setRecurringEventUpdateModalIsOpen(false); hideViewModal(); if (refetchEvents) { @@ -323,7 +323,7 @@ function EventListCardModals({ }); if (data) { - toast.success(t('eventDeleted')); + toast.success(t('eventDeleted') as string); setEventDeleteModalIsOpen(false); hideViewModal(); if (refetchEvents) { diff --git a/src/components/EventManagement/Dashboard/EventDashboard.tsx b/src/components/EventManagement/Dashboard/EventDashboard.tsx index b811bcd137..dfb3eb0245 100644 --- a/src/components/EventManagement/Dashboard/EventDashboard.tsx +++ b/src/components/EventManagement/Dashboard/EventDashboard.tsx @@ -75,6 +75,9 @@ const EventDashboard = (props: { eventId: string }): JSX.Element => { const formattedDate = `${day}${suffix} ${monthNames[monthIndex]} ${year}`; return formattedDate; } + if (!eventData || !eventData.event) { + return ; // Fallback UI while data is loading + } // Render event details return ( @@ -86,36 +89,36 @@ const EventDashboard = (props: { eventId: string }): JSX.Element => {

- {eventData.event.startTime !== null - ? `${formatTime(eventData.event.startTime)}` - : ``} + {eventData?.event?.startTime !== null + ? `${formatTime(eventData?.event?.startTime)}` + : ''} {' '} - {formatDate(eventData.event.startDate)}{' '} + {formatDate(eventData?.event?.startDate)}{' '}

{t('to')}

- {eventData.event.endTime !== null - ? `${formatTime(eventData.event.endTime)}` + {eventData?.event?.endTime !== null + ? `${formatTime(eventData?.event?.endTime)}` : ``} {' '} - {formatDate(eventData.event.endDate)}{' '} + {formatDate(eventData?.event?.endDate)}{' '}

-

{eventData.event.title}

+

{eventData?.event?.title}

- {eventData.event.description} + {eventData?.event?.description}

- Location: {eventData.event.location} + Location: {eventData?.event?.location}

Registrants:{' '} - {eventData.event.attendees.length} + {eventData?.event?.attendees?.length}


diff --git a/src/components/EventManagement/EventActionItems/EventActionItems.module.css b/src/components/EventManagement/EventActionItems/EventActionItems.module.css deleted file mode 100644 index 120177d155..0000000000 --- a/src/components/EventManagement/EventActionItems/EventActionItems.module.css +++ /dev/null @@ -1,206 +0,0 @@ -@media screen and (max-width: 575.5px) { - .mainpageright { - width: 98%; - } -} -.actionItemModal { - max-width: 80vw; - margin-top: 2vh; - margin-left: 13vw; -} -.modalContent { - width: 670px; - max-width: 680px; -} -.dropdown { - background-color: white; - border: 1px solid #31bb6b; - position: relative; - display: inline-block; - margin-top: 10px; - margin-bottom: 10px; - color: #31bb6b; -} -.input { - flex: 1; - position: relative; -} - -.btnsContainer { - display: flex; - margin: 2.5rem 0 2.5rem 0; -} - -.btnsContainer .btnsBlock { - display: flex; -} - -.btnsContainer .btnsBlock button { - margin-left: 1rem; - display: flex; - justify-content: center; - align-items: center; -} - -.btnsContainer .input { - flex: 1; - position: relative; -} - -/* input { - outline: 1px solid var(--bs-gray-400); -} */ - -.btnsContainer .input button { - width: 52px; -} - -.inputField { - margin-top: 10px; - margin-bottom: 10px; - background-color: white; - box-shadow: 0 1px 1px #31bb6b; -} -.inputFieldModal { - margin-bottom: 10px; - background-color: white; - box-shadow: 0 1px 1px #31bb6b; -} -.inputField > button { - padding-top: 10px; - padding-bottom: 10px; -} -.TableImage { - object-fit: cover; - width: 50px !important; - height: 50px !important; - border-radius: 100% !important; -} -.datagrid { - overflow: auto; - border-radius: 10px; -} -.tableHead { - background-color: #31bb6b !important; - color: white; - border-radius: 20px !important; - padding: 20px; - margin-top: 20px; -} - -.tableHead :nth-first-child() { - border-top-left-radius: 20px; -} - -.mainpageright > hr { - margin-top: 10px; - width: 100%; - margin-left: -15px; - margin-right: -15px; - margin-bottom: 20px; -} -.rowBackground { - background-color: var(--bs-white); -} -.tableHeader { - color: var(--bs-black); - font-size: var(--bs-body-font-size); -} -.addButton { - width: 7em; - position: absolute; - right: 1rem; - top: 1rem; -} - -.createModal { - margin-top: 20vh; - margin-left: 13vw; - max-width: 80vw; -} - -.icon { - transform: scale(1.5); - color: var(--bs-danger); - margin-bottom: 1rem; -} - -.message { - display: flex; - justify-content: center; - align-items: center; - flex-direction: column; -} - -.titlemodal { - color: var(--bs-gray-600); - font-weight: 600; - font-size: 20px; - margin-bottom: 20px; - padding-bottom: 5px; - border-bottom: 3px solid var(--bs-primary); - width: 65%; -} - -.editDelBtns { - margin-top: 15px; - border: 1px solid var(--bs-gray-300); - box-shadow: 0 2px 2px var(--bs-gray-300); - padding: 10px 10px; - border-radius: 5px; - background-color: var(--bs-primary); - width: 100%; - font-size: 16px; - color: var(--bs-white); - outline: none; - font-weight: 600; - cursor: pointer; - transition: - transform 0.2s, - box-shadow 0.2s; - width: 100%; -} - -.greenregbtn { - margin-left: 93%; -} - -.datatable { - margin-top: 5rem; -} - -.datediv { - display: flex; - flex-direction: row; -} -.datebox { - width: 90%; - border-radius: 7px; - outline: none; - box-shadow: none; -} -.actionItemsOptionsButton { - width: 24px; - height: 24px; -} - -@-webkit-keyframes load8 { - 0% { - -webkit-transform: rotate(0deg); - transform: rotate(0deg); - } - 100% { - -webkit-transform: rotate(360deg); - transform: rotate(360deg); - } -} -@keyframes load8 { - 0% { - -webkit-transform: rotate(0deg); - transform: rotate(0deg); - } - 100% { - -webkit-transform: rotate(360deg); - transform: rotate(360deg); - } -} diff --git a/src/components/EventManagement/EventActionItems/EventActionItems.test.tsx b/src/components/EventManagement/EventActionItems/EventActionItems.test.tsx deleted file mode 100644 index 9da0b5247a..0000000000 --- a/src/components/EventManagement/EventActionItems/EventActionItems.test.tsx +++ /dev/null @@ -1,1179 +0,0 @@ -import React from 'react'; -import { MockedProvider } from '@apollo/react-testing'; -import { LocalizationProvider } from '@mui/x-date-pickers'; -import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; -import { - act, - fireEvent, - render, - screen, - waitFor, - waitForElementToBeRemoved, -} from '@testing-library/react'; -import 'jest-localstorage-mock'; -import { Provider } from 'react-redux'; -import { BrowserRouter } from 'react-router-dom'; -import userEvent from '@testing-library/user-event'; -import { I18nextProvider } from 'react-i18next'; -import EventActionItems from './EventActionItems'; -import { store } from 'state/store'; -import 'jest-location-mock'; -import { toast } from 'react-toastify'; -import i18nForTest from 'utils/i18nForTest'; -import { StaticMockLink } from 'utils/StaticMockLink'; -import { - CREATE_ACTION_ITEM_MUTATION, - UPDATE_ACTION_ITEM_MUTATION, - DELETE_ACTION_ITEM_MUTATION, -} from 'GraphQl/Mutations/ActionItemMutations'; -import { - ACTION_ITEM_CATEGORY_LIST, - MEMBERS_LIST, -} from 'GraphQl/Queries/Queries'; -import { ACTION_ITEM_LIST_BY_EVENTS } from 'GraphQl/Queries/ActionItemQueries'; - -async function wait(ms = 100): Promise { - await act(() => { - return new Promise((resolve) => { - setTimeout(resolve, ms); - }); - }); -} - -jest.mock('react-toastify', () => ({ - toast: { - success: jest.fn(), - error: jest.fn(), - }, -})); - -const MOCKS = [ - { - request: { - query: CREATE_ACTION_ITEM_MUTATION, - variables: { - assigneeId: '658930fd2caa9d8d6908745c', - actionItemCategoryId: '65f069a53b63ad266db32b3f', - eventId: '123', - preCompletionNotes: 'task to be done with high priority', - dueDate: '2024-04-05', - }, - }, - result: { - data: { - createActionItem: { - _id: 'newly_created_action_item_id', - }, - }, - }, - }, - { - request: { - query: UPDATE_ACTION_ITEM_MUTATION, - variables: { - actionItemId: '_6613ef741677gygwuyu', - assigneeId: '658930fd2caa9d8d690sfhgush', - preCompletionNotes: 'pre completion notes edited', - postCompletionNotes: 'Post Completion Notes', - dueDate: '2024-02-14', - completionDate: '2024-02-21', - isCompleted: false, - }, - }, - result: { - data: { - updateActionItem: { - _id: '_6613ef741677gygwuyu', - }, - }, - }, - }, - { - request: { - query: UPDATE_ACTION_ITEM_MUTATION, - variables: { - actionItemId: '_6613ef741677gygwuyu', - assigneeId: '658930fd2caa9d8d6908745c', - preCompletionNotes: 'Pre Completion Notes', - postCompletionNotes: 'this action item has been completed successfully', - dueDate: '2024-02-21', - completionDate: '2024-02-21', - isCompleted: true, - }, - }, - result: { - data: { - updateActionItem: { - _id: '_6613ef741677gygwuyu', - }, - }, - }, - }, - { - request: { - query: UPDATE_ACTION_ITEM_MUTATION, - variables: { - actionItemId: 'actionItem2', - assigneeId: 'user1', - preCompletionNotes: 'this action item has been made active again', - postCompletionNotes: 'Post Completion Notes', - dueDate: '2024-02-21', - completionDate: '2024-02-21', - isCompleted: false, - }, - }, - result: { - data: { - updateActionItem: { - _id: '_6613ef741677gygwuyu', - }, - }, - }, - }, - { - request: { - query: DELETE_ACTION_ITEM_MUTATION, - variables: { - actionItemId: '_6613ef741677gygwuyu', - }, - }, - result: { - data: { - removeActionItem: { - _id: '_6613ef741677gygwuyu', - __typename: 'ActionItem', - }, - }, - }, - }, - { - request: { - query: ACTION_ITEM_CATEGORY_LIST, - variables: { - organizationId: '111', - }, - }, - result: { - data: { - actionItemCategoriesByOrganization: [ - { - _id: '65f069a53b63ad266db32b3f', - name: 'Default', - isDisabled: false, - __typename: 'ActionItemCategory', - }, - ], - }, - }, - }, - { - request: { - query: MEMBERS_LIST, - variables: { - id: '111', - }, - }, - result: { - data: { - organizations: [ - { - _id: '111', - members: [ - { - createdAt: '2023-04-13', - email: 'testuser4@example.com', - firstName: 'Teresa', - image: null, - lastName: 'Bradley', - organizationsBlockedBy: [], - __typename: 'User', - _id: '658930fd2caa9d8d6908745c', - }, - { - createdAt: '2024-04-13', - email: 'testuser2@example.com', - firstName: 'Anna', - image: null, - lastName: 'Bradley', - organizationsBlockedBy: [], - __typename: 'User', - _id: '658930fd2caa9d8d690sfhgush', - }, - ], - }, - ], - }, - }, - }, - { - request: { - query: ACTION_ITEM_LIST_BY_EVENTS, - variables: { - eventId: '123', - }, - }, - result: { - data: { - actionItemsByEvent: [ - { - _id: '_6613ef741677gygwuyu', - actionItemCategory: { - __typename: 'ActionItemCategory', - _id: '65f069a53b63ad266db32b3j', - name: 'Default', - }, - assignee: { - __typename: 'User', - _id: '658930fd2caa9d8d6908745c', - firstName: 'Burton', - lastName: 'Sanders', - }, - assigner: { - __typename: 'User', - _id: '64378abd85008f171cf2990d', - firstName: 'Wilt', - lastName: 'Shepherd', - }, - assignmentDate: new Date('2024-02-14'), - dueDate: new Date('2024-02-21'), - completionDate: new Date('2024-02-21'), - creator: { - __typename: 'User', - _id: '64378abd85008f171cf2990d', - firstName: 'Wilt', - lastName: 'Shepherd', - }, - event: { - __typename: 'Event', - _id: '123', - title: 'Adult Painting Lessons', - }, - isCompleted: false, - postCompletionNotes: 'Post Completion Notes', - preCompletionNotes: 'Pre Completion Notes', - }, - { - _id: 'actionItem2', - assignee: { - _id: '658930fd2caa9d8d6908745c', - firstName: 'Harve', - lastName: 'Lance', - }, - actionItemCategory: { - _id: 'actionItemCategory1', - name: 'ActionItemCategory 1', - }, - preCompletionNotes: - 'Long Pre Completion Notes Text that exceeds 25 characters', - postCompletionNotes: - 'Long Post Completion Notes Text that exceeds 25 characters', - assignmentDate: new Date('2024-02-14'), - dueDate: new Date('2024-02-21'), - completionDate: new Date('2024-02-21'), - isCompleted: true, - assigner: { - _id: 'user0', - firstName: 'Wilt', - lastName: 'Shepherd', - }, - event: { - _id: 'event1', - title: 'event 1', - }, - creator: { - _id: 'user0', - firstName: 'Wilt', - lastName: 'Shepherd', - }, - }, - { - _id: 'actionItem3', - assignee: { - _id: '658930fd2caa9d8d6908745c', - firstName: 'Harve', - lastName: 'Lance', - }, - actionItemCategory: { - _id: 'actionItemCategory1', - name: 'ActionItemCategory 1', - }, - preCompletionNotes: 'Pre Completion Text', - postCompletionNotes: 'Post Completion Text', - assignmentDate: new Date('2024-02-14'), - dueDate: new Date('2024-02-21'), - completionDate: new Date('2024-02-21'), - isCompleted: true, - assigner: { - _id: 'user0', - firstName: 'Wilt', - lastName: 'Shepherd', - }, - event: { - _id: 'event1', - title: 'event 1', - }, - creator: { - _id: 'user0', - firstName: 'Wilt', - lastName: 'Shepherd', - }, - }, - ], - }, - refetch: jest.fn(), - }, - }, -]; - -const CREATE_ACTION_ITEM_ERROR_MOCK = [ - { - request: { - query: CREATE_ACTION_ITEM_MUTATION, - variables: { - assigneeId: '658930fd2caa9d8d6908745c', - actionItemCategoryId: '65f069a53b63ad266db32b3f', - eventId: '123', - preCompletionNotes: 'task to be done with high priority', - dueDate: '2024-04-05', - }, - }, - result: { - data: { - createActionItem: { - _id: undefined, - }, - }, - }, - }, -]; - -const UPDATE_ACTION_ITEM_ERROR_MOCK = [ - { - request: { - query: ACTION_ITEM_LIST_BY_EVENTS, - variables: { - eventId: '123', - }, - }, - result: { - data: { - actionItemsByEvent: [ - { - _id: '_6613ef741677gygwuyu', - actionItemCategory: { - __typename: 'ActionItemCategory', - _id: '65f069a53b63ad266db32b3j', - name: 'Default', - }, - assignee: { - __typename: 'User', - _id: '658930fd2caa9d8d6908745c', - firstName: 'Burton', - lastName: 'Sanders', - }, - assigner: { - __typename: 'User', - _id: '64378abd85008f171cf2990d', - firstName: 'Wilt', - lastName: 'Shepherd', - }, - assignmentDate: new Date('2024-02-14'), - dueDate: new Date('2024-02-21'), - completionDate: new Date('2024-02-21'), - creator: { - __typename: 'User', - _id: '64378abd85008f171cf2990d', - firstName: 'Wilt', - lastName: 'Shepherd', - }, - event: { - __typename: 'Event', - _id: '123', - title: 'Adult Painting Lessons', - }, - isCompleted: false, - postCompletionNotes: 'Post Completion Note', - preCompletionNotes: 'Pre Completion Note', - }, - ], - }, - refetch: jest.fn(), - }, - }, - { - request: { - query: MEMBERS_LIST, - variables: { - id: '111', - }, - }, - result: { - data: { - organizations: [ - { - _id: '111', - members: [ - { - createdAt: '2023-04-13', - email: 'testuser4@example.com', - firstName: 'Teresa', - image: null, - lastName: 'Bradley', - organizationsBlockedBy: [], - __typename: 'User', - _id: '658930fd2caa9d8d6908745c', - }, - { - createdAt: '2024-04-13', - email: 'testuser2@example.com', - firstName: 'Anna', - image: null, - lastName: 'Bradley', - organizationsBlockedBy: [], - __typename: 'User', - _id: '658930fd2caa9d8d690sfhgush', - }, - ], - }, - ], - }, - }, - }, - { - request: { - query: DELETE_ACTION_ITEM_MUTATION, - variables: { - actionItemId: '_6613ef741677gygwuyu', - }, - }, - error: new Error('Mock Graphql Error'), - }, - { - request: { - query: UPDATE_ACTION_ITEM_MUTATION, - variables: { - actionItemId: '_6613ef741677gygwuyu', - assigneeId: '658930fd2caa9d8d690sfhgush', - preCompletionNotes: 'pre completion notes edited', - postCompletionNotes: '', - dueDate: '2024-02-14', - completionDate: '2024-02-21', - isCompleted: false, - }, - }, - error: new Error('Mock Graphql Error'), - }, -]; - -const NO_ACTION_ITEMS_ERROR_MOCK = [ - { - request: { - query: ACTION_ITEM_LIST_BY_EVENTS, - variables: { - eventId: '123', - }, - }, - result: { - data: { - actionItemsByEvent: [], - }, - refetch: jest.fn(), - }, - }, -]; - -const link = new StaticMockLink(MOCKS, true); -const link2 = new StaticMockLink(CREATE_ACTION_ITEM_ERROR_MOCK, true); -const link3 = new StaticMockLink(UPDATE_ACTION_ITEM_ERROR_MOCK, true); -const link4 = new StaticMockLink(NO_ACTION_ITEMS_ERROR_MOCK, true); - -const translations = JSON.parse( - JSON.stringify( - i18nForTest.getDataByLanguage('en')?.translation.eventActionItems, - ), -); - -describe('Event Action Items Page', () => { - const formData = { - assignee: 'Anna Bradley', - preCompletionNotes: 'pre completion notes edited', - dueDate: '02/14/2024', - completionDate: '02/21/2024', - }; - test('Testing add new action item modal', async () => { - window.location.assign('/event/111/123'); - render( - - - - - - {} - - - - - , - ); - await wait(); - userEvent.click(screen.getByTestId('createEventActionItemBtn')); - - await wait(); - expect( - screen.getByText(translations.actionItemDetails), - ).toBeInTheDocument(); - - const categoryDropdown = screen.getByTestId('formSelectActionItemCategory'); - userEvent.selectOptions(categoryDropdown, 'Default'); - - expect(categoryDropdown).toHaveValue('65f069a53b63ad266db32b3f'); - - const assigneeDropdown = screen.getByTestId('formSelectAssignee'); - userEvent.selectOptions(assigneeDropdown, 'Teresa Bradley'); - - expect(assigneeDropdown).toHaveValue('658930fd2caa9d8d6908745c'); - - fireEvent.change( - screen.getByPlaceholderText(translations.preCompletionNotes), - { - target: { value: 'task to be done with high priority' }, - }, - ); - expect( - screen.getByPlaceholderText(translations.preCompletionNotes), - ).toHaveValue('task to be done with high priority'); - - fireEvent.change(screen.getByLabelText(translations.dueDate), { - target: { value: '04/05/2024' }, - }); - expect(screen.getByLabelText(translations.dueDate)).toHaveValue( - '04/05/2024', - ); - - userEvent.click(screen.getByTestId('createActionItemFormSubmitBtn')); - - await wait(); - - expect(toast.success).toBeCalledWith(translations.successfulCreation); - }); - - test('Display all the action items', async () => { - window.location.assign('/event/111/123'); - render( - - - - - - {} - - - - - , - ); - await wait(); - - expect(screen.getByText('#')).toBeInTheDocument(); - expect(screen.getByText(translations.assignee)).toBeInTheDocument(); - expect( - screen.getByText(translations.actionItemCategory), - ).toBeInTheDocument(); - expect( - screen.getByText(translations.preCompletionNotes), - ).toBeInTheDocument(); - expect( - screen.getByText(translations.postCompletionNotes), - ).toBeInTheDocument(); - - await wait(); - const asigneeAnchorElement = screen.getByText('Burton Sanders'); - expect(asigneeAnchorElement.tagName).toBe('A'); - expect(asigneeAnchorElement).toHaveAttribute('href', '/member/123'); - - expect(screen.getByText('Burton Sanders')).toBeInTheDocument(); - const updateButtons = screen.getAllByTestId('editActionItemModalBtn'); - const previewButtons = screen.getAllByTestId('previewActionItemModalBtn'); - const updateStatusButtons = screen.getAllByTestId( - 'actionItemStatusChangeCheckbox', - ); - expect(updateButtons[0]).toBeInTheDocument(); - expect(previewButtons[0]).toBeInTheDocument(); - expect(updateStatusButtons[0]).toBeInTheDocument(); - - // Truncate notes and long completion notes txt - expect( - screen.getAllByTestId('actionItemPreCompletionNotesOverlay')[1], - ).toHaveTextContent('Long Pre Completion Notes...'); - expect( - screen.getAllByTestId('actionItemPostCompletionNotesOverlay')[0], - ).toHaveTextContent('Long Post Completion Note...'); - expect( - screen.getAllByTestId('actionItemPostCompletionNotesOverlay')[1], - ).toHaveTextContent('Post Completion Text'); - expect( - screen.getAllByTestId('actionItemPreCompletionNotesOverlay')[2], - ).toHaveTextContent('Pre Completion Text'); - }); - - test('opens and closes the update and delete modals through the preview modal', async () => { - window.location.assign('/event/111/123'); - render( - - - - - - {} - - - - - , - ); - - await wait(); - - expect( - screen.getAllByTestId('previewActionItemModalBtn')[0], - ).toBeInTheDocument(); - userEvent.click(screen.getAllByTestId('previewActionItemModalBtn')[0]); - - await waitFor(() => { - return expect( - screen.findByTestId('previewActionItemModalCloseBtn'), - ).resolves.toBeInTheDocument(); - }); - - await waitFor(() => { - expect( - screen.getByTestId('deleteActionItemPreviewModalBtn'), - ).toBeInTheDocument(); - }); - userEvent.click(screen.getByTestId('deleteActionItemPreviewModalBtn')); - - await waitFor(() => { - return expect( - screen.findByTestId('actionItemDeleteModalCloseBtn'), - ).resolves.toBeInTheDocument(); - }); - userEvent.click(screen.getByTestId('actionItemDeleteModalCloseBtn')); - - await waitForElementToBeRemoved(() => - screen.queryByTestId('actionItemDeleteModalCloseBtn'), - ); - - expect( - screen.getByTestId('editActionItemPreviewModalBtn'), - ).toBeInTheDocument(); - userEvent.click(screen.getByTestId('editActionItemPreviewModalBtn')); - - await waitFor(() => { - return expect( - screen.findByTestId('updateActionItemModalCloseBtn'), - ).resolves.toBeInTheDocument(); - }); - userEvent.click(screen.getByTestId('updateActionItemModalCloseBtn')); - - await waitForElementToBeRemoved(() => - screen.queryByTestId('updateActionItemModalCloseBtn'), - ); - }); - test('opens and closes the action item status change modal correctly', async () => { - window.location.assign('/event/111/123'); - render( - - - - - - {} - - - - - , - ); - await wait(); - - await waitFor(() => { - expect( - screen.getAllByTestId('actionItemStatusChangeCheckbox')[0], - ).toBeInTheDocument(); - }); - userEvent.click(screen.getAllByTestId('actionItemStatusChangeCheckbox')[0]); - - await waitFor(() => { - return expect( - screen.findByTestId('actionItemStatusChangeModalCloseBtn'), - ).resolves.toBeInTheDocument(); - }); - userEvent.click(screen.getByTestId('actionItemStatusChangeModalCloseBtn')); - - await waitForElementToBeRemoved(() => - screen.queryByTestId('actionItemStatusChangeModalCloseBtn'), - ); - }); - - test('updates an action item status through the action item status change modal', async () => { - window.location.assign('/event/111/123'); - render( - - - - - - {} - - - - - , - ); - await wait(); - - await waitFor(() => { - expect( - screen.getAllByTestId('actionItemStatusChangeCheckbox')[0], - ).toBeInTheDocument(); - }); - userEvent.click(screen.getAllByTestId('actionItemStatusChangeCheckbox')[0]); - - await waitFor(() => { - expect( - screen.getByTestId('actionItemsStatusChangeNotes'), - ).toBeInTheDocument(); - }); - - const postCompletionNotes = screen.getByTestId( - 'actionItemsStatusChangeNotes', - ); - fireEvent.change(postCompletionNotes, { target: { value: '' } }); - userEvent.type( - postCompletionNotes, - 'this action item has been completed successfully', - ); - - await waitFor(() => { - expect( - screen.getByTestId('actionItemStatusChangeSubmitBtn'), - ).toBeInTheDocument(); - }); - userEvent.click(screen.getByTestId('actionItemStatusChangeSubmitBtn')); - - await waitFor(() => { - expect(toast.success).toBeCalledWith(translations.successfulUpdation); - }); - - await waitFor(() => { - expect( - screen.getAllByTestId('actionItemStatusChangeCheckbox')[1], - ).toBeInTheDocument(); - }); - userEvent.click(screen.getAllByTestId('actionItemStatusChangeCheckbox')[1]); - - await waitFor(() => { - expect( - screen.getByTestId('actionItemsStatusChangeNotes'), - ).toBeInTheDocument(); - }); - - const preCompletionNotes = screen.getByTestId( - 'actionItemsStatusChangeNotes', - ); - fireEvent.change(preCompletionNotes, { target: { value: '' } }); - userEvent.type( - preCompletionNotes, - 'this action item has been made active again', - ); - - await waitFor(() => { - expect( - screen.getByTestId('actionItemStatusChangeSubmitBtn'), - ).toBeInTheDocument(); - }); - userEvent.click(screen.getByTestId('actionItemStatusChangeSubmitBtn')); - - await waitFor(() => { - expect(toast.success).toBeCalledWith(translations.successfulUpdation); - }); - }); - - test('Testing update action item modal', async () => { - window.location.assign('/event/111/123'); - render( - - - - - - {} - - - - - , - ); - await wait(); - - await waitFor(() => { - expect( - screen.getAllByTestId('editActionItemModalBtn')[0], - ).toBeInTheDocument(); - }); - userEvent.click(screen.getAllByTestId('editActionItemModalBtn')[0]); - - await waitFor(() => { - expect(screen.getByTestId('formUpdateAssignee')).toBeInTheDocument(); - }); - - userEvent.selectOptions( - screen.getByTestId('formUpdateAssignee'), - formData.assignee, - ); - - const preCompletionNotes = screen.getByPlaceholderText( - translations.preCompletionNotes, - ); - fireEvent.change(preCompletionNotes, { target: { value: '' } }); - userEvent.type(preCompletionNotes, formData.preCompletionNotes); - - const dueDatePicker = screen.getByLabelText(translations.dueDate); - fireEvent.change(dueDatePicker, { - target: { value: formData.dueDate }, - }); - - const completionDatePicker = screen.getByLabelText( - translations.completionDate, - ); - fireEvent.change(completionDatePicker, { - target: { value: formData.completionDate }, - }); - - await waitFor(() => { - expect( - screen.getByTestId('updateActionItemFormSubmitBtn'), - ).toBeInTheDocument(); - }); - userEvent.click(screen.getByTestId('updateActionItemFormSubmitBtn')); - - await waitFor(() => { - expect(toast.success).toBeCalledWith(translations.successfulUpdation); - }); - }); - test('Testing delete action item modal and delete the record', async () => { - window.location.assign('/event/111/123'); - render( - - - - - - {} - - - - - , - ); - await wait(); - expect( - screen.getAllByTestId('previewActionItemModalBtn')[0], - ).toBeInTheDocument(); - userEvent.click(screen.getAllByTestId('previewActionItemModalBtn')[0]); - - await waitFor(() => { - expect( - screen.getByTestId('deleteActionItemPreviewModalBtn'), - ).toBeInTheDocument(); - }); - userEvent.click(screen.getByTestId('deleteActionItemPreviewModalBtn')); - await wait(); - expect( - screen.getByText(translations.deleteActionItemMsg), - ).toBeInTheDocument(); - userEvent.click(screen.getByText('Yes')); - await wait(); - - expect(toast.success).toBeCalledWith(translations.successfulDeletion); - }); - - test('Testing delete action item modal and does not delete the record', async () => { - window.location.assign('/event/111/123'); - render( - - - - - - {} - - - - - , - ); - await wait(); - expect( - screen.getAllByTestId('previewActionItemModalBtn')[0], - ).toBeInTheDocument(); - userEvent.click(screen.getAllByTestId('previewActionItemModalBtn')[0]); - - await waitFor(() => { - expect( - screen.getByTestId('deleteActionItemPreviewModalBtn'), - ).toBeInTheDocument(); - }); - userEvent.click(screen.getByTestId('deleteActionItemPreviewModalBtn')); - await wait(); - expect( - screen.getByText(translations.deleteActionItemMsg), - ).toBeInTheDocument(); - userEvent.click(screen.getByText('No')); - await wait(); - expect(screen.getByRole('dialog')).toBeInTheDocument(); - expect( - screen.getByText(translations.actionItemDetails), - ).toBeInTheDocument(); - }); - - test('toasts error on unsuccessful deletion', async () => { - window.location.assign('/event/111/123'); - render( - - - - - - {} - - - - - , - ); - await wait(); - - expect( - screen.getAllByTestId('previewActionItemModalBtn')[0], - ).toBeInTheDocument(); - userEvent.click(screen.getAllByTestId('previewActionItemModalBtn')[0]); - - await waitFor(() => { - return expect( - screen.findByTestId('previewActionItemModalCloseBtn'), - ).resolves.toBeInTheDocument(); - }); - - await waitFor(() => { - expect( - screen.getByTestId('deleteActionItemPreviewModalBtn'), - ).toBeInTheDocument(); - }); - userEvent.click(screen.getByTestId('deleteActionItemPreviewModalBtn')); - - await waitFor(() => { - return expect( - screen.findByTestId('actionItemDeleteModalCloseBtn'), - ).resolves.toBeInTheDocument(); - }); - userEvent.click(screen.getByTestId('deleteActionItemBtn')); - - await waitFor(() => { - expect(toast.error).toHaveBeenCalled(); - }); - }); - - test('toasts error on unsuccessful updation', async () => { - window.location.assign('/event/111/123'); - render( - - - - - - {} - - - - - , - ); - await wait(); - - await waitFor(() => { - expect( - screen.getAllByTestId('editActionItemModalBtn')[0], - ).toBeInTheDocument(); - }); - userEvent.click(screen.getAllByTestId('editActionItemModalBtn')[0]); - - await waitFor(() => { - expect(screen.getByTestId('formUpdateAssignee')).toBeInTheDocument(); - }); - - userEvent.selectOptions( - screen.getByTestId('formUpdateAssignee'), - formData.assignee, - ); - - const preCompletionNotes = screen.getByPlaceholderText( - translations.preCompletionNotes, - ); - fireEvent.change(preCompletionNotes, { target: { value: '' } }); - userEvent.type(preCompletionNotes, formData.preCompletionNotes); - - const dueDatePicker = screen.getByLabelText(translations.dueDate); - fireEvent.change(dueDatePicker, { - target: { value: formData.dueDate }, - }); - - const completionDatePicker = screen.getByLabelText( - translations.completionDate, - ); - fireEvent.change(completionDatePicker, { - target: { value: formData.completionDate }, - }); - - await waitFor(() => { - expect( - screen.getByTestId('updateActionItemFormSubmitBtn'), - ).toBeInTheDocument(); - }); - userEvent.click(screen.getByTestId('updateActionItemFormSubmitBtn')); - - await waitFor(() => { - expect(toast.error).toHaveBeenCalled(); - }); - }); - - test('Raises an error when incorrect information is filled while creation', async () => { - window.location.assign('/event/111/123'); - render( - - - - - - {} - - - - - , - ); - await wait(); - userEvent.click(screen.getByTestId('createEventActionItemBtn')); - - await wait(); - expect( - screen.getByText(translations.actionItemDetails), - ).toBeInTheDocument(); - - fireEvent.change( - screen.getByPlaceholderText(translations.preCompletionNotes), - { - target: { value: 'task to be done with high priority' }, - }, - ); - expect( - screen.getByPlaceholderText(translations.preCompletionNotes), - ).toHaveValue('task to be done with high priority'); - - fireEvent.change(screen.getByLabelText(translations.dueDate), { - target: { value: '04/05/2024' }, - }); - expect(screen.getByLabelText(translations.dueDate)).toHaveValue( - '04/05/2024', - ); - - userEvent.click(screen.getByTestId('createActionItemFormSubmitBtn')); - await wait(); - - expect(toast.error).toBeCalled(); - }); - - test('Raises an error when incorrect information is filled while updation', async () => { - window.location.assign('/event/111/123'); - render( - - - - - - {} - - - - - , - ); - await wait(); - const updateButtons = screen.getAllByTestId('editActionItemModalBtn'); - userEvent.click(updateButtons[0]); - - expect( - screen.getByText(translations.actionItemDetails), - ).toBeInTheDocument(); - - userEvent.click(screen.getByTestId('updateActionItemFormSubmitBtn')); - - await wait(); - - expect(toast.error).toBeCalled(); - }); - - test('Displays message when no data is available', async () => { - window.location.assign('/event/111/123'); - render( - - - - - - {} - - - - - , - ); - await wait(); - expect(screen.getByText('Nothing Found !!')).toBeInTheDocument(); - }); - - test('Testing update action modal to have correct initial values', async () => { - window.location.assign('/event/111/123'); - render( - - - - - - {} - - - - - , - ); - await wait(); - const updateButtons = screen.getAllByTestId('editActionItemModalBtn'); - userEvent.click(updateButtons[0]); - - expect(screen.getByText('Action Item Details')).toBeInTheDocument(); - const assigneeDropdown = screen.getByTestId( - 'formUpdateAssignee', - ) as HTMLSelectElement; - expect(assigneeDropdown.value).toBe('658930fd2caa9d8d6908745c'); - expect(assigneeDropdown).toHaveTextContent('Teresa Bradley'); - - expect( - screen.getByPlaceholderText(translations.preCompletionNotes), - ).toHaveValue('Pre Completion Notes'); - const editActionItem = screen.getByRole('button', { - name: translations.editActionItem, - }); - expect(editActionItem).toBeInTheDocument(); - }); -}); diff --git a/src/components/EventManagement/EventActionItems/EventActionItems.tsx b/src/components/EventManagement/EventActionItems/EventActionItems.tsx deleted file mode 100644 index cbe878fa07..0000000000 --- a/src/components/EventManagement/EventActionItems/EventActionItems.tsx +++ /dev/null @@ -1,599 +0,0 @@ -import { useMutation, useQuery } from '@apollo/client'; -import type { Dayjs } from 'dayjs'; -import dayjs from 'dayjs'; -import type { ChangeEvent } from 'react'; -import React, { useEffect, useState } from 'react'; -import { Button, Form } from 'react-bootstrap'; -import { useTranslation } from 'react-i18next'; -import { toast } from 'react-toastify'; -import styles from './EventActionItems.module.css'; -import { DataGrid } from '@mui/x-data-grid'; -import type { GridCellParams } from '@mui/x-data-grid'; -import { Stack } from '@mui/material'; -import Modal from 'react-bootstrap/Modal'; -import { - CREATE_ACTION_ITEM_MUTATION, - DELETE_ACTION_ITEM_MUTATION, - UPDATE_ACTION_ITEM_MUTATION, -} from 'GraphQl/Mutations/ActionItemMutations'; -import type { - InterfaceActionItemCategoryList, - InterfaceActionItemInfo, - InterfaceMembersList, -} from 'utils/interfaces'; -import { DatePicker } from '@mui/x-date-pickers'; -import { - ACTION_ITEM_CATEGORY_LIST, - MEMBERS_LIST, -} from 'GraphQl/Queries/Queries'; -import { ACTION_ITEM_LIST_BY_EVENTS } from 'GraphQl/Queries/ActionItemQueries'; -import { useEventActionColumnConfig } from './useEventActionColumnConfig'; -import ActionItemPreviewModal from 'screens/OrganizationActionItems/ActionItemPreviewModal'; -import ActionItemDeleteModal from 'screens/OrganizationActionItems/ActionItemDeleteModal'; - -function eventActionItems(props: { eventId: string }): JSX.Element { - const { eventId } = props; - const { t } = useTranslation('translation', { - keyPrefix: 'eventActionItems', - }); - const { t: tCommon } = useTranslation('common'); - - const [actionItemPreviewModalIsOpen, setActionItemPreviewModalIsOpen] = - useState(false); - const [actionItemStatusModal, setActionItemStatusModal] = useState(false); - const [isActionItemCompleted, setIsActionItemCompleted] = useState(false); - const [assignmentDate, setAssignmentDate] = useState(new Date()); - const [actionItemCreateModalIsOpen, setActionItemCreateModalIsOpen] = - useState(false); - const [actionItemUpdateModalIsOpen, setActionItemUpdateModalIsOpen] = - useState(false); - const [actionItemDeleteModalIsOpen, setActionItemDeleteModalIsOpen] = - useState(false); - const [dueDate, setDueDate] = useState(new Date()); - const [completionDate, setCompletionDate] = useState(new Date()); - const [actionItemId, setActionItemId] = useState(''); - document.title = t('title'); - const url: string = window.location.href; - const startIdx: number = url.indexOf('/event/') + '/event/'.length; - const orgId: string = url.slice(startIdx, url.indexOf('/', startIdx)); - const [formState, setFormState] = useState({ - actionItemCategoryId: '', - assignee: '', - assigner: '', - assigneeId: '', - preCompletionNotes: '', - postCompletionNotes: '', - isCompleted: false, - }); - const showCreateModal = (): void => { - const newState = !actionItemCreateModalIsOpen; - setActionItemCreateModalIsOpen(newState); - }; - const hideCreateModal = (): void => { - setActionItemCreateModalIsOpen(!actionItemCreateModalIsOpen); - }; - const showUpdateModal = (): void => { - setActionItemUpdateModalIsOpen(!actionItemUpdateModalIsOpen); - }; - const hideUpdateModal = (): void => { - setActionItemId(''); - setActionItemUpdateModalIsOpen(!actionItemUpdateModalIsOpen); - }; - const toggleDeleteModal = (): void => { - setActionItemDeleteModalIsOpen(!actionItemDeleteModalIsOpen); - }; - const setActionItemState = (actionItem: InterfaceActionItemInfo): void => { - setFormState((prevState) => ({ - ...prevState, - assignee: `${actionItem.assignee.firstName} ${actionItem.assignee.lastName}`, - assigner: `${actionItem.assigner.firstName} ${actionItem.assigner.lastName}`, - assigneeId: actionItem.assignee._id, - preCompletionNotes: actionItem.preCompletionNotes, - postCompletionNotes: actionItem.postCompletionNotes, - isCompleted: actionItem.isCompleted, - })); - setActionItemId(actionItem._id); - setDueDate(actionItem.dueDate); - setAssignmentDate(actionItem.assignmentDate); - setCompletionDate(actionItem.completionDate); - }; - const { - data: actionItemCategoriesData, - }: { - data: InterfaceActionItemCategoryList | undefined; - loading: boolean; - error?: Error | undefined; - } = useQuery(ACTION_ITEM_CATEGORY_LIST, { - variables: { - organizationId: orgId, - }, - }); - const actionItemCategories = - actionItemCategoriesData?.actionItemCategoriesByOrganization.filter( - (category) => !category.isDisabled, - ); - const { data: actionItemsData, refetch: actionItemsRefetch } = useQuery( - ACTION_ITEM_LIST_BY_EVENTS, - { - variables: { - eventId, - }, - }, - ); - const { - data: membersData, - }: { - data: InterfaceMembersList | undefined; - loading: boolean; - error?: Error | undefined; - } = useQuery(MEMBERS_LIST, { - variables: { id: orgId }, - }); - const [createActionItem] = useMutation(CREATE_ACTION_ITEM_MUTATION); - const createActionItemHandler = async ( - e: ChangeEvent, - ): Promise => { - e.preventDefault(); - try { - await createActionItem({ - variables: { - assigneeId: formState.assigneeId, - actionItemCategoryId: formState.actionItemCategoryId, - eventId, - preCompletionNotes: formState.preCompletionNotes, - dueDate: dayjs(dueDate).format('YYYY-MM-DD'), - }, - }); - setFormState({ - actionItemCategoryId: '', - assignee: '', - assigner: '', - assigneeId: '', - preCompletionNotes: '', - postCompletionNotes: '', - isCompleted: false, - }); - setDueDate(new Date()); - actionItemsRefetch(); - hideCreateModal(); - toast.success(t('successfulCreation')); - } catch (error: unknown) { - if (error instanceof Error) { - toast.error(error.message); - } - } - }; - useEffect(() => { - actionItemsRefetch({ - eventId, - }); - }, []); - const [updateActionItem] = useMutation(UPDATE_ACTION_ITEM_MUTATION); - const updateActionItemHandler = async ( - e: ChangeEvent, - ): Promise => { - e.preventDefault(); - try { - await updateActionItem({ - variables: { - actionItemId, - assigneeId: formState.assigneeId, - preCompletionNotes: formState.preCompletionNotes, - postCompletionNotes: formState.postCompletionNotes, - dueDate: dayjs(dueDate).format('YYYY-MM-DD'), - completionDate: dayjs(completionDate).format('YYYY-MM-DD'), - isCompleted: formState.isCompleted, - }, - }); - actionItemsRefetch(); - hideUpdateModal(); - hideActionItemStatusModal(); - toast.success(t('successfulUpdation')); - } catch (error: unknown) { - if (error instanceof Error) { - toast.error(error.message); - } - } - }; - const [removeActionItem] = useMutation(DELETE_ACTION_ITEM_MUTATION); - const deleteActionItemHandler = async (): Promise => { - try { - await removeActionItem({ - variables: { - actionItemId, - }, - }); - actionItemsRefetch(); - toggleDeleteModal(); - hidePreviewModal(); - toast.success(t('successfulDeletion')); - } catch (error: unknown) { - if (error instanceof Error) { - toast.error(error.message); - console.log(error.message); - } - } - }; - - const handleActionItemStatusChange = ( - actionItem: InterfaceActionItemInfo, - ): void => { - actionItem = { ...actionItem, isCompleted: !actionItem.isCompleted }; - setIsActionItemCompleted(!actionItem.isCompleted); - setActionItemState(actionItem); - setActionItemStatusModal(true); - }; - - const showPreviewModal = (actionItem: InterfaceActionItemInfo): void => { - setActionItemState(actionItem); - setActionItemPreviewModalIsOpen(true); - }; - - const handleEditClick = (actionItem: InterfaceActionItemInfo): void => { - setActionItemId(actionItem._id); - setActionItemState(actionItem); - showUpdateModal(); - }; - - const hidePreviewModal = (): void => { - setActionItemPreviewModalIsOpen(false); - }; - - const hideActionItemStatusModal = (): void => { - setActionItemStatusModal(false); - setActionItemUpdateModalIsOpen(false); - }; - - const { columns } = useEventActionColumnConfig({ - eventId, - handleActionItemStatusChange, - showPreviewModal, - handleEditClick, - }); - - return ( - <> - -
- {/* create action item modal */} - - -

{t('actionItemDetails')}

- -
- -
- - {t('actionItemCategory')} - - setFormState({ - ...formState, - actionItemCategoryId: e.target.value, - }) - } - > - - {actionItemCategories?.map((category, index) => ( - - ))} - - - - {t('assignee')} - - setFormState({ ...formState, assigneeId: e.target.value }) - } - > - - {membersData?.organizations[0].members?.map((member, index) => ( - - ))} - - - - { - setFormState({ - ...formState, - preCompletionNotes: e.target.value, - }); - }} - /> -
- { - if (date) { - setDueDate(date?.toDate()); - } - }} - /> -
- - -
-
- {/* update action items modal */} - - -

{t('actionItemDetails')}

- -
- -
- - Assignee - - setFormState({ ...formState, assigneeId: e.target.value }) - } - > - - {membersData?.organizations[0].members.map((member, index) => { - const currMemberName = `${member.firstName} ${member.lastName}`; - if (currMemberName !== formState.assignee) { - return ( - - ); - } - })} - - - - { - setFormState({ - ...formState, - preCompletionNotes: e.target.value, - }); - }} - /> -
- { - if (date) { - setDueDate(date?.toDate()); - } - }} - /> -   - { - /* istanbul ignore next */ - if (date) { - setCompletionDate(date?.toDate()); - } - } - } - /> -
-
- -
- -
-
- - {/* preview modal */} - - - {/* Delete Modal */} - - - {/* action item status change modal */} - - -

{t('actionItemStatus')}

- -
- -
- - {isActionItemCompleted - ? t('preCompletionNotes') - : t('postCompletionNotes')} - - { - if (isActionItemCompleted) { - setFormState({ - ...formState, - preCompletionNotes: e.target.value, - }); - } else { - setFormState({ - ...formState, - postCompletionNotes: e.target.value, - }); - } - }} - /> - - -
-
- {actionItemsData && ( -
- row._id} - slots={{ - noRowsOverlay: () => ( - - Nothing Found !! - - ), - }} - sx={{ - '&.MuiDataGrid-root .MuiDataGrid-cell:focus-within': { - outline: 'none !important', - }, - '&.MuiDataGrid-root .MuiDataGrid-columnHeader:focus-within': { - outline: 'none', - }, - '& .MuiDataGrid-row:hover': { - backgroundColor: 'transparent', - }, - '& .MuiDataGrid-row.Mui-hovered': { - backgroundColor: 'transparent', - }, - '& .MuiDataGrid-columnHeaderTitle': { - fontWeight: 700, - }, - }} - getRowClassName={() => `${styles.rowBackground}`} - autoHeight - rowHeight={50} - columnHeaderHeight={40} - rows={actionItemsData?.actionItemsByEvent?.map( - (item: object, index: number) => ({ - ...item, - index: index + 1, - }), - )} - columns={columns} - isRowSelectable={() => false} - /> -
- )} - - ); -} -export default eventActionItems; diff --git a/src/components/EventManagement/EventActionItems/useEventActionColumnConfig.tsx b/src/components/EventManagement/EventActionItems/useEventActionColumnConfig.tsx deleted file mode 100644 index b1ec584f3b..0000000000 --- a/src/components/EventManagement/EventActionItems/useEventActionColumnConfig.tsx +++ /dev/null @@ -1,201 +0,0 @@ -import React from 'react'; -import type { GridCellParams, GridColDef } from '@mui/x-data-grid'; -import { Link } from 'react-router-dom'; -import { Button, OverlayTrigger, Popover } from 'react-bootstrap'; -import styles from './EventActionItems.module.css'; -import type { InterfaceActionItemInfo } from 'utils/interfaces'; -import { useTranslation } from 'react-i18next'; - -export type Props = { - eventId: string; - handleActionItemStatusChange: (actionItem: InterfaceActionItemInfo) => void; - showPreviewModal: (actionItem: InterfaceActionItemInfo) => void; - handleEditClick: (actionItem: InterfaceActionItemInfo) => void; -}; - -type ColumnConfig = { - /** Configuration for the columns of the data grid. */ - columns: GridColDef[]; -}; - -const popover = ( - actionItemId: string, - actionItemNotes: string, -): JSX.Element => { - return ( - - {actionItemNotes} - - ); -}; - -export const useEventActionColumnConfig = ({ - eventId, - handleActionItemStatusChange, - showPreviewModal, - handleEditClick, -}: Props): ColumnConfig => { - const { t } = useTranslation('translation', { - keyPrefix: 'eventActionItems', - }); - const columns: GridColDef[] = [ - { - field: 'serialNo', - headerName: '#', - flex: 1, - minWidth: 50, - align: 'center', - headerAlign: 'center', - headerClassName: `${styles.tableHeader}`, - sortable: false, - renderCell: (params: GridCellParams) => { - return params.row?.index; - }, - }, - { - field: 'assignee', - headerName: 'Assignee', - flex: 2, - minWidth: 150, - align: 'center', - headerAlign: 'center', - headerClassName: `${styles.tableHeader}`, - sortable: false, - renderCell: (params: GridCellParams) => { - return ( - - {params.row?.assignee.firstName + - ' ' + - params.row?.assignee.lastName} - - ); - }, - }, - { - field: 'actionItemCategory', - headerName: 'Action Item Category', - flex: 2, - minWidth: 100, - align: 'center', - headerAlign: 'center', - headerClassName: `${styles.tableHeader}`, - sortable: false, - renderCell: (params: GridCellParams) => { - return params.row.actionItemCategory.name; - }, - }, - { - field: 'notes', - headerName: 'Notes', - minWidth: 150, - align: 'center', - headerAlign: 'center', - headerClassName: `${styles.tableHeader}`, - flex: 2, - sortable: false, - renderCell: (params: GridCellParams) => { - const actionItem = params.row; - return ( - - - {actionItem.preCompletionNotes.length > 25 - ? `${actionItem.preCompletionNotes.substring(0, 25)}...` - : actionItem.preCompletionNotes} - - - ); - }, - }, - { - field: 'completionNotes', - headerName: 'Completion Notes', - minWidth: 150, - align: 'center', - headerAlign: 'center', - headerClassName: `${styles.tableHeader}`, - flex: 2, - sortable: false, - renderCell: (params: GridCellParams) => { - const actionItem = params.row; - return actionItem.isCompleted ? ( - - - {actionItem.postCompletionNotes?.length > 25 - ? `${actionItem.postCompletionNotes.substring(0, 25)}...` - : actionItem.postCompletionNotes} - - - ) : ( - - {t('actionItemActive')} - - ); - }, - }, - { - field: 'options', - headerName: 'Options', - flex: 2, - minWidth: 100, - align: 'center', - headerAlign: 'center', - headerClassName: `${styles.tableHeader}`, - sortable: false, - renderCell: (params: GridCellParams) => { - return ( -
- handleActionItemStatusChange(params.row)} - /> - - -
- ); - }, - }, - ]; - return { - columns, - }; -}; diff --git a/src/components/EventManagement/EventAgendaItems/EventAgendaItems.test.tsx b/src/components/EventManagement/EventAgendaItems/EventAgendaItems.test.tsx index b1b3ed6094..3bce7ad11e 100644 --- a/src/components/EventManagement/EventAgendaItems/EventAgendaItems.test.tsx +++ b/src/components/EventManagement/EventAgendaItems/EventAgendaItems.test.tsx @@ -14,7 +14,7 @@ import { I18nextProvider } from 'react-i18next'; import { Provider } from 'react-redux'; import { BrowserRouter } from 'react-router-dom'; import i18n from 'utils/i18nForTest'; -import { toast } from 'react-toastify'; +// import { toast } from 'react-toastify'; import { LocalizationProvider } from '@mui/x-date-pickers'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; @@ -26,7 +26,7 @@ import EventAgendaItems from './EventAgendaItems'; import { MOCKS, MOCKS_ERROR_QUERY, - MOCKS_ERROR_MUTATION, + // MOCKS_ERROR_MUTATION, } from './EventAgendaItemsMocks'; jest.mock('react-toastify', () => ({ @@ -41,6 +41,15 @@ jest.mock('react-router-dom', () => ({ useParams: () => ({ eventId: '123' }), })); +//temporarily fixes react-beautiful-dnd droppable method's depreciation error +//needs to be fixed in React 19 +jest.spyOn(console, 'error').mockImplementation((message) => { + if (message.includes('Support for defaultProps will be removed')) { + return; + } + console.error(message); +}); + async function wait(ms = 100): Promise { await act(() => { return new Promise((resolve) => { @@ -51,7 +60,7 @@ async function wait(ms = 100): Promise { const link = new StaticMockLink(MOCKS, true); const link2 = new StaticMockLink(MOCKS_ERROR_QUERY, true); -const link3 = new StaticMockLink(MOCKS_ERROR_MUTATION, true); +// const link3 = new StaticMockLink(MOCKS_ERROR_MUTATION, true); const translations = JSON.parse( JSON.stringify(i18n.getDataByLanguage('en')?.translation.agendaItems), diff --git a/src/components/EventManagement/EventAgendaItems/EventAgendaItems.tsx b/src/components/EventManagement/EventAgendaItems/EventAgendaItems.tsx index 73a074ba9f..b49ade4626 100644 --- a/src/components/EventManagement/EventAgendaItems/EventAgendaItems.tsx +++ b/src/components/EventManagement/EventAgendaItems/EventAgendaItems.tsx @@ -126,7 +126,7 @@ function EventAgendaItems(props: { eventId: string }): JSX.Element { }); hideCreateModal(); refetchAgendaItem(); - toast.success(t('agendaItemCreated')); + toast.success(t('agendaItemCreated') as string); } catch (error: unknown) { if (error instanceof Error) { toast.error(error.message); @@ -153,20 +153,20 @@ function EventAgendaItems(props: { eventId: string }): JSX.Element { // Show error message if there is an error loading data if (agendaItemError || agendaCategoryError) { + const errorMessage = + agendaCategoryError?.message || + (agendaItemError as Error)?.message || + 'Unknown error'; + return (
Error occurred while loading{' '} - {agendaCategoryError - ? 'Agenda Categories' - : agendaItemError && 'Agenda Items'} - Data + {agendaCategoryError ? 'Agenda Categories' : 'Agenda Items'} Data
- {agendaCategoryError - ? agendaCategoryError.message - : agendaItemError && (agendaItemError as Error).message} + {errorMessage}
diff --git a/src/components/EventRegistrantsModal/EventRegistrantsModal.tsx b/src/components/EventRegistrantsModal/EventRegistrantsModal.tsx index 85fa1ca426..bd2adc8a2c 100644 --- a/src/components/EventRegistrantsModal/EventRegistrantsModal.tsx +++ b/src/components/EventRegistrantsModal/EventRegistrantsModal.tsx @@ -81,11 +81,13 @@ export const EventRegistrantsModal = (props: ModalPropType): JSX.Element => { }, }) .then(() => { - toast.success(tCommon('addedSuccessfully', { item: 'Attendee' })); + toast.success( + tCommon('addedSuccessfully', { item: 'Attendee' }) as string, + ); attendeesRefetch(); // Refresh the list of attendees }) .catch((err) => { - toast.error(t('errorAddingAttendee')); + toast.error(t('errorAddingAttendee') as string); toast.error(err.message); }); }; @@ -100,11 +102,13 @@ export const EventRegistrantsModal = (props: ModalPropType): JSX.Element => { }, }) .then(() => { - toast.success(tCommon('removedSuccessfully', { item: 'Attendee' })); + toast.success( + tCommon('removedSuccessfully', { item: 'Attendee' }) as string, + ); attendeesRefetch(); // Refresh the list of attendees }) .catch((err) => { - toast.error(t('errorRemovingAttendee')); + toast.error(t('errorRemovingAttendee') as string); toast.error(err.message); }); }; diff --git a/src/components/IconComponent/IconComponent.test.tsx b/src/components/IconComponent/IconComponent.test.tsx index 4e31d9980d..686a8e6f75 100644 --- a/src/components/IconComponent/IconComponent.test.tsx +++ b/src/components/IconComponent/IconComponent.test.tsx @@ -35,10 +35,6 @@ const screenTestIdMap: Record> = { name: 'Action Items', testId: 'Icon-Component-ActionItemIcon', }, - AgendaItemsCategory: { - name: 'Agenda Items Category', - testId: 'Icon-Component-AgendaCategoryIcon', - }, Posts: { name: 'Posts', testId: 'Icon-Component-PostsIcon', @@ -63,10 +59,6 @@ const screenTestIdMap: Record> = { name: 'Check In Registrants', testId: 'Icon-Component-Check-In-Registrants', }, - EventStats: { - name: 'Event Stats', - testId: 'Icon-Component-Event-Stats', - }, Advertisement: { name: 'Advertisement', testId: 'Icon-Component-Advertisement', diff --git a/src/components/IconComponent/IconComponent.tsx b/src/components/IconComponent/IconComponent.tsx index 7aeeacfad4..ec058a2e80 100644 --- a/src/components/IconComponent/IconComponent.tsx +++ b/src/components/IconComponent/IconComponent.tsx @@ -4,11 +4,9 @@ import { NewspaperOutlined, } from '@mui/icons-material'; import { ReactComponent as ActionItemIcon } from 'assets/svgs/actionItem.svg'; -import { ReactComponent as AgendaCategoryIcon } from 'assets/svgs/agenda-category-icon.svg'; import { ReactComponent as BlockUserIcon } from 'assets/svgs/blockUser.svg'; import { ReactComponent as CheckInRegistrantsIcon } from 'assets/svgs/checkInRegistrants.svg'; import { ReactComponent as DashboardIcon } from 'assets/svgs/dashboard.svg'; -import { ReactComponent as EventStatsIcon } from 'assets/svgs/eventStats.svg'; import { ReactComponent as EventsIcon } from 'assets/svgs/events.svg'; import { ReactComponent as FundsIcon } from 'assets/svgs/funds.svg'; import { ReactComponent as ListEventRegistrantsIcon } from 'assets/svgs/listEventRegistrants.svg'; @@ -68,13 +66,6 @@ const iconComponent = (props: InterfaceIconComponent): JSX.Element => { data-testid="Icon-Component-ActionItemIcon" /> ); - case 'Agenda Items Category': - return ( - - ); case 'Posts': return ; case 'Block/Unblock': @@ -112,13 +103,6 @@ const iconComponent = (props: InterfaceIconComponent): JSX.Element => { stroke={props.fill} /> ); - case 'Event Stats': - return ( - - ); case 'Advertisement': return ( { }); describe('Testing Left Drawer component for SUPERADMIN', () => { - test('Component should be rendered properly', () => { - setItem('UserImage', ''); - setItem('UserImage', ''); - setItem('SuperAdmin', true); - setItem('FirstName', 'John'); - setItem('LastName', 'Doe'); + test('Component should be rendered properly', async () => { setItem('UserImage', ''); setItem('SuperAdmin', true); setItem('FirstName', 'John'); setItem('LastName', 'Doe'); - render( - - - - - - - , - ); + + await act(async () => { + render( + + + + + + + , + ); + }); expect(screen.getByText('My Organizations')).toBeInTheDocument(); expect(screen.getByText('Users')).toBeInTheDocument(); expect(screen.getByText('Community Profile')).toBeInTheDocument(); - expect(screen.getByText('Community Profile')).toBeInTheDocument(); expect(screen.getByText('Talawa Admin Portal')).toBeInTheDocument(); const orgsBtn = screen.getByTestId(/orgsBtn/i); const rolesBtn = screen.getByTestId(/rolesBtn/i); const communityProfileBtn = screen.getByTestId(/communityProfileBtn/i); - orgsBtn.click(); + await act(async () => { + orgsBtn.click(); + }); + expect( orgsBtn.className.includes('text-white btn btn-success'), ).toBeTruthy(); expect(rolesBtn.className.includes('text-secondary btn')).toBeTruthy(); - expect(rolesBtn.className.includes('text-secondary btn')).toBeTruthy(); expect( communityProfileBtn.className.includes('text-secondary btn'), ).toBeTruthy(); - // Send to roles screen - userEvent.click(rolesBtn); + await act(async () => { + userEvent.click(rolesBtn); + }); + expect(global.window.location.pathname).toContain('/users'); - userEvent.click(communityProfileBtn); + + await act(async () => { + userEvent.click(communityProfileBtn); + }); }); - test('Testing Drawer when hideDrawer is null', () => { + test('Testing Drawer when hideDrawer is null', async () => { const tempProps: InterfaceLeftDrawerProps = { ...props, hideDrawer: false, }; - render( - - - - - - - , - ); + await act(async () => { + render( + + + + + + + , + ); + }); }); - test('Testing Drawer when hideDrawer is false', () => { + + test('Testing Drawer when hideDrawer is false', async () => { const tempProps: InterfaceLeftDrawerProps = { ...props, hideDrawer: false, }; + + await act(async () => { + render( + + + + + + + , + ); + }); }); - test('Testing Drawer when the screen size is less than or equal to 820px', () => { + + test('Testing Drawer when the screen size is less than or equal to 820px', async () => { const tempProps: InterfaceLeftDrawerProps = { ...props, hideDrawer: false, }; resizeWindow(800); - render( - - - - - - - , - ); + + await act(async () => { + render( + + + + + + + , + ); + }); + expect(screen.getByText('My Organizations')).toBeInTheDocument(); expect(screen.getByText('Talawa Admin Portal')).toBeInTheDocument(); const orgsBtn = screen.getByTestId(/orgsBtn/i); - orgsBtn.click(); + await act(async () => { + orgsBtn.click(); + }); + expect( orgsBtn.className.includes('text-white btn btn-success'), ).toBeTruthy(); }); +}); - describe('Testing Left Drawer component for ADMIN', () => { - test('Components should be rendered properly', () => { +describe('Testing Left Drawer component for ADMIN', () => { + test('Components should be rendered properly', async () => { + await act(async () => { render( @@ -168,24 +197,30 @@ describe('Testing Left Drawer component for SUPERADMIN', () => { , ); + }); + + expect(screen.getByText('My Organizations')).toBeInTheDocument(); + expect(screen.getByText('Talawa Admin Portal')).toBeInTheDocument(); - expect(screen.getByText('My Organizations')).toBeInTheDocument(); - expect(screen.getByText('Talawa Admin Portal')).toBeInTheDocument(); + expect(screen.getAllByText(/admin/i)).toHaveLength(1); - expect(screen.getAllByText(/admin/i)).toHaveLength(1); - expect(screen.getAllByText(/admin/i)).toHaveLength(1); + const orgsBtn = screen.getByTestId(/orgsBtn/i); - const orgsBtn = screen.getByTestId(/orgsBtn/i); + await act(async () => { orgsBtn.click(); - expect( - orgsBtn.className.includes('text-white btn btn-success'), - ).toBeTruthy(); + }); - // These screens arent meant for admins so they should not be present - expect(screen.queryByTestId(/rolesBtn/i)).toBeNull(); + expect( + orgsBtn.className.includes('text-white btn btn-success'), + ).toBeTruthy(); + // These screens aren't meant for admins, so they should not be present + expect(screen.queryByTestId(/rolesBtn/i)).toBeNull(); + + await act(async () => { userEvent.click(orgsBtn); - expect(global.window.location.pathname).toContain('/orglist'); }); + + expect(global.window.location.pathname).toContain('/orglist'); }); }); diff --git a/src/components/LeftDrawerOrg/LeftDrawerOrg.module.css b/src/components/LeftDrawerOrg/LeftDrawerOrg.module.css index 54560e7969..e792ef34bb 100644 --- a/src/components/LeftDrawerOrg/LeftDrawerOrg.module.css +++ b/src/components/LeftDrawerOrg/LeftDrawerOrg.module.css @@ -7,12 +7,21 @@ z-index: 100; display: flex; flex-direction: column; - padding: 0.8rem 1rem 0 1rem; + padding: 0.8rem 0rem 0 1rem; background-color: var(--bs-white); transition: 0.5s; font-family: var(--bs-leftDrawer-font-family); } +.avatarContainer { + display: flex; + justify-content: center; + align-items: center; + margin: 1rem 0; + width: 75%; + height: 75%; +} + .activeDrawer { width: calc(300px + 2rem); position: fixed; @@ -61,6 +70,8 @@ .leftDrawer .optionList { height: 100%; + overflow-y: scroll; + padding-bottom: 1.5rem; } .leftDrawer .optionList button { @@ -114,6 +125,7 @@ .leftDrawer .imageContainer { width: 68px; + margin-left: 0.75rem; margin-right: 8px; } @@ -151,7 +163,7 @@ @media (max-width: 1120px) { .leftDrawer { width: calc(250px + 2rem); - padding: 1rem 1rem 0 1rem; + padding: 1rem 0rem 0 1rem; } } @@ -186,7 +198,7 @@ } @media (max-height: 650px) { .leftDrawer { - padding: 0.5rem 0.8rem 0 0.8rem; + padding: 0.5rem 0rem 0 0.8rem; width: calc(250px); } .leftDrawer .talawaText { @@ -213,7 +225,7 @@ } .leftDrawer .imageContainer { width: 40px; - margin-left: 5px; + margin-left: 0.75rem; margin-right: 12px; } .leftDrawer .imageContainer img { diff --git a/src/components/LeftDrawerOrg/LeftDrawerOrg.test.tsx b/src/components/LeftDrawerOrg/LeftDrawerOrg.test.tsx index 58a7168552..2a0ef3815d 100644 --- a/src/components/LeftDrawerOrg/LeftDrawerOrg.test.tsx +++ b/src/components/LeftDrawerOrg/LeftDrawerOrg.test.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { act } from 'react'; import { fireEvent, render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import 'jest-localstorage-mock'; @@ -12,7 +12,6 @@ import { Provider } from 'react-redux'; import { MockedProvider } from '@apollo/react-testing'; import { store } from 'state/store'; import { ORGANIZATIONS_LIST } from 'GraphQl/Queries/Queries'; -import { act } from 'react-dom/test-utils'; import { StaticMockLink } from 'utils/StaticMockLink'; import { REVOKE_REFRESH_TOKEN } from 'GraphQl/Mutations/mutations'; import useLocalStorage from 'utils/useLocalstorage'; @@ -345,8 +344,8 @@ describe('Testing LeftDrawerOrg component for SUPERADMIN', () => { ); await wait(); resizeWindow(800); - expect(screen.getByText(/Dashboard/i)).toBeInTheDocument(); - expect(screen.getByText(/People/i)).toBeInTheDocument(); + expect(screen.getAllByText(/Dashboard/i)[0]).toBeInTheDocument(); + expect(screen.getAllByText(/People/i)[0]).toBeInTheDocument(); const peopelBtn = screen.getByTestId(/People/i); userEvent.click(peopelBtn); @@ -391,7 +390,7 @@ describe('Testing LeftDrawerOrg component for SUPERADMIN', () => { ); await wait(); expect( - screen.getByText(/Error Occured while loading the Organization/i), + screen.getByText(/Error occured while loading Organization data/i), ).toBeInTheDocument(); }); diff --git a/src/components/LeftDrawerOrg/LeftDrawerOrg.tsx b/src/components/LeftDrawerOrg/LeftDrawerOrg.tsx index 13b5d87947..44f2a12fdd 100644 --- a/src/components/LeftDrawerOrg/LeftDrawerOrg.tsx +++ b/src/components/LeftDrawerOrg/LeftDrawerOrg.tsx @@ -37,7 +37,8 @@ const leftDrawerOrg = ({ setHideDrawer, }: InterfaceLeftDrawerProps): JSX.Element => { const { t: tCommon } = useTranslation('common'); - const [showDropdown, setShowDropdown] = React.useState(false); + const { t: tErrors } = useTranslation('errors'); + const [showDropdown, setShowDropdown] = useState(false); const [organization, setOrganization] = useState(); @@ -58,7 +59,6 @@ const leftDrawerOrg = ({ let isMounted = true; if (data && isMounted) { setOrganization(data?.organizations[0]); - console.log(targets, 'targets'); } return () => { isMounted = false; @@ -95,7 +95,7 @@ const leftDrawerOrg = ({ {/* Organization Section */} -
+
{loading ? ( <>
- Error Occured while loading the Organization + {tErrors('errorLoading', { entity: 'Organization' })} ) : ( @@ -123,6 +123,7 @@ const leftDrawerOrg = ({ ) : ( )} @@ -139,10 +140,10 @@ const leftDrawerOrg = ({
{/* Options List */} +
+ {tCommon('menu')} +
-
- {tCommon('menu')} -
{targets.map(({ name, url }, index) => { return url ? ( diff --git a/src/components/LoginPortalToggle/LoginPortalToggle.test.tsx b/src/components/LoginPortalToggle/LoginPortalToggle.test.tsx index ac971e52ea..327e6cccea 100644 --- a/src/components/LoginPortalToggle/LoginPortalToggle.test.tsx +++ b/src/components/LoginPortalToggle/LoginPortalToggle.test.tsx @@ -1,5 +1,5 @@ -import React from 'react'; -import { act, render } from '@testing-library/react'; +import React, { act } from 'react'; +import { render } from '@testing-library/react'; import { Provider } from 'react-redux'; import { BrowserRouter } from 'react-router-dom'; import { I18nextProvider } from 'react-i18next'; diff --git a/src/components/MemberRequestCard/MemberRequestCard.test.tsx b/src/components/MemberRequestCard/MemberRequestCard.test.tsx index 121213e4e7..a38a046ea2 100644 --- a/src/components/MemberRequestCard/MemberRequestCard.test.tsx +++ b/src/components/MemberRequestCard/MemberRequestCard.test.tsx @@ -1,5 +1,5 @@ -import React from 'react'; -import { act, render, screen } from '@testing-library/react'; +import React, { act } from 'react'; +import { render, screen } from '@testing-library/react'; import { MockedProvider } from '@apollo/react-testing'; import userEvent from '@testing-library/user-event'; import { I18nextProvider } from 'react-i18next'; @@ -16,7 +16,7 @@ const MOCKS = [ { request: { query: ACCEPT_ORGANIZATION_REQUEST_MUTATION, - variable: { id: '123' }, + variables: { id: '123' }, }, result: { data: { @@ -31,7 +31,7 @@ const MOCKS = [ { request: { query: REJECT_ORGANIZATION_REQUEST_MUTATION, - variable: { id: '234' }, + variables: { userid: '234' }, }, result: { data: { @@ -44,7 +44,9 @@ const MOCKS = [ }, }, ]; + const link = new StaticMockLink(MOCKS, true); + async function wait(ms = 100): Promise { await act(() => { return new Promise((resolve) => { @@ -55,7 +57,6 @@ async function wait(ms = 100): Promise { describe('Testing Member Request Card', () => { const props = { - key: '123', id: '1', memberName: 'John Doe', memberLocation: 'India', @@ -89,14 +90,13 @@ describe('Testing Member Request Card', () => { expect(screen.getByText(props.email)).toBeInTheDocument(); }); - it('Should render text elements when props value is not passed', async () => { + it('should render text elements when props value is not passed', async () => { global.confirm = (): boolean => false; render( { window.location.reload(); @@ -136,5 +135,4 @@ function memberRequestCard( ); } -export {}; -export default memberRequestCard; +export default MemberRequestCard; diff --git a/src/components/OrgActionItemCategories/OrgActionItemCategories.module.css b/src/components/OrgActionItemCategories/OrgActionItemCategories.module.css deleted file mode 100644 index ac9f4a5900..0000000000 --- a/src/components/OrgActionItemCategories/OrgActionItemCategories.module.css +++ /dev/null @@ -1,33 +0,0 @@ -.addButton { - width: 7em; - position: absolute; - right: 1rem; - top: 1rem; -} - -.createModal { - margin-top: 20vh; - margin-left: 13vw; - max-width: 80vw; -} - -.icon { - transform: scale(1.5); - color: var(--bs-danger); - margin-bottom: 1rem; -} - -.message { - display: flex; - justify-content: center; - align-items: center; - flex-direction: column; -} - -.titlemodal { - color: var(--bs-gray-600); - font-weight: 600; - font-size: 20px; - margin-top: 1rem; - width: 65%; -} diff --git a/src/components/OrgActionItemCategories/OrgActionItemCategories.test.tsx b/src/components/OrgActionItemCategories/OrgActionItemCategories.test.tsx deleted file mode 100644 index 0665ce6fc4..0000000000 --- a/src/components/OrgActionItemCategories/OrgActionItemCategories.test.tsx +++ /dev/null @@ -1,372 +0,0 @@ -import React from 'react'; -import { - render, - screen, - fireEvent, - waitFor, - act, -} from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import 'jest-localstorage-mock'; -import { MockedProvider } from '@apollo/client/testing'; -import 'jest-location-mock'; -import { I18nextProvider } from 'react-i18next'; -import { Provider } from 'react-redux'; -import { BrowserRouter } from 'react-router-dom'; -import i18n from 'utils/i18nForTest'; -import { toast } from 'react-toastify'; - -import { store } from 'state/store'; -import { StaticMockLink } from 'utils/StaticMockLink'; - -import OrgActionItemCategories from './OrgActionItemCategories'; -import { - MOCKS, - MOCKS_ERROR_QUERY, - MOCKS_ERROR_MUTATIONS, -} from './OrgActionItemCategoryMocks'; - -jest.mock('react-toastify', () => ({ - toast: { - success: jest.fn(), - error: jest.fn(), - }, -})); -jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useParams: () => ({ orgId: '123' }), -})); - -async function wait(ms = 100): Promise { - await act(() => { - return new Promise((resolve) => { - setTimeout(resolve, ms); - }); - }); -} - -const link = new StaticMockLink(MOCKS, true); -const link2 = new StaticMockLink(MOCKS_ERROR_QUERY, true); -const link3 = new StaticMockLink(MOCKS_ERROR_MUTATIONS, true); - -const translations = { - ...JSON.parse( - JSON.stringify( - i18n.getDataByLanguage('en')?.translation.orgActionItemCategories ?? {}, - ), - ), - ...JSON.parse(JSON.stringify(i18n.getDataByLanguage('en')?.common ?? {})), - ...JSON.parse(JSON.stringify(i18n.getDataByLanguage('en')?.errors ?? {})), -}; - -describe('Testing Action Item Categories Component', () => { - test('Component loads correctly', async () => { - window.location.assign('/orgsetting/123'); - const { getByText } = render( - - - - - {} - - - - , - ); - - await wait(); - - await waitFor(() => { - expect(getByText(translations.create)).toBeInTheDocument(); - }); - }); - - test('render error component on unsuccessful query', async () => { - window.location.assign('/orgsetting/123'); - const { queryByText } = render( - - - - - {} - - - - , - ); - - await wait(); - - await waitFor(() => { - expect(queryByText(translations.create)).not.toBeInTheDocument(); - }); - }); - - test('opens and closes create and update modals on button clicks', async () => { - window.location.assign('/orgsetting/123'); - render( - - - - - {} - - - - , - ); - - await wait(); - - await waitFor(() => { - userEvent.click(screen.getByTestId('actionItemCategoryModalOpenBtn')); - userEvent.click(screen.getByTestId('actionItemCategoryModalCloseBtn')); - }); - - await waitFor(() => { - userEvent.click( - screen.getAllByTestId('actionItemCategoryUpdateModalOpenBtn')[0], - ); - userEvent.click(screen.getByTestId('actionItemCategoryModalCloseBtn')); - }); - }); - - test('create a new action item category', async () => { - window.location.assign('/orgsetting/123'); - render( - - - - - {} - - - - , - ); - - await wait(); - - await waitFor(() => { - userEvent.click(screen.getByTestId('actionItemCategoryModalOpenBtn')); - userEvent.type( - screen.getByPlaceholderText(translations.enterName), - 'ActionItemCategory 4', - ); - - userEvent.click(screen.getByTestId('formSubmitButton')); - }); - - await waitFor(() => { - expect(toast.success).toBeCalledWith(translations.successfulCreation); - }); - }); - - test('toast error on unsuccessful creation', async () => { - window.location.assign('/orgsetting/123'); - render( - - - - - {} - - - - , - ); - - await wait(); - - await waitFor(() => { - userEvent.click(screen.getByTestId('actionItemCategoryModalOpenBtn')); - userEvent.type( - screen.getByPlaceholderText(translations.enterName), - 'ActionItemCategory 4', - ); - - userEvent.click(screen.getByTestId('formSubmitButton')); - }); - - await waitFor(() => { - expect(toast.error).toHaveBeenCalled(); - }); - }); - - test('update an action item category', async () => { - window.location.assign('/orgsetting/123'); - render( - - - - - {} - - - - , - ); - - await wait(); - - await waitFor(() => { - userEvent.click( - screen.getAllByTestId('actionItemCategoryUpdateModalOpenBtn')[0], - ); - - const name = screen.getByPlaceholderText(translations.enterName); - fireEvent.change(name, { target: { value: '' } }); - - userEvent.type( - screen.getByPlaceholderText(translations.enterName), - 'ActionItemCategory 1 updated', - ); - - userEvent.click(screen.getByTestId('formSubmitButton')); - }); - - await waitFor(() => { - expect(toast.success).toBeCalledWith(translations.successfulUpdation); - }); - }); - - test('toast error on unsuccessful updation', async () => { - window.location.assign('/orgsetting/123'); - render( - - - - - {} - - - - , - ); - - await wait(); - - await waitFor(() => { - userEvent.click( - screen.getAllByTestId('actionItemCategoryUpdateModalOpenBtn')[0], - ); - - const name = screen.getByPlaceholderText(translations.enterName); - fireEvent.change(name, { target: { value: '' } }); - - userEvent.type( - screen.getByPlaceholderText(translations.enterName), - 'ActionItemCategory 1 updated', - ); - - userEvent.click(screen.getByTestId('formSubmitButton')); - }); - - await waitFor(() => { - expect(toast.error).toHaveBeenCalled(); - }); - }); - - test('toast error on providing the same name on updation', async () => { - window.location.assign('/orgsetting/123'); - render( - - - - - {} - - - - , - ); - - await wait(); - - await waitFor(() => { - userEvent.click( - screen.getAllByTestId('actionItemCategoryUpdateModalOpenBtn')[0], - ); - - const name = screen.getByPlaceholderText(translations.enterName); - fireEvent.change(name, { target: { value: '' } }); - - userEvent.type( - screen.getByPlaceholderText(translations.enterName), - 'ActionItemCategory 1', - ); - - userEvent.click(screen.getByTestId('formSubmitButton')); - }); - - await waitFor(() => { - expect(toast.error).toBeCalledWith(translations.sameNameConflict); - }); - }); - - test('toggle the disablity status of an action item category', async () => { - window.location.assign('/orgsetting/123'); - render( - - - - - {} - - - - , - ); - - await wait(); - - await waitFor(() => { - userEvent.click(screen.getAllByTestId('disabilityStatusButton')[0]); - }); - - await waitFor(() => { - expect(toast.success).toBeCalledWith(translations.categoryDisabled); - }); - - await waitFor(() => { - userEvent.click(screen.getAllByTestId('disabilityStatusButton')[1]); - }); - - await waitFor(() => { - expect(toast.success).toBeCalledWith(translations.categoryEnabled); - }); - }); - - test('toast error on unsuccessful toggling of the disablity status', async () => { - window.location.assign('/orgsetting/123'); - render( - - - - - {} - - - - , - ); - - await wait(); - - await waitFor(() => { - userEvent.click(screen.getAllByTestId('disabilityStatusButton')[0]); - }); - - await waitFor(() => { - expect(toast.error).toHaveBeenCalled(); - }); - - await waitFor(() => { - userEvent.click(screen.getAllByTestId('disabilityStatusButton')[1]); - }); - - await waitFor(() => { - expect(toast.error).toHaveBeenCalled(); - }); - }); -}); diff --git a/src/components/OrgActionItemCategories/OrgActionItemCategories.tsx b/src/components/OrgActionItemCategories/OrgActionItemCategories.tsx deleted file mode 100644 index 72b1fdd23c..0000000000 --- a/src/components/OrgActionItemCategories/OrgActionItemCategories.tsx +++ /dev/null @@ -1,308 +0,0 @@ -import type { ChangeEvent } from 'react'; -import React, { useState } from 'react'; -import { Button, Form, Modal } from 'react-bootstrap'; -import styles from './OrgActionItemCategories.module.css'; -import { useTranslation } from 'react-i18next'; -import { toast } from 'react-toastify'; -import { WarningAmberRounded } from '@mui/icons-material'; - -import { useMutation, useQuery } from '@apollo/client'; -import { - CREATE_ACTION_ITEM_CATEGORY_MUTATION, - UPDATE_ACTION_ITEM_CATEGORY_MUTATION, -} from 'GraphQl/Mutations/mutations'; -import { ACTION_ITEM_CATEGORY_LIST } from 'GraphQl/Queries/Queries'; -import type { InterfaceActionItemCategoryList } from 'utils/interfaces'; -import Loader from 'components/Loader/Loader'; -import { useParams } from 'react-router-dom'; - -type ModalType = 'Create' | 'Update'; - -/** - * Represents the component for managing organization action item categories. - * This component allows creating, updating, enabling, and disabling action item categories. - */ -const OrgActionItemCategories = (): JSX.Element => { - const { t } = useTranslation('translation', { - keyPrefix: 'orgActionItemCategories', - }); - const { t: tCommon } = useTranslation('common'); - - // State variables - const [modalIsOpen, setModalIsOpen] = useState(false); // Controls modal visibility - const [modalType, setModalType] = useState('Create'); // Type of modal (Create or Update) - const [categoryId, setCategoryId] = useState(''); // Current category ID for updating - const [name, setName] = useState(''); // Category name for creation or update - const [currName, setCurrName] = useState(''); // Current category name (used for comparison) - - // Fetch organization ID from URL params - const { orgId: currentUrl } = useParams(); - - // Query to fetch action item categories - const { - data, - loading, - error, - refetch, - }: { - data: InterfaceActionItemCategoryList | undefined; - loading: boolean; - error?: Error | undefined; - refetch: () => void; - } = useQuery(ACTION_ITEM_CATEGORY_LIST, { - variables: { - organizationId: currentUrl, - }, - notifyOnNetworkStatusChange: true, - }); - - // Mutations for creating and updating categories - const [createActionItemCategory] = useMutation( - CREATE_ACTION_ITEM_CATEGORY_MUTATION, - ); - - const [updateActionItemCategory] = useMutation( - UPDATE_ACTION_ITEM_CATEGORY_MUTATION, - ); - - // Handles category creation - const handleCreate = async ( - e: ChangeEvent, - ): Promise => { - e.preventDefault(); - try { - await createActionItemCategory({ - variables: { - name, - organizationId: currentUrl, - }, - }); - - setName(''); - refetch(); - - setModalIsOpen(false); - - toast.success(t('successfulCreation')); - } catch (error: unknown) { - if (error instanceof Error) { - toast.error(error.message); - console.log(error.message); - } - } - }; - - // Handles category update - const handleEdit = async (e: ChangeEvent): Promise => { - e.preventDefault(); - if (name === currName) { - toast.error(t('sameNameConflict')); // Show error if the name is the same - } else { - try { - await updateActionItemCategory({ - variables: { - actionItemCategoryId: categoryId, - name, - }, - }); - - setName(''); // Clear the name input - setCategoryId(''); // Clear the category ID - refetch(); // Refetch the list of categories - setModalIsOpen(false); // Close the modal - - toast.success(t('successfulUpdation')); // Show success toast - } catch (error: unknown) { - if (error instanceof Error) { - toast.error(error.message); // Show error toast - console.log(error.message); // Log the error - } - } - } - }; - - // Handles enabling or disabling a category - const handleStatusChange = async ( - id: string, - disabledStatus: boolean, - ): Promise => { - try { - await updateActionItemCategory({ - variables: { - actionItemCategoryId: id, - isDisabled: !disabledStatus, - }, - }); - - refetch(); // Refetch the list of categories - - toast.success( - disabledStatus ? t('categoryEnabled') : t('categoryDisabled'), - ); // Show success toast - } catch (error: unknown) { - if (error instanceof Error) { - toast.error(error.message); - console.log(error.message); - } - } - }; - - // Shows the modal for creating a new category - const showCreateModal = (): void => { - setModalType('Create'); - setModalIsOpen(true); - }; - - // Shows the modal for updating an existing category - const showUpdateModal = (name: string, id: string): void => { - setCurrName(name); - setName(name); - setCategoryId(id); - setModalType('Update'); - setModalIsOpen(true); - }; - - // Hides the modal and clears input fields - const hideModal = (): void => { - setName(''); - setCategoryId(''); - setModalIsOpen(false); - }; - - // Show loader while data is being fetched - if (loading) { - return ; - } - - // Show error message if there's an error - if (error) { - return ( -
- -
- Error occured while loading Action Item Categories Data -
- {`${error.message}`} -
-
- ); - } - - // Render the list of action item categories - const actionItemCategories = data?.actionItemCategoriesByOrganization; - - return ( - <> - - -
- {actionItemCategories?.map((category, index) => { - return ( -
-
-
- {category.name} -
-
- - -
-
- - {index !== actionItemCategories.length - 1 &&
} -
- ); - })} -
- - {/* Modal for creating or updating categories */} - - -

- {t('actionItemCategoryDetails')} -

- -
- -
- - {t('actionItemCategoryName')} - - { - setName(e.target.value); - }} - /> - - -
-
- - ); -}; - -export default OrgActionItemCategories; diff --git a/src/components/OrgActionItemCategories/OrgActionItemCategoryMocks.ts b/src/components/OrgActionItemCategories/OrgActionItemCategoryMocks.ts deleted file mode 100644 index b03e53c5f5..0000000000 --- a/src/components/OrgActionItemCategories/OrgActionItemCategoryMocks.ts +++ /dev/null @@ -1,174 +0,0 @@ -import { - CREATE_ACTION_ITEM_CATEGORY_MUTATION, - UPDATE_ACTION_ITEM_CATEGORY_MUTATION, -} from 'GraphQl/Mutations/mutations'; - -import { ACTION_ITEM_CATEGORY_LIST } from 'GraphQl/Queries/Queries'; - -export const MOCKS = [ - { - request: { - query: ACTION_ITEM_CATEGORY_LIST, - variables: { organizationId: '123' }, - }, - result: { - data: { - actionItemCategoriesByOrganization: [ - { - _id: '1', - name: 'ActionItemCategory 1', - isDisabled: false, - }, - { - _id: '2', - name: 'ActionItemCategory 2', - isDisabled: true, - }, - { - _id: '3', - name: 'ActionItemCategory 3', - isDisabled: false, - }, - ], - }, - }, - }, - { - request: { - query: CREATE_ACTION_ITEM_CATEGORY_MUTATION, - variables: { name: 'ActionItemCategory 4', organizationId: '123' }, - }, - result: { - data: { - createActionItemCategory: { - _id: '4', - }, - }, - }, - }, - { - request: { - query: UPDATE_ACTION_ITEM_CATEGORY_MUTATION, - variables: { - name: 'ActionItemCategory 1 updated', - actionItemCategoryId: '1', - }, - }, - result: { - data: { - updateActionItemCategory: { - _id: '1', - }, - }, - }, - }, - { - request: { - query: UPDATE_ACTION_ITEM_CATEGORY_MUTATION, - variables: { - isDisabled: true, - actionItemCategoryId: '1', - }, - }, - result: { - data: { - updateActionItemCategory: { - _id: '1', - }, - }, - }, - }, - { - request: { - query: UPDATE_ACTION_ITEM_CATEGORY_MUTATION, - variables: { - isDisabled: false, - actionItemCategoryId: '2', - }, - }, - result: { - data: { - updateActionItemCategory: { - _id: '2', - }, - }, - }, - }, -]; - -export const MOCKS_ERROR_QUERY = [ - { - request: { - query: ACTION_ITEM_CATEGORY_LIST, - variables: { organizationId: '123' }, - }, - error: new Error('Mock Graphql Error'), - }, -]; - -export const MOCKS_ERROR_MUTATIONS = [ - { - request: { - query: ACTION_ITEM_CATEGORY_LIST, - variables: { organizationId: '123' }, - }, - result: { - data: { - actionItemCategoriesByOrganization: [ - { - _id: '1', - name: 'ActionItemCategory 1', - isDisabled: false, - }, - { - _id: '2', - name: 'ActionItemCategory 2', - isDisabled: true, - }, - { - _id: '3', - name: 'ActionItemCategory 3', - isDisabled: false, - }, - ], - }, - }, - }, - { - request: { - query: CREATE_ACTION_ITEM_CATEGORY_MUTATION, - variables: { name: 'ActionItemCategory 4', organizationId: '123' }, - }, - error: new Error('Mock Graphql Error'), - }, - { - request: { - query: UPDATE_ACTION_ITEM_CATEGORY_MUTATION, - variables: { - name: 'ActionItemCategory 1 updated', - actionItemCategoryId: '1', - }, - }, - error: new Error('Mock Graphql Error'), - }, - { - request: { - query: UPDATE_ACTION_ITEM_CATEGORY_MUTATION, - variables: { - isDisabled: true, - actionItemCategoryId: '1', - }, - }, - error: new Error('Mock Graphql Error'), - }, - { - request: { - query: UPDATE_ACTION_ITEM_CATEGORY_MUTATION, - variables: { - isDisabled: false, - actionItemCategoryId: '2', - }, - }, - error: new Error('Mock Graphql Error'), - }, -]; diff --git a/src/components/OrgAdminListCard/OrgAdminListCard.test.tsx b/src/components/OrgAdminListCard/OrgAdminListCard.test.tsx index 282dedb330..7baea946d2 100644 --- a/src/components/OrgAdminListCard/OrgAdminListCard.test.tsx +++ b/src/components/OrgAdminListCard/OrgAdminListCard.test.tsx @@ -1,5 +1,5 @@ -import React from 'react'; -import { act, render, screen, waitFor } from '@testing-library/react'; +import React, { act } from 'react'; +import { render, screen, waitFor } from '@testing-library/react'; import type { RenderResult } from '@testing-library/react'; import { MockedProvider } from '@apollo/react-testing'; import userEvent from '@testing-library/user-event'; diff --git a/src/components/OrgAdminListCard/OrgAdminListCard.tsx b/src/components/OrgAdminListCard/OrgAdminListCard.tsx index 418592819f..f8fc454823 100644 --- a/src/components/OrgAdminListCard/OrgAdminListCard.tsx +++ b/src/components/OrgAdminListCard/OrgAdminListCard.tsx @@ -44,7 +44,7 @@ function orgAdminListCard(props: InterfaceOrgPeopleListCardProps): JSX.Element { }, }); if (data) { - toast.success(t('adminRemoved')); + toast.success(t('adminRemoved') as string); setTimeout(() => { window.location.reload(); }, 2000); diff --git a/src/components/OrgListCard/OrgListCard.test.tsx b/src/components/OrgListCard/OrgListCard.test.tsx index 25d7f01ed1..4072265ea4 100644 --- a/src/components/OrgListCard/OrgListCard.test.tsx +++ b/src/components/OrgListCard/OrgListCard.test.tsx @@ -1,5 +1,5 @@ -import React from 'react'; -import { act, render, screen } from '@testing-library/react'; +import React, { act } from 'react'; +import { render, screen } from '@testing-library/react'; import 'jest-location-mock'; import { I18nextProvider } from 'react-i18next'; diff --git a/src/components/OrgPeopleListCard/OrgPeopleListCard.test.tsx b/src/components/OrgPeopleListCard/OrgPeopleListCard.test.tsx index 57a6e0265f..7cee31107f 100644 --- a/src/components/OrgPeopleListCard/OrgPeopleListCard.test.tsx +++ b/src/components/OrgPeopleListCard/OrgPeopleListCard.test.tsx @@ -1,5 +1,5 @@ -import React from 'react'; -import { act, render, screen } from '@testing-library/react'; +import React, { act } from 'react'; +import { render, screen } from '@testing-library/react'; import { MockedProvider } from '@apollo/react-testing'; import userEvent from '@testing-library/user-event'; import { I18nextProvider } from 'react-i18next'; diff --git a/src/components/OrgPeopleListCard/OrgPeopleListCard.tsx b/src/components/OrgPeopleListCard/OrgPeopleListCard.tsx index f22abc2c01..d24773c02a 100644 --- a/src/components/OrgPeopleListCard/OrgPeopleListCard.tsx +++ b/src/components/OrgPeopleListCard/OrgPeopleListCard.tsx @@ -56,7 +56,7 @@ function orgPeopleListCard( // If the mutation is successful, show a success message and reload the page /* istanbul ignore next */ if (data) { - toast.success(t('memberRemoved')); + toast.success(t('memberRemoved') as string); setTimeout(() => { window.location.reload(); }, 2000); diff --git a/src/components/OrgPostCard/OrgPostCard.test.tsx b/src/components/OrgPostCard/OrgPostCard.test.tsx index 863ed2822b..7105e5e8f2 100644 --- a/src/components/OrgPostCard/OrgPostCard.test.tsx +++ b/src/components/OrgPostCard/OrgPostCard.test.tsx @@ -112,8 +112,8 @@ describe('Testing Organization Post Card', () => { }); const props = { - key: '123', id: '12', + postID: '123', postTitle: 'Event Info', postInfo: 'Time change', postAuthor: 'John Doe', @@ -135,6 +135,20 @@ describe('Testing Organization Post Card', () => { })); global.alert = jest.fn(); + test('Opens post on image click', () => { + const { getByTestId, getByAltText } = render( + + + + + , + ); + userEvent.click(screen.getByAltText('image')); + + expect(getByTestId('card-text')).toBeInTheDocument(); + expect(getByTestId('card-title')).toBeInTheDocument(); + expect(getByAltText('image')).toBeInTheDocument(); + }); test('renders with default props', () => { const { getByAltText, getByTestId } = render( @@ -147,7 +161,6 @@ describe('Testing Organization Post Card', () => { expect(getByTestId('card-title')).toBeInTheDocument(); expect(getByAltText('image')).toBeInTheDocument(); }); - test('toggles "Read more" button', () => { const { getByTestId } = render( @@ -265,6 +278,84 @@ describe('Testing Organization Post Card', () => { { timeout: 2500 }, ); }); + test('Testing post updating functionality fail case', async () => { + const props2 = { + id: '', + postID: '123', + postTitle: 'Event Info', + postInfo: 'Time change', + postAuthor: 'John Doe', + postPhoto: 'test.png', + postVideo: 'test.mp4', + pinned: true, + }; + const { getByTestId } = render( + + + + + , + ); + + await wait(); + userEvent.click(screen.getByAltText('image')); + userEvent.click(screen.getByTestId('moreiconbtn')); + + userEvent.click(screen.getByTestId('editPostModalBtn')); + fireEvent.change(getByTestId('updateTitle'), { + target: { value: 'updated title' }, + }); + fireEvent.change(getByTestId('updateText'), { + target: { value: 'This is a updated text' }, + }); + const postVideoUrlInput = screen.queryByTestId('postVideoUrl'); + if (postVideoUrlInput) { + fireEvent.change(getByTestId('postVideoUrl'), { + target: { value: 'This is a updated video' }, + }); + userEvent.click(screen.getByPlaceholderText(/video/i)); + const input = getByTestId('postVideoUrl'); + const file = new File(['test-video'], 'test.mp4', { type: 'video/mp4' }); + Object.defineProperty(input, 'files', { + value: [file], + }); + fireEvent.change(input); + await waitFor(() => { + convertToBase64(file); + }); + + userEvent.click(screen.getByTestId('closePreview')); + } + const imageUrlInput = screen.queryByTestId('postImageUrl'); + if (imageUrlInput) { + fireEvent.change(getByTestId('postImageUrl'), { + target: { value: 'This is a updated image' }, + }); + userEvent.click(screen.getByPlaceholderText(/image/i)); + const input = getByTestId('postImageUrl'); + const file = new File(['test-image'], 'test.jpg', { type: 'image/jpeg' }); + Object.defineProperty(input, 'files', { + value: [file], + }); + fireEvent.change(input); + + // Simulate the asynchronous base64 conversion function + await waitFor(() => { + convertToBase64(file); // Replace with the expected base64-encoded image + }); + document.getElementById = jest.fn(() => input); + const clearImageButton = getByTestId('closeimage'); + fireEvent.click(clearImageButton); + } + userEvent.click(screen.getByTestId('updatePostBtn')); + + await waitFor( + () => { + expect(window.location.reload).toHaveBeenCalled(); + }, + { timeout: 2500 }, + ); + }); test('Testing pin post functionality', async () => { render( @@ -289,8 +380,8 @@ describe('Testing Organization Post Card', () => { }); test('Testing pin post functionality fail case', async () => { const props2 = { - key: '123', id: '', + postID: '123', postTitle: 'Event Info', postInfo: 'Time change', postAuthor: 'John Doe', @@ -340,8 +431,8 @@ describe('Testing Organization Post Card', () => { }); test('Testing post delete functionality fail case', async () => { const props2 = { - key: '123', id: '', + postID: '123', postTitle: 'Event Info', postInfo: 'Time change', postAuthor: 'John Doe', @@ -380,6 +471,9 @@ describe('Testing Organization Post Card', () => { userEvent.click(screen.getByAltText('image')); userEvent.click(screen.getByTestId('closeiconbtn')); + + //Primary Modal is closed + expect(screen.queryByTestId('moreiconbtn')).not.toBeInTheDocument(); }); test('Testing close functionality of secondary modal', async () => { render( @@ -395,13 +489,30 @@ describe('Testing Organization Post Card', () => { userEvent.click(screen.getByAltText('image')); userEvent.click(screen.getByTestId('moreiconbtn')); userEvent.click(screen.getByTestId('closebtn')); + + //Secondary Modal is closed + expect(screen.queryByTestId('deletePostModalBtn')).not.toBeInTheDocument(); + expect(screen.queryByTestId('editPostModalBtn')).not.toBeInTheDocument(); + expect(screen.queryByTestId('pinpostBtn')).not.toBeInTheDocument(); + expect(screen.queryByTestId('closebtn')).not.toBeInTheDocument(); }); test('renders without "Read more" button when postInfo length is less than or equal to 43', () => { + render( + + + + + , + ); + expect(screen.queryByTestId('toggleBtn')).not.toBeInTheDocument(); + }); + test('renders with "Read more" button when postInfo length is more than 43', () => { const props2 = { - key: '123', id: '12', + postID: '123', postTitle: 'Event Info', - postInfo: 'Lorem ipsum dolor sit amet', + postInfo: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus lacinia odio vitae vestibulum.', // Length is greater than 43 postAuthor: 'John Doe', postPhoto: 'photoLink', postVideo: 'videoLink', @@ -414,6 +525,9 @@ describe('Testing Organization Post Card', () => {
, ); + userEvent.click(screen.getByAltText('image')); + + expect(screen.getByTestId('toggleBtn')).toBeInTheDocument(); }); test('updates state variables correctly when handleEditModal is called', () => { const link2 = new StaticMockLink(MOCKS, true); @@ -430,17 +544,42 @@ describe('Testing Organization Post Card', () => { userEvent.click(screen.getByTestId('editPostModalBtn')); - // expect(screen.queryByTestId('editPostModalBtn')).toBeInTheDocument(); - // expect(screen.queryByTestId('deletePostModalBtn')).not.toBeInTheDocument(); - // expect(screen.queryByTestId('closeiconbtn')).not.toBeInTheDocument(); + //Primary Modal is closed + expect(screen.queryByTestId('closeiconbtn')).not.toBeInTheDocument(); + expect(screen.queryByTestId('moreiconbtn')).not.toBeInTheDocument(); + expect(screen.queryByTestId('toggleBtn')).not.toBeInTheDocument(); + + //Secondary Modal is closed + expect(screen.queryByTestId('deletePostModalBtn')).not.toBeInTheDocument(); + expect(screen.queryByTestId('editPostModalBtn')).not.toBeInTheDocument(); + expect(screen.queryByTestId('pinpostBtn')).not.toBeInTheDocument(); + expect(screen.queryByTestId('closebtn')).not.toBeInTheDocument(); + }); + test('updates state variables correctly when handleDeleteModal is called', () => { + const link2 = new StaticMockLink(MOCKS, true); + render( + + + , + ); + userEvent.click(screen.getByAltText('image')); + + userEvent.click(screen.getByTestId('moreiconbtn')); + + expect(screen.queryByTestId('deletePostModalBtn')).toBeInTheDocument(); + + userEvent.click(screen.getByTestId('deletePostModalBtn')); - // expect(screen.getByTestId('editPostModal')).toHaveClass('show'); - // expect(screen.getByTestId('deletePostModal')).not.toHaveClass('show'); + //Primary Modal is closed + expect(screen.queryByTestId('closeiconbtn')).not.toBeInTheDocument(); + expect(screen.queryByTestId('moreiconbtn')).not.toBeInTheDocument(); + expect(screen.queryByTestId('toggleBtn')).not.toBeInTheDocument(); - // expect(screen.getByTestId('modalVisible')).toBe('false'); - // expect(screen.getByTestId('menuVisible')).toBe('false'); - // expect(screen.getByTestId('showEditModal')).toBe('true'); - // expect(screen.getByTestId('showDeleteModal')).toBe('false'); + //Secondary Modal is closed + expect(screen.queryByTestId('deletePostModalBtn')).not.toBeInTheDocument(); + expect(screen.queryByTestId('editPostModalBtn')).not.toBeInTheDocument(); + expect(screen.queryByTestId('pinpostBtn')).not.toBeInTheDocument(); + expect(screen.queryByTestId('closebtn')).not.toBeInTheDocument(); }); test('clears postvideo state and resets file input value', async () => { const { getByTestId } = render( @@ -513,6 +652,56 @@ describe('Testing Organization Post Card', () => { fireEvent.click(clearImageButton); } }); + test('clears postitle state and resets file input value', async () => { + const { getByTestId } = render( + + + + + , + ); + + await wait(); + + userEvent.click(screen.getByAltText('image')); + userEvent.click(screen.getByTestId('moreiconbtn')); + userEvent.click(screen.getByTestId('editPostModalBtn')); + + fireEvent.change(getByTestId('updateTitle'), { + target: { value: '' }, + }); + + userEvent.click(screen.getByTestId('updatePostBtn')); // Should not update post + + expect(screen.getByTestId('updateTitle')).toHaveValue(''); + expect(screen.getByTestId('closeOrganizationModal')).toBeInTheDocument(); + expect(screen.getByTestId('updatePostBtn')).toBeInTheDocument(); + }); + test('clears postinfo state and resets file input value', async () => { + const { getByTestId } = render( + + + + + , + ); + + await wait(); + + userEvent.click(screen.getByAltText('image')); + userEvent.click(screen.getByTestId('moreiconbtn')); + userEvent.click(screen.getByTestId('editPostModalBtn')); + + fireEvent.change(getByTestId('updateText'), { + target: { value: '' }, + }); + + userEvent.click(screen.getByTestId('updatePostBtn')); // Should not update post + + expect(screen.getByTestId('updateText')).toHaveValue(''); + expect(screen.getByTestId('closeOrganizationModal')).toBeInTheDocument(); + expect(screen.getByTestId('updatePostBtn')).toBeInTheDocument(); + }); test('Testing create organization modal', async () => { setItem('id', '123'); @@ -576,8 +765,8 @@ describe('Testing Organization Post Card', () => { }); test('for rendering when no image and no video is available', async () => { const props2 = { - key: '123', id: '', + postID: '123', postTitle: 'Event Info', postInfo: 'Time change', postAuthor: 'John Doe', diff --git a/src/components/OrgPostCard/OrgPostCard.tsx b/src/components/OrgPostCard/OrgPostCard.tsx index be71622c7c..b7cc419d12 100644 --- a/src/components/OrgPostCard/OrgPostCard.tsx +++ b/src/components/OrgPostCard/OrgPostCard.tsx @@ -16,7 +16,7 @@ import { errorHandler } from 'utils/errorHandler'; import type { InterfacePostForm } from 'utils/interfaces'; import styles from './OrgPostCard.module.css'; interface InterfaceOrgPostCardProps { - key: string; + postID: string; id: string; postTitle: string; postInfo: string; @@ -25,9 +25,13 @@ interface InterfaceOrgPostCardProps { postVideo: string | null; pinned: boolean; } -export default function orgPostCard( +export default function OrgPostCard( props: InterfaceOrgPostCardProps, ): JSX.Element { + const { + postID, // Destructure the key prop from props + // ...rest // Spread the rest of the props + } = props; const [postformState, setPostFormState] = useState({ posttitle: '', postinfo: '', @@ -68,12 +72,13 @@ export default function orgPostCard( } }; const toggleShowEditModal = (): void => { + const { postTitle, postInfo, postPhoto, postVideo, pinned } = props; setPostFormState({ - posttitle: props.postTitle, - postinfo: props.postInfo, - postphoto: props.postPhoto, - postvideo: props.postVideo, - pinned: props.pinned, + posttitle: postTitle, + postinfo: postInfo, + postphoto: postPhoto, + postvideo: postVideo, + pinned: pinned, }); setPostPhotoUpdated(false); setPostVideoUpdated(false); @@ -128,13 +133,14 @@ export default function orgPostCard( } } function handleEditModal(): void { + const { postPhoto, postVideo } = props; setModalVisible(false); setMenuVisible(false); setShowEditModal(true); setPostFormState({ ...postformState, - postphoto: props.postPhoto, - postvideo: props.postVideo, + postphoto: postPhoto, + postvideo: postVideo, }); } function handleDeleteModal(): void { @@ -165,7 +171,7 @@ export default function orgPostCard( }, }); if (data) { - toast.success(t('postDeleted')); + toast.success(t('postDeleted') as string); toggleShowDeleteModal(); setTimeout(() => { window.location.reload(); @@ -203,7 +209,7 @@ export default function orgPostCard( }, }); if (data) { - toast.success(t('postUpdated')); + toast.success(t('postUpdated') as string); setTimeout(() => { window.location.reload(); }, 2000); @@ -216,7 +222,11 @@ export default function orgPostCard( }; return ( <> -
+
({ + toast: { + success: jest.fn(), + error: jest.fn(), + }, +})); + +const link1 = new StaticMockLink(MOCKS); +const link3 = new StaticMockLink(MOCKS_ERROR); +const translations = { + ...JSON.parse( + JSON.stringify( + i18n.getDataByLanguage('en')?.translation.orgActionItemCategories ?? {}, + ), + ), + ...JSON.parse(JSON.stringify(i18n.getDataByLanguage('en')?.common ?? {})), + ...JSON.parse(JSON.stringify(i18n.getDataByLanguage('en')?.errors ?? {})), +}; + +const categoryProps: InterfaceActionItemCategoryModal[] = [ + { + isOpen: true, + hide: jest.fn(), + refetchCategories: jest.fn(), + orgId: 'orgId', + mode: 'create', + category: { + _id: 'categoryId', + name: 'Category 1', + isDisabled: false, + createdAt: '2044-01-01', + creator: { _id: 'userId', firstName: 'John', lastName: 'Doe' }, + }, + }, + { + isOpen: true, + hide: jest.fn(), + refetchCategories: jest.fn(), + orgId: 'orgId', + mode: 'edit', + category: { + _id: 'categoryId', + name: 'Category 1', + isDisabled: false, + createdAt: '2044-01-01', + creator: { _id: 'userId', firstName: 'John', lastName: 'Doe' }, + }, + }, +]; + +const renderCategoryModal = ( + link: ApolloLink, + props: InterfaceActionItemCategoryModal, +): RenderResult => { + return render( + + + + + + + + + , + ); +}; + +const fillFormAndSubmit = async ( + name: string, + isDisabled: boolean, +): Promise => { + const nameInput = screen.getByLabelText('Name *'); + const isDisabledSwitch = screen.getByTestId('isDisabledSwitch'); + const submitBtn = screen.getByTestId('formSubmitButton'); + + fireEvent.change(nameInput, { target: { value: name } }); + if (isDisabled) { + userEvent.click(isDisabledSwitch); + } + userEvent.click(submitBtn); +}; + +describe('Testing Action Item Category Modal', () => { + it('should populate form fields with correct values in edit mode', async () => { + renderCategoryModal(link1, categoryProps[1]); + await waitFor(() => + expect( + screen.getByText(translations.categoryDetails), + ).toBeInTheDocument(), + ); + + expect(screen.getByLabelText('Name *')).toHaveValue('Category 1'); + expect(screen.getByTestId('isDisabledSwitch')).not.toBeChecked(); + }); + + it('should update name when input value changes', async () => { + renderCategoryModal(link1, categoryProps[1]); + const nameInput = screen.getByLabelText('Name *'); + expect(nameInput).toHaveValue('Category 1'); + fireEvent.change(nameInput, { target: { value: 'Category 2' } }); + expect(nameInput).toHaveValue('Category 2'); + }); + + it('should update isDisabled when switch is toggled', async () => { + renderCategoryModal(link1, categoryProps[1]); + const isDisabledSwitch = screen.getByTestId('isDisabledSwitch'); + expect(isDisabledSwitch).not.toBeChecked(); + userEvent.click(isDisabledSwitch); + expect(isDisabledSwitch).toBeChecked(); + }); + + it('should edit category', async () => { + renderCategoryModal(link1, categoryProps[1]); + await fillFormAndSubmit('Category 2', true); + + await waitFor(() => { + expect(categoryProps[1].refetchCategories).toHaveBeenCalled(); + expect(categoryProps[1].hide).toHaveBeenCalled(); + expect(toast.success).toHaveBeenCalledWith( + translations.successfulUpdation, + ); + }); + }); + + it('Edit only Name', async () => { + renderCategoryModal(link1, categoryProps[1]); + await fillFormAndSubmit('Category 2', false); + + await waitFor(() => { + expect(categoryProps[1].refetchCategories).toHaveBeenCalled(); + expect(categoryProps[1].hide).toHaveBeenCalled(); + expect(toast.success).toHaveBeenCalledWith( + translations.successfulUpdation, + ); + }); + }); + + it('Edit only isDisabled', async () => { + renderCategoryModal(link1, categoryProps[1]); + await fillFormAndSubmit('Category 1', true); + + await waitFor(() => { + expect(categoryProps[1].refetchCategories).toHaveBeenCalled(); + expect(categoryProps[1].hide).toHaveBeenCalled(); + expect(toast.success).toHaveBeenCalledWith( + translations.successfulUpdation, + ); + }); + }); + + it('Error in updating category', async () => { + renderCategoryModal(link3, categoryProps[1]); + await fillFormAndSubmit('Category 2', true); + + await waitFor(() => { + expect(toast.error).toHaveBeenCalledWith('Mock Graphql Error'); + }); + }); + + it('should create category', async () => { + renderCategoryModal(link1, categoryProps[0]); + await fillFormAndSubmit('Category 2', true); + + await waitFor(() => { + expect(categoryProps[0].refetchCategories).toHaveBeenCalled(); + expect(categoryProps[0].hide).toHaveBeenCalled(); + expect(toast.success).toHaveBeenCalledWith( + translations.successfulCreation, + ); + }); + }); + + it('Error in creating category', async () => { + renderCategoryModal(link3, categoryProps[0]); + await fillFormAndSubmit('Category 2', true); + + await waitFor(() => { + expect(toast.error).toHaveBeenCalledWith('Mock Graphql Error'); + }); + }); + + it('Try to edit without changing any field', async () => { + renderCategoryModal(link1, categoryProps[1]); + const submitBtn = screen.getByTestId('formSubmitButton'); + userEvent.click(submitBtn); + + await waitFor(() => { + expect(toast.error).toHaveBeenCalledWith(translations.sameNameConflict); + }); + }); +}); diff --git a/src/components/OrgSettings/ActionItemCategories/CategoryModal.tsx b/src/components/OrgSettings/ActionItemCategories/CategoryModal.tsx new file mode 100644 index 0000000000..43018db0ab --- /dev/null +++ b/src/components/OrgSettings/ActionItemCategories/CategoryModal.tsx @@ -0,0 +1,208 @@ +import React, { type ChangeEvent, type FC, useEffect, useState } from 'react'; +import { Button, Form, Modal } from 'react-bootstrap'; +import styles from './OrgActionItemCategories.module.css'; +import { useTranslation } from 'react-i18next'; +import type { InterfaceActionItemCategoryInfo } from 'utils/interfaces'; +import { useMutation } from '@apollo/client'; +import { + CREATE_ACTION_ITEM_CATEGORY_MUTATION, + UPDATE_ACTION_ITEM_CATEGORY_MUTATION, +} from 'GraphQl/Mutations/ActionItemCategoryMutations'; +import { toast } from 'react-toastify'; +import { FormControl, TextField } from '@mui/material'; + +/** + * Props for the `CategoryModal` component. + * + * + * isOpen - The state of the modal. + * hide - The function to hide the modal. + * refetchCategories - The function to refetch the categories. + * orgId - The organization ID. + * category - The category to be edited. + * mode - The mode of the modal. + * @returns The `CategoryModal` component. + */ +export interface InterfaceActionItemCategoryModal { + isOpen: boolean; + hide: () => void; + refetchCategories: () => void; + orgId: string; + category: InterfaceActionItemCategoryInfo | null; + mode: 'create' | 'edit'; +} + +/** + * A modal component for creating and editing action item categories. + * + * @param props - The properties passed to the component. + * @returns The `CategoryModal` component. + */ +const CategoryModal: FC = ({ + category, + hide, + isOpen, + mode, + refetchCategories, + orgId, +}) => { + const { t: tCommon } = useTranslation('common'); + const { t } = useTranslation('translation', { + keyPrefix: 'orgActionItemCategories', + }); + + const [formState, setFormState] = useState({ + name: category?.name ?? '', + isDisabled: category?.isDisabled ?? false, + }); + + const { name, isDisabled } = formState; + + useEffect(() => { + setFormState({ + name: category?.name ?? '', + isDisabled: category?.isDisabled ?? false, + }); + }, [category]); + + // Mutations for creating and updating categories + const [createActionItemCategory] = useMutation( + CREATE_ACTION_ITEM_CATEGORY_MUTATION, + ); + + const [updateActionItemCategory] = useMutation( + UPDATE_ACTION_ITEM_CATEGORY_MUTATION, + ); + + /** + * Handles category creation. + * + * @param e - The form submission event. + */ + const handleCreate = async ( + e: ChangeEvent, + ): Promise => { + e.preventDefault(); + try { + await createActionItemCategory({ + variables: { + name, + isDisabled, + organizationId: orgId, + }, + }); + + refetchCategories(); + hide(); + toast.success(t('successfulCreation')); + } catch (error: unknown) { + toast.error((error as Error).message); + } + }; + + /** + * Handles category update. + * + * @param e - The form submission event. + */ + const handleEdit = async (e: ChangeEvent): Promise => { + e.preventDefault(); + if (name === category?.name && isDisabled === category?.isDisabled) { + toast.error(t('sameNameConflict')); // Show error if the name is the same + } else { + try { + const updatedFields: { [key: string]: string | boolean } = {}; + if (name != category?.name) { + updatedFields.name = name; + } + if (isDisabled != category?.isDisabled) { + updatedFields.isDisabled = isDisabled; + } + + await updateActionItemCategory({ + variables: { + actionItemCategoryId: category?._id, + ...updatedFields, + }, + }); + + setFormState({ + name: '', + isDisabled: false, + }); + refetchCategories(); + hide(); + toast.success(t('successfulUpdation')); + } catch (error: unknown) { + toast.error((error as Error).message); + } + } + }; + + return ( + + +

{t('categoryDetails')}

+ +
+ +
+ {/* Input field to enter amount to be pledged */} + + + + setFormState({ ...formState, name: e.target.value }) + } + required + /> + + + + + setFormState({ + ...formState, + isDisabled: !isDisabled, + }) + } + /> + + + +
+
+
+ ); +}; + +export default CategoryModal; diff --git a/src/components/OrgSettings/ActionItemCategories/OrgActionItemCategories.module.css b/src/components/OrgSettings/ActionItemCategories/OrgActionItemCategories.module.css new file mode 100644 index 0000000000..919421b0f2 --- /dev/null +++ b/src/components/OrgSettings/ActionItemCategories/OrgActionItemCategories.module.css @@ -0,0 +1,138 @@ +/* Button Styles */ +.addButton { + /* Position and size of the button */ + width: 7em; + position: absolute; + right: 1rem; + top: 1rem; +} + +/* Modal Styles */ +.createModal { + /* Position and size of the modal */ + margin-top: 20vh; + margin-left: 13vw; + max-width: 80vw; +} + +.icon { + /* Size and color of the icon */ + transform: scale(1.5); + color: var(--bs-danger); + margin-bottom: 1rem; +} + +.message { + /* Centering the content of the modal */ + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; +} + +.titlemodal { + /* Styling for the modal title */ + color: #707070; + font-weight: 600; + font-size: 32px; + width: 65%; + margin-bottom: 0px; +} + +.modalCloseBtn { + /* Styling for the modal close button */ + width: 40px; + height: 40px; + padding: 1rem; + display: flex; + justify-content: center; + align-items: center; +} + +/* Input Styles */ +.noOutline input { + /* Removing the outline from the input */ + outline: none; +} + +/* Header and Action Item Categories Styles */ +.btnsContainer { + /* Styling for the container of the buttons */ + display: flex; + margin: 0.5rem 0 1.5rem 0; +} + +.btnsContainer .input { + /* Styling for the input field */ + flex: 1; + min-width: 18rem; + position: relative; +} + +.btnsContainer input { + /* Styling for the input border */ + outline: 1px solid var(--bs-gray-400); +} + +.btnsContainer .input button { + /* Styling for the button in the input field */ + width: 52px; +} + +.inputField { + margin-top: 10px; + margin-bottom: 10px; + background-color: white; + box-shadow: 0 1px 1px #31bb6b; +} + +.inputField > button { + padding-top: 10px; + padding-bottom: 10px; +} + +/* Dropdown Styles */ +.dropdown { + /* Styling for the dropdown */ + background-color: white; + border: 1px solid #31bb6b; + position: relative; + display: inline-block; + color: #31bb6b; +} + +/* Datagrid Styles */ +.rowBackground { + /* Styling for the row background */ + background-color: var(--bs-white); + max-height: 120px; +} + +.tableHeader { + /* Styling for the table header */ + background-color: var(--bs-primary); + color: var(--bs-white); + font-size: 1rem; +} + +.chipIcon { + /* Styling for the chip icon */ + height: 0.9rem !important; +} + +.chip { + /* Styling for the chip */ + height: 1.5rem !important; +} + +.active { + /* Styling for the active state */ + background-color: #31bb6a50 !important; +} + +.pending { + /* Styling for the pending state */ + background-color: #ffd76950 !important; + color: #bb952bd0 !important; + border-color: #bb952bd0 !important; +} diff --git a/src/components/OrgSettings/ActionItemCategories/OrgActionItemCategories.test.tsx b/src/components/OrgSettings/ActionItemCategories/OrgActionItemCategories.test.tsx new file mode 100644 index 0000000000..d3698bf346 --- /dev/null +++ b/src/components/OrgSettings/ActionItemCategories/OrgActionItemCategories.test.tsx @@ -0,0 +1,241 @@ +import React from 'react'; +import { MockedProvider } from '@apollo/react-testing'; +import type { RenderResult } from '@testing-library/react'; +import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { I18nextProvider } from 'react-i18next'; +import { Provider } from 'react-redux'; +import { BrowserRouter } from 'react-router-dom'; +import { store } from 'state/store'; +import { StaticMockLink } from 'utils/StaticMockLink'; +import i18n from 'utils/i18nForTest'; +import type { ApolloLink } from '@apollo/client'; +import { MOCKS, MOCKS_EMPTY, MOCKS_ERROR } from './OrgActionItemCategoryMocks'; +import OrgActionItemCategories from './OrgActionItemCategories'; + +jest.mock('react-toastify', () => ({ + toast: { + success: jest.fn(), + error: jest.fn(), + }, +})); + +jest.mock('@mui/x-date-pickers/DateTimePicker', () => { + return { + DateTimePicker: jest.requireActual( + '@mui/x-date-pickers/DesktopDateTimePicker', + ).DesktopDateTimePicker, + }; +}); + +const link1 = new StaticMockLink(MOCKS); +const link2 = new StaticMockLink(MOCKS_EMPTY); +const link3 = new StaticMockLink(MOCKS_ERROR); +const t = { + ...JSON.parse( + JSON.stringify( + i18n.getDataByLanguage('en')?.translation.orgActionItemCategories ?? {}, + ), + ), + ...JSON.parse(JSON.stringify(i18n.getDataByLanguage('en')?.common ?? {})), + ...JSON.parse(JSON.stringify(i18n.getDataByLanguage('en')?.errors ?? {})), +}; + +const renderActionItemCategories = ( + link: ApolloLink, + orgId: string, +): RenderResult => { + return render( + + + + + + + + + , + ); +}; + +describe('Testing Organisation Action Item Categories', () => { + it('should render the Action Item Categories Screen', async () => { + renderActionItemCategories(link1, 'orgId'); + await waitFor(() => { + expect(screen.getByTestId('searchByName')).toBeInTheDocument(); + expect(screen.getByText('Category 1')).toBeInTheDocument(); + expect(screen.getByText('Category 2')).toBeInTheDocument(); + }); + }); + + it('Sort the Categories (asc/desc) by createdAt', async () => { + renderActionItemCategories(link1, 'orgId'); + + const sortBtn = await screen.findByTestId('sort'); + expect(sortBtn).toBeInTheDocument(); + + // Sort by createdAt_DESC + fireEvent.click(sortBtn); + await waitFor(() => { + expect(screen.getByTestId('createdAt_DESC')).toBeInTheDocument(); + }); + fireEvent.click(screen.getByTestId('createdAt_DESC')); + await waitFor(() => { + expect(screen.getAllByTestId('categoryName')[0]).toHaveTextContent( + 'Category 1', + ); + }); + + // Sort by createdAt_ASC + fireEvent.click(sortBtn); + await waitFor(() => { + expect(screen.getByTestId('createdAt_ASC')).toBeInTheDocument(); + }); + fireEvent.click(screen.getByTestId('createdAt_ASC')); + await waitFor(() => { + expect(screen.getAllByTestId('categoryName')[0]).toHaveTextContent( + 'Category 2', + ); + }); + }); + + it('Filter the categories by status (All/Disabled)', async () => { + renderActionItemCategories(link1, 'orgId'); + + const filterBtn = await screen.findByTestId('filter'); + expect(filterBtn).toBeInTheDocument(); + + // Filter by All + fireEvent.click(filterBtn); + await waitFor(() => { + expect(screen.getByTestId('statusAll')).toBeInTheDocument(); + }); + fireEvent.click(screen.getByTestId('statusAll')); + + await waitFor(() => { + expect(screen.getByText('Category 1')).toBeInTheDocument(); + expect(screen.getByText('Category 2')).toBeInTheDocument(); + }); + + // Filter by Disabled + fireEvent.click(filterBtn); + await waitFor(() => { + expect(screen.getByTestId('statusDisabled')).toBeInTheDocument(); + }); + fireEvent.click(screen.getByTestId('statusDisabled')); + await waitFor(() => { + expect(screen.queryByText('Category 1')).toBeNull(); + expect(screen.getByText('Category 2')).toBeInTheDocument(); + }); + }); + + it('Filter the categories by status (Active)', async () => { + renderActionItemCategories(link1, 'orgId'); + + const filterBtn = await screen.findByTestId('filter'); + expect(filterBtn).toBeInTheDocument(); + + fireEvent.click(filterBtn); + await waitFor(() => { + expect(screen.getByTestId('statusActive')).toBeInTheDocument(); + }); + fireEvent.click(screen.getByTestId('statusActive')); + await waitFor(() => { + expect(screen.getByText('Category 1')).toBeInTheDocument(); + expect(screen.queryByText('Category 2')).toBeNull(); + }); + }); + + it('open and closes Create Category modal', async () => { + renderActionItemCategories(link1, 'orgId'); + + const addCategoryBtn = await screen.findByTestId( + 'createActionItemCategoryBtn', + ); + expect(addCategoryBtn).toBeInTheDocument(); + userEvent.click(addCategoryBtn); + + await waitFor(() => expect(screen.getAllByText(t.create)).toHaveLength(2)); + userEvent.click(screen.getByTestId('actionItemCategoryModalCloseBtn')); + await waitFor(() => + expect( + screen.queryByTestId('actionItemCategoryModalCloseBtn'), + ).toBeNull(), + ); + }); + + it('open and closes Edit Category modal', async () => { + renderActionItemCategories(link1, 'orgId'); + + const editCategoryBtn = await screen.findByTestId('editCategoryBtn1'); + await waitFor(() => expect(editCategoryBtn).toBeInTheDocument()); + userEvent.click(editCategoryBtn); + + await waitFor(() => + expect(screen.getByText(t.updateActionItemCategory)).toBeInTheDocument(), + ); + userEvent.click(screen.getByTestId('actionItemCategoryModalCloseBtn')); + await waitFor(() => + expect( + screen.queryByTestId('actionItemCategoryModalCloseBtn'), + ).toBeNull(), + ); + }); + + it('Search categories by name', async () => { + renderActionItemCategories(link1, 'orgId'); + + const searchInput = await screen.findByTestId('searchByName'); + expect(searchInput).toBeInTheDocument(); + + userEvent.type(searchInput, 'Category 1'); + userEvent.click(screen.getByTestId('searchBtn')); + await waitFor(() => { + expect(screen.getByText('Category 1')).toBeInTheDocument(); + expect(screen.queryByText('Category 2')).toBeNull(); + }); + }); + + it('Search categories by name and clear the input by backspace', async () => { + renderActionItemCategories(link1, 'orgId'); + + const searchInput = await screen.findByTestId('searchByName'); + expect(searchInput).toBeInTheDocument(); + + // Clear the search input by backspace + userEvent.type(searchInput, 'A{backspace}'); + await waitFor(() => { + expect(screen.getByText('Category 1')).toBeInTheDocument(); + expect(screen.getByText('Category 2')).toBeInTheDocument(); + }); + }); + + it('Search categories by name on press of ENTER', async () => { + renderActionItemCategories(link1, 'orgId'); + + const searchInput = await screen.findByTestId('searchByName'); + expect(searchInput).toBeInTheDocument(); + + userEvent.type(searchInput, 'Category 1'); + userEvent.type(searchInput, '{enter}'); + await waitFor(() => { + expect(screen.getByText('Category 1')).toBeInTheDocument(); + expect(screen.queryByText('Category 2')).toBeNull(); + }); + }); + + it('should render Empty Action Item Categories Screen', async () => { + renderActionItemCategories(link2, 'orgId'); + await waitFor(() => { + expect(screen.getByTestId('searchByName')).toBeInTheDocument(); + expect(screen.getByText(t.noActionItemCategories)).toBeInTheDocument(); + }); + }); + + it('should render the Action Item Categories Screen with error', async () => { + renderActionItemCategories(link3, 'orgId'); + await waitFor(() => { + expect(screen.getByTestId('errorMsg')).toBeInTheDocument(); + }); + }); +}); diff --git a/src/components/OrgSettings/ActionItemCategories/OrgActionItemCategories.tsx b/src/components/OrgSettings/ActionItemCategories/OrgActionItemCategories.tsx new file mode 100644 index 0000000000..49cf47dd49 --- /dev/null +++ b/src/components/OrgSettings/ActionItemCategories/OrgActionItemCategories.tsx @@ -0,0 +1,418 @@ +import type { FC } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; +import { Button, Dropdown, Form } from 'react-bootstrap'; +import styles from './OrgActionItemCategories.module.css'; +import { useTranslation } from 'react-i18next'; + +import { useQuery } from '@apollo/client'; +import { ACTION_ITEM_CATEGORY_LIST } from 'GraphQl/Queries/Queries'; +import type { InterfaceActionItemCategoryInfo } from 'utils/interfaces'; +import Loader from 'components/Loader/Loader'; +import { + Circle, + Search, + Sort, + WarningAmberRounded, + FilterAltOutlined, +} from '@mui/icons-material'; +import { + DataGrid, + type GridCellParams, + type GridColDef, +} from '@mui/x-data-grid'; +import dayjs from 'dayjs'; +import { Chip, Stack } from '@mui/material'; +import CategoryModal from './CategoryModal'; + +enum ModalState { + SAME = 'same', + DELETE = 'delete', +} + +enum CategoryStatus { + Active = 'active', + Disabled = 'disabled', +} + +interface InterfaceActionItemCategoryProps { + orgId: string; +} + +const dataGridStyle = { + '&.MuiDataGrid-root .MuiDataGrid-cell:focus-within': { + outline: 'none !important', + }, + '&.MuiDataGrid-root .MuiDataGrid-columnHeader:focus-within': { + outline: 'none', + }, + '& .MuiDataGrid-row:hover': { + backgroundColor: 'transparent', + }, + '& .MuiDataGrid-row.Mui-hovered': { + backgroundColor: 'transparent', + }, + '& .MuiDataGrid-root': { + borderRadius: '0.5rem', + }, + '& .MuiDataGrid-main': { + borderRadius: '0.5rem', + }, +}; + +/** + * Represents the component for managing organization action item categories. + * This component allows creating, updating, enabling, and disabling action item categories. + */ +const OrgActionItemCategories: FC = ({ + orgId, +}) => { + const { t } = useTranslation('translation', { + keyPrefix: 'orgActionItemCategories', + }); + const { t: tCommon } = useTranslation('common'); + const { t: tErrors } = useTranslation('errors'); + + const [category, setCategory] = + useState(null); + const [searchTerm, setSearchTerm] = useState(''); + const [searchValue, setSearchValue] = useState(''); + const [sortBy, setSortBy] = useState<'createdAt_ASC' | 'createdAt_DESC'>( + 'createdAt_DESC', + ); + const [status, setStatus] = useState(null); + const [categories, setCategories] = useState< + InterfaceActionItemCategoryInfo[] + >([]); + const [modalMode, setModalMode] = useState<'edit' | 'create'>('create'); + const [modalState, setModalState] = useState<{ + [key in ModalState]: boolean; + }>({ + [ModalState.SAME]: false, + [ModalState.DELETE]: false, + }); + + // Query to fetch action item categories + const { + data: catData, + loading: catLoading, + error: catError, + refetch: refetchCategories, + }: { + data?: { + actionItemCategoriesByOrganization: InterfaceActionItemCategoryInfo[]; + }; + loading: boolean; + error?: Error | undefined; + refetch: () => void; + } = useQuery(ACTION_ITEM_CATEGORY_LIST, { + variables: { + organizationId: orgId, + where: { + name_contains: searchTerm, + is_disabled: !status ? undefined : status === CategoryStatus.Disabled, + }, + orderBy: sortBy, + }, + }); + + const openModal = (modal: ModalState): void => + setModalState((prevState) => ({ ...prevState, [modal]: true })); + + const closeModal = (modal: ModalState): void => + setModalState((prevState) => ({ ...prevState, [modal]: false })); + + const handleOpenModal = useCallback( + ( + category: InterfaceActionItemCategoryInfo | null, + mode: 'edit' | 'create', + ): void => { + setCategory(category); + setModalMode(mode); + openModal(ModalState.SAME); + }, + [openModal], + ); + + useEffect(() => { + if (catData && catData.actionItemCategoriesByOrganization) { + setCategories(catData.actionItemCategoriesByOrganization); + } + }, [catData]); + + // Show loader while data is being fetched + if (catLoading) { + return ; + } + + // Show error message if there's an error + if (catError) { + return ( +
+ +
+ {tErrors('errorLoading', { entity: 'Action Item Categories' })} +
+ {`${catError.message}`} +
+
+ ); + } + + const columns: GridColDef[] = [ + { + field: 'id', + headerName: 'Sr. No.', + flex: 1, + minWidth: 100, + align: 'center', + headerAlign: 'center', + headerClassName: `${styles.tableHeader}`, + sortable: false, + renderCell: (params: GridCellParams) => { + return
{params.row.id}
; + }, + }, + { + field: 'categoryName', + headerName: 'Category', + flex: 2, + align: 'center', + minWidth: 100, + headerAlign: 'center', + sortable: false, + headerClassName: `${styles.tableHeader}`, + renderCell: (params: GridCellParams) => { + return ( +
+ {params.row.name} +
+ ); + }, + }, + { + field: 'status', + headerName: 'Status', + flex: 1, + align: 'center', + minWidth: 100, + headerAlign: 'center', + sortable: false, + headerClassName: `${styles.tableHeader}`, + renderCell: (params: GridCellParams) => { + return ( + } + label={params.row.isDisabled ? 'Disabled' : 'Active'} + variant="outlined" + color="primary" + className={`${styles.chip} ${params.row.isDisabled ? styles.pending : styles.active}`} + /> + ); + }, + }, + { + field: 'createdBy', + headerName: 'Created By', + flex: 1, + align: 'center', + minWidth: 100, + headerAlign: 'center', + sortable: false, + headerClassName: `${styles.tableHeader}`, + renderCell: (params: GridCellParams) => { + return params.row.creator.firstName + ' ' + params.row.creator.lastName; + }, + }, + { + field: 'createdOn', + headerName: 'Created On', + align: 'center', + minWidth: 100, + headerAlign: 'center', + sortable: false, + headerClassName: `${styles.tableHeader}`, + flex: 1, + renderCell: (params: GridCellParams) => { + return ( +
+ {dayjs(params.row.createdAt).format('DD/MM/YYYY')} +
+ ); + }, + }, + { + field: 'action', + headerName: 'Action', + flex: 2, + align: 'center', + minWidth: 100, + headerAlign: 'center', + sortable: false, + headerClassName: `${styles.tableHeader}`, + renderCell: (params: GridCellParams) => { + return ( + + ); + }, + }, + ]; + + return ( +
+ {/* Header with search, filter and Create Button */} +
+
+ setSearchValue(e.target.value)} + onKeyUp={(e) => { + if (e.key === 'Enter') { + setSearchTerm(searchValue); + } else if (e.key === 'Backspace' && searchValue === '') { + setSearchTerm(''); + } + }} + data-testid="searchByName" + /> + +
+
+
+ + + + {tCommon('sort')} + + + setSortBy('createdAt_DESC')} + data-testid="createdAt_DESC" + > + {tCommon('createdLatest')} + + setSortBy('createdAt_ASC')} + data-testid="createdAt_ASC" + > + {tCommon('createdEarliest')} + + + + + + + {t('status')} + + + setStatus(null)} + data-testid="statusAll" + > + {tCommon('all')} + + setStatus(CategoryStatus.Active)} + data-testid="statusActive" + > + {tCommon('active')} + + setStatus(CategoryStatus.Disabled)} + data-testid="statusDisabled" + > + {tCommon('disabled')} + + + +
+
+ +
+
+
+ + {/* Table with Action Item Categories */} + row._id} + slots={{ + noRowsOverlay: () => ( + + {t('noActionItemCategories')} + + ), + }} + sx={dataGridStyle} + getRowClassName={() => `${styles.rowBackground}`} + autoHeight + rowHeight={65} + rows={categories.map((category, index) => ({ + id: index + 1, + ...category, + }))} + columns={columns} + isRowSelectable={() => false} + /> + + closeModal(ModalState.SAME)} + refetchCategories={refetchCategories} + category={category} + orgId={orgId} + mode={modalMode} + /> +
+ ); +}; + +export default OrgActionItemCategories; diff --git a/src/components/OrgSettings/ActionItemCategories/OrgActionItemCategoryMocks.ts b/src/components/OrgSettings/ActionItemCategories/OrgActionItemCategoryMocks.ts new file mode 100644 index 0000000000..10310a01e4 --- /dev/null +++ b/src/components/OrgSettings/ActionItemCategories/OrgActionItemCategoryMocks.ts @@ -0,0 +1,288 @@ +import { + CREATE_ACTION_ITEM_CATEGORY_MUTATION, + UPDATE_ACTION_ITEM_CATEGORY_MUTATION, +} from 'GraphQl/Mutations/mutations'; + +import { ACTION_ITEM_CATEGORY_LIST } from 'GraphQl/Queries/Queries'; + +export const MOCKS = [ + { + request: { + query: ACTION_ITEM_CATEGORY_LIST, + variables: { + organizationId: 'orgId', + where: { name_contains: '' }, + orderBy: 'createdAt_DESC', + }, + }, + result: { + data: { + actionItemCategoriesByOrganization: [ + { + _id: 'categoryId1', + name: 'Category 1', + isDisabled: false, + createdAt: '2024-08-26', + creator: { + _id: 'creatorId1', + firstName: 'Wilt', + lastName: 'Shepherd', + }, + }, + { + _id: 'categoryId2', + name: 'Category 2', + isDisabled: true, + createdAt: '2024-08-25', + creator: { + _id: 'creatorId2', + firstName: 'John', + lastName: 'Doe', + }, + }, + ], + }, + }, + }, + { + request: { + query: ACTION_ITEM_CATEGORY_LIST, + variables: { + organizationId: 'orgId', + where: { name_contains: '' }, + orderBy: 'createdAt_ASC', + }, + }, + result: { + data: { + actionItemCategoriesByOrganization: [ + { + _id: 'categoryId2', + name: 'Category 2', + isDisabled: true, + createdAt: '2024-08-25', + creator: { + _id: 'creatorId2', + firstName: 'John', + lastName: 'Doe', + }, + }, + { + _id: 'categoryId1', + name: 'Category 1', + isDisabled: false, + createdAt: '2024-08-26', + creator: { + _id: 'creatorId1', + firstName: 'Wilt', + lastName: 'Shepherd', + }, + }, + ], + }, + }, + }, + { + request: { + query: ACTION_ITEM_CATEGORY_LIST, + variables: { + organizationId: 'orgId', + where: { name_contains: '', is_disabled: false }, + orderBy: 'createdAt_DESC', + }, + }, + result: { + data: { + actionItemCategoriesByOrganization: [ + { + _id: 'categoryId1', + name: 'Category 1', + isDisabled: false, + createdAt: '2024-08-26', + creator: { + _id: 'creatorId1', + firstName: 'Wilt', + lastName: 'Shepherd', + }, + }, + ], + }, + }, + }, + { + request: { + query: ACTION_ITEM_CATEGORY_LIST, + variables: { + organizationId: 'orgId', + where: { name_contains: '', is_disabled: true }, + orderBy: 'createdAt_DESC', + }, + }, + result: { + data: { + actionItemCategoriesByOrganization: [ + { + _id: 'categoryId2', + name: 'Category 2', + isDisabled: true, + createdAt: '2024-08-25', + creator: { + _id: 'creatorId2', + firstName: 'John', + lastName: 'Doe', + }, + }, + ], + }, + }, + }, + { + request: { + query: ACTION_ITEM_CATEGORY_LIST, + variables: { + organizationId: 'orgId', + where: { name_contains: 'Category 1' }, + orderBy: 'createdAt_DESC', + }, + }, + result: { + data: { + actionItemCategoriesByOrganization: [ + { + _id: 'categoryId1', + name: 'Category 1', + isDisabled: false, + createdAt: '2024-08-26', + creator: { + _id: 'creatorId1', + firstName: 'Wilt', + lastName: 'Shepherd', + }, + }, + ], + }, + }, + }, + { + request: { + query: CREATE_ACTION_ITEM_CATEGORY_MUTATION, + variables: { + name: 'Category 2', + isDisabled: true, + organizationId: 'orgId', + }, + }, + result: { + data: { + createActionItemCategory: { + _id: 'categoryId3', + }, + }, + }, + }, + { + request: { + query: UPDATE_ACTION_ITEM_CATEGORY_MUTATION, + variables: { + name: 'Category 2', + isDisabled: true, + actionItemCategoryId: 'categoryId', + }, + }, + result: { + data: { + updateActionItemCategory: { + _id: 'categoryId', + }, + }, + }, + }, + { + request: { + query: UPDATE_ACTION_ITEM_CATEGORY_MUTATION, + variables: { + name: 'Category 2', + isDisabled: false, + actionItemCategoryId: 'categoryId', + }, + }, + result: { + data: { + updateActionItemCategory: { + _id: 'categoryId', + }, + }, + }, + }, + { + request: { + query: UPDATE_ACTION_ITEM_CATEGORY_MUTATION, + variables: { + name: 'Category 1', + isDisabled: true, + actionItemCategoryId: 'categoryId', + }, + }, + result: { + data: { + updateActionItemCategory: { + _id: 'categoryId', + }, + }, + }, + }, +]; + +export const MOCKS_EMPTY = [ + { + request: { + query: ACTION_ITEM_CATEGORY_LIST, + variables: { + organizationId: 'orgId', + where: { name_contains: '' }, + orderBy: 'createdAt_DESC', + }, + }, + result: { + data: { + actionItemCategoriesByOrganization: [], + }, + }, + }, +]; + +export const MOCKS_ERROR = [ + { + request: { + query: ACTION_ITEM_CATEGORY_LIST, + variables: { + organizationId: 'orgId', + where: { name_contains: '' }, + orderBy: 'createdAt_DESC', + }, + }, + error: new Error('Mock Graphql Error'), + }, + { + request: { + query: CREATE_ACTION_ITEM_CATEGORY_MUTATION, + variables: { + name: 'Category 2', + isDisabled: true, + organizationId: 'orgId', + }, + }, + error: new Error('Mock Graphql Error'), + }, + { + request: { + query: UPDATE_ACTION_ITEM_CATEGORY_MUTATION, + variables: { + name: 'Category 2', + isDisabled: true, + actionItemCategoryId: 'categoryId', + }, + }, + error: new Error('Mock Graphql Error'), + }, +]; diff --git a/src/screens/OrganizationAgendaCategory/AgendaCategoryCreateModal.test.tsx b/src/components/OrgSettings/AgendaItemCategories/AgendaCategoryCreateModal.test.tsx similarity index 100% rename from src/screens/OrganizationAgendaCategory/AgendaCategoryCreateModal.test.tsx rename to src/components/OrgSettings/AgendaItemCategories/AgendaCategoryCreateModal.test.tsx diff --git a/src/screens/OrganizationAgendaCategory/AgendaCategoryCreateModal.tsx b/src/components/OrgSettings/AgendaItemCategories/AgendaCategoryCreateModal.tsx similarity index 100% rename from src/screens/OrganizationAgendaCategory/AgendaCategoryCreateModal.tsx rename to src/components/OrgSettings/AgendaItemCategories/AgendaCategoryCreateModal.tsx diff --git a/src/screens/OrganizationAgendaCategory/AgendaCategoryDeleteModal.tsx b/src/components/OrgSettings/AgendaItemCategories/AgendaCategoryDeleteModal.tsx similarity index 100% rename from src/screens/OrganizationAgendaCategory/AgendaCategoryDeleteModal.tsx rename to src/components/OrgSettings/AgendaItemCategories/AgendaCategoryDeleteModal.tsx diff --git a/src/screens/OrganizationAgendaCategory/AgendaCategoryPreviewModal.tsx b/src/components/OrgSettings/AgendaItemCategories/AgendaCategoryPreviewModal.tsx similarity index 100% rename from src/screens/OrganizationAgendaCategory/AgendaCategoryPreviewModal.tsx rename to src/components/OrgSettings/AgendaItemCategories/AgendaCategoryPreviewModal.tsx diff --git a/src/screens/OrganizationAgendaCategory/AgendaCategoryUpdateModal.test.tsx b/src/components/OrgSettings/AgendaItemCategories/AgendaCategoryUpdateModal.test.tsx similarity index 100% rename from src/screens/OrganizationAgendaCategory/AgendaCategoryUpdateModal.test.tsx rename to src/components/OrgSettings/AgendaItemCategories/AgendaCategoryUpdateModal.test.tsx diff --git a/src/screens/OrganizationAgendaCategory/AgendaCategoryUpdateModal.tsx b/src/components/OrgSettings/AgendaItemCategories/AgendaCategoryUpdateModal.tsx similarity index 100% rename from src/screens/OrganizationAgendaCategory/AgendaCategoryUpdateModal.tsx rename to src/components/OrgSettings/AgendaItemCategories/AgendaCategoryUpdateModal.tsx diff --git a/src/screens/OrganizationAgendaCategory/OrganizationAgendaCategory.module.css b/src/components/OrgSettings/AgendaItemCategories/OrganizationAgendaCategory.module.css similarity index 100% rename from src/screens/OrganizationAgendaCategory/OrganizationAgendaCategory.module.css rename to src/components/OrgSettings/AgendaItemCategories/OrganizationAgendaCategory.module.css diff --git a/src/screens/OrganizationAgendaCategory/OrganizationAgendaCategory.test.tsx b/src/components/OrgSettings/AgendaItemCategories/OrganizationAgendaCategory.test.tsx similarity index 96% rename from src/screens/OrganizationAgendaCategory/OrganizationAgendaCategory.test.tsx rename to src/components/OrgSettings/AgendaItemCategories/OrganizationAgendaCategory.test.tsx index 732db13a74..e05edc665d 100644 --- a/src/screens/OrganizationAgendaCategory/OrganizationAgendaCategory.test.tsx +++ b/src/components/OrgSettings/AgendaItemCategories/OrganizationAgendaCategory.test.tsx @@ -76,7 +76,7 @@ describe('Testing Agenda Categories Component', () => { - {} + {} @@ -96,7 +96,7 @@ describe('Testing Agenda Categories Component', () => { - {} + {} @@ -119,7 +119,7 @@ describe('Testing Agenda Categories Component', () => { - {} + {} @@ -152,7 +152,7 @@ describe('Testing Agenda Categories Component', () => { - {} + {} diff --git a/src/screens/OrganizationAgendaCategory/OrganizationAgendaCategory.tsx b/src/components/OrgSettings/AgendaItemCategories/OrganizationAgendaCategory.tsx similarity index 92% rename from src/screens/OrganizationAgendaCategory/OrganizationAgendaCategory.tsx rename to src/components/OrgSettings/AgendaItemCategories/OrganizationAgendaCategory.tsx index 9fcb3f0a40..884371d862 100644 --- a/src/screens/OrganizationAgendaCategory/OrganizationAgendaCategory.tsx +++ b/src/components/OrgSettings/AgendaItemCategories/OrganizationAgendaCategory.tsx @@ -1,8 +1,7 @@ import React, { useState } from 'react'; -import type { ChangeEvent } from 'react'; +import type { ChangeEvent, FC } from 'react'; import { useTranslation } from 'react-i18next'; import { Button } from 'react-bootstrap'; -import { useParams } from 'react-router-dom'; import { WarningAmberRounded } from '@mui/icons-material'; import { toast } from 'react-toastify'; @@ -17,6 +16,10 @@ import AgendaCategoryCreateModal from './AgendaCategoryCreateModal'; import styles from './OrganizationAgendaCategory.module.css'; import Loader from 'components/Loader/Loader'; +interface InterfaceAgendaCategoryProps { + orgId: string; +} + /** * Component for managing and displaying agenda item categories within an organization. * @@ -24,14 +27,14 @@ import Loader from 'components/Loader/Loader'; * * @returns The rendered component. */ -function organizationAgendaCategory(): JSX.Element { + +const organizationAgendaCategory: FC = ({ + orgId, +}) => { const { t } = useTranslation('translation', { keyPrefix: 'organizationAgendaCategory', }); - // Get the organization ID from URL parameters - const { orgId: currentUrl } = useParams(); - // State for managing modal visibility and form data const [agendaCategoryCreateModalIsOpen, setAgendaCategoryCreateModalIsOpen] = useState(false); @@ -56,7 +59,7 @@ function organizationAgendaCategory(): JSX.Element { error?: unknown | undefined; refetch: () => void; } = useQuery(AGENDA_ITEM_CATEGORY_LIST, { - variables: { organizationId: currentUrl }, + variables: { organizationId: orgId }, notifyOnNetworkStatusChange: true, }); @@ -81,13 +84,13 @@ function organizationAgendaCategory(): JSX.Element { await createAgendaCategory({ variables: { input: { - organizationId: currentUrl, + organizationId: orgId, name: formState.name, description: formState.description, }, }, }); - toast.success(t('agendaCategoryCreated')); + toast.success(t('agendaCategoryCreated') as string); setFormState({ name: '', description: '', createdBy: '' }); refetchAgendaCategory(); hideCreateModal(); @@ -132,7 +135,7 @@ function organizationAgendaCategory(): JSX.Element { } return ( -
+
@@ -179,6 +182,6 @@ function organizationAgendaCategory(): JSX.Element { />
); -} +}; export default organizationAgendaCategory; diff --git a/src/screens/OrganizationAgendaCategory/OrganizationAgendaCategoryErrorMocks.ts b/src/components/OrgSettings/AgendaItemCategories/OrganizationAgendaCategoryErrorMocks.ts similarity index 100% rename from src/screens/OrganizationAgendaCategory/OrganizationAgendaCategoryErrorMocks.ts rename to src/components/OrgSettings/AgendaItemCategories/OrganizationAgendaCategoryErrorMocks.ts diff --git a/src/screens/OrganizationAgendaCategory/OrganizationAgendaCategoryMocks.ts b/src/components/OrgSettings/AgendaItemCategories/OrganizationAgendaCategoryMocks.ts similarity index 100% rename from src/screens/OrganizationAgendaCategory/OrganizationAgendaCategoryMocks.ts rename to src/components/OrgSettings/AgendaItemCategories/OrganizationAgendaCategoryMocks.ts diff --git a/src/components/DeleteOrg/DeleteOrg.module.css b/src/components/OrgSettings/General/DeleteOrg/DeleteOrg.module.css similarity index 100% rename from src/components/DeleteOrg/DeleteOrg.module.css rename to src/components/OrgSettings/General/DeleteOrg/DeleteOrg.module.css diff --git a/src/components/OrgSettings/General/DeleteOrg/DeleteOrg.test.tsx b/src/components/OrgSettings/General/DeleteOrg/DeleteOrg.test.tsx new file mode 100644 index 0000000000..77ffe65c08 --- /dev/null +++ b/src/components/OrgSettings/General/DeleteOrg/DeleteOrg.test.tsx @@ -0,0 +1,307 @@ +import React, { act } from 'react'; +import { MockedProvider } from '@apollo/react-testing'; +import { render, screen, waitFor } from '@testing-library/react'; +import 'jest-location-mock'; +import { I18nextProvider } from 'react-i18next'; +import { Provider } from 'react-redux'; +import { BrowserRouter } from 'react-router-dom'; + +import { + DELETE_ORGANIZATION_MUTATION, + REMOVE_SAMPLE_ORGANIZATION_MUTATION, +} from 'GraphQl/Mutations/mutations'; +import { store } from 'state/store'; +import { StaticMockLink } from 'utils/StaticMockLink'; +import i18nForTest from 'utils/i18nForTest'; +import DeleteOrg from './DeleteOrg'; +import { ToastContainer, toast } from 'react-toastify'; +import { IS_SAMPLE_ORGANIZATION_QUERY } from 'GraphQl/Queries/Queries'; +import useLocalStorage from 'utils/useLocalstorage'; + +const { setItem } = useLocalStorage(); + +async function wait(ms = 1000): Promise { + await act(async () => { + await new Promise((resolve) => setTimeout(resolve, ms)); + }); +} + +const MOCKS = [ + { + request: { + query: IS_SAMPLE_ORGANIZATION_QUERY, + variables: { + isSampleOrganizationId: '123', + }, + }, + result: { + data: { + isSampleOrganization: true, + }, + }, + }, + { + request: { + query: REMOVE_SAMPLE_ORGANIZATION_MUTATION, + }, + result: { + data: { + removeSampleOrganization: true, + }, + }, + }, + { + request: { + query: DELETE_ORGANIZATION_MUTATION, + variables: { + id: '456', + }, + }, + result: { + data: { + removeOrganization: { + _id: '456', + }, + }, + }, + }, +]; + +const MOCKS_WITH_ERROR = [ + { + request: { + query: IS_SAMPLE_ORGANIZATION_QUERY, + variables: { + isSampleOrganizationId: '123', + }, + }, + result: { + data: { + isSampleOrganization: true, + }, + }, + }, + { + request: { + query: DELETE_ORGANIZATION_MUTATION, + variables: { + id: '456', + }, + }, + error: new Error('Failed to delete organization'), + }, + { + request: { + query: REMOVE_SAMPLE_ORGANIZATION_MUTATION, + }, + error: new Error('Failed to delete sample organization'), + }, +]; + +const mockNavgatePush = jest.fn(); +let mockURL = '123'; +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useParams: () => ({ orgId: mockURL }), + useNavigate: () => mockNavgatePush, +})); + +const link = new StaticMockLink(MOCKS, true); +const link2 = new StaticMockLink(MOCKS_WITH_ERROR, true); + +afterEach(() => { + localStorage.clear(); +}); + +describe('Delete Organization Component', () => { + test('should be able to Toggle Delete Organization Modal', async () => { + mockURL = '456'; + setItem('SuperAdmin', true); + await act(async () => { + render( + + + + + + + + + + , + ); + }); + act(() => { + screen.getByTestId(/openDeleteModalBtn/i).click(); + }); + expect(await screen.findByTestId(/orgDeleteModal/i)).toBeInTheDocument(); + act(() => { + screen.getByTestId(/closeDelOrgModalBtn/i).click(); + }); + await waitFor(() => { + expect(screen.queryByTestId(/orgDeleteModal/i)).not.toBeInTheDocument(); + }); + }); + + test('should be able to Toggle Delete Organization Modal When Organization is Sample Organization', async () => { + mockURL = '123'; + setItem('SuperAdmin', true); + await act(async () => { + render( + + + + + + + + + + , + ); + }); + await wait(); + act(() => { + screen.getByTestId(/openDeleteModalBtn/i).click(); + }); + expect(screen.getByTestId(/orgDeleteModal/i)).toBeInTheDocument(); + act(() => { + screen.getByTestId(/closeDelOrgModalBtn/i).click(); + }); + await waitFor(() => { + expect(screen.queryByTestId(/orgDeleteModal/i)).not.toBeInTheDocument(); + }); + }); + + test('Delete organization functionality should work properly', async () => { + mockURL = '456'; + setItem('SuperAdmin', true); + await act(async () => { + render( + + + + + + + + + , + ); + }); + screen.debug(); + act(() => { + screen.getByTestId('openDeleteModalBtn').click(); + }); + screen.debug(); + expect(await screen.findByTestId('orgDeleteModal')).toBeInTheDocument(); + const deleteButton = await screen.findByTestId('deleteOrganizationBtn'); + act(() => { + deleteButton.click(); + }); + }); + + test('Delete organization functionality should work properly for sample org', async () => { + mockURL = '123'; + setItem('SuperAdmin', true); + await act(async () => { + render( + + + + + + + + + , + ); + }); + await waitFor(() => { + expect(screen.getByTestId('openDeleteModalBtn')).toBeInTheDocument(); + }); + act(() => { + screen.getByTestId('openDeleteModalBtn').click(); + }); + await waitFor(() => { + expect(screen.getByTestId('orgDeleteModal')).toBeInTheDocument(); + }); + const deleteButton = await screen.findByTestId('deleteOrganizationBtn'); + act(() => { + deleteButton.click(); + }); + await wait(2000); + expect(mockNavgatePush).toHaveBeenCalledWith('/orglist'); + }); + + test('Error handling for IS_SAMPLE_ORGANIZATION_QUERY mock', async () => { + mockURL = '123'; + setItem('SuperAdmin', true); + jest.spyOn(toast, 'error'); + await act(async () => { + render( + + + + + + + + + , + ); + }); + await waitFor(() => { + expect(screen.getByTestId('openDeleteModalBtn')).toBeInTheDocument(); + }); + act(() => { + screen.getByTestId('openDeleteModalBtn').click(); + }); + await waitFor(() => { + expect(screen.getByTestId('orgDeleteModal')).toBeInTheDocument(); + }); + act(() => { + screen.getByTestId('deleteOrganizationBtn').click(); + }); + await waitFor(() => { + expect(toast.error).toHaveBeenCalledWith( + 'Failed to delete sample organization', + ); + }); + }); + + test('Error handling for DELETE_ORGANIZATION_MUTATION mock', async () => { + mockURL = '456'; + setItem('SuperAdmin', true); + jest.spyOn(toast, 'error'); + + await act(async () => { + render( + + + + + + + + + , + ); + }); + await waitFor(() => { + expect(screen.getByTestId('openDeleteModalBtn')).toBeInTheDocument(); + }); + act(() => { + screen.getByTestId('openDeleteModalBtn').click(); + }); + await waitFor(() => { + expect(screen.getByTestId('orgDeleteModal')).toBeInTheDocument(); + }); + act(() => { + screen.getByTestId('deleteOrganizationBtn').click(); + }); + await waitFor(() => { + expect(toast.error).toHaveBeenCalledWith('Failed to delete organization'); + }); + }); +}); diff --git a/src/components/DeleteOrg/DeleteOrg.tsx b/src/components/OrgSettings/General/DeleteOrg/DeleteOrg.tsx similarity index 99% rename from src/components/DeleteOrg/DeleteOrg.tsx rename to src/components/OrgSettings/General/DeleteOrg/DeleteOrg.tsx index e9370cb1a6..8edf11ca22 100644 --- a/src/components/DeleteOrg/DeleteOrg.tsx +++ b/src/components/OrgSettings/General/DeleteOrg/DeleteOrg.tsx @@ -69,7 +69,7 @@ function deleteOrg(): JSX.Element { // If it's a sample organization, use a specific mutation removeSampleOrganization() .then(() => { - toast.success(t('successfullyDeletedSampleOrganization')); + toast.success(t('successfullyDeletedSampleOrganization') as string); setTimeout(() => { navigate('/orglist'); }, 1000); diff --git a/src/components/OrgSettings/General/GeneralSettings.tsx b/src/components/OrgSettings/General/GeneralSettings.tsx new file mode 100644 index 0000000000..4dbca1b6eb --- /dev/null +++ b/src/components/OrgSettings/General/GeneralSettings.tsx @@ -0,0 +1,73 @@ +import React, { type FC } from 'react'; +import { Card, Col, Form, Row } from 'react-bootstrap'; +import styles from 'screens/OrgSettings/OrgSettings.module.css'; +import OrgProfileFieldSettings from './OrgProfileFieldSettings/OrgProfileFieldSettings'; +import ChangeLanguageDropDown from 'components/ChangeLanguageDropdown/ChangeLanguageDropDown'; +import DeleteOrg from './DeleteOrg/DeleteOrg'; +import OrgUpdate from './OrgUpdate/OrgUpdate'; +import { useTranslation } from 'react-i18next'; + +/** + * Props for the `GeneralSettings` component. + */ +interface InterfaceGeneralSettingsProps { + orgId: string; +} + +/** + * A component for displaying general settings for an organization. + * + * @param props - The properties passed to the component. + * @returns The `GeneralSettings` component. + */ +const GeneralSettings: FC = ({ orgId }) => { + const { t } = useTranslation('translation', { + keyPrefix: 'orgSettings', + }); + + return ( + + + +
+
{t('updateOrganization')}
+
+ + {/* Render organization update component */} + + +
+ + + + +
+
{t('otherSettings')}
+
+ +
+ + {t('changeLanguage')} + + {/* Render language change dropdown component */} + +
+
+
+ + + +
+
{t('manageCustomFields')}
+
+ + {/* Render organization profile field settings component */} + + +
+ +
+ ); +}; + +export default GeneralSettings; diff --git a/src/components/OrgProfileFieldSettings/OrgProfileFieldSettings.module.css b/src/components/OrgSettings/General/OrgProfileFieldSettings/OrgProfileFieldSettings.module.css similarity index 100% rename from src/components/OrgProfileFieldSettings/OrgProfileFieldSettings.module.css rename to src/components/OrgSettings/General/OrgProfileFieldSettings/OrgProfileFieldSettings.module.css diff --git a/src/components/OrgProfileFieldSettings/OrgProfileFieldSettings.test.tsx b/src/components/OrgSettings/General/OrgProfileFieldSettings/OrgProfileFieldSettings.test.tsx similarity index 98% rename from src/components/OrgProfileFieldSettings/OrgProfileFieldSettings.test.tsx rename to src/components/OrgSettings/General/OrgProfileFieldSettings/OrgProfileFieldSettings.test.tsx index 2630c6c4ee..8db8773381 100644 --- a/src/components/OrgProfileFieldSettings/OrgProfileFieldSettings.test.tsx +++ b/src/components/OrgSettings/General/OrgProfileFieldSettings/OrgProfileFieldSettings.test.tsx @@ -1,5 +1,5 @@ -import React from 'react'; -import { act, render, screen } from '@testing-library/react'; +import React, { act } from 'react'; +import { render, screen } from '@testing-library/react'; import { MockedProvider } from '@apollo/react-testing'; import userEvent from '@testing-library/user-event'; import { I18nextProvider } from 'react-i18next'; diff --git a/src/components/OrgProfileFieldSettings/OrgProfileFieldSettings.tsx b/src/components/OrgSettings/General/OrgProfileFieldSettings/OrgProfileFieldSettings.tsx similarity index 96% rename from src/components/OrgProfileFieldSettings/OrgProfileFieldSettings.tsx rename to src/components/OrgSettings/General/OrgProfileFieldSettings/OrgProfileFieldSettings.tsx index 5b67a8525d..dcb6992e21 100644 --- a/src/components/OrgProfileFieldSettings/OrgProfileFieldSettings.tsx +++ b/src/components/OrgSettings/General/OrgProfileFieldSettings/OrgProfileFieldSettings.tsx @@ -13,14 +13,7 @@ import { useTranslation } from 'react-i18next'; import { toast } from 'react-toastify'; import EditOrgCustomFieldDropDown from 'components/EditCustomFieldDropDown/EditCustomFieldDropDown'; import { useParams } from 'react-router-dom'; - -/** - * Interface for custom field data - */ -export interface InterfaceCustomFieldData { - type: string; - name: string; -} +import type { InterfaceCustomFieldData } from 'utils/interfaces'; /** * Component for managing organization profile field settings @@ -71,7 +64,7 @@ const OrgProfileFieldSettings = (): JSX.Element => { ...customFieldData, }, }); - toast.success(t('fieldSuccessMessage')); + toast.success(t('fieldSuccessMessage') as string); setCustomFieldData({ type: '', name: '' }); refetch(); } catch (error) { @@ -89,7 +82,7 @@ const OrgProfileFieldSettings = (): JSX.Element => { }, }); - toast.success(t('fieldRemovalSuccess')); + toast.success(t('fieldRemovalSuccess') as string); refetch(); } catch (error) { toast.error((error as Error).message); diff --git a/src/components/OrgUpdate/OrgUpdate.module.css b/src/components/OrgSettings/General/OrgUpdate/OrgUpdate.module.css similarity index 100% rename from src/components/OrgUpdate/OrgUpdate.module.css rename to src/components/OrgSettings/General/OrgUpdate/OrgUpdate.module.css diff --git a/src/components/OrgUpdate/OrgUpdate.test.tsx b/src/components/OrgSettings/General/OrgUpdate/OrgUpdate.test.tsx similarity index 98% rename from src/components/OrgUpdate/OrgUpdate.test.tsx rename to src/components/OrgSettings/General/OrgUpdate/OrgUpdate.test.tsx index 9c56a61d78..6304bb3ec9 100644 --- a/src/components/OrgUpdate/OrgUpdate.test.tsx +++ b/src/components/OrgSettings/General/OrgUpdate/OrgUpdate.test.tsx @@ -1,6 +1,6 @@ -import React from 'react'; +import React, { act } from 'react'; import { MockedProvider } from '@apollo/react-testing'; -import { act, fireEvent, render, screen } from '@testing-library/react'; +import { fireEvent, render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { I18nextProvider } from 'react-i18next'; import { StaticMockLink } from 'utils/StaticMockLink'; diff --git a/src/components/OrgUpdate/OrgUpdate.tsx b/src/components/OrgSettings/General/OrgUpdate/OrgUpdate.tsx similarity index 99% rename from src/components/OrgUpdate/OrgUpdate.tsx rename to src/components/OrgSettings/General/OrgUpdate/OrgUpdate.tsx index f33c758acd..6e0be28a56 100644 --- a/src/components/OrgUpdate/OrgUpdate.tsx +++ b/src/components/OrgSettings/General/OrgUpdate/OrgUpdate.tsx @@ -144,7 +144,7 @@ function orgUpdate(props: InterfaceOrgUpdateProps): JSX.Element { // istanbul ignore next if (data) { refetch({ id: orgId }); - toast.success(t('successfulUpdated')); + toast.success(t('successfulUpdated') as string); } } catch (error: unknown) { errorHandler(t, error); diff --git a/src/components/OrgUpdate/OrgUpdateMocks.ts b/src/components/OrgSettings/General/OrgUpdate/OrgUpdateMocks.ts similarity index 100% rename from src/components/OrgUpdate/OrgUpdateMocks.ts rename to src/components/OrgSettings/General/OrgUpdate/OrgUpdateMocks.ts diff --git a/src/components/OrganizationCard/OrganizationCard.test.tsx b/src/components/OrganizationCard/OrganizationCard.test.tsx index e0095d20b2..e4abf3a1fd 100644 --- a/src/components/OrganizationCard/OrganizationCard.test.tsx +++ b/src/components/OrganizationCard/OrganizationCard.test.tsx @@ -6,7 +6,6 @@ describe('Testing the Organization Card', () => { test('should render props and text elements test for the page component', () => { const props = { id: '123', - key: '456', image: 'https://via.placeholder.com/80', firstName: 'John', lastName: 'Doe', @@ -24,7 +23,6 @@ describe('Testing the Organization Card', () => { test('Should render text elements when props value is not passed', () => { const props = { id: '123', - key: '456', image: '', firstName: 'John', lastName: 'Doe', diff --git a/src/components/OrganizationCard/OrganizationCard.tsx b/src/components/OrganizationCard/OrganizationCard.tsx index 05f8712ec5..ae513eff5d 100644 --- a/src/components/OrganizationCard/OrganizationCard.tsx +++ b/src/components/OrganizationCard/OrganizationCard.tsx @@ -12,47 +12,45 @@ interface InterfaceOrganizationCardProps { /** * Component to display an organization's card with its image and owner details. * - * @param image - URL of the organization's image. - * @param id - Unique identifier for the organization. - * @param name - Name of the organization. - * @param lastName - Last name of the owner's name. - * @param firstName - First name of the owner's name. + * @param props - Properties for the organization card. * @returns JSX element representing the organization card. */ -function organizationCard(props: InterfaceOrganizationCardProps): JSX.Element { +function OrganizationCard(props: InterfaceOrganizationCardProps): JSX.Element { const uri = '/superorghome/i=' + props.id; return ( - <> - -
-
- {props.image ? ( - - ) : ( - - )} -
-

{props.name}

-
- Owner: - {props.firstName} - -   - {props.lastName} - -
-
+
+ + ); } -export {}; -export default organizationCard; +export default OrganizationCard; diff --git a/src/components/OrganizationCardStart/OrganizationCardStart.test.tsx b/src/components/OrganizationCardStart/OrganizationCardStart.test.tsx index 2a9f03a9be..dd65c8649e 100644 --- a/src/components/OrganizationCardStart/OrganizationCardStart.test.tsx +++ b/src/components/OrganizationCardStart/OrganizationCardStart.test.tsx @@ -6,12 +6,11 @@ describe('Testing the Organization Cards', () => { test('should render props and text elements test for the page component', () => { const props = { id: '123', - key: '456', image: 'https://via.placeholder.com/80', name: 'Sample', }; - render(); + render(); expect(screen.getByText(props.name)).toBeInTheDocument(); }); @@ -19,12 +18,11 @@ describe('Testing the Organization Cards', () => { test('Should render text elements when props value is not passed', () => { const props = { id: '123', - key: '456', image: '', name: 'Sample', }; - render(); + render(); expect(screen.getByText(props.name)).toBeInTheDocument(); }); diff --git a/src/components/ProfileDropdown/ProfileDropdown.test.tsx b/src/components/ProfileDropdown/ProfileDropdown.test.tsx index 0efef50591..82ce420aea 100644 --- a/src/components/ProfileDropdown/ProfileDropdown.test.tsx +++ b/src/components/ProfileDropdown/ProfileDropdown.test.tsx @@ -1,5 +1,5 @@ -import React from 'react'; -import { act, render, screen } from '@testing-library/react'; +import React, { act } from 'react'; +import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { BrowserRouter } from 'react-router-dom'; import ProfileDropdown from './ProfileDropdown'; diff --git a/src/components/RequestsTableItem/RequestsTableItem.test.tsx b/src/components/RequestsTableItem/RequestsTableItem.test.tsx index 866abc7f68..bbd895300b 100644 --- a/src/components/RequestsTableItem/RequestsTableItem.test.tsx +++ b/src/components/RequestsTableItem/RequestsTableItem.test.tsx @@ -1,6 +1,6 @@ -import React from 'react'; +import React, { act } from 'react'; import { MockedProvider } from '@apollo/react-testing'; -import { act, render, screen } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import { I18nextProvider } from 'react-i18next'; import { StaticMockLink } from 'utils/StaticMockLink'; import i18nForTest from 'utils/i18nForTest'; diff --git a/src/components/RequestsTableItem/RequestsTableItem.tsx b/src/components/RequestsTableItem/RequestsTableItem.tsx index 2b647a9ce2..07feb5d289 100644 --- a/src/components/RequestsTableItem/RequestsTableItem.tsx +++ b/src/components/RequestsTableItem/RequestsTableItem.tsx @@ -67,7 +67,7 @@ const RequestsTableItem = (props: Props): JSX.Element => { }); /* istanbul ignore next */ if (data) { - toast.success(t('acceptedSuccessfully')); + toast.success(t('acceptedSuccessfully') as string); resetAndRefetch(); } } catch (error: unknown) { @@ -95,7 +95,7 @@ const RequestsTableItem = (props: Props): JSX.Element => { }); /* istanbul ignore next */ if (data) { - toast.success(t('rejectedSuccessfully')); + toast.success(t('rejectedSuccessfully') as string); resetAndRefetch(); } } catch (error: unknown) { diff --git a/src/components/UserListCard/UserListCard.test.tsx b/src/components/UserListCard/UserListCard.test.tsx index 9f59bb92ce..e2ad552507 100644 --- a/src/components/UserListCard/UserListCard.test.tsx +++ b/src/components/UserListCard/UserListCard.test.tsx @@ -1,5 +1,5 @@ -import React from 'react'; -import { act, render, screen } from '@testing-library/react'; +import React, { act } from 'react'; +import { render, screen } from '@testing-library/react'; import { MockedProvider } from '@apollo/react-testing'; import userEvent from '@testing-library/user-event'; import { I18nextProvider } from 'react-i18next'; @@ -14,7 +14,7 @@ const MOCKS = [ { request: { query: ADD_ADMIN_MUTATION, - variable: { userid: '784', orgid: '554' }, + variables: { userid: '784', orgid: '554' }, }, result: { data: { @@ -28,6 +28,7 @@ const MOCKS = [ }, ]; const link = new StaticMockLink(MOCKS, true); + async function wait(ms = 100): Promise { await act(() => { return new Promise((resolve) => { @@ -41,7 +42,6 @@ describe('Testing User List Card', () => { test('Should render props and text elements test for the page component', async () => { const props = { - key: 123, id: '456', }; @@ -49,7 +49,7 @@ describe('Testing User List Card', () => { - + , @@ -62,7 +62,6 @@ describe('Testing User List Card', () => { test('Should render text elements when props value is not passed', async () => { const props = { - key: 123, id: '456', }; @@ -70,7 +69,7 @@ describe('Testing User List Card', () => { - + , diff --git a/src/components/UserListCard/UserListCard.tsx b/src/components/UserListCard/UserListCard.tsx index 193569424f..76ad110ea1 100644 --- a/src/components/UserListCard/UserListCard.tsx +++ b/src/components/UserListCard/UserListCard.tsx @@ -47,7 +47,7 @@ function userListCard(props: InterfaceUserListCardProps): JSX.Element { /* istanbul ignore next */ if (data) { - toast.success(t('addedAsAdmin')); + toast.success(t('addedAsAdmin') as string); setTimeout(() => { window.location.reload(); }, 2000); diff --git a/src/components/UserPasswordUpdate/UserPasswordUpdate.test.tsx b/src/components/UserPasswordUpdate/UserPasswordUpdate.test.tsx index 4fcbb18070..65f5e40f76 100644 --- a/src/components/UserPasswordUpdate/UserPasswordUpdate.test.tsx +++ b/src/components/UserPasswordUpdate/UserPasswordUpdate.test.tsx @@ -1,5 +1,5 @@ -import React from 'react'; -import { act, render, screen } from '@testing-library/react'; +import React, { act } from 'react'; +import { render, screen } from '@testing-library/react'; import { MockedProvider } from '@apollo/react-testing'; import userEvent from '@testing-library/user-event'; import { I18nextProvider } from 'react-i18next'; @@ -20,7 +20,7 @@ const MOCKS = [ { request: { query: UPDATE_USER_PASSWORD_MUTATION, - variable: { + variables: { previousPassword: 'anshgoyal', newPassword: 'anshgoyalansh', confirmNewPassword: 'anshgoyalansh', @@ -49,15 +49,10 @@ async function wait(ms = 5): Promise { } describe('Testing User Password Update', () => { - const props = { - key: '123', - id: '1', - }; - const formData = { previousPassword: 'Palisadoes', newPassword: 'ThePalisadoesFoundation', - wrongPassword: 'This is wrong passoword', + wrongPassword: 'This is wrong password', confirmNewPassword: 'ThePalisadoesFoundation', }; @@ -67,7 +62,7 @@ describe('Testing User Password Update', () => { render( - + , ); @@ -102,7 +97,7 @@ describe('Testing User Password Update', () => { render( - + , ); @@ -117,7 +112,7 @@ describe('Testing User Password Update', () => { render( - + , ); diff --git a/src/components/UserPasswordUpdate/UserPasswordUpdate.tsx b/src/components/UserPasswordUpdate/UserPasswordUpdate.tsx index e1f8c64f0b..1ea75811c1 100644 --- a/src/components/UserPasswordUpdate/UserPasswordUpdate.tsx +++ b/src/components/UserPasswordUpdate/UserPasswordUpdate.tsx @@ -45,12 +45,12 @@ const UserUpdate: React.FC< !formState.newPassword || !formState.confirmNewPassword ) { - toast.error(t('passCantBeEmpty')); + toast.error(t('passCantBeEmpty') as string); return; } if (formState.newPassword !== formState.confirmNewPassword) { - toast.error(t('passNoMatch')); + toast.error(t('passNoMatch') as string); return; } @@ -64,7 +64,9 @@ const UserUpdate: React.FC< }); /* istanbul ignore next */ if (data) { - toast.success(tCommon('updatedSuccessfully', { item: 'Password' })); + toast.success( + tCommon('updatedSuccessfully', { item: 'Password' }) as string, + ); setTimeout(() => { window.location.reload(); }, 2000); diff --git a/src/components/UserPortal/CommentCard/CommentCard.test.tsx b/src/components/UserPortal/CommentCard/CommentCard.test.tsx index 35b81167fc..f02e5a606a 100644 --- a/src/components/UserPortal/CommentCard/CommentCard.test.tsx +++ b/src/components/UserPortal/CommentCard/CommentCard.test.tsx @@ -1,6 +1,6 @@ -import React from 'react'; +import React, { act } from 'react'; import { MockedProvider } from '@apollo/react-testing'; -import { act, render, screen } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import { I18nextProvider } from 'react-i18next'; import { Provider } from 'react-redux'; import { BrowserRouter } from 'react-router-dom'; diff --git a/src/components/UserPortal/CommentCard/CommentCard.tsx b/src/components/UserPortal/CommentCard/CommentCard.tsx index 026c2f1af3..9e8c46d241 100644 --- a/src/components/UserPortal/CommentCard/CommentCard.tsx +++ b/src/components/UserPortal/CommentCard/CommentCard.tsx @@ -92,7 +92,7 @@ function commentCard(props: InterfaceCommentCardProps): JSX.Element { } } catch (error: unknown) { /* istanbul ignore next */ - toast.error(error); + toast.error(error as string); } } else { try { @@ -109,7 +109,7 @@ function commentCard(props: InterfaceCommentCardProps): JSX.Element { } } catch (error: unknown) { /* istanbul ignore next */ - toast.error(error); + toast.error(error as string); } } }; diff --git a/src/components/UserPortal/ContactCard/ContactCard.test.tsx b/src/components/UserPortal/ContactCard/ContactCard.test.tsx index 7f55196bb0..03136387a7 100644 --- a/src/components/UserPortal/ContactCard/ContactCard.test.tsx +++ b/src/components/UserPortal/ContactCard/ContactCard.test.tsx @@ -1,5 +1,5 @@ -import React from 'react'; -import { act, render, screen } from '@testing-library/react'; +import React, { act } from 'react'; +import { render, screen } from '@testing-library/react'; import { MockedProvider } from '@apollo/react-testing'; import { I18nextProvider } from 'react-i18next'; diff --git a/src/components/UserPortal/DonationCard/DonationCard.test.tsx b/src/components/UserPortal/DonationCard/DonationCard.test.tsx index 4e49ed8b26..cddf62dd6c 100644 --- a/src/components/UserPortal/DonationCard/DonationCard.test.tsx +++ b/src/components/UserPortal/DonationCard/DonationCard.test.tsx @@ -1,5 +1,5 @@ -import React from 'react'; -import { act, render } from '@testing-library/react'; +import React, { act } from 'react'; +import { render } from '@testing-library/react'; import { MockedProvider } from '@apollo/react-testing'; import { I18nextProvider } from 'react-i18next'; diff --git a/src/components/UserPortal/EventCard/EventCard.tsx b/src/components/UserPortal/EventCard/EventCard.tsx index 3b9d328154..e93138ce42 100644 --- a/src/components/UserPortal/EventCard/EventCard.tsx +++ b/src/components/UserPortal/EventCard/EventCard.tsx @@ -103,7 +103,7 @@ function eventCard(props: InterfaceEventCardProps): JSX.Element { } } catch (error: unknown) { /* istanbul ignore next */ - toast.error(error); + toast.error(error as string); } } }; diff --git a/src/components/UserPortal/OrganizationCard/OrganizationCard.test.tsx b/src/components/UserPortal/OrganizationCard/OrganizationCard.test.tsx index dba4286290..ba13fe346d 100644 --- a/src/components/UserPortal/OrganizationCard/OrganizationCard.test.tsx +++ b/src/components/UserPortal/OrganizationCard/OrganizationCard.test.tsx @@ -1,5 +1,5 @@ -import React from 'react'; -import { act, fireEvent, render, screen } from '@testing-library/react'; +import React, { act } from 'react'; +import { fireEvent, render, screen } from '@testing-library/react'; import { MockedProvider } from '@apollo/react-testing'; import { I18nextProvider } from 'react-i18next'; @@ -250,7 +250,7 @@ describe('Testing OrganizationCard Component [User Portal]', () => { fireEvent.click(screen.getByTestId('joinBtn')); await wait(); - expect(toast.success).toHaveBeenCalledWith('users.MembershipRequestSent'); + expect(toast.success).toHaveBeenCalledWith('MembershipRequestSent'); }); test('send membership request to public org', async () => { diff --git a/src/components/UserPortal/OrganizationCard/OrganizationCard.tsx b/src/components/UserPortal/OrganizationCard/OrganizationCard.tsx index b88e17ae95..e1c2c23beb 100644 --- a/src/components/UserPortal/OrganizationCard/OrganizationCard.tsx +++ b/src/components/UserPortal/OrganizationCard/OrganizationCard.tsx @@ -110,23 +110,23 @@ function organizationCard(props: InterfaceOrganizationCardProps): JSX.Element { organizationId: props.id, }, }); - toast.success(t('MembershipRequestSent')); + toast.success(t('MembershipRequestSent') as string); } else { await joinPublicOrganization({ variables: { organizationId: props.id, }, }); - toast.success(t('orgJoined')); + toast.success(t('orgJoined') as string); } refetch(); } catch (error: unknown) { /* istanbul ignore next */ if (error instanceof Error) { if (error.message === 'User is already a member') { - toast.error(t('AlreadyJoined')); + toast.error(t('AlreadyJoined') as string); } else { - toast.error(t('errorOccured')); + toast.error(t('errorOccured') as string); } } } diff --git a/src/components/UserPortal/OrganizationNavbar/OrganizationNavbar.test.tsx b/src/components/UserPortal/OrganizationNavbar/OrganizationNavbar.test.tsx index 027234b641..038ff626df 100644 --- a/src/components/UserPortal/OrganizationNavbar/OrganizationNavbar.test.tsx +++ b/src/components/UserPortal/OrganizationNavbar/OrganizationNavbar.test.tsx @@ -1,7 +1,7 @@ -import React from 'react'; +import React, { act } from 'react'; import { MockedProvider } from '@apollo/react-testing'; import 'jest-localstorage-mock'; -import { act, render, screen } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import { I18nextProvider } from 'react-i18next'; import { Provider } from 'react-redux'; import { BrowserRouter, Router } from 'react-router-dom'; diff --git a/src/components/UserPortal/OrganizationSidebar/OrganizationSidebar.test.tsx b/src/components/UserPortal/OrganizationSidebar/OrganizationSidebar.test.tsx index c5fbcf335e..c08240462a 100644 --- a/src/components/UserPortal/OrganizationSidebar/OrganizationSidebar.test.tsx +++ b/src/components/UserPortal/OrganizationSidebar/OrganizationSidebar.test.tsx @@ -1,5 +1,5 @@ -import React from 'react'; -import { act, render, screen } from '@testing-library/react'; +import React, { act } from 'react'; +import { render, screen } from '@testing-library/react'; import { MockedProvider } from '@apollo/react-testing'; import { I18nextProvider } from 'react-i18next'; diff --git a/src/components/UserPortal/PeopleCard/PeopleCard.test.tsx b/src/components/UserPortal/PeopleCard/PeopleCard.test.tsx index 5cadfa923b..fd5d6c7f93 100644 --- a/src/components/UserPortal/PeopleCard/PeopleCard.test.tsx +++ b/src/components/UserPortal/PeopleCard/PeopleCard.test.tsx @@ -1,5 +1,5 @@ -import React from 'react'; -import { act, render } from '@testing-library/react'; +import React, { act } from 'react'; +import { render } from '@testing-library/react'; import { MockedProvider } from '@apollo/react-testing'; import { I18nextProvider } from 'react-i18next'; diff --git a/src/components/UserPortal/PostCard/PostCard.test.tsx b/src/components/UserPortal/PostCard/PostCard.test.tsx index 64f6670223..f7d9217308 100644 --- a/src/components/UserPortal/PostCard/PostCard.test.tsx +++ b/src/components/UserPortal/PostCard/PostCard.test.tsx @@ -1,6 +1,6 @@ -import React from 'react'; +import React, { act } from 'react'; import { MockedProvider } from '@apollo/react-testing'; -import { act, render, screen } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import { I18nextProvider } from 'react-i18next'; import { Provider } from 'react-redux'; import { BrowserRouter } from 'react-router-dom'; diff --git a/src/components/UserPortal/PostCard/PostCard.tsx b/src/components/UserPortal/PostCard/PostCard.tsx index 3eed167f65..f8fcdaebca 100644 --- a/src/components/UserPortal/PostCard/PostCard.tsx +++ b/src/components/UserPortal/PostCard/PostCard.tsx @@ -128,7 +128,7 @@ export default function postCard(props: InterfacePostCard): JSX.Element { } } catch (error: unknown) { /* istanbul ignore next */ - toast.error(error); + toast.error(error as string); } } else { try { @@ -144,7 +144,7 @@ export default function postCard(props: InterfacePostCard): JSX.Element { } } catch (error: unknown) { /* istanbul ignore next */ - toast.error(error); + toast.error(error as string); } } }; @@ -245,7 +245,7 @@ export default function postCard(props: InterfacePostCard): JSX.Element { props.fetchPosts(); // Refresh the posts toggleEditPost(); - toast.success(tCommon('updatedSuccessfully', { item: 'Post' })); + toast.success(tCommon('updatedSuccessfully', { item: 'Post' }) as string); } catch (error: unknown) { /* istanbul ignore next */ errorHandler(t, error); diff --git a/src/components/UserPortal/PromotedPost/PromotedPost.test.tsx b/src/components/UserPortal/PromotedPost/PromotedPost.test.tsx index b5d753551c..6ec8ec5de7 100644 --- a/src/components/UserPortal/PromotedPost/PromotedPost.test.tsx +++ b/src/components/UserPortal/PromotedPost/PromotedPost.test.tsx @@ -1,5 +1,5 @@ -import React from 'react'; -import { act, render, waitFor } from '@testing-library/react'; +import React, { act } from 'react'; +import { render, waitFor } from '@testing-library/react'; import { MockedProvider } from '@apollo/react-testing'; import { I18nextProvider } from 'react-i18next'; diff --git a/src/components/UserPortal/Register/Register.test.tsx b/src/components/UserPortal/Register/Register.test.tsx index cdcfb9ebc0..f9929a0588 100644 --- a/src/components/UserPortal/Register/Register.test.tsx +++ b/src/components/UserPortal/Register/Register.test.tsx @@ -1,6 +1,6 @@ import type { SetStateAction } from 'react'; -import React from 'react'; -import { act, render, screen } from '@testing-library/react'; +import React, { act } from 'react'; +import { render, screen } from '@testing-library/react'; import { MockedProvider } from '@apollo/react-testing'; import userEvent from '@testing-library/user-event'; import { I18nextProvider } from 'react-i18next'; diff --git a/src/components/UserPortal/Register/Register.tsx b/src/components/UserPortal/Register/Register.tsx index 952acf7987..11a810c955 100644 --- a/src/components/UserPortal/Register/Register.tsx +++ b/src/components/UserPortal/Register/Register.tsx @@ -57,11 +57,11 @@ export default function register(props: InterfaceRegisterProps): JSX.Element { registerVariables.lastName ) ) { - toast.error(t('invalidDetailsMessage')); // Error if fields are missing + toast.error(t('invalidDetailsMessage') as string); // Error if fields are missing } else if ( registerVariables.password !== registerVariables.confirmPassword ) { - toast.error(t('passwordNotMatch')); // Error if passwords do not match + toast.error(t('passwordNotMatch') as string); // Error if passwords do not match } else { try { await registerMutation({ @@ -73,7 +73,7 @@ export default function register(props: InterfaceRegisterProps): JSX.Element { }, }); - toast.success(t('afterRegister')); // Success message + toast.success(t('afterRegister') as string); // Success message // Reset form fields /* istanbul ignore next */ diff --git a/src/components/UserPortal/StartPostModal/StartPostModal.test.tsx b/src/components/UserPortal/StartPostModal/StartPostModal.test.tsx index 5a2e815af4..5c436705cd 100644 --- a/src/components/UserPortal/StartPostModal/StartPostModal.test.tsx +++ b/src/components/UserPortal/StartPostModal/StartPostModal.test.tsx @@ -1,7 +1,7 @@ -import React from 'react'; +import React, { act } from 'react'; import { MockedProvider } from '@apollo/react-testing'; import type { RenderResult } from '@testing-library/react'; -import { act, fireEvent, render, screen } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import { I18nextProvider } from 'react-i18next'; import userEvent from '@testing-library/user-event'; import { CREATE_POST_MUTATION } from 'GraphQl/Mutations/mutations'; diff --git a/src/components/UserPortal/StartPostModal/StartPostModal.tsx b/src/components/UserPortal/StartPostModal/StartPostModal.tsx index 060963865a..d48b8425fa 100644 --- a/src/components/UserPortal/StartPostModal/StartPostModal.tsx +++ b/src/components/UserPortal/StartPostModal/StartPostModal.tsx @@ -93,7 +93,7 @@ const startPostModal = ({ /* istanbul ignore next */ if (data) { toast.dismiss(); - toast.success(t('postNowVisibleInFeed')); + toast.success(t('postNowVisibleInFeed') as string); fetchPosts(); handleHide(); } diff --git a/src/components/UserPortal/UserNavbar/UserNavbar.test.tsx b/src/components/UserPortal/UserNavbar/UserNavbar.test.tsx index 59e3585aa0..8c3447f25a 100644 --- a/src/components/UserPortal/UserNavbar/UserNavbar.test.tsx +++ b/src/components/UserPortal/UserNavbar/UserNavbar.test.tsx @@ -1,6 +1,6 @@ -import React from 'react'; +import React, { act } from 'react'; import { MockedProvider } from '@apollo/react-testing'; -import { act, render, screen } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import { I18nextProvider } from 'react-i18next'; import { Provider } from 'react-redux'; import { BrowserRouter } from 'react-router-dom'; diff --git a/src/components/UserPortal/UserSidebar/UserSidebar.test.tsx b/src/components/UserPortal/UserSidebar/UserSidebar.test.tsx index 2984604951..7218bc745c 100644 --- a/src/components/UserPortal/UserSidebar/UserSidebar.test.tsx +++ b/src/components/UserPortal/UserSidebar/UserSidebar.test.tsx @@ -1,6 +1,6 @@ -import React from 'react'; +import React, { act } from 'react'; import type { RenderResult } from '@testing-library/react'; -import { act, fireEvent, render, screen } from '@testing-library/react'; +import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import { MockedProvider } from '@apollo/react-testing'; import { I18nextProvider } from 'react-i18next'; @@ -19,14 +19,17 @@ import useLocalStorage from 'utils/useLocalstorage'; const { setItem } = useLocalStorage(); const resizeWindow = (width: number): void => { - window.innerWidth = width; - fireEvent(window, new Event('resize')); + act(() => { + window.innerWidth = width; + fireEvent(window, new Event('resize')); + }); }; const props = { hideDrawer: true, setHideDrawer: jest.fn(), }; + const MOCKS = [ { request: { @@ -366,38 +369,48 @@ describe('Testing UserSidebar Component [User Portal]', () => { }); test('Component should be rendered properly', async () => { - renderUserSidebar('properId', link); - await wait(); + await act(async () => { + renderUserSidebar('properId', link); + await wait(); + }); }); test('Component should be rendered properly when userImage is present', async () => { - renderUserSidebar('imagePresent', link); - await wait(); + await act(async () => { + renderUserSidebar('imagePresent', link); + await wait(); + }); }); test('Component should be rendered properly when organizationImage is present', async () => { - renderUserSidebar('imagePresent', link); - await wait(); + await act(async () => { + renderUserSidebar('imagePresent', link); + await wait(); + }); }); test('Component should be rendered properly when joinedOrganizations list is empty', async () => { - renderUserSidebar('orgEmpty', link); - await wait(); + await act(async () => { + renderUserSidebar('orgEmpty', link); + await wait(); + }); }); - test('Testing Drawer when the screen size is less than or equal to 820px', () => { - resizeWindow(800); - render( - - - - - - - - - , - ); + test('Testing Drawer when the screen size is less than or equal to 820px', async () => { + await act(async () => { + resizeWindow(800); + render( + + + + + + + + + , + ); + }); expect(screen.getByText('My Organizations')).toBeInTheDocument(); expect(screen.getByText('Settings')).toBeInTheDocument(); expect(screen.getByText('Talawa User Portal')).toBeInTheDocument(); @@ -405,13 +418,21 @@ describe('Testing UserSidebar Component [User Portal]', () => { const orgsBtn = screen.getAllByTestId(/orgsBtn/i); - orgsBtn[0].click(); - expect( - orgsBtn[0].className.includes('text-white btn btn-success'), - ).toBeTruthy(); - settingsBtn.click(); - expect( - settingsBtn.className.includes('text-white btn btn-success'), - ).toBeTruthy(); + act(() => { + orgsBtn[0].click(); + }); + await waitFor(() => + expect( + orgsBtn[0].className.includes('text-white btn btn-success'), + ).toBeTruthy(), + ); + act(() => { + settingsBtn.click(); + }); + await waitFor(() => + expect( + settingsBtn.className.includes('text-white btn btn-success'), + ).toBeTruthy(), + ); }); }); diff --git a/src/components/UserPortal/UserSidebarOrg/UserSidebarOrg.test.tsx b/src/components/UserPortal/UserSidebarOrg/UserSidebarOrg.test.tsx index d9d70478b3..2f28d9afd1 100644 --- a/src/components/UserPortal/UserSidebarOrg/UserSidebarOrg.test.tsx +++ b/src/components/UserPortal/UserSidebarOrg/UserSidebarOrg.test.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { act } from 'react'; import { fireEvent, render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import 'jest-localstorage-mock'; @@ -12,7 +12,6 @@ import { Provider } from 'react-redux'; import { MockedProvider } from '@apollo/react-testing'; import { store } from 'state/store'; import { ORGANIZATIONS_LIST } from 'GraphQl/Queries/Queries'; -import { act } from 'react-dom/test-utils'; import { StaticMockLink } from 'utils/StaticMockLink'; import { REVOKE_REFRESH_TOKEN } from 'GraphQl/Mutations/mutations'; import useLocalStorage from 'utils/useLocalstorage'; @@ -332,7 +331,7 @@ describe('Testing LeftDrawerOrg component for SUPERADMIN', () => { ); await wait(); resizeWindow(800); - expect(screen.getByText(/People/i)).toBeInTheDocument(); + expect(screen.getAllByText(/People/i)[0]).toBeInTheDocument(); const peopelBtn = screen.getByTestId(/People/i); userEvent.click(peopelBtn); diff --git a/src/components/UsersTableItem/UserTableItem.test.tsx b/src/components/UsersTableItem/UserTableItem.test.tsx index bfa0a31c86..e87b41f7f2 100644 --- a/src/components/UsersTableItem/UserTableItem.test.tsx +++ b/src/components/UsersTableItem/UserTableItem.test.tsx @@ -1,6 +1,6 @@ -import React from 'react'; +import React, { act } from 'react'; import { MockedProvider } from '@apollo/react-testing'; -import { act, fireEvent, render, screen } from '@testing-library/react'; +import { fireEvent, render, screen } from '@testing-library/react'; import { I18nextProvider } from 'react-i18next'; import { toast } from 'react-toastify'; import { StaticMockLink } from 'utils/StaticMockLink'; diff --git a/src/components/UsersTableItem/UsersTableItem.tsx b/src/components/UsersTableItem/UsersTableItem.tsx index 9d6ec65187..9e94b8a9f5 100644 --- a/src/components/UsersTableItem/UsersTableItem.tsx +++ b/src/components/UsersTableItem/UsersTableItem.tsx @@ -56,7 +56,9 @@ const UsersTableItem = (props: Props): JSX.Element => { }, }); if (data) { - toast.success(tCommon('removedSuccessfully', { item: 'User' })); + toast.success( + tCommon('removedSuccessfully', { item: 'User' }) as string, + ); resetAndRefetch(); } } catch (error: unknown) { @@ -77,7 +79,7 @@ const UsersTableItem = (props: Props): JSX.Element => { }, }); if (data) { - toast.success(t('roleUpdated')); + toast.success(t('roleUpdated') as string); resetAndRefetch(); } } catch (error: unknown) { diff --git a/src/components/Venues/VenueModal.test.tsx b/src/components/Venues/VenueModal.test.tsx index 652816adaf..b299c8ff20 100644 --- a/src/components/Venues/VenueModal.test.tsx +++ b/src/components/Venues/VenueModal.test.tsx @@ -1,7 +1,7 @@ -import React from 'react'; +import React, { act } from 'react'; import { MockedProvider } from '@apollo/react-testing'; import type { RenderResult } from '@testing-library/react'; -import { act, render, screen, fireEvent } from '@testing-library/react'; +import { render, screen, fireEvent } from '@testing-library/react'; import { Provider } from 'react-redux'; import { BrowserRouter } from 'react-router-dom'; import 'jest-location-mock'; diff --git a/src/components/Venues/VenueModal.tsx b/src/components/Venues/VenueModal.tsx index fd672d4ea2..476964b910 100644 --- a/src/components/Venues/VenueModal.tsx +++ b/src/components/Venues/VenueModal.tsx @@ -77,13 +77,13 @@ const VenueModal = ({ */ const handleSubmit = useCallback(async () => { if (formState.name.trim().length === 0) { - toast.error(t('venueTitleError')); + toast.error(t('venueTitleError') as string); return; } const capacityNum = parseInt(formState.capacity); if (isNaN(capacityNum) || capacityNum <= 0) { - toast.error(t('venueCapacityError')); + toast.error(t('venueCapacityError') as string); return; } @@ -100,7 +100,9 @@ const VenueModal = ({ }); /* istanbul ignore next */ if (data) { - toast.success(edit ? t('venueUpdated') : t('venueAdded')); + toast.success( + edit ? (t('venueUpdated') as string) : (t('venueAdded') as string), + ); refetchVenues(); onHide(); setFormState({ diff --git a/src/components/plugins/DummyPlugin/DummyPlugin.test.jsx b/src/components/plugins/DummyPlugin/DummyPlugin.test.jsx index ae792966fe..e1abb52a1e 100644 --- a/src/components/plugins/DummyPlugin/DummyPlugin.test.jsx +++ b/src/components/plugins/DummyPlugin/DummyPlugin.test.jsx @@ -1,15 +1,16 @@ +import React from 'react'; import { MockedProvider } from '@apollo/react-testing'; import { render } from '@testing-library/react'; import { Provider } from 'react-redux'; import { BrowserRouter } from 'react-router-dom'; import { I18nextProvider } from 'react-i18next'; -import React from 'react'; - import { store } from 'state/store'; import DummyPlugin from './DummyPlugin'; import i18nForTest from 'utils/i18nForTest'; import { StaticMockLink } from 'utils/StaticMockLink'; + const link = new StaticMockLink([], true); + describe('Testing dummy plugin', () => { test('should render props and text elements test for the page component', () => { const { getByText } = render( @@ -21,7 +22,7 @@ describe('Testing dummy plugin', () => { - + , ); expect(getByText(/Welcome to the Dummy Plugin!/i)).toBeInTheDocument(); diff --git a/src/components/plugins/DummyPlugin/DummyPlugin.tsx b/src/components/plugins/DummyPlugin/DummyPlugin.tsx index d3c8ed8d41..5b837ea076 100644 --- a/src/components/plugins/DummyPlugin/DummyPlugin.tsx +++ b/src/components/plugins/DummyPlugin/DummyPlugin.tsx @@ -9,7 +9,7 @@ import AddOn from 'components/AddOn/AddOn'; * * @returns JSX.Element - Renders the `AddOn` component containing a welcome message. */ -function dummyPlugin(): JSX.Element { +function DummyPlugin(): JSX.Element { return (
Welcome to the Dummy Plugin!
@@ -17,8 +17,4 @@ function dummyPlugin(): JSX.Element { ); } -dummyPlugin.defaultProps = {}; - -dummyPlugin.propTypes = {}; - -export default dummyPlugin; +export default DummyPlugin; diff --git a/src/components/plugins/DummyPlugin2/DummyPlugin2.test.jsx b/src/components/plugins/DummyPlugin2/DummyPlugin2.test.jsx index bcec97367c..c2cfe03a1e 100644 --- a/src/components/plugins/DummyPlugin2/DummyPlugin2.test.jsx +++ b/src/components/plugins/DummyPlugin2/DummyPlugin2.test.jsx @@ -5,14 +5,14 @@ import { BrowserRouter } from 'react-router-dom'; import { store } from 'state/store'; import DummyPlugin2 from './DummyPlugin2'; -describe('Testing dummy plugin 2', () => { - test('should render props and text elements test for the page component', () => { +describe('Testing DummyPlugin2', () => { + test('should render DummyPlugin2 component', () => { render( - + , ); }); }); diff --git a/src/components/plugins/DummyPlugin2/DummyPlugin2.tsx b/src/components/plugins/DummyPlugin2/DummyPlugin2.tsx index 9d7e5e4498..dca6d63ee3 100644 --- a/src/components/plugins/DummyPlugin2/DummyPlugin2.tsx +++ b/src/components/plugins/DummyPlugin2/DummyPlugin2.tsx @@ -7,12 +7,8 @@ import React from 'react'; * This component currently does not have any additional functionality * or properties. */ -function dummyPlugin2(): JSX.Element { +function DummyPlugin2(): JSX.Element { return
; } -dummyPlugin2.defaultProps = {}; - -dummyPlugin2.propTypes = {}; - -export default dummyPlugin2; +export default DummyPlugin2; diff --git a/src/index.tsx b/src/index.tsx index 44cb0578b6..ec1d45ae69 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,5 +1,5 @@ import React, { Suspense } from 'react'; -import ReactDOM from 'react-dom'; +import { createRoot } from 'react-dom/client'; import { BrowserRouter } from 'react-router-dom'; import type { NormalizedCacheObject } from '@apollo/client'; import { @@ -44,6 +44,7 @@ const theme = createTheme({ }); import useLocalStorage from 'utils/useLocalstorage'; import i18n from './utils/i18n'; +import { requestMiddleware, responseMiddleware } from 'utils/timezoneUtils'; const { getItem } = useLocalStorage(); const authLink = setContext((_, { headers }) => { @@ -123,7 +124,13 @@ const splitLink = split( httpLink, ); -const combinedLink = ApolloLink.from([errorLink, authLink, splitLink]); +const combinedLink = ApolloLink.from([ + errorLink, + authLink, + requestMiddleware, + responseMiddleware, + splitLink, +]); const client: ApolloClient = new ApolloClient({ cache: new InMemoryCache(), @@ -131,7 +138,10 @@ const client: ApolloClient = new ApolloClient({ }); const fallbackLoader =
; -ReactDOM.render( +const container = document.getElementById('root'); +const root = createRoot(container!); // Note the use of '!' is to assert the container is not null + +root.render( @@ -146,5 +156,4 @@ ReactDOM.render( , - document.getElementById('root'), ); diff --git a/src/react-app-env.d.ts b/src/react-app-env.d.ts deleted file mode 100644 index 6431bc5fc6..0000000000 --- a/src/react-app-env.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// diff --git a/src/screens/BlockUser/BlockUser.test.tsx b/src/screens/BlockUser/BlockUser.test.tsx index bb490fbe3f..c851470d9b 100644 --- a/src/screens/BlockUser/BlockUser.test.tsx +++ b/src/screens/BlockUser/BlockUser.test.tsx @@ -1,6 +1,6 @@ -import React from 'react'; +import React, { act } from 'react'; import { MockedProvider } from '@apollo/react-testing'; -import { act, render, screen } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { BLOCK_USER_MUTATION, diff --git a/src/screens/BlockUser/BlockUser.tsx b/src/screens/BlockUser/BlockUser.tsx index f32ffe6091..1f36257bb1 100644 --- a/src/screens/BlockUser/BlockUser.tsx +++ b/src/screens/BlockUser/BlockUser.tsx @@ -1,5 +1,5 @@ import { useMutation, useQuery } from '@apollo/client'; -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useState, useCallback } from 'react'; import { Dropdown, Form, Table } from 'react-bootstrap'; import Button from 'react-bootstrap/Button'; import { toast } from 'react-toastify'; @@ -83,90 +83,99 @@ const Requests = (): JSX.Element => { return; } - if (showBlockedMembers == false) { - setMembersData(memberData?.organizationsMemberConnection.edges); + if (!showBlockedMembers) { + setMembersData(memberData?.organizationsMemberConnection.edges || []); } else { const blockUsers = memberData?.organizationsMemberConnection.edges.filter( (user: InterfaceMember) => user.organizationsBlockedBy.some((org) => org._id === currentUrl), ); - setMembersData(blockUsers); + setMembersData(blockUsers || []); } - }, [memberData, showBlockedMembers]); + }, [memberData, showBlockedMembers, currentUrl]); // Handler for blocking a user - const handleBlockUser = async (userId: string): Promise => { - try { - const { data } = await blockUser({ - variables: { - userId, - orgId: currentUrl, - }, - }); - /* istanbul ignore next */ - if (data) { - toast.success(t('blockedSuccessfully')); - memberRefetch(); + const handleBlockUser = useCallback( + async (userId: string): Promise => { + try { + const { data } = await blockUser({ + variables: { + userId, + orgId: currentUrl, + }, + }); + /* istanbul ignore next */ + if (data) { + toast.success(t('blockedSuccessfully') as string); + memberRefetch(); + } + } catch (error: unknown) { + /* istanbul ignore next */ + errorHandler(t, error); } - } catch (error: unknown) { - /* istanbul ignore next */ - errorHandler(t, error); - } - }; + }, + [blockUser, currentUrl, memberRefetch, t], + ); // Handler for unblocking a user - const handleUnBlockUser = async (userId: string): Promise => { - try { - const { data } = await unBlockUser({ - variables: { - userId, - orgId: currentUrl, - }, - }); - /* istanbul ignore next */ - if (data) { - toast.success(t('Un-BlockedSuccessfully')); - memberRefetch(); + const handleUnBlockUser = useCallback( + async (userId: string): Promise => { + try { + const { data } = await unBlockUser({ + variables: { + userId, + orgId: currentUrl, + }, + }); + /* istanbul ignore next */ + if (data) { + toast.success(t('Un-BlockedSuccessfully') as string); + memberRefetch(); + } + } catch (error: unknown) { + /* istanbul ignore next */ + errorHandler(t, error); } - } catch (error: unknown) { - /* istanbul ignore next */ - errorHandler(t, error); - } - }; + }, + [unBlockUser, currentUrl, memberRefetch, t], + ); // Display error if member query fails - /* istanbul ignore next */ - if (memberError) { - toast.error(memberError.message); - } + useEffect(() => { + if (memberError) { + toast.error(memberError.message); + } + }, [memberError]); // Search handler - const handleSearch = (value: string): void => { - setSearchByName(value); - memberRefetch({ - orgId: currentUrl, - firstName_contains: searchByFirstName ? value : '', - lastName_contains: searchByFirstName ? '' : value, - }); - }; + const handleSearch = useCallback( + (value: string): void => { + setSearchByName(value); + memberRefetch({ + orgId: currentUrl, + firstName_contains: searchByFirstName ? value : '', + lastName_contains: searchByFirstName ? '' : value, + }); + }, + [searchByFirstName, memberRefetch, currentUrl], + ); // Search by Enter key - const handleSearchByEnter = ( - e: React.KeyboardEvent, - ): void => { - if (e.key === 'Enter') { - const { value } = e.currentTarget; - handleSearch(value); - } - }; + const handleSearchByEnter = useCallback( + (e: React.KeyboardEvent): void => { + if (e.key === 'Enter') { + const { value } = e.currentTarget; + handleSearch(value); + } + }, + [handleSearch], + ); // Search button click handler - const handleSearchByBtnClick = (): void => { - const inputValue = - (document.getElementById('searchBlockedUsers') as HTMLInputElement) - ?.value || ''; + const handleSearchByBtnClick = useCallback((): void => { + const inputValue = searchByName; handleSearch(inputValue); - }; + }, [handleSearch, searchByName]); // Header titles for the table const headerTitles: string[] = [ @@ -195,6 +204,8 @@ const Requests = (): JSX.Element => { data-testid="searchByName" autoComplete="off" required + value={searchByName} + onChange={(e) => setSearchByName(e.target.value)} onKeyUp={handleSearchByEnter} />
{/* Table */} - {loadingMembers == false && + {loadingMembers === false && membersData.length === 0 && searchByName.length > 0 ? (
@@ -269,7 +280,7 @@ const Requests = (): JSX.Element => { {tCommon('noResultsFoundFor')} "{searchByName}"
- ) : loadingMembers == false && membersData.length === 0 ? ( + ) : loadingMembers === false && membersData.length === 0 ? (

{t('noSpammerFound')}

diff --git a/src/screens/CommunityProfile/CommunityProfile.test.tsx b/src/screens/CommunityProfile/CommunityProfile.test.tsx index 033c48464f..d7e056caa4 100644 --- a/src/screens/CommunityProfile/CommunityProfile.test.tsx +++ b/src/screens/CommunityProfile/CommunityProfile.test.tsx @@ -1,6 +1,6 @@ -import React from 'react'; +import React, { act } from 'react'; import { MockedProvider } from '@apollo/react-testing'; -import { act, render, screen } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import 'jest-localstorage-mock'; import 'jest-location-mock'; @@ -213,53 +213,53 @@ describe('Testing Community Profile Screen', () => { , ); - await wait(); + }); + await wait(); - const communityName = screen.getByPlaceholderText(/Community Name/i); - const websiteLink = screen.getByPlaceholderText(/Website Link/i); - const logo = screen.getByTestId(/fileInput/i); - const facebook = screen.getByTestId(/facebook/i); - const instagram = screen.getByTestId(/instagram/i); - const X = screen.getByTestId(/X/i); - const linkedIn = screen.getByTestId(/linkedIn/i); - const github = screen.getByTestId(/github/i); - const youtube = screen.getByTestId(/youtube/i); - const reddit = screen.getByTestId(/reddit/i); - const slack = screen.getByTestId(/slack/i); - const saveChangesBtn = screen.getByTestId(/saveChangesBtn/i); - const resetChangeBtn = screen.getByTestId(/resetChangesBtn/i); + const communityName = screen.getByPlaceholderText(/Community Name/i); + const websiteLink = screen.getByPlaceholderText(/Website Link/i); + const logo = screen.getByTestId(/fileInput/i); + const facebook = screen.getByTestId(/facebook/i); + const instagram = screen.getByTestId(/instagram/i); + const X = screen.getByTestId(/X/i); + const linkedIn = screen.getByTestId(/linkedIn/i); + const github = screen.getByTestId(/github/i); + const youtube = screen.getByTestId(/youtube/i); + const reddit = screen.getByTestId(/reddit/i); + const slack = screen.getByTestId(/slack/i); + const saveChangesBtn = screen.getByTestId(/saveChangesBtn/i); + const resetChangeBtn = screen.getByTestId(/resetChangesBtn/i); - userEvent.type(communityName, profileVariables.name); - userEvent.type(websiteLink, profileVariables.websiteLink); - userEvent.type(facebook, profileVariables.socialUrl); - userEvent.type(instagram, profileVariables.socialUrl); - userEvent.type(X, profileVariables.socialUrl); - userEvent.type(linkedIn, profileVariables.socialUrl); - userEvent.type(github, profileVariables.socialUrl); - userEvent.type(youtube, profileVariables.socialUrl); - userEvent.type(reddit, profileVariables.socialUrl); - userEvent.type(slack, profileVariables.socialUrl); - userEvent.upload(logo, profileVariables.logo); - await wait(); + userEvent.type(communityName, profileVariables.name); + userEvent.type(websiteLink, profileVariables.websiteLink); + userEvent.type(facebook, profileVariables.socialUrl); + userEvent.type(instagram, profileVariables.socialUrl); + userEvent.type(X, profileVariables.socialUrl); + userEvent.type(linkedIn, profileVariables.socialUrl); + userEvent.type(github, profileVariables.socialUrl); + userEvent.type(youtube, profileVariables.socialUrl); + userEvent.type(reddit, profileVariables.socialUrl); + userEvent.type(slack, profileVariables.socialUrl); + userEvent.upload(logo, profileVariables.logo); + await wait(); - expect(communityName).toHaveValue(profileVariables.name); - expect(websiteLink).toHaveValue(profileVariables.websiteLink); - // expect(logo).toBeTruthy(); - expect(facebook).toHaveValue(profileVariables.socialUrl); - expect(instagram).toHaveValue(profileVariables.socialUrl); - expect(X).toHaveValue(profileVariables.socialUrl); - expect(linkedIn).toHaveValue(profileVariables.socialUrl); - expect(github).toHaveValue(profileVariables.socialUrl); - expect(youtube).toHaveValue(profileVariables.socialUrl); - expect(reddit).toHaveValue(profileVariables.socialUrl); - expect(slack).toHaveValue(profileVariables.socialUrl); - expect(saveChangesBtn).not.toBeDisabled(); - expect(resetChangeBtn).not.toBeDisabled(); - await wait(); + expect(communityName).toHaveValue(profileVariables.name); + expect(websiteLink).toHaveValue(profileVariables.websiteLink); + // expect(logo).toBeTruthy(); + expect(facebook).toHaveValue(profileVariables.socialUrl); + expect(instagram).toHaveValue(profileVariables.socialUrl); + expect(X).toHaveValue(profileVariables.socialUrl); + expect(linkedIn).toHaveValue(profileVariables.socialUrl); + expect(github).toHaveValue(profileVariables.socialUrl); + expect(youtube).toHaveValue(profileVariables.socialUrl); + expect(reddit).toHaveValue(profileVariables.socialUrl); + expect(slack).toHaveValue(profileVariables.socialUrl); + expect(saveChangesBtn).not.toBeDisabled(); + expect(resetChangeBtn).not.toBeDisabled(); + await wait(); - userEvent.click(saveChangesBtn); - await wait(); - }); + userEvent.click(saveChangesBtn); + await wait(); }); test('If the queried data has some fields null then the input field should be empty', async () => { diff --git a/src/screens/CommunityProfile/CommunityProfile.tsx b/src/screens/CommunityProfile/CommunityProfile.tsx index 2f2d956b55..02f8b8aca4 100644 --- a/src/screens/CommunityProfile/CommunityProfile.tsx +++ b/src/screens/CommunityProfile/CommunityProfile.tsx @@ -146,7 +146,7 @@ const CommunityProfile = (): JSX.Element => { }, }, }); - toast.success(t('profileChangedMsg')); + toast.success(t('profileChangedMsg') as string); } catch (error: unknown) { /* istanbul ignore next */ errorHandler(t, error as Error); @@ -179,7 +179,7 @@ const CommunityProfile = (): JSX.Element => { resetPreLoginImageryId: preLoginData?._id, }, }); - toast.success(t(`resetData`)); + toast.success(t(`resetData`) as string); } catch (error: unknown) { /* istanbul ignore next */ errorHandler(t, error as Error); diff --git a/src/screens/EventManagement/EventManagement.module.css b/src/screens/EventManagement/EventManagement.module.css deleted file mode 100644 index f7e911887c..0000000000 --- a/src/screens/EventManagement/EventManagement.module.css +++ /dev/null @@ -1,8 +0,0 @@ -.content { - width: 100%; - height: 100%; - min-height: 80vh; - box-sizing: border-box; - background: #ffffff; - border-radius: 1rem; -} diff --git a/src/screens/EventManagement/EventManagement.tsx b/src/screens/EventManagement/EventManagement.tsx index adcc536a08..10207f5c6a 100644 --- a/src/screens/EventManagement/EventManagement.tsx +++ b/src/screens/EventManagement/EventManagement.tsx @@ -1,18 +1,16 @@ import React, { useState } from 'react'; import Row from 'react-bootstrap/Row'; import Col from 'react-bootstrap/Col'; -import styles from './EventManagement.module.css'; import { Navigate, useNavigate, useParams } from 'react-router-dom'; -import { ReactComponent as AngleLeftIcon } from 'assets/svgs/angleLeft.svg'; -import { ReactComponent as EventDashboardIcon } from 'assets/svgs/eventDashboard.svg'; +import { FaChevronLeft, FaTasks } from 'react-icons/fa'; +import { MdOutlineDashboard } from 'react-icons/md'; import { ReactComponent as EventRegistrantsIcon } from 'assets/svgs/people.svg'; -import { ReactComponent as EventActionsIcon } from 'assets/svgs/settings.svg'; +import { IoMdStats } from 'react-icons/io'; import { ReactComponent as EventAgendaItemsIcon } from 'assets/svgs/agenda-items.svg'; -import { ReactComponent as EventStatisticsIcon } from 'assets/svgs/eventStats.svg'; import { useTranslation } from 'react-i18next'; -import { Button } from 'react-bootstrap'; +import { Button, Dropdown } from 'react-bootstrap'; import EventDashboard from 'components/EventManagement/Dashboard/EventDashboard'; -import EventActionItems from 'components/EventManagement/EventActionItems/EventActionItems'; +import OrganizationActionItems from 'screens/OrganizationActionItems/OrganizationActionItems'; import EventAgendaItems from 'components/EventManagement/EventAgendaItems/EventAgendaItems'; import useLocalStorage from 'utils/useLocalstorage'; @@ -27,7 +25,7 @@ const eventDashboardTabs: { }[] = [ { value: 'dashboard', - icon: , + icon: , }, { value: 'registrants', @@ -35,7 +33,7 @@ const eventDashboardTabs: { }, { value: 'eventActions', - icon: , + icon: , }, { value: 'eventAgendas', @@ -43,7 +41,7 @@ const eventDashboardTabs: { }, { value: 'eventStats', - icon: , + icon: , }, ]; @@ -107,15 +105,6 @@ const EventManagement = (): JSX.Element => { // State hook for managing the currently selected tab const [tab, setTab] = useState('dashboard'); - /** - * Handles tab button clicks to update the selected tab. - * - * @param value - The value representing the tab to select - */ - const handleClick = (value: TabOptions): void => { - setTab(value); - }; - /** * Renders a button for each tab with the appropriate icon and label. * @@ -133,86 +122,123 @@ const EventManagement = (): JSX.Element => { const selected = tab === value; const variant = selected ? 'success' : 'light'; const translatedText = t(value); + const className = selected - ? 'px-4' - : 'text-secondary border-secondary-subtle px-4'; + ? 'px-4 d-flex align-items-center shadow' + : 'text-secondary bg-white px-4 d-flex align-items-center rounded shadow'; const props = { variant, className, - key: value, + style: { height: '2.5rem' }, size: 'sm' as 'sm' | 'lg', - onClick: () => handleClick(value), + onClick: () => setTab(value), 'data-testid': `${value}Btn`, }; return ( - ); }; + const handleBack = (): void => { + /*istanbul ignore next*/ + userRole === 'USER' + ? navigate(`/user/events/${orgId}`) + : navigate(`/orgevents/${orgId}`); + }; + return ( -
-
- { - /*istanbul ignore next*/ - userRole === 'USER' - ? navigate(`/user/events/${orgId}`) - : navigate(`/orgevents/${orgId}`); - }} - className="mt-1" - /> -
- {eventDashboardTabs.map(renderButton)} -
-
- - - {/* Render content based on the selected tab */} - {(() => { - switch (tab) { - case 'dashboard': - return ( -
- -
- ); - case 'registrants': - return ( -
-

Event Registrants

-
- ); - case 'eventActions': - return ( -
- -
- ); - case 'eventAgendas': - return ( -
- -
- ); - case 'eventStats': - return ( -
-

Event Statistics

-
- ); - } - })()} +
+ + +
+ + {eventDashboardTabs.map(renderButton)} +
+ + + + {t(tab)} + + + {/* Render dropdown items for each settings category */} + {eventDashboardTabs.map(({ value, icon }, index) => ( + setTab(value) + } + className={`d-flex gap-2 ${tab === value && 'text-secondary'}`} + > + {icon} {t(value)} + + ))} + + + + +
+
+ + {/* Render content based on the selected settings category */} + {(() => { + switch (tab) { + case 'dashboard': + return ( +
+ +
+ ); + case 'registrants': + return ( +
+

Event Registrants

+
+ ); + case 'eventActions': + return ( +
+ +
+ ); + case 'eventAgendas': + return ( +
+ +
+ ); + case 'eventStats': + return ( +
+

Event Statistics

+
+ ); + } + })()}
); }; diff --git a/src/screens/ForgotPassword/ForgotPassword.test.tsx b/src/screens/ForgotPassword/ForgotPassword.test.tsx index eb62cb0178..be1b1706f8 100644 --- a/src/screens/ForgotPassword/ForgotPassword.test.tsx +++ b/src/screens/ForgotPassword/ForgotPassword.test.tsx @@ -1,13 +1,13 @@ import React from 'react'; import { MockedProvider } from '@apollo/react-testing'; -import { act, render, screen } from '@testing-library/react'; +import { act, render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import 'jest-localstorage-mock'; import 'jest-location-mock'; import { I18nextProvider } from 'react-i18next'; import { Provider } from 'react-redux'; import { BrowserRouter } from 'react-router-dom'; -import { ToastContainer } from 'react-toastify'; +import { toast, ToastContainer } from 'react-toastify'; import { GENERATE_OTP_MUTATION } from 'GraphQl/Mutations/mutations'; import { store } from 'state/store'; @@ -19,6 +19,14 @@ import useLocalStorage from 'utils/useLocalstorage'; const { setItem, removeItem } = useLocalStorage(); +jest.mock('react-toastify', () => ({ + toast: { + success: jest.fn(), + error: jest.fn(), + warn: jest.fn(), + }, +})); + const MOCKS = [ { request: { @@ -159,7 +167,9 @@ describe('Testing Forgot Password screen', () => { ); userEvent.click(screen.getByText('Get OTP')); - await wait(); + await waitFor(() => { + expect(toast.success).toHaveBeenCalled(); + }); }); test('Testing forgot password functionality', async () => { @@ -294,7 +304,6 @@ describe('Testing Forgot Password screen', () => { - @@ -310,11 +319,9 @@ describe('Testing Forgot Password screen', () => { ); userEvent.click(screen.getByText('Get OTP')); - await wait(); - - expect( - await screen.findByText(translations.emailNotRegistered), - ).toBeInTheDocument(); + await waitFor(() => { + expect(toast.warn).toHaveBeenCalledWith(translations.emailNotRegistered); + }); }); test('Testing forgot password functionality, when there is an error except unregistered email and api failure', async () => { @@ -323,7 +330,6 @@ describe('Testing Forgot Password screen', () => { - @@ -331,11 +337,9 @@ describe('Testing Forgot Password screen', () => { , ); userEvent.click(screen.getByText('Get OTP')); - await wait(); - - expect( - await screen.findByText(translations.errorSendingMail), - ).toBeInTheDocument(); + await waitFor(() => { + expect(toast.error).toHaveBeenCalledWith(translations.errorSendingMail); + }); }); test('Testing forgot password functionality, when talawa api failed', async () => { @@ -347,7 +351,6 @@ describe('Testing Forgot Password screen', () => { - @@ -362,11 +365,11 @@ describe('Testing Forgot Password screen', () => { formData.email, ); userEvent.click(screen.getByText('Get OTP')); - await wait(); - - expect( - await screen.findByText(translations.talawaApiUnavailable), - ).toBeInTheDocument(); + await waitFor(() => { + expect(toast.error).toHaveBeenCalledWith( + translations.talawaApiUnavailable, + ); + }); }); test('Testing forgot password functionality, when otp token is not present', async () => { diff --git a/src/screens/ForgotPassword/ForgotPassword.tsx b/src/screens/ForgotPassword/ForgotPassword.tsx index 06761879a5..ba7b5d21c7 100644 --- a/src/screens/ForgotPassword/ForgotPassword.tsx +++ b/src/screens/ForgotPassword/ForgotPassword.tsx @@ -90,20 +90,16 @@ const ForgotPassword = (): JSX.Element => { }, }); - if (data) { - setItem('otpToken', data.otp.otpToken); - toast.success(t('OTPsent')); - setShowEnterEmail(false); - } + setItem('otpToken', data.otp.otpToken); + toast.success(t('OTPsent')); + setShowEnterEmail(false); } catch (error: unknown) { - if (error instanceof Error) { - if (error.message === 'User not found') { - toast.warn(tErrors('emailNotRegistered')); - } else if (error.message === 'Failed to fetch') { - toast.error(tErrors('talawaApiUnavailable')); - } else { - toast.error(tErrors('errorSendingMail')); - } + if ((error as Error).message === 'User not found') { + toast.warn(tErrors('emailNotRegistered')); + } else if ((error as Error).message === 'Failed to fetch') { + toast.error(tErrors('talawaApiUnavailable')); + } else { + toast.error(tErrors('errorSendingMail')); } } }; @@ -120,7 +116,7 @@ const ForgotPassword = (): JSX.Element => { const { userOtp, newPassword, confirmNewPassword } = forgotPassFormData; if (newPassword !== confirmNewPassword) { - toast.error(t('passwordMismatches')); + toast.error(t('passwordMismatches') as string); return; } @@ -141,7 +137,7 @@ const ForgotPassword = (): JSX.Element => { /* istanbul ignore next */ if (data) { - toast.success(t('passwordChanges')); + toast.success(t('passwordChanges') as string); setShowEnterEmail(true); setForgotPassFormData({ userOtp: '', diff --git a/src/screens/FundCampaignPledge/FundCampaignPledge.test.tsx b/src/screens/FundCampaignPledge/FundCampaignPledge.test.tsx index 25e049e19d..3fb5993775 100644 --- a/src/screens/FundCampaignPledge/FundCampaignPledge.test.tsx +++ b/src/screens/FundCampaignPledge/FundCampaignPledge.test.tsx @@ -85,17 +85,6 @@ describe('Testing Campaign Pledge Screen', () => { jest.clearAllMocks(); }); - afterEach(() => { - cleanup(); - }); - - it('should render the Campaign Pledge screen', async () => { - renderFundCampaignPledge(link1); - await waitFor(() => { - expect(screen.getByTestId('searchPledger')).toBeInTheDocument(); - }); - }); - it('should redirect to fallback URL if URL params are undefined', async () => { render( @@ -122,6 +111,13 @@ describe('Testing Campaign Pledge Screen', () => { }); }); + it('should render the Campaign Pledge screen', async () => { + renderFundCampaignPledge(link1); + await waitFor(() => { + expect(screen.getByTestId('searchPledger')).toBeInTheDocument(); + }); + }); + it('open and closes Create Pledge modal', async () => { renderFundCampaignPledge(link1); @@ -213,6 +209,19 @@ describe('Testing Campaign Pledge Screen', () => { await waitFor(() => { expect(screen.getByTestId('searchPledger')).toBeInTheDocument(); }); + const searchPledger = await screen.findByTestId('searchPledger'); + expect(searchPledger).toBeInTheDocument(); + + fireEvent.click(screen.getByTestId('filter')); + await waitFor(() => { + expect(screen.getByTestId('amount_DESC')).toBeInTheDocument(); + }); + fireEvent.click(screen.getByTestId('amount_DESC')); + + await waitFor(() => { + expect(screen.getByText('John Doe')).toBeInTheDocument(); + expect(screen.queryByText('Jane Doe')).toBeInTheDocument(); + }); expect(screen.getByText('John Doe')).toBeInTheDocument(); expect(screen.getByText('John Doe2')).toBeInTheDocument(); diff --git a/src/screens/FundCampaignPledge/FundCampaignPledge.tsx b/src/screens/FundCampaignPledge/FundCampaignPledge.tsx index 9b593f2951..f7e339dc89 100644 --- a/src/screens/FundCampaignPledge/FundCampaignPledge.tsx +++ b/src/screens/FundCampaignPledge/FundCampaignPledge.tsx @@ -130,7 +130,7 @@ const fundCampaignPledge = (): JSX.Element => { 'YYYY-MM-DD', ).toDate(); - const { pledges, totalPledged } = useMemo(() => { + const { pledges, totalPledged, fundName } = useMemo(() => { let totalPledged = 0; const pledges = pledgeData?.getFundraisingCampaigns[0].pledges.filter((pledge) => { @@ -141,7 +141,9 @@ const fundCampaignPledge = (): JSX.Element => { return fullName.toLowerCase().includes(search); }); }) ?? []; - return { pledges, totalPledged }; + const fundName = + pledgeData?.getFundraisingCampaigns[0].fundId.name ?? tCommon('Funds'); + return { pledges, totalPledged, fundName }; }, [pledgeData, searchTerm]); useEffect(() => { @@ -392,7 +394,7 @@ const fundCampaignPledge = (): JSX.Element => { () => history.go(-2) } > - {tCommon('Funds')} + {fundName} { () => history.back() } > - {t('campaigns')} + {campaignInfo?.name} {t('pledges')} diff --git a/src/screens/FundCampaignPledge/PledgeDeleteModal.tsx b/src/screens/FundCampaignPledge/PledgeDeleteModal.tsx index 2ecf6c7c23..288baa16ef 100644 --- a/src/screens/FundCampaignPledge/PledgeDeleteModal.tsx +++ b/src/screens/FundCampaignPledge/PledgeDeleteModal.tsx @@ -61,7 +61,7 @@ const PledgeDeleteModal: React.FC = ({ }); refetchPledge(); hide(); - toast.success(t('pledgeDeleted')); + toast.success(t('pledgeDeleted') as string); } catch (error: unknown) { toast.error((error as Error).message); } diff --git a/src/screens/FundCampaignPledge/PledgeModal.tsx b/src/screens/FundCampaignPledge/PledgeModal.tsx index ab126101de..782a051277 100644 --- a/src/screens/FundCampaignPledge/PledgeModal.tsx +++ b/src/screens/FundCampaignPledge/PledgeModal.tsx @@ -157,7 +157,7 @@ const PledgeModal: React.FC = ({ ...updatedFields, }, }); - toast.success(t('pledgeUpdated')); + toast.success(t('pledgeUpdated') as string); refetchPledge(); hide(); } catch (error: unknown) { @@ -183,7 +183,7 @@ const PledgeModal: React.FC = ({ }, }); - toast.success(t('pledgeCreated')); + toast.success(t('pledgeCreated') as string); refetchPledge(); setFormState({ pledgeUsers: [], diff --git a/src/screens/FundCampaignPledge/PledgesMocks.ts b/src/screens/FundCampaignPledge/PledgesMocks.ts index 0dc9c10e56..2a583fa887 100644 --- a/src/screens/FundCampaignPledge/PledgesMocks.ts +++ b/src/screens/FundCampaignPledge/PledgesMocks.ts @@ -6,7 +6,48 @@ import { import { MEMBERS_LIST } from 'GraphQl/Queries/Queries'; import { FUND_CAMPAIGN_PLEDGE } from 'GraphQl/Queries/fundQueries'; +const memberList = { + request: { + query: MEMBERS_LIST, + variables: { + id: 'orgId', + }, + }, + result: { + data: { + organizations: [ + { + _id: 'orgId', + members: [ + { + createdAt: '2023-04-13T04:53:17.742Z', + email: 'testuser4@example.com', + firstName: 'John', + image: 'img-url', + lastName: 'Doe', + organizationsBlockedBy: [], + __typename: 'User', + _id: '1', + }, + { + createdAt: '2024-04-13T04:53:17.742Z', + email: 'testuser2@example.com', + firstName: 'Anna', + image: null, + lastName: 'Bradley', + organizationsBlockedBy: [], + __typename: 'User', + _id: '2', + }, + ], + }, + ], + }, + }, +}; + export const MOCKS = [ + memberList, { request: { query: FUND_CAMPAIGN_PLEDGE, @@ -21,6 +62,9 @@ export const MOCKS = [ data: { getFundraisingCampaigns: [ { + fundId: { + name: 'Fund 1', + }, name: 'Campaign Name', fundingGoal: 1000, currency: 'USD', @@ -40,60 +84,6 @@ export const MOCKS = [ lastName: 'Doe', image: 'img-url', }, - { - _id: '2', - firstName: 'John', - lastName: 'Doe2', - image: 'img-url2', - }, - { - _id: '3', - firstName: 'John', - lastName: 'Doe3', - image: 'img-url3', - }, - { - _id: '4', - firstName: 'John', - lastName: 'Doe4', - image: 'img-url4', - }, - { - _id: '5', - firstName: 'John', - lastName: 'Doe5', - image: 'img-url5', - }, - { - _id: '6', - firstName: 'John', - lastName: 'Doe6', - image: 'img-url6', - }, - { - _id: '7', - firstName: 'John', - lastName: 'Doe7', - image: 'img-url7', - }, - { - _id: '8', - firstName: 'John', - lastName: 'Doe8', - image: 'img-url8', - }, - { - _id: '9', - firstName: 'John', - lastName: 'Doe9', - image: 'img-url9', - }, - { - _id: '10', - firstName: 'John', - lastName: 'Doe10', - image: null, - }, ], }, { @@ -131,6 +121,9 @@ export const MOCKS = [ data: { getFundraisingCampaigns: [ { + fundId: { + name: 'Fund 1', + }, name: 'Campaign Name', fundingGoal: 1000, currency: 'USD', @@ -187,6 +180,9 @@ export const MOCKS = [ data: { getFundraisingCampaigns: [ { + fundId: { + name: 'Fund 1', + }, name: 'Campaign Name', fundingGoal: 1000, currency: 'USD', @@ -206,6 +202,60 @@ export const MOCKS = [ lastName: 'Doe', image: null, }, + { + _id: '2', + firstName: 'John', + lastName: 'Doe2', + image: 'img-url2', + }, + { + _id: '3', + firstName: 'John', + lastName: 'Doe3', + image: 'img-url3', + }, + { + _id: '4', + firstName: 'John', + lastName: 'Doe4', + image: 'img-url4', + }, + { + _id: '5', + firstName: 'John', + lastName: 'Doe5', + image: 'img-url5', + }, + { + _id: '6', + firstName: 'John', + lastName: 'Doe6', + image: 'img-url6', + }, + { + _id: '7', + firstName: 'John', + lastName: 'Doe7', + image: 'img-url7', + }, + { + _id: '8', + firstName: 'John', + lastName: 'Doe8', + image: 'img-url8', + }, + { + _id: '9', + firstName: 'John', + lastName: 'Doe9', + image: 'img-url9', + }, + { + _id: '10', + firstName: 'John', + lastName: 'Doe10', + image: null, + }, ], }, { @@ -243,6 +293,9 @@ export const MOCKS = [ data: { getFundraisingCampaigns: [ { + fundId: { + name: 'Fund 1', + }, name: 'Campaign Name', fundingGoal: 1000, currency: 'USD', @@ -303,6 +356,7 @@ export const MOCKS = [ ]; export const MOCKS_FUND_CAMPAIGN_PLEDGE_ERROR = [ + memberList, { request: { query: FUND_CAMPAIGN_PLEDGE, @@ -318,6 +372,7 @@ export const MOCKS_FUND_CAMPAIGN_PLEDGE_ERROR = [ ]; export const MOCKS_DELETE_PLEDGE_ERROR = [ + memberList, { request: { query: DELETE_PLEDGE, @@ -330,6 +385,7 @@ export const MOCKS_DELETE_PLEDGE_ERROR = [ ]; export const EMPTY_MOCKS = [ + memberList, { request: { query: FUND_CAMPAIGN_PLEDGE, @@ -344,6 +400,9 @@ export const EMPTY_MOCKS = [ data: { getFundraisingCampaigns: [ { + fundId: { + name: 'Fund 1', + }, name: 'Campaign Name', fundingGoal: 1000, currency: 'USD', @@ -358,45 +417,7 @@ export const EMPTY_MOCKS = [ ]; export const PLEDGE_MODAL_MOCKS = [ - { - request: { - query: MEMBERS_LIST, - variables: { - id: 'orgId', - }, - }, - result: { - data: { - organizations: [ - { - _id: 'orgId', - members: [ - { - createdAt: '2023-04-13T04:53:17.742Z', - email: 'testuser4@example.com', - firstName: 'John', - image: 'img-url', - lastName: 'Doe', - organizationsBlockedBy: [], - __typename: 'User', - _id: '1', - }, - { - createdAt: '2024-04-13T04:53:17.742Z', - email: 'testuser2@example.com', - firstName: 'Anna', - image: null, - lastName: 'Bradley', - organizationsBlockedBy: [], - __typename: 'User', - _id: '2', - }, - ], - }, - ], - }, - }, - }, + memberList, { request: { query: UPDATE_PLEDGE, diff --git a/src/screens/LoginPage/LoginPage.test.tsx b/src/screens/LoginPage/LoginPage.test.tsx index 1eeb683654..3733a454dd 100644 --- a/src/screens/LoginPage/LoginPage.test.tsx +++ b/src/screens/LoginPage/LoginPage.test.tsx @@ -1,6 +1,6 @@ -import React from 'react'; +import React, { act } from 'react'; import { MockedProvider } from '@apollo/react-testing'; -import { act, render, screen, fireEvent, within } from '@testing-library/react'; +import { render, screen, fireEvent, within } from '@testing-library/react'; import { Provider } from 'react-redux'; import { BrowserRouter } from 'react-router-dom'; import userEvent from '@testing-library/user-event'; diff --git a/src/screens/LoginPage/LoginPage.tsx b/src/screens/LoginPage/LoginPage.tsx index d69f4f88d5..56b9012ee8 100644 --- a/src/screens/LoginPage/LoginPage.tsx +++ b/src/screens/LoginPage/LoginPage.tsx @@ -175,7 +175,7 @@ const loginPage = (): JSX.Element => { return data.recaptcha; } catch (error) { /* istanbul ignore next */ - toast.error(t('captchaError')); + toast.error(t('captchaError') as string); } }; @@ -198,7 +198,7 @@ const loginPage = (): JSX.Element => { const isVerified = await verifyRecaptcha(recaptchaToken); /* istanbul ignore next */ if (!isVerified) { - toast.error(t('Please_check_the_captcha')); + toast.error(t('Please_check_the_captcha') as string); return; } const isValidatedString = (value: string): boolean => @@ -239,7 +239,9 @@ const loginPage = (): JSX.Element => { /* istanbul ignore next */ if (signUpData) { toast.success( - t(role === 'admin' ? 'successfullyRegistered' : 'afterRegister'), + t( + role === 'admin' ? 'successfullyRegistered' : 'afterRegister', + ) as string, ); setShowTab('LOGIN'); setSignFormState({ @@ -256,20 +258,20 @@ const loginPage = (): JSX.Element => { errorHandler(t, error); } } else { - toast.warn(t('passwordMismatches')); + toast.warn(t('passwordMismatches') as string); } } else { if (!isValidatedString(signfirstName)) { - toast.warn(t('firstName_invalid')); + toast.warn(t('firstName_invalid') as string); } if (!isValidatedString(signlastName)) { - toast.warn(t('lastName_invalid')); + toast.warn(t('lastName_invalid') as string); } if (!validatePassword(signPassword)) { - toast.warn(t('password_invalid')); + toast.warn(t('password_invalid') as string); } if (signEmail.length < 8) { - toast.warn(t('email_invalid')); + toast.warn(t('email_invalid') as string); } } }; @@ -279,7 +281,7 @@ const loginPage = (): JSX.Element => { const isVerified = await verifyRecaptcha(recaptchaToken); /* istanbul ignore next */ if (!isVerified) { - toast.error(t('Please_check_the_captcha')); + toast.error(t('Please_check_the_captcha') as string); return; } @@ -300,7 +302,7 @@ const loginPage = (): JSX.Element => { appUserProfile.isSuperAdmin || appUserProfile.adminFor.length !== 0; if (role === 'admin' && !isAdmin) { - toast.warn(tErrors('notAuthorised')); + toast.warn(tErrors('notAuthorised') as string); return; } const loggedInUserId = user._id; @@ -324,7 +326,7 @@ const loginPage = (): JSX.Element => { navigate(role === 'admin' ? '/orglist' : '/user/organizations'); } else { - toast.warn(tErrors('notFound')); + toast.warn(tErrors('notFound') as string); } } catch (error) { /* istanbul ignore next */ diff --git a/src/screens/ManageTag/ManageTag.tsx b/src/screens/ManageTag/ManageTag.tsx index 5262724892..86a44fb169 100644 --- a/src/screens/ManageTag/ManageTag.tsx +++ b/src/screens/ManageTag/ManageTag.tsx @@ -113,7 +113,7 @@ function ManageTag(): JSX.Element { userTagAssignedMembersRefetch(); toggleUnassignTagModal(); - toast.success(t('successfullyUnassigned')); + toast.success(t('successfullyUnassigned') as string); } catch (error: unknown) { /* istanbul ignore next */ if (error instanceof Error) { diff --git a/src/screens/MemberDetail/MemberDetail.test.tsx b/src/screens/MemberDetail/MemberDetail.test.tsx index e094354552..7b3707754c 100644 --- a/src/screens/MemberDetail/MemberDetail.test.tsx +++ b/src/screens/MemberDetail/MemberDetail.test.tsx @@ -442,7 +442,6 @@ describe('MemberDetail', () => { test('should display warnings for blank form submission', async () => { jest.spyOn(toast, 'warning'); const props = { - key: '123', id: '1', toggleStateValue: jest.fn(), }; @@ -452,7 +451,7 @@ describe('MemberDetail', () => { - + @@ -467,6 +466,7 @@ describe('MemberDetail', () => { expect(toast.warning).toHaveBeenCalledWith('Last Name cannot be blank!'); expect(toast.warning).toHaveBeenCalledWith('Email cannot be blank!'); }); + test('display admin', async () => { const props = { id: 'rishav-jha-mech', diff --git a/src/screens/MemberDetail/MemberDetail.tsx b/src/screens/MemberDetail/MemberDetail.tsx index ef623146d4..6d92ccbb4a 100644 --- a/src/screens/MemberDetail/MemberDetail.tsx +++ b/src/screens/MemberDetail/MemberDetail.tsx @@ -194,7 +194,7 @@ const MemberDetail: React.FC = ({ id }): JSX.Element => { setItem('Email', email); setItem('UserImage', image); } - toast.success(tCommon('successfullyUpdated')); + toast.success(tCommon('successfullyUpdated') as string); } } catch (error: unknown) { if (error instanceof Error) { diff --git a/src/screens/OrgContribution/OrgContribution.test.tsx b/src/screens/OrgContribution/OrgContribution.test.tsx index d2abc134df..f9f63c6807 100644 --- a/src/screens/OrgContribution/OrgContribution.test.tsx +++ b/src/screens/OrgContribution/OrgContribution.test.tsx @@ -1,6 +1,6 @@ -import React from 'react'; +import React, { act } from 'react'; import { MockedProvider } from '@apollo/react-testing'; -import { act, render } from '@testing-library/react'; +import { render } from '@testing-library/react'; import { Provider } from 'react-redux'; import { BrowserRouter } from 'react-router-dom'; import 'jest-location-mock'; diff --git a/src/screens/OrgList/OrgList.test.tsx b/src/screens/OrgList/OrgList.test.tsx index e7e0f7d4be..5b889ff07d 100644 --- a/src/screens/OrgList/OrgList.test.tsx +++ b/src/screens/OrgList/OrgList.test.tsx @@ -515,22 +515,21 @@ describe('Organisations Page testing as Admin', () => { ); await wait(); + }); + const sortDropdown = await waitFor(() => screen.getByTestId('sort')); + expect(sortDropdown).toBeInTheDocument(); - const searchInput = screen.getByTestId('sort'); - expect(searchInput).toBeInTheDocument(); - - const inputText = screen.getByTestId('sortOrgs'); + const sortToggle = screen.getByTestId('sortOrgs'); - fireEvent.click(inputText); - const toggleText = screen.getByTestId('latest'); + fireEvent.click(sortToggle); + const latestOption = await waitFor(() => screen.getByTestId('latest')); - fireEvent.click(toggleText); + fireEvent.click(latestOption); - expect(searchInput).toBeInTheDocument(); - fireEvent.click(inputText); - const toggleTite = screen.getByTestId('oldest'); - fireEvent.click(toggleTite); - expect(searchInput).toBeInTheDocument(); - }); + expect(sortDropdown).toBeInTheDocument(); + fireEvent.click(sortToggle); + const oldestOption = await waitFor(() => screen.getByTestId('oldest')); + fireEvent.click(oldestOption); + expect(sortDropdown).toBeInTheDocument(); }); }); diff --git a/src/screens/OrgList/OrgList.tsx b/src/screens/OrgList/OrgList.tsx index 5c91678217..7023bfa9b1 100644 --- a/src/screens/OrgList/OrgList.tsx +++ b/src/screens/OrgList/OrgList.tsx @@ -169,11 +169,11 @@ function orgList(): JSX.Element { const triggerCreateSampleOrg = (): void => { createSampleOrganization() .then(() => { - toast.success(t('sampleOrgSuccess')); + toast.success(t('sampleOrgSuccess') as string); window.location.reload(); }) .catch(() => { - toast.error(t('sampleOrgDuplicate')); + toast.error(t('sampleOrgDuplicate') as string); }); }; diff --git a/src/screens/OrgPost/OrgPost.test.tsx b/src/screens/OrgPost/OrgPost.test.tsx index 465da98642..9829589350 100644 --- a/src/screens/OrgPost/OrgPost.test.tsx +++ b/src/screens/OrgPost/OrgPost.test.tsx @@ -1,11 +1,11 @@ import { MockedProvider } from '@apollo/react-testing'; -import { act, fireEvent, render, screen } from '@testing-library/react'; +import { fireEvent, render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import 'jest-location-mock'; import { I18nextProvider } from 'react-i18next'; import { Provider } from 'react-redux'; import { BrowserRouter } from 'react-router-dom'; -import React from 'react'; +import React, { act } from 'react'; import { CREATE_POST_MUTATION } from 'GraphQl/Mutations/mutations'; import { ORGANIZATION_POST_LIST } from 'GraphQl/Queries/Queries'; import { ToastContainer } from 'react-toastify'; @@ -286,25 +286,34 @@ describe('Organisation Post Page', () => { , ); + }); + await wait(); - await wait(); - - const searchInput = screen.getByTestId('searchByName'); - expect(searchInput).toHaveAttribute('placeholder', 'Search By Title'); + const searchInput = screen.getByTestId('searchByName'); + expect(searchInput).toHaveAttribute('placeholder', 'Search By Title'); - const inputText = screen.getByTestId('searchBy'); + const inputText = screen.getByTestId('searchBy'); + await act(async () => { fireEvent.click(inputText); - const toggleText = screen.getByTestId('Text'); + }); + const toggleText = screen.getByTestId('Text'); + + await act(async () => { fireEvent.click(toggleText); + }); - expect(searchInput).toHaveAttribute('placeholder', 'Search By Text'); + expect(searchInput).toHaveAttribute('placeholder', 'Search By Text'); + await act(async () => { fireEvent.click(inputText); - const toggleTite = screen.getByTestId('searchTitle'); + }); + const toggleTite = screen.getByTestId('searchTitle'); + await act(async () => { fireEvent.click(toggleTite); - expect(searchInput).toHaveAttribute('placeholder', 'Search By Title'); }); + + expect(searchInput).toHaveAttribute('placeholder', 'Search By Title'); }); test('Testing search latest and oldest toggle', async () => { await act(async () => { @@ -320,25 +329,34 @@ describe('Organisation Post Page', () => { , ); + }); + await wait(); - await wait(); - - const searchInput = screen.getByTestId('sort'); - expect(searchInput).toBeInTheDocument(); + const searchInput = screen.getByTestId('sort'); + expect(searchInput).toBeInTheDocument(); - const inputText = screen.getByTestId('sortpost'); + const inputText = screen.getByTestId('sortpost'); + await act(async () => { fireEvent.click(inputText); - const toggleText = screen.getByTestId('latest'); + }); + + const toggleText = screen.getByTestId('latest'); + await act(async () => { fireEvent.click(toggleText); + }); - expect(searchInput).toBeInTheDocument(); + expect(searchInput).toBeInTheDocument(); + await act(async () => { fireEvent.click(inputText); - const toggleTite = screen.getByTestId('oldest'); + }); + + const toggleTite = screen.getByTestId('oldest'); + await act(async () => { fireEvent.click(toggleTite); - expect(searchInput).toBeInTheDocument(); }); + expect(searchInput).toBeInTheDocument(); }); test('After creating a post, the data should be refetched', async () => { const refetchMock = jest.fn(); @@ -575,34 +593,43 @@ describe('Organisation Post Page', () => { , ); + }); + await wait(); - await wait(); - + await act(async () => { userEvent.click(screen.getByTestId('createPostModalBtn')); + }); - const postTitleInput = screen.getByTestId('modalTitle'); + const postTitleInput = screen.getByTestId('modalTitle'); + await act(async () => { fireEvent.change(postTitleInput, { target: { value: 'Test Post' } }); + }); - const postInfoTextarea = screen.getByTestId('modalinfo'); + const postInfoTextarea = screen.getByTestId('modalinfo'); + await act(async () => { fireEvent.change(postInfoTextarea, { target: { value: 'Test post information' }, }); + }); - const videoFile = new File(['video content'], 'video.mp4', { - type: 'video/mp4', - }); + const videoFile = new File(['video content'], 'video.mp4', { + type: 'video/mp4', + }); + await act(async () => { userEvent.upload(screen.getByTestId('addMediaField'), videoFile); + }); - // Check if the video is displayed - const videoPreview = await screen.findByTestId('videoPreview'); - expect(videoPreview).toBeInTheDocument(); + // Check if the video is displayed + const videoPreview = await screen.findByTestId('videoPreview'); + expect(videoPreview).toBeInTheDocument(); - // Check if the close button for the video works - const closeVideoPreviewButton = screen.getByTestId('mediaCloseButton'); + // Check if the close button for the video works + const closeVideoPreviewButton = screen.getByTestId('mediaCloseButton'); + await act(async () => { fireEvent.click(closeVideoPreviewButton); - expect(videoPreview).not.toBeInTheDocument(); }); + expect(videoPreview).not.toBeInTheDocument(); }); test('Sorting posts by pinned status', async () => { // Mocked data representing posts with different pinned statuses diff --git a/src/screens/OrgPost/OrgPost.tsx b/src/screens/OrgPost/OrgPost.tsx index e1a0559f11..1931f6c76a 100644 --- a/src/screens/OrgPost/OrgPost.tsx +++ b/src/screens/OrgPost/OrgPost.tsx @@ -170,7 +170,7 @@ function orgPost(): JSX.Element { /* istanbul ignore next */ if (data) { - toast.success(t('postCreatedSuccess')); + toast.success(t('postCreatedSuccess') as string); refetch(); setPostFormState({ posttitle: '', @@ -309,7 +309,7 @@ function orgPost(): JSX.Element {
} + /> + + + + + + , + ); +}; -async function wait(ms = 100): Promise { - await act(() => { - return new Promise((resolve) => { - setTimeout(resolve, ms); - }); +describe('Organisation Settings Page', () => { + beforeAll(() => { + jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useParams: () => ({ orgId: 'orgId' }), + })); }); -} - -const translations = JSON.parse( - JSON.stringify(i18nForTest.getDataByLanguage('en')?.translation.orgSettings), -); -afterEach(() => { - localStorage.clear(); -}); - -describe('Organisation Settings Page', () => { - test('correct mock data should be queried', async () => { - const dataQuery1 = MOCKS[1]?.result?.data?.removeOrganization; - expect(dataQuery1).toEqual([ - { - _id: 123, - }, - ]); + afterAll(() => { + jest.clearAllMocks(); }); - test('should render props and text elements test for the screen', async () => { - window.location.assign('/orgsetting/123'); - setItem('SuperAdmin', true); + it('should redirect to fallback URL if URL params are undefined', async () => { render( - - + + - + + } /> +
} + /> + - + , ); - - await wait(); - - expect(screen.getAllByText(/Delete Organization/i)).toHaveLength(3); - expect( - screen.getByText( - /By clicking on Delete Organization button the organization will be permanently deleted along with its events, tags and all related data/i, - ), - ).toBeInTheDocument(); - expect(screen.getByText(/Other Settings/i)).toBeInTheDocument(); - expect(screen.getByText(/Change Language/i)).toBeInTheDocument(); - expect(window.location).toBeAt('/orgsetting/123'); + await waitFor(() => { + expect(screen.getByTestId('paramsError')).toBeInTheDocument(); + }); }); - test('should render appropriate settings based on the orgSetting state', async () => { - window.location.assign('/orgsetting/123'); - setItem('SuperAdmin', true); - - const { queryByText } = render( - - - - - - - - - , - ); - - await wait(); + test('should render the organisation settings page', async () => { + renderOrganisationSettings(link1); await waitFor(() => { - userEvent.click(screen.getByTestId('actionItemCategoriesSettings')); + expect(screen.getByTestId('generalSettings')).toBeInTheDocument(); expect( - queryByText(translations.actionItemCategories), + screen.getByTestId('actionItemCategoriesSettings'), ).toBeInTheDocument(); + expect( + screen.getByTestId('agendaItemCategoriesSettings'), + ).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('generalSettings')); + + await waitFor(() => { + expect(screen.getByTestId('generalTab')).toBeInTheDocument(); + }); + + userEvent.click(screen.getByTestId('actionItemCategoriesSettings')); + await waitFor(() => { + expect(screen.getByTestId('actionItemCategoriesTab')).toBeInTheDocument(); }); + userEvent.click(screen.getByTestId('agendaItemCategoriesSettings')); await waitFor(() => { - userEvent.click(screen.getByTestId('generalSettings')); - expect(queryByText(translations.updateOrganization)).toBeInTheDocument(); + expect(screen.getByTestId('agendaItemCategoriesTab')).toBeInTheDocument(); }); }); }); diff --git a/src/screens/OrgSettings/OrgSettings.tsx b/src/screens/OrgSettings/OrgSettings.tsx index 336c4eb427..e4ae5424a6 100644 --- a/src/screens/OrgSettings/OrgSettings.tsx +++ b/src/screens/OrgSettings/OrgSettings.tsx @@ -1,18 +1,21 @@ import React, { useState } from 'react'; -import ChangeLanguageDropDown from 'components/ChangeLanguageDropdown/ChangeLanguageDropDown'; -import DeleteOrg from 'components/DeleteOrg/DeleteOrg'; -import OrgUpdate from 'components/OrgUpdate/OrgUpdate'; -import { Button, Card, Dropdown, Form } from 'react-bootstrap'; -import Col from 'react-bootstrap/Col'; -import Row from 'react-bootstrap/Row'; +import { Button, Dropdown, Row, Col } from 'react-bootstrap'; import { useTranslation } from 'react-i18next'; import styles from './OrgSettings.module.css'; -import OrgProfileFieldSettings from 'components/OrgProfileFieldSettings/OrgProfileFieldSettings'; -import OrgActionItemCategories from 'components/OrgActionItemCategories/OrgActionItemCategories'; -import { useParams } from 'react-router-dom'; +import OrgActionItemCategories from 'components/OrgSettings/ActionItemCategories/OrgActionItemCategories'; +import OrganizationAgendaCategory from 'components/OrgSettings/AgendaItemCategories/OrganizationAgendaCategory'; +import { Navigate, useParams } from 'react-router-dom'; +import GeneralSettings from 'components/OrgSettings/General/GeneralSettings'; // Type representing the different settings categories available -type SettingType = 'general' | 'actionItemCategories'; +type SettingType = 'general' | 'actionItemCategories' | 'agendaItemCategories'; + +// List of available settings categories +const settingtabs: SettingType[] = [ + 'general', + 'actionItemCategories', + 'agendaItemCategories', +]; /** * The `orgSettings` component provides a user interface for managing various settings related to an organization. @@ -29,11 +32,7 @@ function orgSettings(): JSX.Element { keyPrefix: 'orgSettings', }); - // List of available settings categories - const orgSettings: SettingType[] = ['general', 'actionItemCategories']; - - // State to manage the currently selected settings category - const [orgSetting, setOrgSetting] = useState('general'); + const [tab, setTab] = useState('general'); // Set the document title using the translated title for this page document.title = t('title'); @@ -41,130 +40,89 @@ function orgSettings(): JSX.Element { // Get the organization ID from the URL parameters const { orgId } = useParams(); + if (!orgId) { + return ; + } + return ( - <> -
- - -
- {/* Render buttons for each settings category */} - {orgSettings.map((setting, index) => ( - + ))} +
+ + {/* Dropdown menu for selecting settings category */} + + + {t(tab)} + + + {/* Render dropdown items for each settings category */} + {settingtabs.map((setting, index) => ( + setOrgSetting(setting)} - data-testid={`${setting}Settings`} + onClick={ + /* istanbul ignore next */ + () => setTab(setting) + } + className={tab === setting ? 'text-secondary' : ''} > {t(setting)} - + ))} -
+ + + - {/* Dropdown menu for selecting settings category */} - - - {t(orgSetting)} - - - {/* Render dropdown items for each settings category */} - {orgSettings.map((setting, index) => ( - setOrgSetting(setting) - } - className={orgSetting === setting ? 'text-secondary' : ''} - > - {t(setting)} - - ))} - - - - - -
-
+ +
+ - {/* Render content based on the selected settings category */} - {orgSetting === 'general' && ( - - - -
-
- {t('updateOrganization')} -
-
- - {/* Render organization update component if orgId is available */} - {orgId && } - -
- - - - -
-
{t('otherSettings')}
-
- -
- - {t('changeLanguage')} - - {/* Render language change dropdown component */} - -
-
-
- - - -
-
- {t('manageCustomFields')} -
-
- - {/* Render organization profile field settings component if orgId is available */} - {orgId && } - -
- -
- )} - - {orgSetting === 'actionItemCategories' && ( - -
-
- {t('actionItemCategories')} + {/* Render content based on the selected settings category */} + {(() => { + switch (tab) { + case 'general': + return ( +
+ +
+ ); + case 'actionItemCategories': + return ( +
+ +
+ ); + case 'agendaItemCategories': + return ( +
+
-
-
- {/* Render action item categories component if orgId is available */} - {orgId && } -
- - )} -
- + ); + } + })()} +
); } diff --git a/src/screens/OrganizationActionItems/ActionItemCreateModal.tsx b/src/screens/OrganizationActionItems/ActionItemCreateModal.tsx deleted file mode 100644 index 00284468f1..0000000000 --- a/src/screens/OrganizationActionItems/ActionItemCreateModal.tsx +++ /dev/null @@ -1,167 +0,0 @@ -import React from 'react'; -import { Modal, Form, Button } from 'react-bootstrap'; -import type { ChangeEvent } from 'react'; -import styles from './OrganizationActionItems.module.css'; -import { DatePicker } from '@mui/x-date-pickers'; -import dayjs from 'dayjs'; -import type { Dayjs } from 'dayjs'; - -import type { - InterfaceActionItemCategoryInfo, - InterfaceMemberInfo, -} from 'utils/interfaces'; - -/** - * Interface for the form state used in the `ActionItemCreateModal` component. - */ -interface InterfaceFormStateType { - actionItemCategoryId: string; - assigneeId: string; - eventId?: string; - preCompletionNotes: string; -} - -/** - * Props for the `ActionItemCreateModal` component. - */ -interface InterfaceActionItemCreateModalProps { - actionItemCreateModalIsOpen: boolean; - hideCreateModal: () => void; - formState: InterfaceFormStateType; - setFormState: (state: React.SetStateAction) => void; - createActionItemHandler: (e: ChangeEvent) => Promise; - t: (key: string) => string; - actionItemCategories: InterfaceActionItemCategoryInfo[] | undefined; - membersData: InterfaceMemberInfo[] | undefined; - dueDate: Date | null; - setDueDate: (state: React.SetStateAction) => void; -} - -/** - * A modal component for creating action items. - * - * @param props - The properties passed to the component. - * @returns The `ActionItemCreateModal` component. - */ -const ActionItemCreateModal: React.FC = ({ - actionItemCreateModalIsOpen, - hideCreateModal, - formState, - setFormState, - createActionItemHandler, - t, - actionItemCategories, - membersData, - dueDate, - setDueDate, -}) => { - return ( - <> - - -

{t('actionItemDetails')}

- -
- -
- - {t('actionItemCategory')} - - setFormState({ - ...formState, - actionItemCategoryId: e.target.value, - }) - } - > - - {actionItemCategories?.map((category, index) => ( - - ))} - - - - - {t('assignee')} - - setFormState({ ...formState, assigneeId: e.target.value }) - } - > - - {membersData?.map((member, index) => ( - - ))} - - - - - { - setFormState({ - ...formState, - preCompletionNotes: e.target.value, - }); - }} - /> - -
- { - if (date) { - setDueDate(date?.toDate()); - } - }} - /> -
- - - -
-
- - ); -}; - -export default ActionItemCreateModal; diff --git a/src/screens/OrganizationActionItems/ActionItemDeleteModal.tsx b/src/screens/OrganizationActionItems/ActionItemDeleteModal.tsx deleted file mode 100644 index 22725b5f56..0000000000 --- a/src/screens/OrganizationActionItems/ActionItemDeleteModal.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import React from 'react'; -import { Modal, Button } from 'react-bootstrap'; -import styles from './OrganizationActionItems.module.css'; - -/** - * Props for the `ActionItemPreviewModal` component. - */ -interface InterfaceActionItemCreateModalProps { - actionItemDeleteModalIsOpen: boolean; - deleteActionItemHandler: () => Promise; - toggleDeleteModal: () => void; - t: (key: string) => string; - tCommon: (key: string) => string; -} - -/** - * A modal component for confirming the deletion of an action item. - * - * @param props - The properties passed to the component. - * @returns The `ActionItemPreviewModal` component. - */ -const ActionItemPreviewModal: React.FC = ({ - actionItemDeleteModalIsOpen, - deleteActionItemHandler, - toggleDeleteModal, - t, - tCommon, -}) => { - return ( - <> - - - - {t('deleteActionItem')} - - - {t('deleteActionItemMsg')} - - - - - - - ); -}; - -export default ActionItemPreviewModal; diff --git a/src/screens/OrganizationActionItems/ActionItemPreviewModal.tsx b/src/screens/OrganizationActionItems/ActionItemPreviewModal.tsx deleted file mode 100644 index ae2043bf88..0000000000 --- a/src/screens/OrganizationActionItems/ActionItemPreviewModal.tsx +++ /dev/null @@ -1,143 +0,0 @@ -import React from 'react'; -import { Modal, Form, Button } from 'react-bootstrap'; - -import styles from './OrganizationActionItems.module.css'; -import dayjs from 'dayjs'; -/** - * State object for form details related to an action item. - */ -interface InterfaceFormStateType { - assigneeId: string; - assignee: string; - assigner: string; - isCompleted: boolean; - preCompletionNotes: string; - postCompletionNotes: string; -} -/** - * Props for the `ActionItemPreviewModal` component. - */ -interface InterfaceActionItemCreateModalProps { - actionItemPreviewModalIsOpen: boolean; - hidePreviewModal: () => void; - showUpdateModal: () => void; - toggleDeleteModal: () => void; - formState: InterfaceFormStateType; - t: (key: string) => string; - dueDate: Date | null; - completionDate: Date | null; - assignmentDate: Date | null; -} - -/** - * A modal component for previewing the details of an action item. - * - * @param props - The properties passed to the component. - * @returns The `ActionItemPreviewModal` component. - */ -const ActionItemPreviewModal: React.FC = ({ - actionItemPreviewModalIsOpen, - hidePreviewModal, - showUpdateModal, - toggleDeleteModal, - formState, - t, - dueDate, - completionDate, - assignmentDate, -}) => { - return ( - <> - - -

{t('actionItemDetails')}

- -
- -
-
-

- {t('assignee')}:{' '} - {formState.assignee} -

-

- {t('assigner')}:{' '} - {formState.assigner} -

-

- {t('preCompletionNotes')}: - - {formState.preCompletionNotes} - -

-

- {t('postCompletionNotes')}: - - {formState.postCompletionNotes} - -

-

- {t('assignmentDate')}:{' '} - - {dayjs(assignmentDate).format('YYYY-MM-DD')} - -

-

- {t('dueDate')}:{' '} - - {dayjs(dueDate).format('YYYY-MM-DD')} - -

-

- {t('completionDate')}:{' '} - - {dayjs(completionDate).format('YYYY-MM-DD')} - -

-

- {t('status')}:{' '} - - {formState.isCompleted ? 'Completed' : 'Active'} - -

-
-
- - -
-
-
-
- - ); -}; - -export default ActionItemPreviewModal; diff --git a/src/screens/OrganizationActionItems/ActionItemUpdateModal.test.tsx b/src/screens/OrganizationActionItems/ActionItemUpdateModal.test.tsx deleted file mode 100644 index f52da23530..0000000000 --- a/src/screens/OrganizationActionItems/ActionItemUpdateModal.test.tsx +++ /dev/null @@ -1,235 +0,0 @@ -import React from 'react'; -import { render, screen, fireEvent } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import ActionItemUpdateModal from './ActionItemUpdateModal'; -import { MockedProvider } from '@apollo/react-testing'; -import { I18nextProvider } from 'react-i18next'; -import { Provider } from 'react-redux'; -import { BrowserRouter } from 'react-router-dom'; -import { store } from 'state/store'; -import i18nForTest from 'utils/i18nForTest'; -import { LocalizationProvider } from '@mui/x-date-pickers'; -import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; -import type { InterfaceMemberInfo } from 'utils/interfaces'; -import { t } from 'i18next'; - -const mockMembersData: InterfaceMemberInfo[] = [ - { - _id: '1', - firstName: 'John', - lastName: 'Doe', - email: 'john.doe@example.com', - image: 'https://example.com/john-doe.jpg', - createdAt: '2022-01-01T00:00:00.000Z', - organizationsBlockedBy: [], - }, - { - _id: '2', - firstName: 'Jane', - lastName: 'Smith', - email: 'jane.smith@example.com', - image: 'https://example.com/jane-smith.jpg', - createdAt: '2022-02-01T00:00:00.000Z', - organizationsBlockedBy: [], - }, -]; - -const mockFormState = { - assigneeId: '1', - assignee: 'John Doe', - assigner: 'Jane Smith', - isCompleted: false, - preCompletionNotes: 'Test pre-completion notes', - postCompletionNotes: '', -}; - -const mockDueDate = new Date('2023-05-01'); -const mockCompletionDate = new Date('2023-05-15'); - -const mockHideUpdateModal = jest.fn(); -const mockSetFormState = jest.fn(); -const mockUpdateActionItemHandler = jest.fn(); -const mockSetDueDate = jest.fn(); -const mockSetCompletionDate = jest.fn(); -const mockT = (key: string): string => key; - -describe('ActionItemUpdateModal', () => { - test('renders modal correctly', () => { - render( - - - - - - - - - - - , - ); - - expect(screen.getByText('actionItemDetails')).toBeInTheDocument(); - expect( - screen.getByTestId('updateActionItemModalCloseBtn'), - ).toBeInTheDocument(); - expect(screen.getByTestId('formUpdateAssignee')).toBeInTheDocument(); - expect(screen.getByLabelText('preCompletionNotes')).toBeInTheDocument(); - expect(screen.getByLabelText('dueDate')).toBeInTheDocument(); - expect(screen.getByLabelText('completionDate')).toBeInTheDocument(); - expect(screen.getByTestId('editActionItemBtn')).toBeInTheDocument(); - }); - - test('closes modal when close button is clicked', () => { - render( - - - - - - - - - - - , - ); - - fireEvent.click(screen.getByTestId('updateActionItemModalCloseBtn')); - expect(mockHideUpdateModal).toHaveBeenCalled(); - }); - - test('updates form state when assignee is changed', () => { - render( - - - - - - - - - - - , - ); - - const assigneeSelect = screen.getByTestId('formUpdateAssignee'); - userEvent.selectOptions(assigneeSelect, '2'); - expect(mockSetFormState).toHaveBeenCalledWith({ - ...mockFormState, - assigneeId: '2', - }); - }); - - test('tests the condition for formState.preCompletionNotes', () => { - const mockFormState = { - assigneeId: '1', - assignee: 'John Doe', - assigner: 'Jane Smith', - isCompleted: false, - preCompletionNotes: '', - postCompletionNotes: '', - }; - render( - - - - - - - - - - - , - ); - const preCompletionNotesInput = screen.getByLabelText('preCompletionNotes'); - fireEvent.change(preCompletionNotesInput, { - target: { value: 'New pre-completion notes' }, - }); - expect(mockSetFormState).toHaveBeenCalledWith({ - ...mockFormState, - preCompletionNotes: 'New pre-completion notes', - }); - }); - - test('calls updateActionItemHandler when form is submitted', () => { - render( - - - - - - - - - - - , - ); - - fireEvent.submit(screen.getByTestId('editActionItemBtn')); - expect(mockUpdateActionItemHandler).toHaveBeenCalled(); - }); -}); diff --git a/src/screens/OrganizationActionItems/ActionItemUpdateModal.tsx b/src/screens/OrganizationActionItems/ActionItemUpdateModal.tsx deleted file mode 100644 index 89a262fe0b..0000000000 --- a/src/screens/OrganizationActionItems/ActionItemUpdateModal.tsx +++ /dev/null @@ -1,172 +0,0 @@ -import React from 'react'; -import { Modal, Form, Button } from 'react-bootstrap'; -import type { ChangeEvent } from 'react'; -import { DatePicker } from '@mui/x-date-pickers'; -import dayjs from 'dayjs'; -import type { Dayjs } from 'dayjs'; - -import styles from './OrganizationActionItems.module.css'; -import type { InterfaceMemberInfo } from 'utils/interfaces'; - -/** - * InterfaceFormStateType is an object containing the form state - */ -interface InterfaceFormStateType { - assigneeId: string; - assignee: string; - assigner: string; - isCompleted: boolean; - preCompletionNotes: string; - postCompletionNotes: string; -} - -/** - * ActionItemUpdateModal component is used to update the action item details like assignee, preCompletionNotes, dueDate, completionDate - * @param actionItemUpdateModalIsOpen - boolean value to check if the modal is open or not - * @param hideUpdateModal - function to hide the modal - * @param formState - object containing the form state - * @param setFormState - function to set the form state - * @param updateActionItemHandler - function to update the action item - * @param t - i18n function to translate the text - * @param membersData - array of members data - * @param dueDate - due date of the action item - * @param setDueDate - function to set the due date - * @param completionDate - completion date of the action item - * @param setCompletionDate - function to set the completion date - * @returns returns the ActionItemUpdateModal component - */ -interface InterfaceActionItemCreateModalProps { - actionItemUpdateModalIsOpen: boolean; - hideUpdateModal: () => void; - formState: InterfaceFormStateType; - setFormState: (state: React.SetStateAction) => void; - updateActionItemHandler: (e: ChangeEvent) => Promise; - t: (key: string) => string; - membersData: InterfaceMemberInfo[] | undefined; - dueDate: Date | null; - setDueDate: (state: React.SetStateAction) => void; - completionDate: Date | null; - setCompletionDate: (state: React.SetStateAction) => void; -} - -const ActionItemUpdateModal: React.FC = ({ - actionItemUpdateModalIsOpen, - hideUpdateModal, - formState, - setFormState, - updateActionItemHandler, - t, - membersData, - dueDate, - setDueDate, - completionDate, - setCompletionDate, -}) => { - return ( - <> - - -

{t('actionItemDetails')}

- -
- -
- - {t('assignee')} - - setFormState({ ...formState, assigneeId: e.target.value }) - } - > - - {membersData?.map((member: InterfaceMemberInfo) => { - const currMemberName = `${member.firstName} ${member.lastName}`; - if (currMemberName !== formState.assignee) { - return ( - - ); - } - })} - - - - - { - setFormState({ - ...formState, - preCompletionNotes: e.target.value, - }); - }} - className="mb-2" - /> - -
- { - /* istanbul ignore next */ - if (date) { - setDueDate(date?.toDate()); - } - } - } - /> -   - { - /* istanbul ignore next */ - if (date) { - setCompletionDate(date?.toDate()); - } - } - } - /> -
- - - -
-
- - ); -}; - -export default ActionItemUpdateModal; diff --git a/src/screens/OrganizationActionItems/ItemDeleteModal.test.tsx b/src/screens/OrganizationActionItems/ItemDeleteModal.test.tsx new file mode 100644 index 0000000000..3d45e12a25 --- /dev/null +++ b/src/screens/OrganizationActionItems/ItemDeleteModal.test.tsx @@ -0,0 +1,120 @@ +import React from 'react'; +import type { ApolloLink } from '@apollo/client'; +import { MockedProvider } from '@apollo/react-testing'; +import { LocalizationProvider } from '@mui/x-date-pickers'; +import type { RenderResult } from '@testing-library/react'; +import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { I18nextProvider } from 'react-i18next'; +import { Provider } from 'react-redux'; +import { BrowserRouter } from 'react-router-dom'; +import { store } from 'state/store'; +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; +import i18nForTest from '../../utils/i18nForTest'; +import { MOCKS, MOCKS_ERROR } from './OrganizationActionItem.mocks'; +import { StaticMockLink } from 'utils/StaticMockLink'; +import { toast } from 'react-toastify'; +import ItemDeleteModal, { + type InterfaceItemDeleteModalProps, +} from './ItemDeleteModal'; + +jest.mock('react-toastify', () => ({ + toast: { + success: jest.fn(), + error: jest.fn(), + }, +})); + +const link1 = new StaticMockLink(MOCKS); +const link2 = new StaticMockLink(MOCKS_ERROR); +const t = JSON.parse( + JSON.stringify( + i18nForTest.getDataByLanguage('en')?.translation.organizationActionItems, + ), +); + +const itemProps: InterfaceItemDeleteModalProps = { + isOpen: true, + hide: jest.fn(), + actionItemsRefetch: jest.fn(), + actionItem: { + _id: 'actionItemId1', + assignee: { + _id: 'userId1', + firstName: 'John', + lastName: 'Doe', + image: null, + }, + actionItemCategory: { + _id: 'actionItemCategoryId1', + name: 'Category 1', + }, + preCompletionNotes: 'Notes 1', + postCompletionNotes: 'Cmp Notes 1', + assignmentDate: new Date('2024-08-27'), + dueDate: new Date('2044-08-30'), + completionDate: new Date('2044-09-03'), + isCompleted: true, + event: null, + allotedHours: 24, + assigner: { + _id: 'userId2', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + }, + creator: { + _id: 'userId2', + firstName: 'Wilt', + lastName: 'Shepherd', + }, + }, +}; + +const renderItemDeleteModal = ( + link: ApolloLink, + props: InterfaceItemDeleteModalProps, +): RenderResult => { + return render( + + + + + + + + + + + , + ); +}; + +describe('Testing ItemDeleteModal', () => { + it('should render ItemDeleteModal', () => { + renderItemDeleteModal(link1, itemProps); + expect(screen.getByText(t.deleteActionItem)).toBeInTheDocument(); + }); + + it('should successfully Delete Action Item', async () => { + renderItemDeleteModal(link1, itemProps); + expect(screen.getByTestId('deleteyesbtn')).toBeInTheDocument(); + + fireEvent.click(screen.getByTestId('deleteyesbtn')); + + await waitFor(() => { + expect(itemProps.actionItemsRefetch).toHaveBeenCalled(); + expect(itemProps.hide).toHaveBeenCalled(); + expect(toast.success).toHaveBeenCalledWith(t.successfulDeletion); + }); + }); + + it('should fail to Delete Action Item', async () => { + renderItemDeleteModal(link2, itemProps); + expect(screen.getByTestId('deleteyesbtn')).toBeInTheDocument(); + fireEvent.click(screen.getByTestId('deleteyesbtn')); + + await waitFor(() => { + expect(toast.error).toHaveBeenCalledWith('Mock Graphql Error'); + }); + }); +}); diff --git a/src/screens/OrganizationActionItems/ItemDeleteModal.tsx b/src/screens/OrganizationActionItems/ItemDeleteModal.tsx new file mode 100644 index 0000000000..2526486993 --- /dev/null +++ b/src/screens/OrganizationActionItems/ItemDeleteModal.tsx @@ -0,0 +1,92 @@ +import React from 'react'; +import { Modal, Button } from 'react-bootstrap'; +import styles from './OrganizationActionItems.module.css'; +import { useMutation } from '@apollo/client'; +import { DELETE_ACTION_ITEM_MUTATION } from 'GraphQl/Mutations/ActionItemMutations'; +import { toast } from 'react-toastify'; +import { useTranslation } from 'react-i18next'; +import type { InterfaceActionItemInfo } from 'utils/interfaces'; + +/** + * Props for the `ItemDeleteModal` component. + */ +export interface InterfaceItemDeleteModalProps { + isOpen: boolean; + hide: () => void; + actionItem: InterfaceActionItemInfo | null; + actionItemsRefetch: () => void; +} + +/** + * A modal component for confirming the deletion of an action item. + * + * @param props - The properties passed to the component. + * @returns The `ItemDeleteModal` component. + */ +const ItemDeleteModal: React.FC = ({ + isOpen, + hide, + actionItem, + actionItemsRefetch, +}) => { + const { t } = useTranslation('translation', { + keyPrefix: 'organizationActionItems', + }); + const { t: tCommon } = useTranslation('common'); + + const [removeActionItem] = useMutation(DELETE_ACTION_ITEM_MUTATION); + + /** + * Handles the action item deletion. + */ + const deleteActionItemHandler = async (): Promise => { + try { + await removeActionItem({ + variables: { + actionItemId: actionItem?._id, + }, + }); + + actionItemsRefetch(); + hide(); + toast.success(t('successfulDeletion')); + } catch (error: unknown) { + toast.error((error as Error).message); + } + }; + return ( + <> + + +

{t('deleteActionItem')}

+ +
+ +

{t('deleteActionItemMsg')}

+
+ + + + +
+ + ); +}; + +export default ItemDeleteModal; diff --git a/src/screens/OrganizationActionItems/ItemModal.test.tsx b/src/screens/OrganizationActionItems/ItemModal.test.tsx new file mode 100644 index 0000000000..6901e16291 --- /dev/null +++ b/src/screens/OrganizationActionItems/ItemModal.test.tsx @@ -0,0 +1,373 @@ +import React from 'react'; +import type { ApolloLink } from '@apollo/client'; +import { MockedProvider } from '@apollo/react-testing'; +import { LocalizationProvider } from '@mui/x-date-pickers'; +import type { RenderResult } from '@testing-library/react'; +import { + fireEvent, + render, + screen, + waitFor, + within, +} from '@testing-library/react'; +import { I18nextProvider } from 'react-i18next'; +import { Provider } from 'react-redux'; +import { BrowserRouter } from 'react-router-dom'; +import { store } from 'state/store'; +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; +import i18n from '../../utils/i18nForTest'; +import { MOCKS, MOCKS_ERROR } from './OrganizationActionItem.mocks'; +import { StaticMockLink } from 'utils/StaticMockLink'; +import { toast } from 'react-toastify'; +import type { InterfaceItemModalProps } from './ItemModal'; +import ItemModal from './ItemModal'; + +jest.mock('react-toastify', () => ({ + toast: { + success: jest.fn(), + error: jest.fn(), + warning: jest.fn(), + }, +})); + +const link1 = new StaticMockLink(MOCKS); +const link2 = new StaticMockLink(MOCKS_ERROR); +const t = { + ...JSON.parse( + JSON.stringify( + i18n.getDataByLanguage('en')?.translation.organizationActionItems ?? {}, + ), + ), + ...JSON.parse(JSON.stringify(i18n.getDataByLanguage('en')?.common ?? {})), + ...JSON.parse(JSON.stringify(i18n.getDataByLanguage('en')?.errors ?? {})), +}; + +const itemProps: InterfaceItemModalProps[] = [ + { + isOpen: true, + hide: jest.fn(), + orgId: 'orgId', + actionItemsRefetch: jest.fn(), + editMode: false, + actionItem: null, + }, + { + isOpen: true, + hide: jest.fn(), + orgId: 'orgId', + actionItemsRefetch: jest.fn(), + editMode: true, + actionItem: { + _id: 'actionItemId1', + assignee: { + _id: 'userId1', + firstName: 'Harve', + lastName: 'Lance', + image: '', + }, + actionItemCategory: { + _id: 'categoryId1', + name: 'Category 1', + }, + preCompletionNotes: 'Notes 1', + postCompletionNotes: 'Cmp Notes 1', + assignmentDate: new Date('2024-08-27'), + dueDate: new Date('2044-08-30'), + completionDate: new Date('2044-09-03'), + isCompleted: true, + event: null, + allotedHours: 24, + assigner: { + _id: 'userId2', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + }, + creator: { + _id: 'userId2', + firstName: 'Wilt', + lastName: 'Shepherd', + }, + }, + }, + { + isOpen: true, + hide: jest.fn(), + orgId: 'orgId', + actionItemsRefetch: jest.fn(), + editMode: true, + actionItem: { + _id: 'actionItemId2', + assignee: { + _id: 'userId2', + firstName: 'Wilt', + lastName: 'Shepherd', + image: '', + }, + actionItemCategory: { + _id: 'categoryId2', + name: 'Category 2', + }, + preCompletionNotes: 'Notes 2', + postCompletionNotes: null, + assignmentDate: new Date('2024-08-27'), + dueDate: new Date('2044-09-30'), + completionDate: new Date('2044-10-03'), + isCompleted: false, + event: null, + allotedHours: null, + assigner: { + _id: 'userId2', + firstName: 'Wilt', + lastName: 'Shepherd', + image: 'wilt-image', + }, + creator: { + _id: 'userId2', + firstName: 'Wilt', + lastName: 'Shepherd', + }, + }, + }, +]; + +const renderItemModal = ( + link: ApolloLink, + props: InterfaceItemModalProps, +): RenderResult => { + return render( + + + + + + + + + + + , + ); +}; + +describe('Testing ItemModal', () => { + it('Create Action Item', async () => { + renderItemModal(link1, itemProps[0]); + expect(screen.getAllByText(t.createActionItem)).toHaveLength(2); + + // Select Category 1 + const categorySelect = await screen.findByTestId('categorySelect'); + expect(categorySelect).toBeInTheDocument(); + const inputField = within(categorySelect).getByRole('combobox'); + fireEvent.mouseDown(inputField); + + const categoryOption = await screen.findByText('Category 1'); + expect(categoryOption).toBeInTheDocument(); + fireEvent.click(categoryOption); + + // Select Assignee + const memberSelect = await screen.findByTestId('memberSelect'); + expect(memberSelect).toBeInTheDocument(); + const memberInputField = within(memberSelect).getByRole('combobox'); + fireEvent.mouseDown(memberInputField); + + const memberOption = await screen.findByText('Harve Lance'); + expect(memberOption).toBeInTheDocument(); + fireEvent.click(memberOption); + + // Select Due Date + fireEvent.change(screen.getByLabelText(t.dueDate), { + target: { value: '02/01/2044' }, + }); + + // Select Allotted Hours (try all options) + const allotedHours = screen.getByLabelText(t.allotedHours); + const allotedHoursOptions = ['', '-1', '9']; + + allotedHoursOptions.forEach((option) => { + fireEvent.change(allotedHours, { target: { value: option } }); + expect(allotedHours).toHaveValue(parseInt(option) > 0 ? option : ''); + }); + + // Add Pre Completion Notes + fireEvent.change(screen.getByLabelText(t.preCompletionNotes), { + target: { value: 'Notes' }, + }); + + // Click Submit + const submitButton = screen.getByTestId('submitBtn'); + expect(submitButton).toBeInTheDocument(); + fireEvent.click(submitButton); + + await waitFor(() => { + expect(itemProps[0].actionItemsRefetch).toHaveBeenCalled(); + expect(itemProps[0].hide).toHaveBeenCalled(); + expect(toast.success).toHaveBeenCalledWith(t.successfulCreation); + }); + }); + + it('Update Action Item (completed)', async () => { + renderItemModal(link1, itemProps[1]); + expect(screen.getAllByText(t.updateActionItem)).toHaveLength(2); + + // Update Category + const categorySelect = await screen.findByTestId('categorySelect'); + expect(categorySelect).toBeInTheDocument(); + const inputField = within(categorySelect).getByRole('combobox'); + fireEvent.mouseDown(inputField); + + const categoryOption = await screen.findByText('Category 2'); + expect(categoryOption).toBeInTheDocument(); + fireEvent.click(categoryOption); + + // Update Allotted Hours (try all options) + const allotedHours = screen.getByLabelText(t.allotedHours); + const allotedHoursOptions = ['', '-1', '19']; + + allotedHoursOptions.forEach((option) => { + fireEvent.change(allotedHours, { target: { value: option } }); + expect(allotedHours).toHaveValue(parseInt(option) > 0 ? option : ''); + }); + + // Update Post Completion Notes + fireEvent.change(screen.getByLabelText(t.postCompletionNotes), { + target: { value: 'Cmp Notes 2' }, + }); + + // Click Submit + const submitButton = screen.getByTestId('submitBtn'); + expect(submitButton).toBeInTheDocument(); + fireEvent.click(submitButton); + + await waitFor(() => { + expect(itemProps[1].actionItemsRefetch).toHaveBeenCalled(); + expect(itemProps[1].hide).toHaveBeenCalled(); + expect(toast.success).toHaveBeenCalledWith(t.successfulUpdation); + }); + }); + + it('Update Action Item (not completed)', async () => { + renderItemModal(link1, itemProps[2]); + expect(screen.getAllByText(t.updateActionItem)).toHaveLength(2); + + // Update Category + const categorySelect = await screen.findByTestId('categorySelect'); + expect(categorySelect).toBeInTheDocument(); + const inputField = within(categorySelect).getByRole('combobox'); + fireEvent.mouseDown(inputField); + + const categoryOption = await screen.findByText('Category 1'); + expect(categoryOption).toBeInTheDocument(); + fireEvent.click(categoryOption); + + // Update Assignee + const memberSelect = await screen.findByTestId('memberSelect'); + expect(memberSelect).toBeInTheDocument(); + const memberInputField = within(memberSelect).getByRole('combobox'); + fireEvent.mouseDown(memberInputField); + + const memberOption = await screen.findByText('Harve Lance'); + expect(memberOption).toBeInTheDocument(); + fireEvent.click(memberOption); + + // Update Allotted Hours (try all options) + const allotedHours = screen.getByLabelText(t.allotedHours); + const allotedHoursOptions = ['', '-1', '19']; + + allotedHoursOptions.forEach((option) => { + fireEvent.change(allotedHours, { target: { value: option } }); + expect(allotedHours).toHaveValue(parseInt(option) > 0 ? option : ''); + }); + + // Update Due Date + fireEvent.change(screen.getByLabelText(t.dueDate), { + target: { value: '02/01/2044' }, + }); + + // Update Pre Completion Notes + fireEvent.change(screen.getByLabelText(t.preCompletionNotes), { + target: { value: 'Notes 3' }, + }); + + // Click Submit + const submitButton = screen.getByTestId('submitBtn'); + expect(submitButton).toBeInTheDocument(); + fireEvent.click(submitButton); + + await waitFor(() => { + expect(itemProps[2].actionItemsRefetch).toHaveBeenCalled(); + expect(itemProps[2].hide).toHaveBeenCalled(); + expect(toast.success).toHaveBeenCalledWith(t.successfulUpdation); + }); + }); + + it('Try adding negative Allotted Hours', async () => { + renderItemModal(link1, itemProps[0]); + expect(screen.getAllByText(t.createActionItem)).toHaveLength(2); + const allotedHours = screen.getByLabelText(t.allotedHours); + fireEvent.change(allotedHours, { target: { value: '-1' } }); + + await waitFor(() => { + expect(allotedHours).toHaveValue(''); + }); + + fireEvent.change(allotedHours, { target: { value: '' } }); + + await waitFor(() => { + expect(allotedHours).toHaveValue(''); + }); + + fireEvent.change(allotedHours, { target: { value: '0' } }); + await waitFor(() => { + expect(allotedHours).toHaveValue('0'); + }); + + fireEvent.change(allotedHours, { target: { value: '19' } }); + await waitFor(() => { + expect(allotedHours).toHaveValue('19'); + }); + }); + + it('should fail to Create Action Item', async () => { + renderItemModal(link2, itemProps[0]); + // Click Submit + const submitButton = screen.getByTestId('submitBtn'); + expect(submitButton).toBeInTheDocument(); + fireEvent.click(submitButton); + + await waitFor(() => { + expect(toast.error).toHaveBeenCalledWith('Mock Graphql Error'); + }); + }); + + it('No Fields Updated while Updating', async () => { + renderItemModal(link2, itemProps[1]); + // Click Submit + const submitButton = screen.getByTestId('submitBtn'); + expect(submitButton).toBeInTheDocument(); + fireEvent.click(submitButton); + + await waitFor(() => { + expect(toast.warning).toHaveBeenCalledWith(t.noneUpdated); + }); + }); + + it('should fail to Update Action Item', async () => { + renderItemModal(link2, itemProps[1]); + expect(screen.getAllByText(t.updateActionItem)).toHaveLength(2); + + // Update Post Completion Notes + fireEvent.change(screen.getByLabelText(t.postCompletionNotes), { + target: { value: 'Cmp Notes 2' }, + }); + + // Click Submit + const submitButton = screen.getByTestId('submitBtn'); + expect(submitButton).toBeInTheDocument(); + fireEvent.click(submitButton); + + await waitFor(() => { + expect(toast.error).toHaveBeenCalledWith('Mock Graphql Error'); + }); + }); +}); diff --git a/src/screens/OrganizationActionItems/ItemModal.tsx b/src/screens/OrganizationActionItems/ItemModal.tsx new file mode 100644 index 0000000000..d7a38ee0df --- /dev/null +++ b/src/screens/OrganizationActionItems/ItemModal.tsx @@ -0,0 +1,440 @@ +import React, { useEffect, useMemo, useState } from 'react'; +import { Modal, Form, Button } from 'react-bootstrap'; +import type { ChangeEvent, FC } from 'react'; +import styles from './OrganizationActionItems.module.css'; +import { DatePicker } from '@mui/x-date-pickers'; +import dayjs from 'dayjs'; +import type { Dayjs } from 'dayjs'; + +import type { + InterfaceActionItemCategoryInfo, + InterfaceActionItemCategoryList, + InterfaceActionItemInfo, + InterfaceMemberInfo, + InterfaceMembersList, +} from 'utils/interfaces'; +import { useTranslation } from 'react-i18next'; +import { toast } from 'react-toastify'; +import { useMutation, useQuery } from '@apollo/client'; +import { + CREATE_ACTION_ITEM_MUTATION, + UPDATE_ACTION_ITEM_MUTATION, +} from 'GraphQl/Mutations/ActionItemMutations'; +import { ACTION_ITEM_CATEGORY_LIST } from 'GraphQl/Queries/ActionItemCategoryQueries'; +import { MEMBERS_LIST } from 'GraphQl/Queries/Queries'; +import { Autocomplete, FormControl, TextField } from '@mui/material'; + +/** + * Interface for the form state used in the `ItemModal` component. + */ +interface InterfaceFormStateType { + dueDate: Date; + actionItemCategoryId: string; + assigneeId: string; + eventId?: string; + preCompletionNotes: string; + postCompletionNotes: string | null; + allotedHours: number | null; + isCompleted: boolean; +} + +/** + * Props for the `ItemModal` component. + */ +export interface InterfaceItemModalProps { + isOpen: boolean; + hide: () => void; + orgId: string; + actionItemsRefetch: () => void; + actionItem: InterfaceActionItemInfo | null; + editMode: boolean; +} + +/** + * Initializes the form state for the `ItemModal` component. + * + * @param actionItem - The action item to be edited. + * @returns + */ + +const initializeFormState = ( + actionItem: InterfaceActionItemInfo | null, +): InterfaceFormStateType => ({ + dueDate: actionItem?.dueDate || new Date(), + actionItemCategoryId: actionItem?.actionItemCategory?._id || '', + assigneeId: actionItem?.assignee._id || '', + preCompletionNotes: actionItem?.preCompletionNotes || '', + postCompletionNotes: actionItem?.postCompletionNotes || null, + allotedHours: actionItem?.allotedHours || null, + isCompleted: actionItem?.isCompleted || false, +}); + +/** + * A modal component for creating action items. + * + * @param props - The properties passed to the component. + * @returns The `ItemModal` component. + */ +const ItemModal: FC = ({ + isOpen, + hide, + orgId, + actionItem, + editMode, + actionItemsRefetch, +}) => { + const { t } = useTranslation('translation', { + keyPrefix: 'organizationActionItems', + }); + + const [actionItemCategory, setActionItemCategory] = + useState(null); + const [assignee, setAssignee] = useState(null); + + const [formState, setFormState] = useState( + initializeFormState(actionItem), + ); + + const { + dueDate, + actionItemCategoryId, + assigneeId, + preCompletionNotes, + postCompletionNotes, + allotedHours, + isCompleted, + } = formState; + + /** + * Query to fetch action item categories for the organization. + */ + const { + data: actionItemCategoriesData, + }: { + data: InterfaceActionItemCategoryList | undefined; + } = useQuery(ACTION_ITEM_CATEGORY_LIST, { + variables: { + organizationId: orgId, + where: { is_disabled: false }, + }, + }); + + /** + * Query to fetch members of the organization. + */ + const { + data: membersData, + }: { + data: InterfaceMembersList | undefined; + } = useQuery(MEMBERS_LIST, { + variables: { id: orgId }, + }); + + const actionItemCategories = useMemo( + () => actionItemCategoriesData?.actionItemCategoriesByOrganization || [], + [actionItemCategoriesData], + ); + + const members = useMemo( + () => membersData?.organizations[0].members || [], + [membersData], + ); + + /** + * Mutation to create & update a new action item. + */ + const [createActionItem] = useMutation(CREATE_ACTION_ITEM_MUTATION); + const [updateActionItem] = useMutation(UPDATE_ACTION_ITEM_MUTATION); + + /** + * Handler function to update the form state. + * + * @param field - The field to be updated. + * @param value - The value to be set. + * @returns void + */ + const handleFormChange = ( + field: keyof InterfaceFormStateType, + value: string | number | boolean | Date | undefined | null, + ): void => { + setFormState((prevState) => ({ ...prevState, [field]: value })); + }; + + /** + * Handler function to create a new action item. + * + * @param e - The form submit event. + * @returns A promise that resolves when the action item is created. + */ + const createActionItemHandler = async ( + e: ChangeEvent, + ): Promise => { + e.preventDefault(); + try { + await createActionItem({ + variables: { + assigneeId: assignee?._id, + actionItemCategoryId: actionItemCategory?._id, + preCompletionNotes: preCompletionNotes, + allotedHours: allotedHours, + dueDate: dayjs(dueDate).format('YYYY-MM-DD'), + }, + }); + + // Reset form and date after successful creation + setFormState(initializeFormState(null)); + setActionItemCategory(null); + setAssignee(null); + + actionItemsRefetch(); + hide(); + toast.success(t('successfulCreation')); + } catch (error: unknown) { + toast.error((error as Error).message); + } + }; + + /** + * Handles the form submission for updating an action item. + * + * @param e - The form submission event. + */ + const updateActionItemHandler = async ( + e: ChangeEvent, + ): Promise => { + e.preventDefault(); + try { + const updatedFields: { + [key: string]: number | string | boolean | Date | undefined | null; + } = {}; + + if (actionItemCategoryId !== actionItem?.actionItemCategory?._id) { + updatedFields.actionItemCategoryId = actionItemCategoryId; + } + if (assigneeId !== actionItem?.assignee._id) { + updatedFields.assigneeId = assigneeId; + } + + if (preCompletionNotes !== actionItem?.preCompletionNotes) { + updatedFields.preCompletionNotes = preCompletionNotes; + } + + if (postCompletionNotes !== actionItem?.postCompletionNotes) { + updatedFields.postCompletionNotes = postCompletionNotes; + } + + if (allotedHours !== actionItem?.allotedHours) { + updatedFields.allotedHours = allotedHours; + } + + if (dueDate !== actionItem?.dueDate) { + updatedFields.dueDate = dayjs(dueDate).format('YYYY-MM-DD'); + } + + if (Object.keys(updatedFields).length === 0) { + toast.warning(t('noneUpdated')); + return; + } + + await updateActionItem({ + variables: { + actionItemId: actionItem?._id, + assigneeId: assigneeId, + ...updatedFields, + }, + }); + + setFormState(initializeFormState(null)); + actionItemsRefetch(); + hide(); + toast.success(t('successfulUpdation')); + } catch (error: unknown) { + toast.error((error as Error).message); + } + }; + + useEffect(() => { + setFormState(initializeFormState(actionItem)); + setActionItemCategory( + actionItemCategories.find( + (category) => category._id === actionItem?.actionItemCategory?._id, + ) || null, + ); + setAssignee( + members.find((member) => member._id === actionItem?.assignee._id) || null, + ); + }, [actionItem, actionItemCategories, members]); + + return ( + + +

+ {editMode ? t('updateActionItem') : t('createActionItem')} +

+ +
+ +
+ + option._id === value._id} + filterSelectedOptions={true} + getOptionLabel={(item: InterfaceActionItemCategoryInfo): string => + item.name + } + onChange={(_, newCategory): void => { + /* istanbul ignore next */ + handleFormChange( + 'actionItemCategoryId', + newCategory?._id ?? '', + ); + setActionItemCategory(newCategory); + }} + renderInput={(params) => ( + + )} + /> + {isCompleted && ( + <> + {/* Input text Component to add alloted Hours for action item */} + + + handleFormChange( + 'allotedHours', + e.target.value === '' || parseInt(e.target.value) < 0 + ? null + : parseInt(e.target.value), + ) + } + /> + + + )} + + {!isCompleted && ( + <> + + + option._id === value._id + } + filterSelectedOptions={true} + getOptionLabel={(member: InterfaceMemberInfo): string => + `${member.firstName} ${member.lastName}` + } + onChange={(_, newAssignee): void => { + /* istanbul ignore next */ + handleFormChange('assigneeId', newAssignee?._id ?? ''); + setAssignee(newAssignee); + }} + renderInput={(params) => ( + + )} + /> + + + + {/* Date Calendar Component to select due date of an action item */} + { + /* istanbul ignore next */ + if (date) handleFormChange('dueDate', date.toDate()); + }} + /> + + {/* Input text Component to add alloted Hours for action item */} + + + handleFormChange( + 'allotedHours', + e.target.value === '' || parseInt(e.target.value) < 0 + ? null + : parseInt(e.target.value), + ) + } + /> + + + + {/* Input text Component to add notes for action item */} + + + handleFormChange('preCompletionNotes', e.target.value) + } + /> + + + )} + + {isCompleted && ( + + + handleFormChange('postCompletionNotes', e.target.value) + } + /> + + )} + + +
+
+
+ ); +}; + +export default ItemModal; diff --git a/src/screens/OrganizationActionItems/ItemUpdateStatusModal.test.tsx b/src/screens/OrganizationActionItems/ItemUpdateStatusModal.test.tsx new file mode 100644 index 0000000000..c1fee119cc --- /dev/null +++ b/src/screens/OrganizationActionItems/ItemUpdateStatusModal.test.tsx @@ -0,0 +1,173 @@ +import React from 'react'; +import type { ApolloLink } from '@apollo/client'; +import { MockedProvider } from '@apollo/react-testing'; +import { LocalizationProvider } from '@mui/x-date-pickers'; +import type { RenderResult } from '@testing-library/react'; +import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { I18nextProvider } from 'react-i18next'; +import { Provider } from 'react-redux'; +import { BrowserRouter } from 'react-router-dom'; +import { store } from 'state/store'; +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; +import i18nForTest from '../../utils/i18nForTest'; +import { MOCKS, MOCKS_ERROR } from './OrganizationActionItem.mocks'; +import { StaticMockLink } from 'utils/StaticMockLink'; +import { toast } from 'react-toastify'; +import ItemUpdateStatusModal, { + type InterfaceItemUpdateStatusModalProps, +} from './ItemUpdateStatusModal'; + +jest.mock('react-toastify', () => ({ + toast: { + success: jest.fn(), + error: jest.fn(), + }, +})); + +const link1 = new StaticMockLink(MOCKS); +const link2 = new StaticMockLink(MOCKS_ERROR); +const t = JSON.parse( + JSON.stringify( + i18nForTest.getDataByLanguage('en')?.translation.organizationActionItems, + ), +); + +const itemProps: InterfaceItemUpdateStatusModalProps[] = [ + { + isOpen: true, + hide: jest.fn(), + actionItemsRefetch: jest.fn(), + actionItem: { + _id: 'actionItemId1', + assignee: { + _id: 'userId1', + firstName: 'John', + lastName: 'Doe', + image: null, + }, + actionItemCategory: { + _id: 'actionItemCategoryId1', + name: 'Category 1', + }, + preCompletionNotes: 'Notes 1', + postCompletionNotes: 'Cmp Notes 1', + assignmentDate: new Date('2024-08-27'), + dueDate: new Date('2044-08-30'), + completionDate: new Date('2044-09-03'), + isCompleted: true, + event: null, + allotedHours: 24, + assigner: { + _id: 'userId2', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + }, + creator: { + _id: 'userId2', + firstName: 'Wilt', + lastName: 'Shepherd', + }, + }, + }, + { + isOpen: true, + hide: jest.fn(), + actionItemsRefetch: jest.fn(), + actionItem: { + _id: 'actionItemId1', + assignee: { + _id: 'userId1', + firstName: 'John', + lastName: 'Doe', + image: null, + }, + actionItemCategory: { + _id: 'actionItemCategoryId1', + name: 'Category 1', + }, + preCompletionNotes: 'Notes 1', + postCompletionNotes: null, + assignmentDate: new Date('2024-08-27'), + dueDate: new Date('2044-08-30'), + completionDate: new Date('2044-09-03'), + isCompleted: false, + event: null, + allotedHours: 24, + assigner: { + _id: 'userId2', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + }, + creator: { + _id: 'userId2', + firstName: 'Wilt', + lastName: 'Shepherd', + }, + }, + }, +]; + +const renderItemUpdateStatusModal = ( + link: ApolloLink, + props: InterfaceItemUpdateStatusModalProps, +): RenderResult => { + return render( + + + + + + + + + + + , + ); +}; + +describe('Testing ItemUpdateStatusModal', () => { + it('Update Status of Completed ActionItem', async () => { + renderItemUpdateStatusModal(link1, itemProps[0]); + expect(screen.getByText(t.actionItemStatus)).toBeInTheDocument(); + const yesBtn = await screen.findByTestId('yesBtn'); + fireEvent.click(yesBtn); + + await waitFor(() => { + expect(itemProps[0].actionItemsRefetch).toHaveBeenCalled(); + expect(itemProps[0].hide).toHaveBeenCalled(); + expect(toast.success).toHaveBeenCalledWith(t.successfulUpdation); + }); + }); + + it('Update Status of Pending ActionItem', async () => { + renderItemUpdateStatusModal(link1, itemProps[1]); + expect(screen.getByText(t.actionItemStatus)).toBeInTheDocument(); + + const notes = await screen.findByLabelText(t.postCompletionNotes); + fireEvent.change(notes, { target: { value: 'Cmp Notes 1' } }); + + const createBtn = await screen.findByTestId('createBtn'); + fireEvent.click(createBtn); + + await waitFor(() => { + expect(itemProps[1].actionItemsRefetch).toHaveBeenCalled(); + expect(itemProps[1].hide).toHaveBeenCalled(); + expect(toast.success).toHaveBeenCalledWith(t.successfulUpdation); + }); + }); + + it('should fail to Update status of Action Item', async () => { + renderItemUpdateStatusModal(link2, itemProps[0]); + + expect(screen.getByText(t.actionItemStatus)).toBeInTheDocument(); + const yesBtn = await screen.findByTestId('yesBtn'); + fireEvent.click(yesBtn); + + await waitFor(() => { + expect(toast.error).toHaveBeenCalledWith('Mock Graphql Error'); + }); + }); +}); diff --git a/src/screens/OrganizationActionItems/ItemUpdateStatusModal.tsx b/src/screens/OrganizationActionItems/ItemUpdateStatusModal.tsx new file mode 100644 index 0000000000..44ac0e63e6 --- /dev/null +++ b/src/screens/OrganizationActionItems/ItemUpdateStatusModal.tsx @@ -0,0 +1,129 @@ +import React, { type FC, type FormEvent, useEffect, useState } from 'react'; +import { Modal, Button, Form } from 'react-bootstrap'; +import { useTranslation } from 'react-i18next'; +import { FormControl, TextField } from '@mui/material'; +import styles from './OrganizationActionItems.module.css'; +import { useMutation } from '@apollo/client'; +import { UPDATE_ACTION_ITEM_MUTATION } from 'GraphQl/Mutations/ActionItemMutations'; +import { toast } from 'react-toastify'; +import type { InterfaceActionItemInfo } from 'utils/interfaces'; + +export interface InterfaceItemUpdateStatusModalProps { + isOpen: boolean; + hide: () => void; + actionItemsRefetch: () => void; + actionItem: InterfaceActionItemInfo | null; +} + +const ItemUpdateStatusModal: FC = ({ + hide, + isOpen, + actionItemsRefetch, + actionItem, +}) => { + const { t } = useTranslation('translation', { + keyPrefix: 'organizationActionItems', + }); + const { t: tCommon } = useTranslation('common'); + + const [isCompleted, setIsCompleted] = useState( + actionItem?.isCompleted ?? false, + ); + const [postCompletionNotes, setPostCompletionNotes] = useState( + actionItem?.postCompletionNotes ?? '', + ); + + /** + * Mutation to update an action item. + */ + const [updateActionItem] = useMutation(UPDATE_ACTION_ITEM_MUTATION); + + /** + * Handles the form submission for updating an action item. + * + * @param e - The form submission event. + */ + const updateActionItemHandler = async ( + e: FormEvent, + ): Promise => { + e.preventDefault(); + + try { + await updateActionItem({ + variables: { + actionItemId: actionItem?._id, + assigneeId: actionItem?.assignee?._id, + postCompletionNotes: isCompleted ? '' : postCompletionNotes, + isCompleted: !isCompleted, + }, + }); + + actionItemsRefetch(); + hide(); + toast.success(t('successfulUpdation')); + } catch (error: unknown) { + toast.error((error as Error).message); + } + }; + + useEffect(() => { + if (actionItem) { + setIsCompleted(actionItem?.isCompleted); + setPostCompletionNotes(actionItem?.postCompletionNotes ?? ''); + } + }, [actionItem]); + + return ( + + +

{t('actionItemStatus')}

+ +
+ +
+ {!isCompleted ? ( + + setPostCompletionNotes(e.target.value)} + /> + + ) : ( +

{t('updateStatusMsg')}

+ )} + + {isCompleted ? ( +
+ + +
+ ) : ( + + )} +
+
+
+ ); +}; + +export default ItemUpdateStatusModal; diff --git a/src/screens/OrganizationActionItems/ItemViewModal.test.tsx b/src/screens/OrganizationActionItems/ItemViewModal.test.tsx new file mode 100644 index 0000000000..13c208b854 --- /dev/null +++ b/src/screens/OrganizationActionItems/ItemViewModal.test.tsx @@ -0,0 +1,147 @@ +import React from 'react'; +import type { ApolloLink } from '@apollo/client'; +import { MockedProvider } from '@apollo/react-testing'; +import { LocalizationProvider } from '@mui/x-date-pickers'; +import type { RenderResult } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; +import { I18nextProvider } from 'react-i18next'; +import { Provider } from 'react-redux'; +import { BrowserRouter } from 'react-router-dom'; +import { store } from 'state/store'; +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; +import i18nForTest from '../../utils/i18nForTest'; +import { MOCKS } from './OrganizationActionItem.mocks'; +import { StaticMockLink } from 'utils/StaticMockLink'; +import ItemViewModal, { type InterfaceViewModalProps } from './ItemViewModal'; + +jest.mock('react-toastify', () => ({ + toast: { + success: jest.fn(), + error: jest.fn(), + }, +})); + +const link1 = new StaticMockLink(MOCKS); +const t = JSON.parse( + JSON.stringify( + i18nForTest.getDataByLanguage('en')?.translation.organizationActionItems, + ), +); + +const itemProps: InterfaceViewModalProps[] = [ + { + isOpen: true, + hide: jest.fn(), + item: { + _id: 'actionItemId1', + assignee: { + _id: 'userId1', + firstName: 'John', + lastName: 'Doe', + image: null, + }, + actionItemCategory: { + _id: 'actionItemCategoryId1', + name: 'Category 1', + }, + preCompletionNotes: 'Notes 1', + postCompletionNotes: 'Cmp Notes 1', + assignmentDate: new Date('2024-08-27'), + dueDate: new Date('2044-08-30'), + completionDate: new Date('2044-09-03'), + isCompleted: true, + event: null, + allotedHours: 24, + assigner: { + _id: 'userId2', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + }, + creator: { + _id: 'userId2', + firstName: 'Wilt', + lastName: 'Shepherd', + }, + }, + }, + { + isOpen: true, + hide: jest.fn(), + item: { + _id: 'actionItemId2', + assignee: { + _id: 'userId1', + firstName: 'Jane', + lastName: 'Doe', + image: 'image-url', + }, + actionItemCategory: { + _id: 'actionItemCategoryId2', + name: 'Category 2', + }, + preCompletionNotes: 'Notes 2', + postCompletionNotes: null, + assignmentDate: new Date('2024-08-27'), + dueDate: new Date('2044-09-30'), + completionDate: new Date('2044-10-03'), + isCompleted: false, + event: null, + allotedHours: null, + assigner: { + _id: 'userId2', + firstName: 'Wilt', + lastName: 'Shepherd', + image: 'wilt-image', + }, + creator: { + _id: 'userId2', + firstName: 'Wilt', + lastName: 'Shepherd', + }, + }, + }, +]; + +const renderItemViewModal = ( + link: ApolloLink, + props: InterfaceViewModalProps, +): RenderResult => { + return render( + + + + + + + + + + + , + ); +}; + +describe('Testing ItemViewModal', () => { + it('should render ItemViewModal with pending item & assignee with null image', () => { + renderItemViewModal(link1, itemProps[0]); + expect(screen.getByText(t.actionItemDetails)).toBeInTheDocument(); + expect(screen.getByTestId('John_avatar')).toBeInTheDocument(); + expect(screen.getByTestId('Wilt_avatar')).toBeInTheDocument(); + expect(screen.getByLabelText(t.postCompletionNotes)).toBeInTheDocument(); + expect(screen.getByLabelText(t.allotedHours)).toBeInTheDocument(); + expect(screen.getByLabelText(t.allotedHours)).toHaveValue('24'); + }); + + it('should render ItemViewModal with completed item & assignee with null image', () => { + renderItemViewModal(link1, itemProps[1]); + expect(screen.getByText(t.actionItemDetails)).toBeInTheDocument(); + expect(screen.getByTestId('Jane_image')).toBeInTheDocument(); + expect(screen.getByTestId('Wilt_image')).toBeInTheDocument(); + expect( + screen.queryByLabelText(t.postCompletionNotes), + ).not.toBeInTheDocument(); + expect(screen.getByLabelText(t.allotedHours)).toBeInTheDocument(); + expect(screen.getByLabelText(t.allotedHours)).toHaveValue('-'); + }); +}); diff --git a/src/screens/OrganizationActionItems/ItemViewModal.tsx b/src/screens/OrganizationActionItems/ItemViewModal.tsx new file mode 100644 index 0000000000..84d9a3e7fe --- /dev/null +++ b/src/screens/OrganizationActionItems/ItemViewModal.tsx @@ -0,0 +1,233 @@ +import { DatePicker } from '@mui/x-date-pickers'; +import React from 'react'; +import dayjs from 'dayjs'; +import type { FC } from 'react'; +import { Button, Form, Modal } from 'react-bootstrap'; +import type { InterfaceActionItemInfo } from 'utils/interfaces'; +import styles from './OrganizationActionItems.module.css'; +import { useTranslation } from 'react-i18next'; +import { FormControl, TextField } from '@mui/material'; +import { TaskAlt, HistoryToggleOff } from '@mui/icons-material'; +import Avatar from 'components/Avatar/Avatar'; + +export interface InterfaceViewModalProps { + isOpen: boolean; + hide: () => void; + item: InterfaceActionItemInfo; +} + +/** + * A modal dialog for viewing action item details. + * + * @param isOpen - Indicates whether the modal is open. + * @param hide - Function to close the modal. + * @param item - The action item object to be displayed. + * + * @returns The rendered modal component. + * + * The `ItemViewModal` component displays all the fields of an action item in a modal dialog. + * It includes fields for assignee, assigner, category, pre and post completion notes, assignment date, due date, completion date, and event. + */ + +const ItemViewModal: FC = ({ isOpen, hide, item }) => { + const { t } = useTranslation('translation', { + keyPrefix: 'organizationActionItems', + }); + const { t: tCommon } = useTranslation('common'); + + const { + actionItemCategory, + assignee, + assigner, + completionDate, + dueDate, + isCompleted, + postCompletionNotes, + preCompletionNotes, + allotedHours, + } = item; + + return ( + + +

{t('actionItemDetails')}

+ +
+ +
+ + + + + + + + + {assignee.image ? ( + Assignee + ) : ( +
+ +
+ )} + + ), + }} + /> +
+ + + {assigner.image ? ( + Assignee + ) : ( +
+ +
+ )} + + ), + }} + /> +
+
+ + {/* Status of Action Item */} + + {isCompleted ? ( + + ) : ( + + )} + + ), + style: { + color: isCompleted ? 'green' : '#ed6c02', + }, + }} + inputProps={{ + style: { + WebkitTextFillColor: isCompleted ? 'green' : '#ed6c02', + }, + }} + disabled + /> + + + + + {/* Date Calendar Component to display due date of Action Item */} + + + {/* Date Calendar Component to display completion Date of Action Item */} + {isCompleted && ( + + )} + + + + + + + {isCompleted && ( + + + + )} +
+
+
+ ); +}; +export default ItemViewModal; diff --git a/src/screens/OrganizationActionItems/OrganizationActionItem.mocks.ts b/src/screens/OrganizationActionItems/OrganizationActionItem.mocks.ts new file mode 100644 index 0000000000..0a324d39ac --- /dev/null +++ b/src/screens/OrganizationActionItems/OrganizationActionItem.mocks.ts @@ -0,0 +1,482 @@ +import dayjs from 'dayjs'; +import { + CREATE_ACTION_ITEM_MUTATION, + DELETE_ACTION_ITEM_MUTATION, + UPDATE_ACTION_ITEM_MUTATION, +} from 'GraphQl/Mutations/ActionItemMutations'; +import { + ACTION_ITEM_CATEGORY_LIST, + ACTION_ITEM_LIST, + MEMBERS_LIST, +} from 'GraphQl/Queries/Queries'; +import { act } from 'react-dom/test-utils'; + +const baseActionItem = { + assigner: { + _id: 'userId2', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + }, + creator: { + _id: 'userId2', + firstName: 'Wilt', + lastName: 'Shepherd', + __typename: 'User', + }, +}; + +const actionItem1 = { + _id: 'actionItemId1', + assignee: { + _id: 'userId1', + firstName: 'John', + lastName: 'Doe', + image: null, + }, + actionItemCategory: { + _id: 'actionItemCategoryId1', + name: 'Category 1', + }, + preCompletionNotes: 'Notes 1', + postCompletionNotes: 'Cmp Notes 1', + assignmentDate: '2024-08-27', + dueDate: '2044-08-30', + completionDate: '2044-09-03', + isCompleted: true, + event: null, + allotedHours: 24, + ...baseActionItem, +}; + +const actionItem2 = { + _id: 'actionItemId2', + assignee: { + _id: 'userId1', + firstName: 'Jane', + lastName: 'Doe', + image: 'image-url', + }, + actionItemCategory: { + _id: 'actionItemCategoryId2', + name: 'Category 2', + }, + preCompletionNotes: 'Notes 2', + postCompletionNotes: null, + assignmentDate: '2024-08-27', + dueDate: '2044-09-30', + completionDate: '2044-10-03', + isCompleted: false, + event: null, + allotedHours: null, + ...baseActionItem, +}; + +const memberListQuery = { + request: { + query: MEMBERS_LIST, + variables: { id: 'orgId' }, + }, + result: { + data: { + organizations: [ + { + _id: 'orgId', + members: [ + { + _id: 'userId1', + firstName: 'Harve', + lastName: 'Lance', + email: 'harve@example.com', + image: '', + organizationsBlockedBy: [], + createdAt: '2024-02-14', + }, + { + _id: 'userId2', + firstName: 'Wilt', + lastName: 'Shepherd', + email: 'wilt@example.com', + image: '', + organizationsBlockedBy: [], + createdAt: '2024-02-14', + }, + ], + }, + ], + }, + }, +}; + +const actionItemCategoryListQuery = { + request: { + query: ACTION_ITEM_CATEGORY_LIST, + variables: { + organizationId: 'orgId', + where: { is_disabled: false }, + }, + }, + result: { + data: { + actionItemCategoriesByOrganization: [ + { + _id: 'categoryId1', + name: 'Category 1', + isDisabled: false, + createdAt: '2024-08-26', + creator: { + _id: 'creatorId1', + firstName: 'Wilt', + lastName: 'Shepherd', + }, + }, + { + _id: 'categoryId2', + name: 'Category 2', + isDisabled: true, + createdAt: '2024-08-25', + creator: { + _id: 'creatorId2', + firstName: 'John', + lastName: 'Doe', + }, + }, + ], + }, + }, +}; + +export const MOCKS = [ + { + request: { + query: ACTION_ITEM_LIST, + variables: { + organizationId: 'orgId', + orderBy: null, + where: { + assigneeName: '', + }, + }, + }, + result: { + data: { + actionItemsByOrganization: [actionItem1, actionItem2], + }, + }, + }, + { + request: { + query: ACTION_ITEM_LIST, + variables: { + organizationId: 'orgId', + orderBy: null, + where: { + categoryName: '', + }, + }, + }, + result: { + data: { + actionItemsByOrganization: [actionItem1, actionItem2], + }, + }, + }, + { + request: { + query: ACTION_ITEM_LIST, + variables: { + organizationId: 'orgId', + orderBy: 'dueDate_ASC', + where: { + assigneeName: '', + }, + }, + }, + result: { + data: { + actionItemsByOrganization: [actionItem1, actionItem2], + }, + }, + }, + { + request: { + query: ACTION_ITEM_LIST, + variables: { + organizationId: 'orgId', + orderBy: 'dueDate_DESC', + where: { + assigneeName: '', + }, + }, + }, + result: { + data: { + actionItemsByOrganization: [actionItem2, actionItem1], + }, + }, + }, + { + request: { + query: ACTION_ITEM_LIST, + variables: { + organizationId: 'orgId', + orderBy: null, + where: { + assigneeName: '', + is_completed: true, + }, + }, + }, + result: { + data: { + actionItemsByOrganization: [actionItem1], + }, + }, + }, + { + request: { + query: ACTION_ITEM_LIST, + variables: { + organizationId: 'orgId', + orderBy: null, + where: { + assigneeName: '', + is_completed: false, + }, + }, + }, + result: { + data: { + actionItemsByOrganization: [actionItem2], + }, + }, + }, + { + request: { + query: ACTION_ITEM_LIST, + variables: { + organizationId: 'orgId', + orderBy: null, + where: { + assigneeName: 'John', + }, + }, + }, + result: { + data: { + actionItemsByOrganization: [actionItem1], + }, + }, + }, + { + request: { + query: ACTION_ITEM_LIST, + variables: { + organizationId: 'orgId', + orderBy: null, + where: { + categoryName: 'Category 1', + }, + }, + }, + result: { + data: { + actionItemsByOrganization: [actionItem1], + }, + }, + }, + { + request: { + query: DELETE_ACTION_ITEM_MUTATION, + variables: { + actionItemId: 'actionItemId1', + }, + }, + result: { + data: { + removeActionItem: { + _id: 'actionItemId1', + }, + }, + }, + }, + { + request: { + query: UPDATE_ACTION_ITEM_MUTATION, + variables: { + actionItemId: 'actionItemId1', + assigneeId: 'userId1', + postCompletionNotes: '', + isCompleted: false, + }, + }, + result: { + data: { + updateActionItem: { + _id: 'actionItemId1', + }, + }, + }, + }, + { + request: { + query: UPDATE_ACTION_ITEM_MUTATION, + variables: { + actionItemId: 'actionItemId1', + assigneeId: 'userId1', + actionItemCategoryId: 'categoryId2', + postCompletionNotes: 'Cmp Notes 2', + allotedHours: 19, + }, + }, + result: { + data: { + updateActionItem: { + _id: 'actionItemId1', + }, + }, + }, + }, + { + request: { + query: UPDATE_ACTION_ITEM_MUTATION, + variables: { + actionItemId: 'actionItemId2', + assigneeId: 'userId1', + actionItemCategoryId: 'categoryId1', + preCompletionNotes: 'Notes 3', + allotedHours: 19, + dueDate: '2044-01-02', + }, + }, + result: { + data: { + updateActionItem: { + _id: 'actionItemId1', + }, + }, + }, + }, + { + request: { + query: UPDATE_ACTION_ITEM_MUTATION, + variables: { + actionItemId: 'actionItemId1', + assigneeId: 'userId1', + postCompletionNotes: 'Cmp Notes 1', + isCompleted: true, + }, + }, + result: { + data: { + updateActionItem: { + _id: 'actionItemId1', + }, + }, + }, + }, + { + request: { + query: CREATE_ACTION_ITEM_MUTATION, + variables: { + assigneeId: 'userId1', + actionItemCategoryId: 'categoryId1', + preCompletionNotes: 'Notes', + allotedHours: 9, + dueDate: '2044-01-02', + }, + }, + result: { + data: { + createActionItem: { + _id: 'actionItemId1', + }, + }, + }, + }, + memberListQuery, + actionItemCategoryListQuery, +]; + +export const MOCKS_ERROR = [ + { + request: { + query: ACTION_ITEM_LIST, + variables: { + organizationId: 'orgId', + orderBy: null, + where: { + assigneeName: '', + }, + }, + }, + error: new Error('Mock Graphql Error'), + }, + { + request: { + query: DELETE_ACTION_ITEM_MUTATION, + variables: { + actionItemId: 'actionItemId1', + }, + }, + error: new Error('Mock Graphql Error'), + }, + { + request: { + query: UPDATE_ACTION_ITEM_MUTATION, + variables: { + actionItemId: 'actionItemId1', + assigneeId: 'userId1', + postCompletionNotes: '', + isCompleted: false, + }, + }, + error: new Error('Mock Graphql Error'), + }, + { + request: { + query: CREATE_ACTION_ITEM_MUTATION, + variables: { + preCompletionNotes: '', + allotedHours: null, + dueDate: dayjs().format('YYYY-MM-DD'), + }, + }, + error: new Error('Mock Graphql Error'), + }, + { + request: { + query: UPDATE_ACTION_ITEM_MUTATION, + variables: { + actionItemId: 'actionItemId1', + assigneeId: 'userId1', + postCompletionNotes: 'Cmp Notes 2', + }, + }, + error: new Error('Mock Graphql Error'), + }, + memberListQuery, + actionItemCategoryListQuery, +]; + +export const MOCKS_EMPTY = [ + { + request: { + query: ACTION_ITEM_LIST, + variables: { + organizationId: 'orgId', + orderBy: null, + where: { + assigneeName: '', + }, + }, + }, + result: { + data: { + actionItemsByOrganization: [], + }, + }, + }, + memberListQuery, + actionItemCategoryListQuery, +]; diff --git a/src/screens/OrganizationActionItems/OrganizationActionItemMocks.ts b/src/screens/OrganizationActionItems/OrganizationActionItemMocks.ts deleted file mode 100644 index acbb243e5d..0000000000 --- a/src/screens/OrganizationActionItems/OrganizationActionItemMocks.ts +++ /dev/null @@ -1,417 +0,0 @@ -import { CREATE_ACTION_ITEM_MUTATION } from 'GraphQl/Mutations/mutations'; - -import { - ACTION_ITEM_CATEGORY_LIST, - ACTION_ITEM_LIST, - MEMBERS_LIST, -} from 'GraphQl/Queries/Queries'; - -export const MOCKS = [ - { - request: { - query: ACTION_ITEM_CATEGORY_LIST, - variables: { organizationId: '123' }, - }, - result: { - data: { - actionItemCategoriesByOrganization: [ - { - _id: 'actionItemCategory1', - name: 'ActionItemCategory 1', - isDisabled: false, - }, - ], - }, - }, - }, - { - request: { - query: MEMBERS_LIST, - variables: { id: '123' }, - }, - result: { - data: { - organizations: [ - { - _id: '123', - members: [ - { - _id: 'user1', - firstName: 'Harve', - lastName: 'Lance', - email: 'harve@example.com', - image: '', - organizationsBlockedBy: [], - createdAt: '2024-02-14', - }, - ], - }, - ], - }, - }, - }, - { - request: { - query: ACTION_ITEM_LIST, - variables: { - organizationId: '123', - orderBy: 'createdAt_DESC', - actionItemCategoryId: '', - isActive: false, - isCompleted: false, - }, - }, - result: { - data: { - actionItemsByOrganization: [ - { - _id: 'actionItem1', - assignee: { - _id: 'user1', - firstName: 'Harve', - lastName: 'Lance', - }, - actionItemCategory: { - _id: 'actionItemCategory1', - name: 'ActionItemCategory 1', - }, - preCompletionNotes: 'Pre Completion Notes', - postCompletionNotes: 'Post Completion Notes', - assignmentDate: '2024-02-14', - dueDate: '2024-02-21', - completionDate: '2024-02-21', - isCompleted: false, - assigner: { - _id: 'user0', - firstName: 'Wilt', - lastName: 'Shepherd', - }, - event: { - _id: 'event1', - title: 'event 1', - }, - creator: { - _id: 'user0', - firstName: 'Wilt', - lastName: 'Shepherd', - }, - }, - { - _id: 'actionItem2', - assignee: { - _id: 'user2', - firstName: 'John', - lastName: 'Doe', - }, - actionItemCategory: { - _id: 'actionItemCategory2', - name: 'ActionItemCategory 2', - }, - preCompletionNotes: 'Pre Completion Notes', - postCompletionNotes: 'Post Completion Notes', - assignmentDate: '2024-02-14', - dueDate: '2024-02-21', - completionDate: '2024-02-21', - isCompleted: false, - assigner: { - _id: 'user0', - firstName: 'Wilt', - lastName: 'Shepherd', - }, - event: null, - creator: { - _id: 'user0', - firstName: 'Wilt', - lastName: 'Shepherd', - }, - }, - ], - }, - }, - }, - { - request: { - query: ACTION_ITEM_LIST, - variables: { - organizationId: '123', - eventId: 'event1', - orderBy: 'createdAt_DESC', - }, - }, - result: { - data: { - actionItemsByOrganization: [ - { - _id: 'actionItem1', - assignee: { - _id: 'user1', - firstName: 'Harve', - lastName: 'Lance', - }, - actionItemCategory: { - _id: 'actionItemCategory1', - name: 'ActionItemCategory 1', - }, - preCompletionNotes: 'Pre Completion Notes', - postCompletionNotes: 'Post Completion Notes', - assignmentDate: '2024-02-14', - dueDate: '2024-02-21', - completionDate: '2024-02-21', - isCompleted: false, - assigner: { - _id: 'user0', - firstName: 'Wilt', - lastName: 'Shepherd', - }, - event: { - _id: 'event1', - title: 'event 1', - }, - creator: { - _id: 'user0', - firstName: 'Wilt', - lastName: 'Shepherd', - }, - }, - ], - }, - }, - }, - { - request: { - query: ACTION_ITEM_LIST, - variables: { - organizationId: '123', - orderBy: 'createdAt_ASC', - actionItemCategoryId: '', - isActive: false, - isCompleted: false, - }, - }, - result: { - data: { - actionItemsByOrganization: [ - { - _id: 'actionItem3', - assignee: { - _id: 'user1', - firstName: 'Praise', - lastName: 'Norris', - }, - actionItemCategory: { - _id: 'actionItemCategory3', - name: 'ActionItemCategory 3', - }, - preCompletionNotes: 'Pre Completion Notes', - postCompletionNotes: 'Post Completion Notes', - assignmentDate: '2024-02-14', - dueDate: '2024-02-21', - completionDate: '2024-02-21', - isCompleted: false, - assigner: { - _id: 'user0', - firstName: 'Wilt', - lastName: 'Shepherd', - }, - event: { - _id: 'event1', - title: 'event 1', - }, - creator: { - _id: 'user0', - firstName: 'Wilt', - lastName: 'Shepherd', - }, - }, - ], - }, - }, - }, - { - request: { - query: ACTION_ITEM_LIST, - variables: { - organizationId: '123', - orderBy: 'createdAt_ASC', - actionItemCategoryId: '', - isActive: true, - isCompleted: false, - }, - }, - result: { - data: { - actionItemsByOrganization: [ - { - _id: 'actionItem1', - assignee: { - _id: 'user1', - firstName: 'Harve', - lastName: 'Lance', - }, - actionItemCategory: { - _id: 'actionItemCategory1', - name: 'ActionItemCategory 1', - }, - preCompletionNotes: 'Pre Completion Notes', - postCompletionNotes: 'Post Completion Notes', - assignmentDate: '2024-02-14', - dueDate: '2024-02-21', - completionDate: '2024-02-21', - isCompleted: true, - assigner: { - _id: 'user0', - firstName: 'Wilt', - lastName: 'Shepherd', - }, - event: { - _id: 'event1', - title: 'event 1', - }, - creator: { - _id: 'user0', - firstName: 'Wilt', - lastName: 'Shepherd', - }, - }, - ], - }, - }, - }, - { - request: { - query: ACTION_ITEM_LIST, - variables: { - organizationId: '123', - orderBy: 'createdAt_ASC', - actionItemCategoryId: '', - isActive: false, - isCompleted: true, - }, - }, - result: { - data: { - actionItemsByOrganization: [ - { - _id: 'actionItem1', - assignee: { - _id: 'user1', - firstName: 'Harve', - lastName: 'Lance', - }, - actionItemCategory: { - _id: 'actionItemCategory1', - name: 'ActionItemCategory 1', - }, - preCompletionNotes: 'Pre Completion Notes', - postCompletionNotes: 'Post Completion Notes', - assignmentDate: '2024-02-14', - dueDate: '2024-02-21', - completionDate: '2024-02-21', - isCompleted: true, - assigner: { - _id: 'user0', - firstName: 'Wilt', - lastName: 'Shepherd', - }, - event: { - _id: 'event1', - title: 'event 1', - }, - creator: { - _id: 'user0', - firstName: 'Wilt', - lastName: 'Shepherd', - }, - }, - ], - }, - }, - }, - { - request: { - query: ACTION_ITEM_LIST, - variables: { - organizationId: '123', - orderBy: 'createdAt_ASC', - actionItemCategoryId: 'actionItemCategory1', - isActive: false, - isCompleted: true, - }, - }, - result: { - data: { - actionItemsByOrganization: [ - { - _id: 'actionItem1', - assignee: { - _id: 'user1', - firstName: 'Harve', - lastName: 'Lance', - }, - actionItemCategory: { - _id: 'actionItemCategory1', - name: 'ActionItemCategory 1', - }, - preCompletionNotes: 'Pre Completion Notes', - postCompletionNotes: 'Post Completion Notes', - assignmentDate: '2024-02-14', - dueDate: '2024-02-21', - completionDate: '2024-02-21', - isCompleted: true, - assigner: { - _id: 'user0', - firstName: 'Wilt', - lastName: 'Shepherd', - }, - event: { - _id: 'event1', - title: 'event 1', - }, - creator: { - _id: 'user0', - firstName: 'Wilt', - lastName: 'Shepherd', - }, - }, - ], - }, - }, - }, - { - request: { - query: CREATE_ACTION_ITEM_MUTATION, - variables: { - actionItemCategoryId: 'actionItemCategory1', - assigneeId: 'user1', - preCompletionNotes: 'pre completion notes', - dueDate: '2024-02-14', - }, - }, - result: { - data: { - createActionItem: { - _id: 'actionItem2', - }, - }, - }, - }, - { - request: { - query: CREATE_ACTION_ITEM_MUTATION, - variables: { - eventId: 'event1', - actionItemCategoryId: 'actionItemCategory1', - assigneeId: 'user1', - preCompletionNotes: 'pre completion notes', - dueDate: '2024-02-14', - }, - }, - result: { - data: { - createActionItem: { - _id: 'actionItem2', - }, - }, - }, - }, -]; diff --git a/src/screens/OrganizationActionItems/OrganizationActionItems.module.css b/src/screens/OrganizationActionItems/OrganizationActionItems.module.css index 7a67362c8b..48720ac902 100644 --- a/src/screens/OrganizationActionItems/OrganizationActionItems.module.css +++ b/src/screens/OrganizationActionItems/OrganizationActionItems.module.css @@ -8,26 +8,6 @@ margin-left: 13vw; } -.btnsContainer { - display: flex; - gap: 10px; -} - -.btnsContainer .btnsBlock { - display: flex; - gap: 10px; -} - -.btnsContainer button { - display: flex; - justify-content: center; - align-items: center; -} - -.container { - min-height: 100vh; -} - .datediv { display: flex; flex-direction: row; @@ -46,10 +26,6 @@ margin-left: 5px; } -.dropdown { - display: block; -} - .dropdownToggle { margin-bottom: 0; display: flex; @@ -108,10 +84,6 @@ hr { flex-direction: column; } -.organizationActionItemsContainer h2 { - margin: 0.6rem 0; -} - .preview { display: flex; flex-direction: row; @@ -128,16 +100,6 @@ hr { display: inline; } -.titlemodal { - color: var(--bs-gray-600); - font-weight: 600; - font-size: 20px; - margin-bottom: 20px; - padding-bottom: 5px; - border-bottom: 3px solid var(--bs-primary); - width: 65%; -} - .view { margin-left: 2%; font-weight: 600; @@ -145,24 +107,127 @@ hr { color: var(--bs-gray-600); } -@media (max-width: 767px) { - .btnsContainer { - margin-bottom: 0; - display: flex; - flex-direction: column; - } +/* header (search, filter, dropdown) */ +.btnsContainer { + display: flex; + margin: 0.5rem 0 1.5rem 0; +} + +.btnsContainer .input { + flex: 1; + min-width: 18rem; + position: relative; +} + +.btnsContainer input { + outline: 1px solid var(--bs-gray-400); +} + +.btnsContainer .input button { + width: 52px; +} + +.noOutline input { + outline: none; +} + +.noOutline input:disabled { + -webkit-text-fill-color: black !important; +} + +.noOutline textarea:disabled { + -webkit-text-fill-color: black !important; +} + +.inputField { + margin-top: 10px; + margin-bottom: 10px; + background-color: white; + box-shadow: 0 1px 1px #31bb6b; +} + +.inputField > button { + padding-top: 10px; + padding-bottom: 10px; +} - .btnsContainer .btnsBlock .dropdownToggle { - flex-grow: 1; - } +.dropdown { + background-color: white; + border: 1px solid #31bb6b; + position: relative; + display: inline-block; + color: #31bb6b; +} - .btnsContainer button { - width: 100%; - } +/* Action Items Data Grid */ +.rowBackground { + background-color: var(--bs-white); + max-height: 120px; +} + +.tableHeader { + background-color: var(--bs-primary); + color: var(--bs-white); + font-size: 1rem; +} + +.chipIcon { + height: 0.9rem !important; +} + +.chip { + height: 1.5rem !important; +} + +.active { + background-color: #31bb6a50 !important; +} + +.pending { + background-color: #ffd76950 !important; + color: #bb952bd0 !important; + border-color: #bb952bd0 !important; +} + +/* Modals */ +.itemModal { + max-width: 80vw; + margin-top: 2vh; + margin-left: 13vw; +} + +.titlemodal { + color: #707070; + font-weight: 600; + font-size: 32px; + width: 65%; + margin-bottom: 0px; +} + +.modalCloseBtn { + width: 40px; + height: 40px; + padding: 1rem; + display: flex; + justify-content: center; + align-items: center; +} + +.imageContainer { + display: flex; + align-items: center; + justify-content: center; + margin-right: 0.5rem; +} + +.TableImage { + object-fit: cover; + width: 25px !important; + height: 25px !important; + border-radius: 100% !important; +} - .createActionItemButton { - position: absolute; - top: 1rem; - right: 2rem; - } +.avatarContainer { + width: 28px; + height: 26px; } diff --git a/src/screens/OrganizationActionItems/OrganizationActionItems.test.tsx b/src/screens/OrganizationActionItems/OrganizationActionItems.test.tsx index 7a9060a892..c163ff9546 100644 --- a/src/screens/OrganizationActionItems/OrganizationActionItems.test.tsx +++ b/src/screens/OrganizationActionItems/OrganizationActionItems.test.tsx @@ -1,35 +1,23 @@ import React from 'react'; -import { - render, - screen, - fireEvent, - waitFor, - act, - waitForElementToBeRemoved, -} from '@testing-library/react'; +import { MockedProvider } from '@apollo/react-testing'; +import { LocalizationProvider } from '@mui/x-date-pickers'; +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; +import type { RenderResult } from '@testing-library/react'; +import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import 'jest-localstorage-mock'; -import { MockedProvider } from '@apollo/client/testing'; -import 'jest-location-mock'; import { I18nextProvider } from 'react-i18next'; import { Provider } from 'react-redux'; -import { BrowserRouter } from 'react-router-dom'; -import i18n from 'utils/i18nForTest'; -import { toast } from 'react-toastify'; -import { LocalizationProvider } from '@mui/x-date-pickers'; -import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; - +import { MemoryRouter, Route, Routes } from 'react-router-dom'; import { store } from 'state/store'; import { StaticMockLink } from 'utils/StaticMockLink'; - -import OrganizationActionItems from './OrganizationActionItems'; +import i18n from 'utils/i18nForTest'; +import OrganizationActionItems from 'screens/OrganizationActionItems/OrganizationActionItems'; +import type { ApolloLink } from '@apollo/client'; import { - MOCKS_ERROR_ACTION_ITEM_CATEGORY_LIST_QUERY, - MOCKS_ERROR_ACTION_ITEM_LIST_QUERY, - MOCKS_ERROR_MEMBERS_LIST_QUERY, - MOCKS_ERROR_MUTATIONS, -} from './OrganizationActionItemsErrorMocks'; -import { MOCKS } from './OrganizationActionItemMocks'; + MOCKS, + MOCKS_EMPTY, + MOCKS_ERROR, +} from './OrganizationActionItem.mocks'; jest.mock('react-toastify', () => ({ toast: { @@ -46,29 +34,10 @@ jest.mock('@mui/x-date-pickers/DateTimePicker', () => { }; }); -jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useParams: () => ({ orgId: '123' }), -})); - -async function wait(ms = 100): Promise { - await act(() => { - return new Promise((resolve) => { - setTimeout(resolve, ms); - }); - }); -} - -const link = new StaticMockLink(MOCKS, true); -const link2 = new StaticMockLink( - MOCKS_ERROR_ACTION_ITEM_CATEGORY_LIST_QUERY, - true, -); -const link3 = new StaticMockLink(MOCKS_ERROR_MEMBERS_LIST_QUERY, true); -const link4 = new StaticMockLink(MOCKS_ERROR_ACTION_ITEM_LIST_QUERY, true); -const link5 = new StaticMockLink(MOCKS_ERROR_MUTATIONS, true); - -const translations = { +const link1 = new StaticMockLink(MOCKS); +const link2 = new StaticMockLink(MOCKS_ERROR); +const link3 = new StaticMockLink(MOCKS_EMPTY); +const t = { ...JSON.parse( JSON.stringify( i18n.getDataByLanguage('en')?.translation.organizationActionItems ?? {}, @@ -78,542 +47,331 @@ const translations = { ...JSON.parse(JSON.stringify(i18n.getDataByLanguage('en')?.errors ?? {})), }; -describe('Testing Action Item Categories Component', () => { - const formData = { - actionItemCategory: 'ActionItemCategory 1', - assignee: 'Harve Lance', - preCompletionNotes: 'pre completion notes', - dueDate: '02/14/2024', - }; - - test('Component loads correctly', async () => { - const { getByText } = render( - +const renderOrganizationActionItems = (link: ApolloLink): RenderResult => { + return render( + + - + - {} + + } + /> +
} + /> + - + - , - ); - - await wait(); + + , + ); +}; - await waitFor(() => { - expect(getByText(translations.create)).toBeInTheDocument(); - }); +describe('Testing Organization Action Items Screen', () => { + beforeAll(() => { + jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useParams: () => ({ orgId: 'orgId', eventId: 'eventId' }), + })); }); - test('render error component on unsuccessful action item category list query', async () => { - const { queryByText } = render( - - - - - {} - - - - , - ); - - await wait(); - - await waitFor(() => { - expect(queryByText(translations.create)).not.toBeInTheDocument(); - }); + afterAll(() => { + jest.clearAllMocks(); }); - test('render error component on unsuccessful members list query', async () => { - const { queryByText } = render( - - - + it('should redirect to fallback URL if URL params are undefined', async () => { + render( + + + - {} + + } + /> +
} + /> + - - + + , ); - - await wait(); - await waitFor(() => { - expect(queryByText(translations.create)).not.toBeInTheDocument(); + expect(screen.getByTestId('paramsError')).toBeInTheDocument(); }); }); - test('render error component on unsuccessful action item list query', async () => { - const { queryByText } = render( - - - - - {} - - - - , - ); - - await wait(); - + it('should render Organization Action Items screen', async () => { + renderOrganizationActionItems(link1); await waitFor(() => { - expect(queryByText(translations.create)).not.toBeInTheDocument(); + expect(screen.getByTestId('searchBy')).toBeInTheDocument(); + expect(screen.getByText('John Doe')).toBeInTheDocument(); + expect(screen.getByText('Jane Doe')).toBeInTheDocument(); }); }); - test('sorts action items in earliest or latest first order based on orderBy', async () => { - render( - - - - - {} - - - - , - ); + it('Sort Action Items descending by dueDate', async () => { + renderOrganizationActionItems(link1); - await wait(); + const sortBtn = await screen.findByTestId('sort'); + expect(sortBtn).toBeInTheDocument(); + // Sort by dueDate_DESC + fireEvent.click(sortBtn); await waitFor(() => { - expect(screen.getByTestId('sortActionItems')).toBeInTheDocument(); + expect(screen.getByTestId('dueDate_DESC')).toBeInTheDocument(); }); - userEvent.click(screen.getByTestId('sortActionItems')); - + fireEvent.click(screen.getByTestId('dueDate_DESC')); await waitFor(() => { - expect(screen.getByTestId('earliest')).toBeInTheDocument(); - }); - userEvent.click(screen.getByTestId('earliest')); - - await waitFor(() => { - expect(screen.getByTestId('sortActionItems')).toHaveTextContent( - translations.earliest, - ); - }); - - await waitFor(() => { - expect(screen.getByTestId('sortActionItems')).toBeInTheDocument(); - }); - userEvent.click(screen.getByTestId('sortActionItems')); - - await waitFor(() => { - expect(screen.getByTestId('latest')).toBeInTheDocument(); - }); - userEvent.click(screen.getByTestId('latest')); - - await waitFor(() => { - expect(screen.getByTestId('sortActionItems')).toHaveTextContent( - translations.latest, + expect(screen.getAllByTestId('categoryName')[0]).toHaveTextContent( + 'Category 2', ); }); }); - test('applies and then clears filters one by one', async () => { - render( - - - - - {} - - - - , - ); - - await wait(); + it('Sort Action Items ascending by dueDate', async () => { + renderOrganizationActionItems(link1); - await waitFor(() => { - expect(screen.getByTestId('sortActionItems')).toBeInTheDocument(); - }); - userEvent.click(screen.getByTestId('sortActionItems')); + const sortBtn = await screen.findByTestId('sort'); + expect(sortBtn).toBeInTheDocument(); + // Sort by dueDate_ASC + fireEvent.click(sortBtn); await waitFor(() => { - expect(screen.getByTestId('earliest')).toBeInTheDocument(); + expect(screen.getByTestId('dueDate_ASC')).toBeInTheDocument(); }); - userEvent.click(screen.getByTestId('earliest')); - - // all the action items ordered by earliest first + fireEvent.click(screen.getByTestId('dueDate_ASC')); await waitFor(() => { - expect(screen.getByTestId('sortActionItems')).toHaveTextContent( - translations.earliest, + expect(screen.getAllByTestId('categoryName')[0]).toHaveTextContent( + 'Category 1', ); }); + }); - await waitFor(() => { - expect(screen.getByTestId('selectActionItemStatus')).toBeInTheDocument(); - }); - userEvent.click(screen.getByTestId('selectActionItemStatus')); - - await waitFor(() => { - expect(screen.getByTestId('activeActionItems')).toBeInTheDocument(); - }); - userEvent.click(screen.getByTestId('activeActionItems')); + it('Filter Action Items by status (All/Pending)', async () => { + renderOrganizationActionItems(link1); - // all the action items that are active - await waitFor(() => { - expect(screen.getByTestId('selectActionItemStatus')).toHaveTextContent( - translations.active, - ); - }); + const filterBtn = await screen.findByTestId('filter'); + expect(filterBtn).toBeInTheDocument(); + // Filter by All + fireEvent.click(filterBtn); await waitFor(() => { - expect(screen.getByTestId('selectActionItemStatus')).toBeInTheDocument(); + expect(screen.getByTestId('statusAll')).toBeInTheDocument(); }); - userEvent.click(screen.getByTestId('selectActionItemStatus')); + fireEvent.click(screen.getByTestId('statusAll')); await waitFor(() => { - expect(screen.getByTestId('completedActionItems')).toBeInTheDocument(); + expect(screen.getByText('Category 1')).toBeInTheDocument(); + expect(screen.getByText('Category 2')).toBeInTheDocument(); }); - userEvent.click(screen.getByTestId('completedActionItems')); - // all the action items that are completed + // Filter by Pending + fireEvent.click(filterBtn); await waitFor(() => { - expect(screen.getByTestId('selectActionItemStatus')).toHaveTextContent( - translations.completed, - ); + expect(screen.getByTestId('statusPending')).toBeInTheDocument(); }); - + fireEvent.click(screen.getByTestId('statusPending')); await waitFor(() => { - expect( - screen.getByTestId('selectActionItemCategory'), - ).toBeInTheDocument(); + expect(screen.queryByText('Category 1')).toBeNull(); + expect(screen.getByText('Category 2')).toBeInTheDocument(); }); - userEvent.click(screen.getByTestId('selectActionItemCategory')); + }); - await waitFor(() => { - expect( - screen.getAllByTestId('actionItemCategory')[0], - ).toBeInTheDocument(); - }); - userEvent.click(screen.getAllByTestId('actionItemCategory')[0]); + it('Filter Action Items by status (Completed)', async () => { + renderOrganizationActionItems(link1); - // action items belonging to this action item category - await waitFor(() => { - expect(screen.getByTestId('selectActionItemCategory')).toHaveTextContent( - 'ActionItemCategory 1', - ); - }); + const filterBtn = await screen.findByTestId('filter'); + expect(filterBtn).toBeInTheDocument(); + fireEvent.click(filterBtn); await waitFor(() => { - expect( - screen.getByTestId('clearActionItemCategoryFilter'), - ).toBeInTheDocument(); + expect(screen.getByTestId('statusCompleted')).toBeInTheDocument(); }); - // remove the action item category filter - userEvent.click(screen.getByTestId('clearActionItemCategoryFilter')); - + fireEvent.click(screen.getByTestId('statusCompleted')); await waitFor(() => { - expect( - screen.queryByTestId('clearActionItemCategoryFilter'), - ).not.toBeInTheDocument(); + expect(screen.getByText('Category 1')).toBeInTheDocument(); + expect(screen.queryByText('Category 2')).toBeNull(); }); + }); - await waitFor(() => { - expect( - screen.getByTestId('clearActionItemStatusFilter'), - ).toBeInTheDocument(); - }); - // remove the action item status filter - userEvent.click(screen.getByTestId('clearActionItemStatusFilter')); + it('open and close Item modal (create)', async () => { + renderOrganizationActionItems(link1); - await waitFor(() => { - expect(screen.getByTestId('selectActionItemStatus')).toHaveTextContent( - translations.status, - ); - expect(screen.getByTestId('selectActionItemCategory')).toHaveTextContent( - translations.actionItemCategory, - ); - }); - }); + const addItemBtn = await screen.findByTestId('createActionItemBtn'); + expect(addItemBtn).toBeInTheDocument(); + userEvent.click(addItemBtn); - test('applies and then clears all the filters', async () => { - render( - - - - - {} - - - - , + await waitFor(() => + expect(screen.getAllByText(t.createActionItem)).toHaveLength(2), + ); + userEvent.click(screen.getByTestId('modalCloseBtn')); + await waitFor(() => + expect(screen.queryByTestId('modalCloseBtn')).toBeNull(), ); + }); - await wait(); + it('open and close Item modal (view)', async () => { + renderOrganizationActionItems(link1); - await waitFor(() => { - expect(screen.getByTestId('sortActionItems')).toBeInTheDocument(); - }); - userEvent.click(screen.getByTestId('sortActionItems')); + const viewItemBtn = await screen.findByTestId('viewItemBtn1'); + expect(viewItemBtn).toBeInTheDocument(); + userEvent.click(viewItemBtn); - await waitFor(() => { - expect(screen.getByTestId('earliest')).toBeInTheDocument(); - }); - userEvent.click(screen.getByTestId('earliest')); + await waitFor(() => + expect(screen.getByText(t.actionItemDetails)).toBeInTheDocument(), + ); + userEvent.click(screen.getByTestId('modalCloseBtn')); + await waitFor(() => + expect(screen.queryByTestId('modalCloseBtn')).toBeNull(), + ); + }); - // all the action items ordered by earliest first - await waitFor(() => { - expect(screen.getByTestId('sortActionItems')).toHaveTextContent( - translations.earliest, - ); - }); + it('open and closes Item modal (edit)', async () => { + renderOrganizationActionItems(link1); - await waitFor(() => { - expect(screen.getByTestId('selectActionItemStatus')).toBeInTheDocument(); - }); - userEvent.click(screen.getByTestId('selectActionItemStatus')); + const editItemBtn = await screen.findByTestId('editItemBtn1'); + await waitFor(() => expect(editItemBtn).toBeInTheDocument()); + userEvent.click(editItemBtn); - await waitFor(() => { - expect(screen.getByTestId('activeActionItems')).toBeInTheDocument(); - }); - userEvent.click(screen.getByTestId('activeActionItems')); + await waitFor(() => + expect(screen.getAllByText(t.updateActionItem)).toHaveLength(2), + ); + userEvent.click(screen.getByTestId('modalCloseBtn')); + await waitFor(() => + expect(screen.queryByTestId('modalCloseBtn')).toBeNull(), + ); + }); - // all the action items that are active - await waitFor(() => { - expect(screen.getByTestId('selectActionItemStatus')).toHaveTextContent( - translations.active, - ); - }); + it('open and closes Item modal (delete)', async () => { + renderOrganizationActionItems(link1); - await waitFor(() => { - expect(screen.getByTestId('selectActionItemStatus')).toBeInTheDocument(); - }); - userEvent.click(screen.getByTestId('selectActionItemStatus')); + const deleteItemBtn = await screen.findByTestId('deleteItemBtn1'); + expect(deleteItemBtn).toBeInTheDocument(); + userEvent.click(deleteItemBtn); - await waitFor(() => { - expect(screen.getByTestId('completedActionItems')).toBeInTheDocument(); - }); - userEvent.click(screen.getByTestId('completedActionItems')); + await waitFor(() => + expect(screen.getByText(t.deleteActionItem)).toBeInTheDocument(), + ); + userEvent.click(screen.getByTestId('modalCloseBtn')); + await waitFor(() => + expect(screen.queryByTestId('modalCloseBtn')).toBeNull(), + ); + }); - // all the action items that are completed - await waitFor(() => { - expect(screen.getByTestId('selectActionItemStatus')).toHaveTextContent( - translations.completed, - ); - }); + it('open and closes Item modal (update status)', async () => { + renderOrganizationActionItems(link1); - await waitFor(() => { - expect( - screen.getByTestId('selectActionItemCategory'), - ).toBeInTheDocument(); - }); - userEvent.click(screen.getByTestId('selectActionItemCategory')); + const statusCheckbox = await screen.findByTestId('statusCheckbox1'); + expect(statusCheckbox).toBeInTheDocument(); + userEvent.click(statusCheckbox); - await waitFor(() => { - expect( - screen.getAllByTestId('actionItemCategory')[0], - ).toBeInTheDocument(); - }); - userEvent.click(screen.getAllByTestId('actionItemCategory')[0]); + await waitFor(() => + expect(screen.getByText(t.actionItemStatus)).toBeInTheDocument(), + ); + userEvent.click(screen.getByTestId('modalCloseBtn')); + await waitFor(() => + expect(screen.queryByTestId('modalCloseBtn')).toBeNull(), + ); + }); - // action items belonging to this action item category - await waitFor(() => { - expect(screen.getByTestId('selectActionItemCategory')).toHaveTextContent( - 'ActionItemCategory 1', - ); - }); + it('Search action items by assignee', async () => { + renderOrganizationActionItems(link1); - await waitFor(() => { - expect(screen.getByTestId('clearFilters')).toBeInTheDocument(); - }); - userEvent.click(screen.getByTestId('clearFilters')); + const searchByToggle = await screen.findByTestId('searchByToggle'); + expect(searchByToggle).toBeInTheDocument(); - // filters cleared, all the action items belonging to the organization + userEvent.click(searchByToggle); await waitFor(() => { - expect(screen.getByTestId('sortActionItems')).toHaveTextContent( - translations.latest, - ); - expect(screen.getByTestId('selectActionItemStatus')).toHaveTextContent( - translations.status, - ); - expect(screen.getByTestId('selectActionItemCategory')).toHaveTextContent( - translations.actionItemCategory, - ); + expect(screen.getByTestId('assignee')).toBeInTheDocument(); }); - }); - test('opens and closes the create action item modal', async () => { - render( - - - - - - {} - - - - - , - ); + userEvent.click(screen.getByTestId('assignee')); - await wait(); + const searchInput = await screen.findByTestId('searchBy'); + expect(searchInput).toBeInTheDocument(); + userEvent.type(searchInput, 'John'); + userEvent.click(screen.getByTestId('searchBtn')); await waitFor(() => { - expect(screen.getByTestId('createActionItemBtn')).toBeInTheDocument(); + expect(screen.getByText('Category 1')).toBeInTheDocument(); + expect(screen.queryByText('Category 2')).toBeNull(); }); - userEvent.click(screen.getByTestId('createActionItemBtn')); - - await waitFor(() => { - return expect( - screen.findByTestId('createActionItemModalCloseBtn'), - ).resolves.toBeInTheDocument(); - }); - userEvent.click(screen.getByTestId('createActionItemModalCloseBtn')); - - await waitForElementToBeRemoved(() => - screen.queryByTestId('createActionItemModalCloseBtn'), - ); }); - test('creates new action item', async () => { - render( - - - - - - {} - - - - - , - ); + it('Search action items by category', async () => { + renderOrganizationActionItems(link1); - await wait(); + const searchByToggle = await screen.findByTestId('searchByToggle'); + expect(searchByToggle).toBeInTheDocument(); + userEvent.click(searchByToggle); await waitFor(() => { - expect(screen.getByTestId('createActionItemBtn')).toBeInTheDocument(); + expect(screen.getByTestId('category')).toBeInTheDocument(); }); - userEvent.click(screen.getByTestId('createActionItemBtn')); - await waitFor(() => { - return expect( - screen.findByTestId('createActionItemModalCloseBtn'), - ).resolves.toBeInTheDocument(); - }); + userEvent.click(screen.getByTestId('category')); + const searchInput = await screen.findByTestId('searchBy'); + expect(searchInput).toBeInTheDocument(); + + userEvent.type(searchInput, 'Category 1'); + userEvent.click(screen.getByTestId('searchBtn')); await waitFor(() => { - expect( - screen.getByTestId('formSelectActionItemCategory'), - ).toBeInTheDocument(); + expect(screen.getByText('Category 1')).toBeInTheDocument(); + expect(screen.queryByText('Category 2')).toBeNull(); }); + }); - userEvent.selectOptions( - screen.getByTestId('formSelectActionItemCategory'), - formData.actionItemCategory, - ); - - userEvent.selectOptions( - screen.getByTestId('formSelectAssignee'), - formData.assignee, - ); - - userEvent.type( - screen.getByPlaceholderText(translations.preCompletionNotes), - formData.preCompletionNotes, - ); - - const dueDatePicker = screen.getByLabelText(translations.dueDate); - fireEvent.change(dueDatePicker, { - target: { value: formData.dueDate }, - }); + it('Search action items by name and clear the input by backspace', async () => { + renderOrganizationActionItems(link1); - userEvent.click(screen.getByTestId('createActionItemFormSubmitBtn')); + const searchInput = await screen.findByTestId('searchBy'); + expect(searchInput).toBeInTheDocument(); + // Clear the search input by backspace + userEvent.type(searchInput, 'A{backspace}'); await waitFor(() => { - expect(toast.success).toBeCalledWith(translations.successfulCreation); + expect(screen.getByText('Category 1')).toBeInTheDocument(); + expect(screen.getByText('Category 2')).toBeInTheDocument(); }); }); - test('toasts error on unsuccessful creation', async () => { - render( - - - - - - {} - - - - - , - ); - - await wait(); + it('Search action items by name on press of ENTER', async () => { + renderOrganizationActionItems(link1); - await waitFor(() => { - expect(screen.getByTestId('createActionItemBtn')).toBeInTheDocument(); - }); - userEvent.click(screen.getByTestId('createActionItemBtn')); + const searchInput = await screen.findByTestId('searchBy'); + expect(searchInput).toBeInTheDocument(); + userEvent.type(searchInput, 'John'); + userEvent.type(searchInput, '{enter}'); await waitFor(() => { - return expect( - screen.findByTestId('createActionItemModalCloseBtn'), - ).resolves.toBeInTheDocument(); + expect(screen.getByText('Category 1')).toBeInTheDocument(); + expect(screen.queryByText('Category 2')).toBeNull(); }); + }); + it('should render Empty Action Item Categories Screen', async () => { + renderOrganizationActionItems(link3); await waitFor(() => { - expect( - screen.getByTestId('formSelectActionItemCategory'), - ).toBeInTheDocument(); - }); - - userEvent.selectOptions( - screen.getByTestId('formSelectActionItemCategory'), - formData.actionItemCategory, - ); - - userEvent.selectOptions( - screen.getByTestId('formSelectAssignee'), - formData.assignee, - ); - - userEvent.type( - screen.getByPlaceholderText(translations.preCompletionNotes), - formData.preCompletionNotes, - ); - - const dueDatePicker = screen.getByLabelText(translations.dueDate); - fireEvent.change(dueDatePicker, { - target: { value: formData.dueDate }, + expect(screen.getByTestId('searchBy')).toBeInTheDocument(); + expect(screen.getByText(t.noActionItems)).toBeInTheDocument(); }); + }); - userEvent.click(screen.getByTestId('createActionItemFormSubmitBtn')); - + it('should render the Action Item Categories Screen with error', async () => { + renderOrganizationActionItems(link2); await waitFor(() => { - expect(toast.error).toHaveBeenCalled(); + expect(screen.getByTestId('errorMsg')).toBeInTheDocument(); }); }); - - test('Testing Only Action Items Displaying', async () => { - const mockApp = render( - - - - - - {} - - - - - , - ); - - await waitFor(mockApp.asFragment); - - const actionItem = screen.getByText(/John Doe/i); - - expect(actionItem).toContainHTML('John Doe'); - }); }); diff --git a/src/screens/OrganizationActionItems/OrganizationActionItems.tsx b/src/screens/OrganizationActionItems/OrganizationActionItems.tsx index a1c822d0b4..ba48f212e0 100644 --- a/src/screens/OrganizationActionItems/OrganizationActionItems.tsx +++ b/src/screens/OrganizationActionItems/OrganizationActionItems.tsx @@ -1,31 +1,71 @@ -import React, { useState } from 'react'; -import type { ChangeEvent } from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { Button, Dropdown } from 'react-bootstrap'; -import { useParams } from 'react-router-dom'; +import { Button, Dropdown, Form } from 'react-bootstrap'; +import { Navigate, useParams } from 'react-router-dom'; -import SortIcon from '@mui/icons-material/Sort'; -import { WarningAmberRounded } from '@mui/icons-material'; +import { + Circle, + FilterAltOutlined, + Search, + Sort, + WarningAmberRounded, +} from '@mui/icons-material'; import dayjs from 'dayjs'; -import { toast } from 'react-toastify'; -import { useMutation, useQuery } from '@apollo/client'; -import { - ACTION_ITEM_CATEGORY_LIST, - ACTION_ITEM_LIST, - MEMBERS_LIST, -} from 'GraphQl/Queries/Queries'; -import { CREATE_ACTION_ITEM_MUTATION } from 'GraphQl/Mutations/mutations'; +import { useQuery } from '@apollo/client'; +import { ACTION_ITEM_LIST } from 'GraphQl/Queries/Queries'; import type { - InterfaceActionItemCategoryList, + InterfaceActionItemInfo, InterfaceActionItemList, - InterfaceMembersList, } from 'utils/interfaces'; -import ActionItemsContainer from 'components/ActionItems/ActionItemsContainer'; -import ActionItemCreateModal from './ActionItemCreateModal'; import styles from './OrganizationActionItems.module.css'; import Loader from 'components/Loader/Loader'; +import { + DataGrid, + type GridCellParams, + type GridColDef, +} from '@mui/x-data-grid'; +import { Chip, Stack } from '@mui/material'; +import ItemViewModal from './ItemViewModal'; +import ItemModal from './ItemModal'; +import ItemDeleteModal from './ItemDeleteModal'; +import Avatar from 'components/Avatar/Avatar'; +import ItemUpdateStatusModal from './ItemUpdateStatusModal'; + +enum ItemStatus { + Pending = 'pending', + Completed = 'completed', + Late = 'late', +} + +enum ModalState { + SAME = 'same', + DELETE = 'delete', + VIEW = 'view', + STATUS = 'status', +} + +const dataGridStyle = { + '&.MuiDataGrid-root .MuiDataGrid-cell:focus-within': { + outline: 'none !important', + }, + '&.MuiDataGrid-root .MuiDataGrid-columnHeader:focus-within': { + outline: 'none', + }, + '& .MuiDataGrid-row:hover': { + backgroundColor: 'transparent', + }, + '& .MuiDataGrid-row.Mui-hovered': { + backgroundColor: 'transparent', + }, + '& .MuiDataGrid-root': { + borderRadius: '0.5rem', + }, + '& .MuiDataGrid-main': { + borderRadius: '0.5rem', + }, +}; /** * Component for managing and displaying action items within an organization. @@ -39,57 +79,51 @@ function organizationActionItems(): JSX.Element { keyPrefix: 'organizationActionItems', }); const { t: tCommon } = useTranslation('common'); + const { t: tErrors } = useTranslation('errors'); // Get the organization ID from URL parameters - const { orgId: currentUrl } = useParams(); + const { orgId, eventId } = useParams(); - // State for managing modal visibility and form data - const [actionItemCreateModalIsOpen, setActionItemCreateModalIsOpen] = - useState(false); - const [dueDate, setDueDate] = useState(new Date()); - const [orderBy, setOrderBy] = useState<'Latest' | 'Earliest'>('Latest'); - const [actionItemStatus, setActionItemStatus] = useState(''); - const [actionItemCategoryId, setActionItemCategoryId] = useState(''); - const [actionItemCategoryName, setActionItemCategoryName] = useState(''); + if (!orgId) { + return ; + } - const [formState, setFormState] = useState({ - actionItemCategoryId: '', - assigneeId: '', - preCompletionNotes: '', + const [actionItem, setActionItem] = useState( + null, + ); + const [modalMode, setModalMode] = useState<'create' | 'edit'>('create'); + const [searchValue, setSearchValue] = useState(''); + const [searchTerm, setSearchTerm] = useState(''); + const [sortBy, setSortBy] = useState<'dueDate_ASC' | 'dueDate_DESC' | null>( + null, + ); + const [status, setStatus] = useState(null); + const [searchBy, setSearchBy] = useState<'assignee' | 'category'>('assignee'); + const [modalState, setModalState] = useState<{ + [key in ModalState]: boolean; + }>({ + [ModalState.SAME]: false, + [ModalState.DELETE]: false, + [ModalState.VIEW]: false, + [ModalState.STATUS]: false, }); - /** - * Query to fetch action item categories for the organization. - */ - const { - data: actionItemCategoriesData, - loading: actionItemCategoriesLoading, - error: actionItemCategoriesError, - }: { - data: InterfaceActionItemCategoryList | undefined; - loading: boolean; - error?: Error | undefined; - } = useQuery(ACTION_ITEM_CATEGORY_LIST, { - variables: { - organizationId: currentUrl, - }, - notifyOnNetworkStatusChange: true, - }); + const openModal = (modal: ModalState): void => + setModalState((prevState) => ({ ...prevState, [modal]: true })); - /** - * Query to fetch members of the organization. - */ - const { - data: membersData, - loading: membersLoading, - error: membersError, - }: { - data: InterfaceMembersList | undefined; - loading: boolean; - error?: Error | undefined; - } = useQuery(MEMBERS_LIST, { - variables: { id: currentUrl }, - }); + const closeModal = (modal: ModalState): void => + setModalState((prevState) => ({ ...prevState, [modal]: false })); + + const handleModalClick = useCallback( + (actionItem: InterfaceActionItemInfo | null, modal: ModalState): void => { + if (modal === ModalState.SAME) { + setModalMode(actionItem ? 'edit' : 'create'); + } + setActionItem(actionItem); + openModal(modal); + }, + [openModal], + ); /** * Query to fetch action items for the organization based on filters and sorting. @@ -106,335 +140,407 @@ function organizationActionItems(): JSX.Element { refetch: () => void; } = useQuery(ACTION_ITEM_LIST, { variables: { - organizationId: currentUrl, - actionItemCategoryId, - orderBy: orderBy === 'Latest' ? 'createdAt_DESC' : 'createdAt_ASC', - isActive: actionItemStatus === 'Active' ? true : false, - isCompleted: actionItemStatus === 'Completed' ? true : false, + organizationId: orgId, + eventId: eventId, + orderBy: sortBy, + where: { + assigneeName: searchBy === 'assignee' ? searchTerm : undefined, + categoryName: searchBy === 'category' ? searchTerm : undefined, + is_completed: + status === null ? undefined : status === ItemStatus.Completed, + }, }, - notifyOnNetworkStatusChange: true, }); - /** - * Mutation to create a new action item. - */ - const [createActionItem] = useMutation(CREATE_ACTION_ITEM_MUTATION); - - /** - * Handler function to create a new action item. - * - * @param e - The form submit event. - * @returns A promise that resolves when the action item is created. - */ - const createActionItemHandler = async ( - e: ChangeEvent, - ): Promise => { - e.preventDefault(); - try { - await createActionItem({ - variables: { - assigneeId: formState.assigneeId, - actionItemCategoryId: formState.actionItemCategoryId, - preCompletionNotes: formState.preCompletionNotes, - dueDate: dayjs(dueDate).format('YYYY-MM-DD'), - }, - }); - - // Reset form and date after successful creation - setFormState({ - assigneeId: '', - actionItemCategoryId: '', - preCompletionNotes: '', - }); - - setDueDate(new Date()); - - actionItemsRefetch(); - hideCreateModal(); - toast.success(t('successfulCreation')); - } catch (error: unknown) { - if (error instanceof Error) { - toast.error(error.message); - console.log(error.message); - } - } - }; - - /** - * Toggles the visibility of the create action item modal. - */ - const showCreateModal = (): void => { - setActionItemCreateModalIsOpen(!actionItemCreateModalIsOpen); - }; - - /** - * Hides the create action item modal. - */ - const hideCreateModal = (): void => { - setActionItemCreateModalIsOpen(!actionItemCreateModalIsOpen); - }; - - /** - * Handles sorting action items by date. - * - * @param sort - The sorting order ('Latest' or 'Earliest'). - */ - const handleSorting = (sort: string): void => { - if (sort === 'Latest') { - setOrderBy('Latest'); - } else { - setOrderBy('Earliest'); - } - }; - - /** - * Filters action items by status. - * - * @param status - The status to filter by ('Active' or 'Completed'). - */ - const handleStatusFilter = (status: string): void => { - if (status === 'Active') { - setActionItemStatus('Active'); - } else { - setActionItemStatus('Completed'); - } - }; - - /** - * Clears all filters applied to the action items. - */ - const handleClearFilters = (): void => { - setActionItemCategoryId(''); - setActionItemCategoryName(''); - setActionItemStatus(''); - setOrderBy('Latest'); - }; + const actionItems = useMemo( + () => actionItemsData?.actionItemsByOrganization || [], + [actionItemsData], + ); - if (actionItemCategoriesLoading || membersLoading || actionItemsLoading) { + if (actionItemsLoading) { return ; } - if (actionItemCategoriesError || membersError || actionItemsError) { + if (actionItemsError) { return ( -
-
- -
- Error occured while loading{' '} - {actionItemCategoriesError - ? 'Action Item Categories' - : membersError - ? 'Members List' - : 'Action Items List'}{' '} - Data -
- {actionItemCategoriesError - ? actionItemCategoriesError.message - : membersError - ? membersError.message - : actionItemsError?.message} -
-
+
+ +
+ {tErrors('errorLoading', { entity: 'Action Items' })} +
+ {`${actionItemsError.message}`} +
); } - const actionItemCategories = - actionItemCategoriesData?.actionItemCategoriesByOrganization.filter( - (category) => !category.isDisabled, - ); - - const actionItemOnly = actionItemsData?.actionItemsByOrganization.filter( - (item) => item.event == null, - ); + const columns: GridColDef[] = [ + { + field: 'assignee', + headerName: 'Assignee', + flex: 1, + align: 'left', + minWidth: 100, + headerAlign: 'center', + sortable: false, + headerClassName: `${styles.tableHeader}`, + renderCell: (params: GridCellParams) => { + const { _id, firstName, lastName, image } = params.row.assignee; + return ( +
+ {image ? ( + Assignee + ) : ( +
+ +
+ )} + {params.row.assignee.firstName + ' ' + params.row.assignee.lastName} +
+ ); + }, + }, + { + field: 'itemCategory', + headerName: 'Item Category', + flex: 1, + align: 'center', + minWidth: 100, + headerAlign: 'center', + sortable: false, + headerClassName: `${styles.tableHeader}`, + renderCell: (params: GridCellParams) => { + return ( +
+ {params.row.actionItemCategory?.name} +
+ ); + }, + }, + { + field: 'status', + headerName: 'Status', + flex: 1, + align: 'center', + headerAlign: 'center', + sortable: false, + headerClassName: `${styles.tableHeader}`, + renderCell: (params: GridCellParams) => { + return ( + } + label={params.row.isCompleted ? 'Completed' : 'Pending'} + variant="outlined" + color="primary" + className={`${styles.chip} ${params.row.isCompleted ? styles.active : styles.pending}`} + /> + ); + }, + }, + { + field: 'allotedHours', + headerName: 'Alloted Hours', + align: 'center', + headerAlign: 'center', + sortable: false, + headerClassName: `${styles.tableHeader}`, + flex: 1, + renderCell: (params: GridCellParams) => { + return ( +
{params.row.allotedHours ?? '-'}
+ ); + }, + }, + { + field: 'dueDate', + headerName: 'Due Date', + align: 'center', + headerAlign: 'center', + sortable: false, + headerClassName: `${styles.tableHeader}`, + flex: 1, + renderCell: (params: GridCellParams) => { + return ( +
+ {dayjs(params.row.dueDate).format('DD/MM/YYYY')} +
+ ); + }, + }, + { + field: 'options', + headerName: 'Options', + align: 'center', + flex: 1, + minWidth: 100, + headerAlign: 'center', + sortable: false, + headerClassName: `${styles.tableHeader}`, + renderCell: (params: GridCellParams) => { + return ( + <> + + + + + ); + }, + }, + { + field: 'completed', + headerName: 'Completed', + align: 'center', + flex: 1, + minWidth: 100, + headerAlign: 'center', + sortable: false, + headerClassName: `${styles.tableHeader}`, + renderCell: (params: GridCellParams) => { + return ( +
+ handleModalClick(params.row, ModalState.STATUS)} + /> +
+ ); + }, + }, + ]; return ( -
-
-
-
-
-
-
+ {/* Table with Action Items */} + row._id} + slots={{ + noRowsOverlay: () => ( + + {t('noActionItems')} + + ), + }} + sx={dataGridStyle} + getRowClassName={() => `${styles.rowBackground}`} + autoHeight + rowHeight={65} + rows={actionItems.map((actionItem, index) => ({ + id: index + 1, + ...actionItem, + }))} + columns={columns} + isRowSelectable={() => false} + /> - -
+ {/* Item Modal (Create/Edit) */} + closeModal(ModalState.SAME)} + orgId={orgId} + actionItemsRefetch={actionItemsRefetch} + actionItem={actionItem} + editMode={modalMode === 'edit'} + /> + + closeModal(ModalState.DELETE)} + actionItem={actionItem} + actionItemsRefetch={actionItemsRefetch} + /> - {/* Create Modal */} - closeModal(ModalState.STATUS)} + actionItemsRefetch={actionItemsRefetch} /> + + {/* View Modal */} + {actionItem && ( + closeModal(ModalState.VIEW)} + item={actionItem} + /> + )}
); } diff --git a/src/screens/OrganizationActionItems/OrganizationActionItemsErrorMocks.ts b/src/screens/OrganizationActionItems/OrganizationActionItemsErrorMocks.ts deleted file mode 100644 index bedba6572b..0000000000 --- a/src/screens/OrganizationActionItems/OrganizationActionItemsErrorMocks.ts +++ /dev/null @@ -1,266 +0,0 @@ -import { CREATE_ACTION_ITEM_MUTATION } from 'GraphQl/Mutations/mutations'; - -import { - ACTION_ITEM_CATEGORY_LIST, - ACTION_ITEM_LIST, - MEMBERS_LIST, -} from 'GraphQl/Queries/Queries'; - -export const MOCKS_ERROR_ACTION_ITEM_CATEGORY_LIST_QUERY = [ - { - request: { - query: ACTION_ITEM_CATEGORY_LIST, - variables: { organizationId: '123' }, - }, - error: new Error('Mock Graphql Error'), - }, -]; - -export const MOCKS_ERROR_MEMBERS_LIST_QUERY = [ - { - request: { - query: ACTION_ITEM_CATEGORY_LIST, - variables: { organizationId: '123' }, - }, - result: { - data: { - actionItemCategoriesByOrganization: [ - { - _id: 'actionItemCategory1', - name: 'ActionItemCategory 1', - isDisabled: false, - }, - ], - }, - }, - }, - { - request: { - query: MEMBERS_LIST, - variables: { id: '123' }, - }, - error: new Error('Mock Graphql Error'), - }, -]; - -export const MOCKS_ERROR_ACTION_ITEM_LIST_QUERY = [ - { - request: { - query: ACTION_ITEM_CATEGORY_LIST, - variables: { organizationId: '123' }, - }, - result: { - data: { - actionItemCategoriesByOrganization: [ - { - _id: 'actionItemCategory1', - name: 'ActionItemCategory 1', - isDisabled: false, - }, - ], - }, - }, - }, - { - request: { - query: MEMBERS_LIST, - variables: { id: '123' }, - }, - result: { - data: { - organizations: [ - { - _id: '123', - members: [ - { - _id: 'user1', - firstName: 'Harve', - lastName: 'Lance', - email: 'harve@example.com', - image: '', - organizationsBlockedBy: [], - createdAt: '2024-02-14', - }, - ], - }, - ], - }, - }, - }, - { - request: { - query: ACTION_ITEM_LIST, - variables: { id: '123' }, - }, - error: new Error('Mock Graphql Error'), - }, -]; - -export const MOCKS_ERROR_MUTATIONS = [ - { - request: { - query: ACTION_ITEM_CATEGORY_LIST, - variables: { organizationId: '123' }, - }, - result: { - data: { - actionItemCategoriesByOrganization: [ - { - _id: 'actionItemCategory1', - name: 'ActionItemCategory 1', - isDisabled: false, - }, - ], - }, - }, - }, - { - request: { - query: MEMBERS_LIST, - variables: { id: '123' }, - }, - result: { - data: { - organizations: [ - { - _id: '123', - members: [ - { - _id: 'user1', - firstName: 'Harve', - lastName: 'Lance', - email: 'harve@example.com', - image: '', - organizationsBlockedBy: [], - createdAt: '2024-02-14', - }, - ], - }, - ], - }, - }, - }, - { - request: { - query: ACTION_ITEM_LIST, - variables: { - organizationId: '123', - orderBy: 'createdAt_DESC', - actionItemCategoryId: '', - isActive: false, - isCompleted: false, - }, - }, - result: { - data: { - actionItemsByOrganization: [ - { - _id: 'actionItem1', - assignee: { - _id: 'user1', - firstName: 'Harve', - lastName: 'Lance', - }, - actionItemCategory: { - _id: 'actionItemCategory1', - name: 'ActionItemCategory 1', - }, - preCompletionNotes: 'Pre Completion Notes', - postCompletionNotes: 'Post Completion Notes', - assignmentDate: '2024-02-14', - dueDate: '2024-02-21', - completionDate: '2024-02-21', - isCompleted: false, - assigner: { - _id: 'user0', - firstName: 'Wilt', - lastName: 'Shepherd', - }, - event: { - _id: 'event1', - title: 'event 1', - }, - creator: { - _id: 'user0', - firstName: 'Wilt', - lastName: 'Shepherd', - }, - }, - ], - }, - }, - }, - { - request: { - query: ACTION_ITEM_LIST, - variables: { - organizationId: '123', - eventId: 'event1', - orderBy: 'createdAt_DESC', - }, - }, - result: { - data: { - actionItemsByOrganization: [ - { - _id: 'actionItem1', - assignee: { - _id: 'user1', - firstName: 'Harve', - lastName: 'Lance', - }, - actionItemCategory: { - _id: 'actionItemCategory1', - name: 'ActionItemCategory 1', - }, - preCompletionNotes: 'Pre Completion Notes', - postCompletionNotes: 'Post Completion Notes', - assignmentDate: '2024-02-14', - dueDate: '2024-02-21', - completionDate: '2024-02-21', - isCompleted: false, - assigner: { - _id: 'user0', - firstName: 'Wilt', - lastName: 'Shepherd', - }, - event: { - _id: 'event1', - title: 'event 1', - }, - creator: { - _id: 'user0', - firstName: 'Wilt', - lastName: 'Shepherd', - }, - }, - ], - }, - }, - }, - { - request: { - query: CREATE_ACTION_ITEM_MUTATION, - variables: { - actionItemCategoryId: 'actionItemCategory1', - assigneeId: 'user1', - preCompletionNotes: 'pre completion notes', - dueDate: '2024-02-14', - }, - }, - error: new Error('Mock Graphql Error'), - }, - { - request: { - query: CREATE_ACTION_ITEM_MUTATION, - variables: { - eventId: 'event1', - actionItemCategoryId: 'actionItemCategory1', - assigneeId: 'user1', - preCompletionNotes: 'pre completion notes', - dueDate: '2024-02-14', - }, - }, - error: new Error('Mock Graphql Error'), - }, -]; diff --git a/src/screens/OrganizationDashboard/OrganizationDashboard.test.tsx b/src/screens/OrganizationDashboard/OrganizationDashboard.test.tsx index e377826a73..dde6b3120f 100644 --- a/src/screens/OrganizationDashboard/OrganizationDashboard.test.tsx +++ b/src/screens/OrganizationDashboard/OrganizationDashboard.test.tsx @@ -1,5 +1,5 @@ import { MockedProvider } from '@apollo/react-testing'; -import { act, fireEvent, render, screen } from '@testing-library/react'; +import { fireEvent, render, screen } from '@testing-library/react'; import 'jest-location-mock'; import { I18nextProvider } from 'react-i18next'; import { Provider } from 'react-redux'; @@ -13,7 +13,7 @@ import i18nForTest from 'utils/i18nForTest'; import useLocalStorage from 'utils/useLocalstorage'; import OrganizationDashboard from './OrganizationDashboard'; import { EMPTY_MOCKS, ERROR_MOCKS, MOCKS } from './OrganizationDashboardMocks'; -import React from 'react'; +import React, { act } from 'react'; const { setItem } = useLocalStorage(); import type { InterfaceQueryOrganizationEventListItem } from 'utils/interfaces'; diff --git a/src/screens/OrganizationEvents/OrganizationEvents.tsx b/src/screens/OrganizationEvents/OrganizationEvents.tsx index 2998e89c39..4c90777c6e 100644 --- a/src/screens/OrganizationEvents/OrganizationEvents.tsx +++ b/src/screens/OrganizationEvents/OrganizationEvents.tsx @@ -170,8 +170,8 @@ function organizationEvents(): JSX.Element { endDate: dayjs(endDate).format('YYYY-MM-DD'), allDay: alldaychecked, location: formState.location, - startTime: !alldaychecked ? formState.startTime + 'Z' : undefined, - endTime: !alldaychecked ? formState.endTime + 'Z' : undefined, + startTime: !alldaychecked ? formState.startTime : undefined, + endTime: !alldaychecked ? formState.endTime : undefined, recurrenceStartDate: recurringchecked ? dayjs(recurrenceStartDate).format('YYYY-MM-DD') : undefined, @@ -196,7 +196,7 @@ function organizationEvents(): JSX.Element { }); if (createEventData) { - toast.success(t('eventCreated')); + toast.success(t('eventCreated') as string); refetchEvents(); hideCreateEventModal(); setFormState({ diff --git a/src/screens/OrganizationEvents/OrganizationEventsMocks.ts b/src/screens/OrganizationEvents/OrganizationEventsMocks.ts index 3ff2d73d5a..4f844d33fa 100644 --- a/src/screens/OrganizationEvents/OrganizationEventsMocks.ts +++ b/src/screens/OrganizationEvents/OrganizationEventsMocks.ts @@ -106,8 +106,8 @@ export const MOCKS = [ startDate: '2022-03-28', endDate: '2022-03-30', allDay: false, - startTime: '09:00:00Z', - endTime: '17:00:00Z', + startTime: '09:00:00', + endTime: '17:00:00', }, }, result: { @@ -132,8 +132,8 @@ export const MOCKS = [ startDate: '2022-03-28', endDate: '2022-03-30', allDay: false, - startTime: '09:00:00Z', - endTime: '17:00:00Z', + startTime: '09:00:00', + endTime: '17:00:00', recurrenceStartDate: '2022-03-28', recurrenceEndDate: null, frequency: 'DAILY', @@ -162,8 +162,8 @@ export const MOCKS = [ startDate: '2022-03-28', endDate: '2022-03-30', allDay: false, - startTime: '09:00:00Z', - endTime: '17:00:00Z', + startTime: '09:00:00', + endTime: '17:00:00', recurrenceStartDate: '2022-03-28', recurrenceEndDate: null, frequency: 'WEEKLY', diff --git a/src/screens/OrganizationFundCampaign/CampaignDeleteModal.test.tsx b/src/screens/OrganizationFundCampaign/CampaignDeleteModal.test.tsx deleted file mode 100644 index 7baf9bd933..0000000000 --- a/src/screens/OrganizationFundCampaign/CampaignDeleteModal.test.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import React from 'react'; -import type { ApolloLink } from '@apollo/client'; -import { MockedProvider } from '@apollo/react-testing'; -import { LocalizationProvider } from '@mui/x-date-pickers'; -import type { RenderResult } from '@testing-library/react'; -import { fireEvent, render, screen, waitFor } from '@testing-library/react'; -import { I18nextProvider } from 'react-i18next'; -import { Provider } from 'react-redux'; -import { BrowserRouter } from 'react-router-dom'; -import { store } from 'state/store'; -import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; -import i18nForTest from '../../utils/i18nForTest'; -import { StaticMockLink } from 'utils/StaticMockLink'; -import { toast } from 'react-toastify'; -import { MOCKS, MOCK_ERROR } from './OrganizationFundCampaignMocks'; -import CampaignDeleteModal, { - type InterfaceDeleteCampaignModal, -} from './CampaignDeleteModal'; - -jest.mock('react-toastify', () => ({ - toast: { - success: jest.fn(), - error: jest.fn(), - }, -})); - -const link1 = new StaticMockLink(MOCKS); -const link2 = new StaticMockLink(MOCK_ERROR); -const translations = JSON.parse( - JSON.stringify(i18nForTest.getDataByLanguage('en')?.translation.fundCampaign), -); - -const campaignProps: InterfaceDeleteCampaignModal = { - isOpen: true, - hide: jest.fn(), - campaign: { - _id: 'campaignId1', - name: 'Campaign 1', - fundingGoal: 100, - startDate: new Date('2021-01-01'), - endDate: new Date('2024-01-01'), - currency: 'USD', - createdAt: '2021-01-01', - }, - refetchCampaign: jest.fn(), -}; -const renderFundDeleteModal = ( - link: ApolloLink, - props: InterfaceDeleteCampaignModal, -): RenderResult => { - return render( - - - - - - - - - - - , - ); -}; - -describe('CampaignDeleteModal', () => { - it('should render CampaignDeleteModal', () => { - renderFundDeleteModal(link1, campaignProps); - expect(screen.getByTestId('deleteCampaignCloseBtn')).toBeInTheDocument(); - }); - - it('should successfully Delete Campaign', async () => { - renderFundDeleteModal(link1, campaignProps); - expect(screen.getByTestId('deleteCampaignCloseBtn')).toBeInTheDocument(); - - fireEvent.click(screen.getByTestId('deleteyesbtn')); - - await waitFor(() => { - expect(campaignProps.refetchCampaign).toHaveBeenCalled(); - expect(campaignProps.hide).toHaveBeenCalled(); - expect(toast.success).toHaveBeenCalledWith(translations.deletedCampaign); - }); - }); - - it('should fail to Delete Campaign', async () => { - renderFundDeleteModal(link2, campaignProps); - expect(screen.getByTestId('deleteCampaignCloseBtn')).toBeInTheDocument(); - - fireEvent.click(screen.getByTestId('deleteyesbtn')); - - await waitFor(() => { - expect(toast.error).toHaveBeenCalledWith('Mock graphql error'); - }); - }); -}); diff --git a/src/screens/OrganizationFundCampaign/CampaignDeleteModal.tsx b/src/screens/OrganizationFundCampaign/CampaignDeleteModal.tsx deleted file mode 100644 index c6c0b5a35e..0000000000 --- a/src/screens/OrganizationFundCampaign/CampaignDeleteModal.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import React from 'react'; -import { Button, Modal } from 'react-bootstrap'; -import styles from './OrganizationFundCampaign.module.css'; -import { useMutation } from '@apollo/client'; -import { DELETE_CAMPAIGN_MUTATION } from 'GraphQl/Mutations/CampaignMutation'; -import type { InterfaceCampaignInfo } from 'utils/interfaces'; -import { toast } from 'react-toastify'; -import { useTranslation } from 'react-i18next'; - -/** - * Props for the CampaignDeleteModal component. - */ -export interface InterfaceDeleteCampaignModal { - isOpen: boolean; - hide: () => void; - campaign: InterfaceCampaignInfo | null; - refetchCampaign: () => void; -} - -/** - * Modal component for confirming the deletion of a campaign. - * - * @param props - The props for the CampaignDeleteModal component. - * @returns JSX.Element - */ -const CampaignDeleteModal: React.FC = ({ - isOpen, - hide, - campaign, - refetchCampaign, -}) => { - const { t } = useTranslation('translation', { - keyPrefix: 'fundCampaign', - }); - const { t: tCommon } = useTranslation('common'); - - const [deleteCampaign] = useMutation(DELETE_CAMPAIGN_MUTATION); - - /** - * Handles the campaign deletion. - * - * @returns Promise - */ - const deleteCampaignHandler = async (): Promise => { - try { - await deleteCampaign({ - variables: { - id: campaign?._id, - }, - }); - toast.success(t('deletedCampaign')); - refetchCampaign(); - hide(); - } catch (error: unknown) { - toast.error((error as Error).message); - } - }; - return ( - <> - - -

{t('deleteCampaign')}

- -
- -

{t('deleteCampaignMsg')}

-
- - - - -
- - ); -}; -export default CampaignDeleteModal; diff --git a/src/screens/OrganizationFundCampaign/CampaignModal.tsx b/src/screens/OrganizationFundCampaign/CampaignModal.tsx index 1a31dd8242..63d1e8de3a 100644 --- a/src/screens/OrganizationFundCampaign/CampaignModal.tsx +++ b/src/screens/OrganizationFundCampaign/CampaignModal.tsx @@ -106,7 +106,7 @@ const CampaignModal: React.FC = ({ fundId, }, }); - toast.success(t('createdCampaign')); + toast.success(t('createdCampaign') as string); setFormState({ campaignName: '', campaignCurrency: 'USD', @@ -166,7 +166,7 @@ const CampaignModal: React.FC = ({ }); refetchCampaign(); hide(); - toast.success(t('updatedCampaign')); + toast.success(t('updatedCampaign') as string); } catch (error: unknown) { toast.error((error as Error).message); } diff --git a/src/screens/OrganizationFundCampaign/OrganizationFundCampagins.tsx b/src/screens/OrganizationFundCampaign/OrganizationFundCampagins.tsx index 8abba9bab6..1c6dbf2cc6 100644 --- a/src/screens/OrganizationFundCampaign/OrganizationFundCampagins.tsx +++ b/src/screens/OrganizationFundCampaign/OrganizationFundCampagins.tsx @@ -13,7 +13,6 @@ import React, { useCallback, useMemo, useState } from 'react'; import dayjs from 'dayjs'; import Loader from 'components/Loader/Loader'; import CampaignModal from './CampaignModal'; -import CampaignDeleteModal from './CampaignDeleteModal'; import { FUND_CAMPAIGN } from 'GraphQl/Queries/fundQueries'; import styles from './OrganizationFundCampaign.module.css'; import { currencySymbols } from 'utils/currency'; @@ -43,33 +42,26 @@ const dataGridStyle = { }, }; -enum ModalState { - SAME = 'same', - DELETE = 'delete', -} /** * `orgFundCampaign` component displays a list of fundraising campaigns for a specific fund within an organization. - * It allows users to search, sort, view, edit, and delete campaigns. + * It allows users to search, sort, view and edit campaigns. * * ### Functionality * - Displays a data grid with campaigns information, including their names, start and end dates, funding goals, and actions. * - Provides search functionality to filter campaigns by name. * - Offers sorting options based on funding goal and end date. - * - Opens modals for creating, editing, or deleting campaigns. + * - Opens modals for creating or editing campaigns. * * * ### State * - `campaign`: The current campaign being edited or deleted. * - `searchTerm`: The term used for searching campaigns by name. * - `sortBy`: The current sorting criteria for campaigns. - * - `modalState`: An object indicating the visibility of different modals (`same` for create/edit and `delete` for deletion). + * - `modalState`: An object indicating the visibility of different modals (`same` for create/edit). * - `campaignModalMode`: Determines if the modal is in 'edit' or 'create' mode. * * ### Methods - * - `openModal(modal: ModalState)`: Opens the specified modal. - * - `closeModal(modal: ModalState)`: Closes the specified modal. * - `handleOpenModal(campaign: InterfaceCampaignInfo | null, mode: 'edit' | 'create')`: Opens the modal for creating or editing a campaign. - * - `handleDeleteClick(campaign: InterfaceCampaignInfo)`: Opens the delete confirmation modal. * - `handleClick(campaignId: string)`: Navigates to the pledge details page for a specific campaign. * * ### GraphQL Queries @@ -77,7 +69,7 @@ enum ModalState { * * ### Rendering * - Renders a `DataGrid` component with campaigns information. - * - Displays modals for creating, editing, and deleting campaigns. + * - Displays modals for creating and editing campaigns. * - Shows error and loading states using `Loader` and error message components. * * @returns The rendered component including breadcrumbs, search and filter controls, data grid, and modals. @@ -99,36 +91,18 @@ const orgFundCampaign = (): JSX.Element => { const [searchTerm, setSearchTerm] = useState(''); const [sortBy, setSortBy] = useState(null); - const [modalState, setModalState] = useState<{ - [key in ModalState]: boolean; - }>({ - [ModalState.SAME]: false, - [ModalState.DELETE]: false, - }); + const [modalState, setModalState] = useState(false); const [campaignModalMode, setCampaignModalMode] = useState<'edit' | 'create'>( 'create', ); - const openModal = (modal: ModalState): void => - setModalState((prevState) => ({ ...prevState, [modal]: true })); - - const closeModal = (modal: ModalState): void => - setModalState((prevState) => ({ ...prevState, [modal]: false })); const handleOpenModal = useCallback( (campaign: InterfaceCampaignInfo | null, mode: 'edit' | 'create'): void => { setCampaign(campaign); setCampaignModalMode(mode); - openModal(ModalState.SAME); - }, - [openModal], - ); - - const handleDeleteClick = useCallback( - (campaign: InterfaceCampaignInfo): void => { - setCampaign(campaign); - openModal(ModalState.DELETE); + setModalState(true); }, - [openModal], + [], ); const { @@ -157,10 +131,11 @@ const orgFundCampaign = (): JSX.Element => { navigate(`/fundCampaignPledge/${orgId}/${campaignId}`); }; - const campaigns = useMemo(() => { - if (campaignData?.getFundById?.campaigns) - return campaignData.getFundById.campaigns; - return []; + const { campaigns, fundName, isArchived } = useMemo(() => { + const fundName = campaignData?.getFundById?.name || 'Fund'; + const isArchived = campaignData?.getFundById?.isArchived || false; + const campaigns = campaignData?.getFundById?.campaigns || []; + return { fundName, campaigns, isArchived }; }, [campaignData]); if (campaignLoading) { @@ -319,17 +294,6 @@ const orgFundCampaign = (): JSX.Element => { > - ); }, @@ -370,9 +334,9 @@ const orgFundCampaign = (): JSX.Element => { data-testid="fundsLink" onClick={() => navigate(`/orgfunds/${orgId}`)} > - {tCommon('Funds')} + {fundName} - FundRaising Campaign + {t('title')}
@@ -440,6 +404,7 @@ const orgFundCampaign = (): JSX.Element => { className={styles.orgFundCampaignButton} onClick={() => handleOpenModal(null, 'create')} data-testid="addCampaignBtn" + disabled={isArchived} > {t('addCampaign')} @@ -474,21 +439,14 @@ const orgFundCampaign = (): JSX.Element => { {/* Create Campaign ModalState */} closeModal(ModalState.SAME)} + isOpen={modalState} + hide={() => setModalState(false)} refetchCampaign={refetchCampaign} fundId={fundId} orgId={orgId} campaign={campaign} mode={campaignModalMode} /> - - closeModal(ModalState.DELETE)} - campaign={campaign} - refetchCampaign={refetchCampaign} - />
); }; diff --git a/src/screens/OrganizationFundCampaign/OrganizationFundCampaign.test.tsx b/src/screens/OrganizationFundCampaign/OrganizationFundCampaign.test.tsx index 74f646f51a..9c169e355a 100644 --- a/src/screens/OrganizationFundCampaign/OrganizationFundCampaign.test.tsx +++ b/src/screens/OrganizationFundCampaign/OrganizationFundCampaign.test.tsx @@ -173,22 +173,6 @@ describe('FundCampaigns Screen', () => { ); }); - it('open and closes delete campaign modal', async () => { - renderFundCampaign(link1); - - const deleteCampaignBtn = await screen.findAllByTestId('deleteCampaignBtn'); - await waitFor(() => expect(deleteCampaignBtn[0]).toBeInTheDocument()); - userEvent.click(deleteCampaignBtn[0]); - - await waitFor(() => - expect(screen.getByText(translations.deleteCampaign)).toBeInTheDocument(), - ); - userEvent.click(screen.getByTestId('deleteCampaignCloseBtn')); - await waitFor(() => - expect(screen.queryByTestId('deleteCampaignCloseBtn')).toBeNull(), - ); - }); - it('Search the Campaigns list by Name', async () => { renderFundCampaign(link1); const searchField = await screen.findByTestId('searchFullName'); diff --git a/src/screens/OrganizationFundCampaign/OrganizationFundCampaignMocks.ts b/src/screens/OrganizationFundCampaign/OrganizationFundCampaignMocks.ts index 6ec2521665..fa871c3593 100644 --- a/src/screens/OrganizationFundCampaign/OrganizationFundCampaignMocks.ts +++ b/src/screens/OrganizationFundCampaign/OrganizationFundCampaignMocks.ts @@ -1,6 +1,5 @@ import { CREATE_CAMPAIGN_MUTATION, - DELETE_CAMPAIGN_MUTATION, UPDATE_CAMPAIGN_MUTATION, } from 'GraphQl/Mutations/CampaignMutation'; import { FUND_CAMPAIGN } from 'GraphQl/Queries/fundQueries'; @@ -18,6 +17,8 @@ export const MOCKS = [ result: { data: { getFundById: { + name: 'Fund 1', + isArchived: false, campaigns: [ { _id: 'campaignId1', @@ -52,6 +53,8 @@ export const MOCKS = [ result: { data: { getFundById: { + name: 'Fund 1', + isArchived: false, campaigns: [ { _id: '2', @@ -78,6 +81,8 @@ export const MOCKS = [ result: { data: { getFundById: { + name: 'Fund 1', + isArchived: false, campaigns: [ { _id: '1', @@ -112,6 +117,8 @@ export const MOCKS = [ result: { data: { getFundById: { + name: 'Fund 1', + isArchived: false, campaigns: [ { _id: '2', @@ -146,6 +153,8 @@ export const MOCKS = [ result: { data: { getFundById: { + name: 'Fund 1', + isArchived: false, campaigns: [ { _id: '2', @@ -180,6 +189,8 @@ export const MOCKS = [ result: { data: { getFundById: { + name: 'Fund 1', + isArchived: false, campaigns: [ { _id: '1', @@ -242,21 +253,6 @@ export const MOCKS = [ }, }, }, - { - request: { - query: DELETE_CAMPAIGN_MUTATION, - variables: { - id: 'campaignId1', - }, - }, - result: { - data: { - removeFundraisingCampaign: { - _id: 'campaignId1', - }, - }, - }, - }, ]; export const MOCK_ERROR = [ @@ -299,15 +295,6 @@ export const MOCK_ERROR = [ }, error: new Error('Mock graphql error'), }, - { - request: { - query: DELETE_CAMPAIGN_MUTATION, - variables: { - id: 'campaignId1', - }, - }, - error: new Error('Mock graphql error'), - }, ]; export const EMPTY_MOCKS = [ @@ -323,6 +310,8 @@ export const EMPTY_MOCKS = [ result: { data: { getFundById: { + name: 'Fund 1', + isArchived: false, campaigns: [], }, }, diff --git a/src/screens/OrganizationFunds/FundDeleteModal.test.tsx b/src/screens/OrganizationFunds/FundDeleteModal.test.tsx deleted file mode 100644 index 41fcd3ec5e..0000000000 --- a/src/screens/OrganizationFunds/FundDeleteModal.test.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import React from 'react'; -import type { ApolloLink } from '@apollo/client'; -import { MockedProvider } from '@apollo/react-testing'; -import { LocalizationProvider } from '@mui/x-date-pickers'; -import type { RenderResult } from '@testing-library/react'; -import { fireEvent, render, screen, waitFor } from '@testing-library/react'; -import { I18nextProvider } from 'react-i18next'; -import { Provider } from 'react-redux'; -import { BrowserRouter } from 'react-router-dom'; -import { store } from 'state/store'; -import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; -import i18nForTest from '../../utils/i18nForTest'; -import { StaticMockLink } from 'utils/StaticMockLink'; -import { toast } from 'react-toastify'; -import type { InterfaceDeleteFundModal } from './FundDeleteModal'; -import FundDeleteModal from './FundDeleteModal'; -import { MOCKS, MOCKS_ERROR } from './OrganizationFundsMocks'; - -jest.mock('react-toastify', () => ({ - toast: { - success: jest.fn(), - error: jest.fn(), - }, -})); - -const link1 = new StaticMockLink(MOCKS); -const link2 = new StaticMockLink(MOCKS_ERROR); -const translations = JSON.parse( - JSON.stringify(i18nForTest.getDataByLanguage('en')?.translation.funds), -); - -const fundProps: InterfaceDeleteFundModal = { - isOpen: true, - hide: jest.fn(), - fund: { - _id: 'fundId', - name: 'Fund 1', - refrenceNumber: '1111', - taxDeductible: true, - isArchived: false, - isDefault: false, - createdAt: '2024-06-22', - organizationId: 'orgId', - creator: { - _id: 'creatorId1', - firstName: 'John', - lastName: 'Doe', - }, - }, - refetchFunds: jest.fn(), -}; -const renderFundDeleteModal = ( - link: ApolloLink, - props: InterfaceDeleteFundModal, -): RenderResult => { - return render( - - - - - - - - - - - , - ); -}; - -describe('FundDeleteModal', () => { - it('should render FundDeleteModal', () => { - renderFundDeleteModal(link1, fundProps); - expect(screen.getByTestId('deleteFundCloseBtn')).toBeInTheDocument(); - }); - - it('should successfully Delete Fund', async () => { - renderFundDeleteModal(link1, fundProps); - expect(screen.getByTestId('deleteFundCloseBtn')).toBeInTheDocument(); - - fireEvent.click(screen.getByTestId('deleteyesbtn')); - - await waitFor(() => { - expect(fundProps.refetchFunds).toHaveBeenCalled(); - expect(fundProps.hide).toHaveBeenCalled(); - expect(toast.success).toHaveBeenCalledWith(translations.fundDeleted); - }); - }); - - it('should fail to Delete Fund', async () => { - renderFundDeleteModal(link2, fundProps); - expect(screen.getByTestId('deleteFundCloseBtn')).toBeInTheDocument(); - - fireEvent.click(screen.getByTestId('deleteyesbtn')); - - await waitFor(() => { - expect(toast.error).toHaveBeenCalledWith('Mock graphql error'); - }); - }); -}); diff --git a/src/screens/OrganizationFunds/FundDeleteModal.tsx b/src/screens/OrganizationFunds/FundDeleteModal.tsx deleted file mode 100644 index bca92614ca..0000000000 --- a/src/screens/OrganizationFunds/FundDeleteModal.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import React from 'react'; -import { useTranslation } from 'react-i18next'; -import type { InterfaceFundInfo } from 'utils/interfaces'; -import styles from './OrganizationFunds.module.css'; -import { Button, Modal } from 'react-bootstrap'; -import { REMOVE_FUND_MUTATION } from 'GraphQl/Mutations/FundMutation'; -import { useMutation } from '@apollo/client'; -import { toast } from 'react-toastify'; - -export interface InterfaceDeleteFundModal { - isOpen: boolean; - hide: () => void; - fund: InterfaceFundInfo | null; - refetchFunds: () => void; -} -/** - * `FundDeleteModal` component provides a modal dialog for confirming the deletion of a fund. - * It prompts the user to confirm or cancel the deletion of a specific fund. - * - * ### Props - * - `isOpen`: A boolean indicating whether the modal is open or closed. - * - `hide`: A function to close the modal. - * - `fund`: The fund object to be deleted or `null` if no fund is selected. - * - `refetchFunds`: A function to refetch the list of funds after a successful deletion. - * - * ### Methods - * - `deleteFundHandler()`: Asynchronously handles the deletion of the fund using the `REMOVE_FUND_MUTATION` mutation. - * - `onClose()`: Closes the modal without deleting the fund. - * - * ### Behavior - * - Displays a confirmation modal when `isOpen` is `true`. - * - On confirmation, it triggers the `deleteFundHandler` to perform the deletion. - * - On successful deletion, it calls `refetchFunds`, hides the modal, and shows a success toast notification. - * - On failure, it shows an error toast notification. - * - * @returns The rendered modal dialog. - */ -const FundDeleteModal: React.FC = ({ - isOpen, - hide, - fund, - refetchFunds, -}) => { - const { t } = useTranslation('translation', { - keyPrefix: 'funds', - }); - const { t: tCommon } = useTranslation('common'); - - const [deleteFund] = useMutation(REMOVE_FUND_MUTATION); - - const deleteFundHandler = async (): Promise => { - try { - await deleteFund({ - variables: { - id: fund?._id, - }, - }); - refetchFunds(); - hide(); - toast.success(t('fundDeleted')); - } catch (error: unknown) { - toast.error((error as Error).message); - } - }; - - return ( - <> - - -

{t('fundDelete')}

- -
- -

{t('deleteFundMsg')}

-
- - - - -
- - ); -}; - -export default FundDeleteModal; diff --git a/src/screens/OrganizationFunds/FundModal.tsx b/src/screens/OrganizationFunds/FundModal.tsx index d347d02111..0f112cba9b 100644 --- a/src/screens/OrganizationFunds/FundModal.tsx +++ b/src/screens/OrganizationFunds/FundModal.tsx @@ -107,7 +107,7 @@ const FundModal: React.FC = ({ taxDeductible: false, isArchived: false, }); - toast.success(t('fundCreated')); + toast.success(t('fundCreated') as string); refetchFunds(); hide(); } catch (error: unknown) { @@ -155,7 +155,7 @@ const FundModal: React.FC = ({ }); refetchFunds(); hide(); - toast.success(t('fundUpdated')); + toast.success(t('fundUpdated') as string); } catch (error: unknown) { if (error instanceof Error) { toast.error(error.message); diff --git a/src/screens/OrganizationFunds/OrganizationFunds.test.tsx b/src/screens/OrganizationFunds/OrganizationFunds.test.tsx index a1b49e68eb..c6983e1d6d 100644 --- a/src/screens/OrganizationFunds/OrganizationFunds.test.tsx +++ b/src/screens/OrganizationFunds/OrganizationFunds.test.tsx @@ -149,22 +149,6 @@ describe('OrganizationFunds Screen =>', () => { ); }); - it('open and closes delete fund modal', async () => { - renderOrganizationFunds(link1); - - const deleteFundBtn = await screen.findAllByTestId('deleteFundBtn'); - await waitFor(() => expect(deleteFundBtn[0]).toBeInTheDocument()); - userEvent.click(deleteFundBtn[0]); - - await waitFor(() => - expect(screen.getByText(translations.fundDelete)).toBeInTheDocument(), - ); - userEvent.click(screen.getByTestId('deleteFundCloseBtn')); - await waitFor(() => - expect(screen.queryByTestId('deleteFundCloseBtn')).toBeNull(), - ); - }); - it('Search the Funds list by name', async () => { renderOrganizationFunds(link1); const searchField = await screen.findByTestId('searchByName'); diff --git a/src/screens/OrganizationFunds/OrganizationFunds.tsx b/src/screens/OrganizationFunds/OrganizationFunds.tsx index 56ed17b377..29ffb0d865 100644 --- a/src/screens/OrganizationFunds/OrganizationFunds.tsx +++ b/src/screens/OrganizationFunds/OrganizationFunds.tsx @@ -13,7 +13,6 @@ import React, { useCallback, useMemo, useState } from 'react'; import dayjs from 'dayjs'; import Loader from 'components/Loader/Loader'; import FundModal from './FundModal'; -import FundDeleteModal from './FundDeleteModal'; import { FUND_LIST } from 'GraphQl/Queries/fundQueries'; import styles from './OrganizationFunds.module.css'; import type { InterfaceFundInfo } from 'utils/interfaces'; @@ -39,24 +38,20 @@ const dataGridStyle = { }, }; -enum ModalState { - SAME = 'same', - DELETE = 'delete', -} /** * `organizationFunds` component displays a list of funds for a specific organization, - * allowing users to search, sort, view, edit, and delete funds. + * allowing users to search, sort, view and edit funds. * * This component utilizes the `DataGrid` from Material-UI to present the list of funds in a tabular format, * and includes functionality for filtering and sorting. It also handles the opening and closing of modals - * for creating, editing, and deleting funds. + * for creating and editing. * * It includes: * - A search input field to filter funds by name. * - A dropdown menu to sort funds by creation date. * - A button to create a new fund. * - A table to display the list of funds with columns for fund details and actions. - * - Modals for creating, editing, and deleting funds. + * - Modals for creating and editing funds. * * ### GraphQL Queries * - `FUND_LIST`: Fetches a list of funds for the given organization, filtered and sorted based on the provided parameters. @@ -68,14 +63,11 @@ enum ModalState { * - `fund`: The currently selected fund for editing or deletion. * - `searchTerm`: The current search term used for filtering funds. * - `sortBy`: The current sorting order for funds. - * - `modalState`: The state of the modals (edit/create or delete). + * - `modalState`: The state of the modals (edit/create). * - `fundModalMode`: The mode of the fund modal (edit or create). * * ### Methods - * - `openModal(modal: ModalState)`: Opens the specified modal. - * - `closeModal(modal: ModalState)`: Closes the specified modal. * - `handleOpenModal(fund: InterfaceFundInfo | null, mode: 'edit' | 'create')`: Opens the fund modal with the given fund and mode. - * - `handleDeleteClick(fund: InterfaceFundInfo)`: Opens the delete modal for the specified fund. * - `handleClick(fundId: string)`: Navigates to the campaign page for the specified fund. * * @returns The rendered component. @@ -99,29 +91,18 @@ const organizationFunds = (): JSX.Element => { 'createdAt_DESC', ); - const [modalState, setModalState] = useState<{ - [key in ModalState]: boolean; - }>({ - [ModalState.SAME]: false, - [ModalState.DELETE]: false, - }); + const [modalState, setModalState] = useState(false); const [fundModalMode, setFundModalMode] = useState<'edit' | 'create'>( 'create', ); - const openModal = (modal: ModalState): void => - setModalState((prevState) => ({ ...prevState, [modal]: true })); - - const closeModal = (modal: ModalState): void => - setModalState((prevState) => ({ ...prevState, [modal]: false })); - const handleOpenModal = useCallback( (fund: InterfaceFundInfo | null, mode: 'edit' | 'create'): void => { setFund(fund); setFundModalMode(mode); - openModal(ModalState.SAME); + setModalState(true); }, - [openModal], + [], ); const { @@ -144,14 +125,6 @@ const organizationFunds = (): JSX.Element => { }, }); - const handleDeleteClick = useCallback( - (fund: InterfaceFundInfo): void => { - setFund(fund); - openModal(ModalState.DELETE); - }, - [openModal], - ); - const funds = useMemo(() => fundData?.fundsByOrganization ?? [], [fundData]); const handleClick = (fundId: string): void => { @@ -277,15 +250,6 @@ const organizationFunds = (): JSX.Element => { > - ); }, @@ -405,20 +369,13 @@ const organizationFunds = (): JSX.Element => { isRowSelectable={() => false} /> closeModal(ModalState.SAME)} + isOpen={modalState} + hide={() => setModalState(false)} refetchFunds={refetchFunds} fund={fund} orgId={orgId} mode={fundModalMode} /> - - closeModal(ModalState.DELETE)} - fund={fund} - refetchFunds={refetchFunds} - />
); }; diff --git a/src/screens/OrganizationFunds/OrganizationFundsMocks.ts b/src/screens/OrganizationFunds/OrganizationFundsMocks.ts index cd03dc7f31..81e5a0fb64 100644 --- a/src/screens/OrganizationFunds/OrganizationFundsMocks.ts +++ b/src/screens/OrganizationFunds/OrganizationFundsMocks.ts @@ -1,6 +1,5 @@ import { CREATE_FUND_MUTATION, - REMOVE_FUND_MUTATION, UPDATE_FUND_MUTATION, } from 'GraphQl/Mutations/FundMutation'; import { FUND_LIST } from 'GraphQl/Queries/fundQueries'; @@ -169,21 +168,6 @@ export const MOCKS = [ }, }, }, - { - request: { - query: REMOVE_FUND_MUTATION, - variables: { - id: 'fundId', - }, - }, - result: { - data: { - removeFund: { - _id: 'fundId', - }, - }, - }, - }, ]; export const NO_FUNDS = [ @@ -230,15 +214,6 @@ export const MOCKS_ERROR = [ }, error: new Error('Mock graphql error'), }, - { - request: { - query: REMOVE_FUND_MUTATION, - variables: { - id: 'fundId', - }, - }, - error: new Error('Mock graphql error'), - }, { request: { query: UPDATE_FUND_MUTATION, diff --git a/src/screens/OrganizationPeople/AddMember.tsx b/src/screens/OrganizationPeople/AddMember.tsx index 71c1fb1fb7..4c1f15106e 100644 --- a/src/screens/OrganizationPeople/AddMember.tsx +++ b/src/screens/OrganizationPeople/AddMember.tsx @@ -31,6 +31,7 @@ import type { InterfaceQueryUserListItem, } from 'utils/interfaces'; import styles from './OrganizationPeople.module.css'; +import Avatar from 'components/Avatar/Avatar'; const StyledTableCell = styled(TableCell)(({ theme }) => ({ [`&.${tableCellClasses.head}`]: { @@ -101,7 +102,7 @@ function AddMember(): JSX.Element { orgid: currentUrl, }, }); - toast.success(tCommon('addedSuccessfully', { item: 'Member' })); + toast.success(tCommon('addedSuccessfully', { item: 'Member' }) as string); memberRefetch({ orgId: currentUrl, }); @@ -193,11 +194,11 @@ function AddMember(): JSX.Element { createUserVariables.lastName ) ) { - toast.error(translateOrgPeople('invalidDetailsMessage')); + toast.error(translateOrgPeople('invalidDetailsMessage') as string); } else if ( createUserVariables.password !== createUserVariables.confirmPassword ) { - toast.error(translateOrgPeople('passwordNotMatch')); + toast.error(translateOrgPeople('passwordNotMatch') as string); } else { try { const registeredUser = await registerMutation({ @@ -366,6 +367,9 @@ function AddMember(): JSX.Element { # + + {translateAddMember('profile')} + {translateAddMember('user')} @@ -389,6 +393,24 @@ function AddMember(): JSX.Element { {index + 1} + + {userDetails.user.image ? ( + avatar + ) : ( + + )} + { userEvent.click(screen.getByTestId('existingUser')); await wait(); - expect(screen.getByTestId('addExistingUserModal')).toBeInTheDocument(); + expect( + screen.getAllByTestId('addExistingUserModal').length, + ).toBeGreaterThan(0); await wait(); - userEvent.click(screen.getByTestId('addBtn')); + const addBtn = screen.getAllByTestId('addBtn'); + userEvent.click(addBtn[0]); }); test('Open and search existing user', async () => { @@ -1353,3 +1382,54 @@ describe('Organization People Page', () => { expect(screen.queryByText(/Nothing Found !!/i)).toBeInTheDocument(); }); }); + +test('Open and check if profile image is displayed for existing user', async () => { + window.location.assign('/orgpeople/orgid'); + render( + + + + + + + + + , + ); + + // Wait for the component to finish rendering + await wait(); + + // Click on the dropdown toggle to open the menu + userEvent.click(screen.getByTestId('addMembers')); + await wait(); + + // Click on the "Admins" option in the dropdown menu + userEvent.click(screen.getByTestId('existingUser')); + await wait(); + + expect(screen.getByTestId('addExistingUserModal')).toBeInTheDocument(); + await wait(); + + expect(screen.getAllByTestId('user').length).toBeGreaterThan(0); + await wait(); + + // Check if the image is rendered + expect(screen.getAllByTestId('profileImage').length).toBeGreaterThan(0); + await wait(); + + const images = await screen.findAllByAltText('avatar'); + expect(images.length).toBeGreaterThan(0); + await wait(); + + const avatarImages = await screen.findAllByAltText('Dummy Avatar'); + expect(avatarImages.length).toBeGreaterThan(0); + await wait(); +}); diff --git a/src/screens/OrganizationTags/OrganizationTags.tsx b/src/screens/OrganizationTags/OrganizationTags.tsx index 0dcefc17e3..e4bf0604e9 100644 --- a/src/screens/OrganizationTags/OrganizationTags.tsx +++ b/src/screens/OrganizationTags/OrganizationTags.tsx @@ -99,7 +99,7 @@ function OrganizationTags(): JSX.Element { }); if (data) { - toast.success(t('tagCreationSuccess')); + toast.success(t('tagCreationSuccess') as string); orgUserTagsRefetch(); setTagName(''); setCreateTagModalIsOpen(false); @@ -123,7 +123,7 @@ function OrganizationTags(): JSX.Element { orgUserTagsRefetch(); toggleRemoveUserTagModal(); - toast.success(t('tagRemovalSuccess')); + toast.success(t('tagRemovalSuccess') as string); } catch (error: unknown) { /* istanbul ignore next */ if (error instanceof Error) { diff --git a/src/screens/Requests/Requests.test.tsx b/src/screens/Requests/Requests.test.tsx index bf1a533870..4606fdae08 100644 --- a/src/screens/Requests/Requests.test.tsx +++ b/src/screens/Requests/Requests.test.tsx @@ -1,6 +1,6 @@ -import React from 'react'; +import React, { act } from 'react'; import { MockedProvider } from '@apollo/react-testing'; -import { act, render, screen } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import 'jest-localstorage-mock'; import 'jest-location-mock'; import { I18nextProvider } from 'react-i18next'; diff --git a/src/screens/Requests/Requests.tsx b/src/screens/Requests/Requests.tsx index a8d9f40315..38bc51195e 100644 --- a/src/screens/Requests/Requests.tsx +++ b/src/screens/Requests/Requests.tsx @@ -102,7 +102,7 @@ const Requests = (): JSX.Element => { } if (orgsData.organizationsConnection.length === 0) { - toast.warning(t('noOrgError')); + toast.warning(t('noOrgError') as string); } }, [orgsData]); diff --git a/src/screens/SubTags/SubTags.tsx b/src/screens/SubTags/SubTags.tsx index 9c8ac44191..b381c9f816 100644 --- a/src/screens/SubTags/SubTags.tsx +++ b/src/screens/SubTags/SubTags.tsx @@ -126,7 +126,7 @@ function SubTags(): JSX.Element { /* istanbul ignore next */ if (data) { - toast.success(t('tagCreationSuccess')); + toast.success(t('tagCreationSuccess') as string); subTagsRefetch(); setTagName(''); setAddSubTagModalIsOpen(false); @@ -150,7 +150,7 @@ function SubTags(): JSX.Element { subTagsRefetch(); toggleRemoveUserTagModal(); - toast.success(t('tagRemovalSuccess')); + toast.success(t('tagRemovalSuccess') as string); } catch (error: unknown) { /* istanbul ignore next */ if (error instanceof Error) { diff --git a/src/screens/UserPortal/Campaigns/Campaigns.test.tsx b/src/screens/UserPortal/Campaigns/Campaigns.test.tsx index 443d643cff..17b7eec4d5 100644 --- a/src/screens/UserPortal/Campaigns/Campaigns.test.tsx +++ b/src/screens/UserPortal/Campaigns/Campaigns.test.tsx @@ -155,14 +155,17 @@ describe('Testing User Campaigns Screen', () => { it('Check if All details are rendered correctly', async () => { renderCampaigns(link1); + + const detailContainer = await screen.findByTestId('detailContainer1'); + const detailContainer2 = await screen.findByTestId('detailContainer2'); await waitFor(() => { - const detailContainer = screen.getByTestId('detailContainer1'); + expect(detailContainer).toBeInTheDocument(); + expect(detailContainer2).toBeInTheDocument(); expect(detailContainer).toHaveTextContent('School Campaign'); expect(detailContainer).toHaveTextContent('$22000'); expect(detailContainer).toHaveTextContent('2024-07-28'); expect(detailContainer).toHaveTextContent('2025-08-31'); expect(detailContainer).toHaveTextContent('Active'); - const detailContainer2 = screen.getByTestId('detailContainer2'); expect(detailContainer2).toHaveTextContent('Hospital Campaign'); expect(detailContainer2).toHaveTextContent('$9000'); expect(detailContainer2).toHaveTextContent('2024-07-28'); @@ -291,18 +294,6 @@ describe('Testing User Campaigns Screen', () => { }); }); - it('Redirect to My Pledges screen', async () => { - renderCampaigns(link1); - - const myPledgesBtn = await screen.findByText(cTranslations.myPledges); - expect(myPledgesBtn).toBeInTheDocument(); - userEvent.click(myPledgesBtn); - - await waitFor(() => { - expect(screen.getByTestId('pledgeScreen')).toBeInTheDocument(); - }); - }); - it('open and closes add pledge modal', async () => { renderCampaigns(link1); @@ -318,4 +309,16 @@ describe('Testing User Campaigns Screen', () => { expect(screen.queryByTestId('pledgeModalCloseBtn')).toBeNull(), ); }); + + it('Redirect to My Pledges screen', async () => { + renderCampaigns(link1); + + const myPledgesBtn = await screen.findByText(cTranslations.myPledges); + expect(myPledgesBtn).toBeInTheDocument(); + userEvent.click(myPledgesBtn); + + await waitFor(() => { + expect(screen.getByTestId('pledgeScreen')).toBeInTheDocument(); + }); + }); }); diff --git a/src/screens/UserPortal/Campaigns/Campaigns.tsx b/src/screens/UserPortal/Campaigns/Campaigns.tsx index 4aeca6876d..e4483f87fe 100644 --- a/src/screens/UserPortal/Campaigns/Campaigns.tsx +++ b/src/screens/UserPortal/Campaigns/Campaigns.tsx @@ -244,8 +244,12 @@ const Campaigns = (): JSX.Element => { {campaign.fundingGoal} Raised: $0 - Start Date: {campaign.startDate} - End Date: {campaign.endDate} + + Start Date: {campaign.startDate as unknown as string} + + + End Date: {campaign.endDate as unknown as string} +
diff --git a/src/screens/UserPortal/Campaigns/CampaignsMocks.ts b/src/screens/UserPortal/Campaigns/CampaignsMocks.ts index 7b91fac025..f64401bca5 100644 --- a/src/screens/UserPortal/Campaigns/CampaignsMocks.ts +++ b/src/screens/UserPortal/Campaigns/CampaignsMocks.ts @@ -1,6 +1,63 @@ import { USER_DETAILS } from 'GraphQl/Queries/Queries'; import { USER_FUND_CAMPAIGNS } from 'GraphQl/Queries/fundQueries'; +const userDetailsQuery = { + request: { + query: USER_DETAILS, + variables: { + id: 'userId', + }, + }, + result: { + data: { + user: { + user: { + _id: 'userId', + joinedOrganizations: [ + { + _id: '6537904485008f171cf29924', + __typename: 'Organization', + }, + ], + firstName: 'Harve', + lastName: 'Lance', + email: 'testuser1@example.com', + image: null, + createdAt: '2023-04-13T04:53:17.742Z', + birthDate: null, + educationGrade: null, + employmentStatus: null, + gender: null, + maritalStatus: null, + phone: null, + address: { + line1: 'Line1', + countryCode: 'CountryCode', + city: 'CityName', + state: 'State', + __typename: 'Address', + }, + registeredEvents: [], + membershipRequests: [], + __typename: 'User', + }, + appUserProfile: { + _id: '67078abd85008f171cf2991d', + adminFor: [], + isSuperAdmin: false, + appLanguageCode: 'en', + pluginCreationAllowed: true, + createdOrganizations: [], + createdEvents: [], + eventAdmin: [], + __typename: 'AppUserProfile', + }, + __typename: 'UserData', + }, + }, + }, +}; + export const MOCKS = [ { request: { @@ -173,62 +230,7 @@ export const MOCKS = [ }, }, }, - { - request: { - query: USER_DETAILS, - variables: { - id: 'userId', - }, - }, - result: { - data: { - user: { - user: { - _id: 'userId', - joinedOrganizations: [ - { - _id: '6537904485008f171cf29924', - __typename: 'Organization', - }, - ], - firstName: 'Harve', - lastName: 'Lance', - email: 'testuser1@example.com', - image: null, - createdAt: '2023-04-13T04:53:17.742Z', - birthDate: null, - educationGrade: null, - employmentStatus: null, - gender: null, - maritalStatus: null, - phone: null, - address: { - line1: 'Line1', - countryCode: 'CountryCode', - city: 'CityName', - state: 'State', - __typename: 'Address', - }, - registeredEvents: [], - membershipRequests: [], - __typename: 'User', - }, - appUserProfile: { - _id: '67078abd85008f171cf2991d', - adminFor: [], - isSuperAdmin: false, - appLanguageCode: 'en', - pluginCreationAllowed: true, - createdOrganizations: [], - createdEvents: [], - eventAdmin: [], - __typename: 'AppUserProfile', - }, - __typename: 'UserData', - }, - }, - }, - }, + userDetailsQuery, ]; export const EMPTY_MOCKS = [ @@ -249,62 +251,7 @@ export const EMPTY_MOCKS = [ }, }, }, - { - request: { - query: USER_DETAILS, - variables: { - id: 'userId', - }, - }, - result: { - data: { - user: { - user: { - _id: 'userId', - joinedOrganizations: [ - { - _id: '6537904485008f171cf29924', - __typename: 'Organization', - }, - ], - firstName: 'Harve', - lastName: 'Lance', - email: 'testuser1@example.com', - image: null, - createdAt: '2023-04-13T04:53:17.742Z', - birthDate: null, - educationGrade: null, - employmentStatus: null, - gender: null, - maritalStatus: null, - phone: null, - address: { - line1: 'Line1', - countryCode: 'CountryCode', - city: 'CityName', - state: 'State', - __typename: 'Address', - }, - registeredEvents: [], - membershipRequests: [], - __typename: 'User', - }, - appUserProfile: { - _id: '67078abd85008f171cf2991d', - adminFor: [], - isSuperAdmin: false, - appLanguageCode: 'en', - pluginCreationAllowed: true, - createdOrganizations: [], - createdEvents: [], - eventAdmin: [], - __typename: 'AppUserProfile', - }, - __typename: 'UserData', - }, - }, - }, - }, + userDetailsQuery, ]; export const USER_FUND_CAMPAIGNS_ERROR = [ @@ -321,4 +268,5 @@ export const USER_FUND_CAMPAIGNS_ERROR = [ }, error: new Error('Error fetching campaigns'), }, + userDetailsQuery, ]; diff --git a/src/screens/UserPortal/Campaigns/PledgeModal.tsx b/src/screens/UserPortal/Campaigns/PledgeModal.tsx index 6ab2832c2f..d9076f52c8 100644 --- a/src/screens/UserPortal/Campaigns/PledgeModal.tsx +++ b/src/screens/UserPortal/Campaigns/PledgeModal.tsx @@ -166,7 +166,7 @@ const PledgeModal: React.FC = ({ ...updatedFields, }, }); - toast.success(t('pledgeUpdated')); + toast.success(t('pledgeUpdated') as string); refetchPledge(); hide(); } catch (error: unknown) { @@ -198,7 +198,7 @@ const PledgeModal: React.FC = ({ }, }); - toast.success(t('pledgeCreated')); + toast.success(t('pledgeCreated') as string); refetchPledge(); setFormState({ pledgeUsers: [], diff --git a/src/screens/UserPortal/Chat/Chat.test.tsx b/src/screens/UserPortal/Chat/Chat.test.tsx index 66e6115ce7..4a0dfbcf4e 100644 --- a/src/screens/UserPortal/Chat/Chat.test.tsx +++ b/src/screens/UserPortal/Chat/Chat.test.tsx @@ -1,5 +1,5 @@ -import React from 'react'; -import { act, fireEvent, render, screen } from '@testing-library/react'; +import React, { act } from 'react'; +import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import { MockedProvider } from '@apollo/react-testing'; import { I18nextProvider, useTranslation } from 'react-i18next'; @@ -2259,15 +2259,26 @@ describe('Testing Chat Screen [User Portal]', () => { , ); + screen.debug(); + await waitFor(() => { + const closeMenuBtn = screen.queryByTestId('closeMenu'); + expect(closeMenuBtn).toBeInTheDocument(); + if (closeMenuBtn) { + closeMenuBtn.click(); + } else { + throw new Error('Close menu button not found'); + } + }); - await wait(); - - const closeMenubtn = screen.getByTestId('closeMenu'); - expect(closeMenubtn).toBeInTheDocument(); - closeMenubtn.click(); - const openMenuBtn = screen.getByTestId('openMenu'); - expect(openMenuBtn).toBeInTheDocument(); - openMenuBtn.click(); + await waitFor(() => { + const openMenuBtn = screen.queryByTestId('openMenu'); + expect(openMenuBtn).toBeInTheDocument(); + if (openMenuBtn) { + openMenuBtn.click(); + } else { + throw new Error('Open menu button not found'); + } + }); }); test('Testing sidebar when the screen size is less than or equal to 820px', async () => { @@ -2293,12 +2304,16 @@ describe('Testing Chat Screen [User Portal]', () => { , ); - await wait(); - expect(screen.getByText('My Organizations')).toBeInTheDocument(); - expect(screen.getByText('Talawa User Portal')).toBeInTheDocument(); - - const chatBtn = screen.getByTestId('chatBtn'); - - chatBtn.click(); + screen.debug(); + await waitFor(() => { + expect(screen.getByText('My Organizations')).toBeInTheDocument(); + expect(screen.getByText('Talawa User Portal')).toBeInTheDocument(); + }); + await waitFor(() => { + const chatBtn = screen.getByTestId('chatBtn'); + act(() => { + chatBtn.click(); + }); + }); }); }); diff --git a/src/screens/UserPortal/Donate/Donate.test.tsx b/src/screens/UserPortal/Donate/Donate.test.tsx index fc30ba7503..c4d435415e 100644 --- a/src/screens/UserPortal/Donate/Donate.test.tsx +++ b/src/screens/UserPortal/Donate/Donate.test.tsx @@ -1,5 +1,5 @@ -import React from 'react'; -import { act, render, screen } from '@testing-library/react'; +import React, { act } from 'react'; +import { render, screen } from '@testing-library/react'; import { MockedProvider } from '@apollo/react-testing'; import { I18nextProvider } from 'react-i18next'; @@ -16,6 +16,7 @@ import Donate from './Donate'; import userEvent from '@testing-library/user-event'; import useLocalStorage from 'utils/useLocalstorage'; import { DONATE_TO_ORGANIZATION } from 'GraphQl/Mutations/mutations'; +import { toast } from 'react-toastify'; const MOCKS = [ { @@ -136,6 +137,13 @@ jest.mock('react-router-dom', () => ({ useParams: () => ({ orgId: '' }), })); +jest.mock('react-toastify', () => ({ + toast: { + error: jest.fn(), + success: jest.fn(), + }, +})); + describe('Testing Donate Screen [User Portal]', () => { Object.defineProperty(window, 'matchMedia', { writable: true, @@ -280,4 +288,103 @@ describe('Testing Donate Screen [User Portal]', () => { userEvent.click(screen.getByTestId('donateBtn')); await wait(); }); + + test('displays error toast for donation amount below minimum', async () => { + render( + + + + + + + + + , + ); + + await wait(); + + userEvent.type(screen.getByTestId('donationAmount'), '0.5'); + userEvent.click(screen.getByTestId('donateBtn')); + + await wait(); + + expect(toast.error).toHaveBeenCalledWith( + 'Donation amount must be between 1 and 10000000.', + ); + }); + + test('displays error toast for donation amount above maximum', async () => { + render( + + + + + + + + + , + ); + + await wait(); + + userEvent.type(screen.getByTestId('donationAmount'), '10000001'); + userEvent.click(screen.getByTestId('donateBtn')); + + await wait(); + + expect(toast.error).toHaveBeenCalledWith( + 'Donation amount must be between 1 and 10000000.', + ); + }); + + test('displays error toast for empty donation amount', async () => { + render( + + + + + + + + + , + ); + + await wait(); + + userEvent.click(screen.getByTestId('donateBtn')); + + await wait(); + + expect(toast.error).toHaveBeenCalledWith( + 'Please enter a numerical value for the donation amount.', + ); + }); + + test('displays error toast for invalid (non-numeric) donation amount', async () => { + render( + + + + + + + + + , + ); + + await wait(); + + userEvent.type(screen.getByTestId('donationAmount'), 'abc'); + userEvent.click(screen.getByTestId('donateBtn')); + + await wait(); + + expect(toast.error).toHaveBeenCalledWith( + 'Please enter a numerical value for the donation amount.', + ); + }); }); diff --git a/src/screens/UserPortal/Donate/Donate.tsx b/src/screens/UserPortal/Donate/Donate.tsx index 4a87bd2490..c6e6d918d5 100644 --- a/src/screens/UserPortal/Donate/Donate.tsx +++ b/src/screens/UserPortal/Donate/Donate.tsx @@ -128,6 +128,28 @@ export default function donate(): JSX.Element { }, [donationData]); const donateToOrg = (): void => { + // check if the amount is non empty and is a number + if (amount === '' || Number.isNaN(Number(amount))) { + toast.error(t(`invalidAmount`)); + return; + } + + // check if the amount is non negative and within the range + const minDonation = 1; + const maxDonation = 10000000; + if ( + Number(amount) <= 0 || + Number(amount) < minDonation || + Number(amount) > maxDonation + ) { + toast.error( + t(`donationOutOfRange`, { min: minDonation, max: maxDonation }), + ); + return; + } + + const formattedAmount = Number(amount.trim()); + try { donate({ variables: { @@ -135,12 +157,12 @@ export default function donate(): JSX.Element { createDonationOrgId2: organizationId, payPalId: 'paypalId', nameOfUser: userName, - amount: Number(amount), + amount: formattedAmount, nameOfOrg: organizationDetails.name, }, }); refetch(); - toast.success(t(`success`)); + toast.success(t(`success`) as string); } catch (error: unknown) { /* istanbul ignore next */ errorHandler(t, error); @@ -216,6 +238,9 @@ export default function donate(): JSX.Element { />
+ + {t('donationAmountDescription')} +