From 52279f9f9b3d970cba5a7d6203d57e2b53570553 Mon Sep 17 00:00:00 2001 From: Vamsi Krishna Rolla Date: Fri, 9 May 2025 17:43:20 -0400 Subject: [PATCH 01/28] Code files to fetch, update & delete issues --- package-lock.json | 139 ++++---- package.json | 2 +- src/actions/bmdashboard/issueChartActions.js | 48 +++ .../BMDashboard/Issues/IssuesList.css | 109 ++++++ .../BMDashboard/Issues/IssuesList.jsx | 322 ++++++++++++++++++ .../BMDashboard/Issues/issueCharts.js | 2 +- .../WeeklyProjectSummary.jsx | 9 +- src/constants/bmdashboard/issueConstants.js | 3 + src/reducers/bmdashboard/issueReducer.js | 17 +- src/reducers/index.js | 2 + src/utils/URL.js | 2 + 11 files changed, 584 insertions(+), 71 deletions(-) create mode 100644 src/components/BMDashboard/Issues/IssuesList.css create mode 100644 src/components/BMDashboard/Issues/IssuesList.jsx diff --git a/package-lock.json b/package-lock.json index bd5cba6b38..2512761dcf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7644,15 +7644,15 @@ "integrity": "sha512-ij4wRiunFfaJxjB0BdrYHIH8FxBJpOwNPhhAcunlmPdXudL1WQV1qoP9un6JsEBAgQH+7UXyyjh0g7jTxXK6tg==" }, "@emotion/babel-plugin": { - "version": "11.11.0", - "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz", - "integrity": "sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==", + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", + "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", "requires": { "@babel/helper-module-imports": "^7.16.7", "@babel/runtime": "^7.18.3", - "@emotion/hash": "^0.9.1", - "@emotion/memoize": "^0.8.1", - "@emotion/serialize": "^1.1.2", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.3.3", "babel-plugin-macros": "^3.1.0", "convert-source-map": "^1.5.0", "escape-string-regexp": "^4.0.0", @@ -7674,78 +7674,78 @@ } }, "@emotion/cache": { - "version": "11.11.0", - "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.11.0.tgz", - "integrity": "sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==", - "requires": { - "@emotion/memoize": "^0.8.1", - "@emotion/sheet": "^1.2.2", - "@emotion/utils": "^1.2.1", - "@emotion/weak-memoize": "^0.3.1", + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", + "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", + "requires": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", "stylis": "4.2.0" } }, "@emotion/hash": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.1.tgz", - "integrity": "sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==" + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==" }, "@emotion/memoize": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", - "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==" }, "@emotion/react": { - "version": "11.11.1", - "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.11.1.tgz", - "integrity": "sha512-5mlW1DquU5HaxjLkfkGN1GA/fvVGdyHURRiX/0FHl2cfIfRxSOfmxEH5YS43edp0OldZrZ+dkBKbngxcNCdZvA==", + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", + "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", "requires": { "@babel/runtime": "^7.18.3", - "@emotion/babel-plugin": "^11.11.0", - "@emotion/cache": "^11.11.0", - "@emotion/serialize": "^1.1.2", - "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1", - "@emotion/utils": "^1.2.1", - "@emotion/weak-memoize": "^0.3.1", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", "hoist-non-react-statics": "^3.3.1" } }, "@emotion/serialize": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.2.tgz", - "integrity": "sha512-zR6a/fkFP4EAcCMQtLOhIgpprZOwNmCldtpaISpvz348+DP4Mz8ZoKaGGCQpbzepNIUWbq4w6hNZkwDyKoS+HA==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", "requires": { - "@emotion/hash": "^0.9.1", - "@emotion/memoize": "^0.8.1", - "@emotion/unitless": "^0.8.1", - "@emotion/utils": "^1.2.1", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.2", "csstype": "^3.0.2" } }, "@emotion/sheet": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.2.tgz", - "integrity": "sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==" + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==" }, "@emotion/unitless": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", - "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==" + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==" }, "@emotion/use-insertion-effect-with-fallbacks": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz", - "integrity": "sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz", + "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==" }, "@emotion/utils": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.1.tgz", - "integrity": "sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==" + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", + "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==" }, "@emotion/weak-memoize": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz", - "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==" + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==" }, "@eslint/eslintrc": { "version": "0.4.3", @@ -7784,18 +7784,27 @@ } }, "@floating-ui/core": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.3.1.tgz", - "integrity": "sha512-Bu+AMaXNjrpjh41znzHqaz3r2Nr8hHuHZT6V2LBKMhyMl0FgKA62PNYbqnfgmzOhoWZj70Zecisbo4H1rotP5g==" + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.0.tgz", + "integrity": "sha512-FRdBLykrPPA6P76GGGqlex/e7fbe0F1ykgxHYNXQsH/iTEtjMj/f9bpY5oQqbjt5VgZvgz/uKXbGuROijh3VLA==", + "requires": { + "@floating-ui/utils": "^0.2.9" + } }, "@floating-ui/dom": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.4.5.tgz", - "integrity": "sha512-96KnRWkRnuBSSFbj0sFGwwOUd8EkiecINVl0O9wiZlZ64EkpyAOG3Xc2vKKNJmru0Z7RqWNymA+6b8OZqjgyyw==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.0.tgz", + "integrity": "sha512-lGTor4VlXcesUMh1cupTUTDoCxMb0V6bm3CnxHzQcw8Eaf1jQbgQX4i02fYgT0vJ82tb5MZ4CZk1LRGkktJCzg==", "requires": { - "@floating-ui/core": "^1.3.1" + "@floating-ui/core": "^1.7.0", + "@floating-ui/utils": "^0.2.9" } }, + "@floating-ui/utils": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz", + "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==" + }, "@fortawesome/fontawesome-common-types": { "version": "0.2.36", "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.36.tgz", @@ -29823,9 +29832,9 @@ } }, "react-select": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/react-select/-/react-select-5.7.2.tgz", - "integrity": "sha512-cTlJkQ8YjV6T/js8wW0owTzht0hHGABh29vjLscY4HfZGkv7hc3FFTmRp9NzY/Ib1uQ36GieAKEjxpHdpCFpcA==", + "version": "5.10.1", + "resolved": "https://registry.npmjs.org/react-select/-/react-select-5.10.1.tgz", + "integrity": "sha512-roPEZUL4aRZDx6DcsD+ZNreVl+fM8VsKn0Wtex1v4IazH60ILp5xhdlp464IsEAlJdXeD+BhDAFsBVMfvLQueA==", "requires": { "@babel/runtime": "^7.12.0", "@emotion/cache": "^11.4.0", @@ -29835,7 +29844,7 @@ "memoize-one": "^6.0.0", "prop-types": "^15.6.0", "react-transition-group": "^4.3.0", - "use-isomorphic-layout-effect": "^1.1.2" + "use-isomorphic-layout-effect": "^1.2.0" } }, "react-smooth": { @@ -34106,9 +34115,9 @@ "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==" }, "use-isomorphic-layout-effect": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz", - "integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.2.0.tgz", + "integrity": "sha512-q6ayo8DWoPZT0VdG4u3D3uxcgONP3Mevx2i2b0434cwWBoL+aelL1DzkXI6w3PhTZzUeR2kaVlZn70iCiseP6w==" }, "use-memo-one": { "version": "1.1.3", diff --git a/package.json b/package.json index c17a5410e1..b0698f8ab0 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,7 @@ "react-router-dom": "^5.2.0", "react-router-hash-link": "^2.3.1", "react-scripts": "^4.0.3", - "react-select": "^5.7.2", + "react-select": "^5.10.1", "react-sticky": "^6.0.3", "react-table": "^6.10.0", "react-toastify": "^5.3.1", diff --git a/src/actions/bmdashboard/issueChartActions.js b/src/actions/bmdashboard/issueChartActions.js index 961a2c41e9..cdafb63047 100644 --- a/src/actions/bmdashboard/issueChartActions.js +++ b/src/actions/bmdashboard/issueChartActions.js @@ -6,6 +6,8 @@ import { FETCH_ISSUE_TYPES_YEARS_REQUEST, FETCH_ISSUE_TYPES_YEARS_SUCCESS, FETCH_ISSUE_TYPES_YEARS_FAILURE, + UPDATE_ISSUE, + DELETE_ISSUE, } from '../../constants/bmdashboard/issueConstants'; import { ENDPOINTS } from '../../utils/URL'; // Import the endpoints @@ -28,6 +30,52 @@ export const fetchIssues = filters => async dispatch => { } }; +// Action to fetch issues for the bar chart +export const fetchOpenIssues = () => async dispatch => { + try { + dispatch({ type: FETCH_ISSUES_BARCHART_REQUEST }); + + const { data } = await axios.get(ENDPOINTS.BM_ISSUE_CHART); + + dispatch({ + type: FETCH_ISSUES_BARCHART_SUCCESS, + payload: data, + }); + } catch (error) { + dispatch({ + type: FETCH_ISSUES_BARCHART_FAILURE, + payload: error.message || 'Failed to fetch issues', + }); + } +}; + +export const updateIssue = (issueId, updates) => async dispatch => { + try { + // 1. Send update to backend + const response = await axios.patch(ENDPOINTS.BM_ISSUE_UPDATE(issueId), updates); + + // 2. Dispatch action to update Redux store + dispatch({ type: UPDATE_ISSUE, payload: response.data }); + } catch (error) { + dispatch({ + type: FETCH_ISSUES_BARCHART_FAILURE, + payload: error.message || 'Failed to update issue', + }); + } +}; + +export const deleteIssue = (issueId) => async dispatch => { + try { + await axios.delete(ENDPOINTS.BM_ISSUE_UPDATE(issueId)); + dispatch({ type: DELETE_ISSUE, payload: issueId }); + } catch (error) { + dispatch({ + type: FETCH_ISSUES_BARCHART_FAILURE, + payload: error.message || 'Failed to delete issue', + }); + } +}; + // Action to fetch issue types and years (used for dropdowns) export const fetchIssueTypesAndYears = () => async dispatch => { try { diff --git a/src/components/BMDashboard/Issues/IssuesList.css b/src/components/BMDashboard/Issues/IssuesList.css new file mode 100644 index 0000000000..ecfe9e9eb7 --- /dev/null +++ b/src/components/BMDashboard/Issues/IssuesList.css @@ -0,0 +1,109 @@ + + +/* Main table layout */ +.issue-table { + border-collapse: separate; + border-spacing: 0 12px; /* vertical spacing between rows */ + width: 100%; + background-color: #fff; + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + } + + /* Header cells */ + .issue-table thead th { + font-weight: 600; + font-size: 14px; + color: #6c757d; + text-align: left; + padding: 12px 16px; + border-bottom: 1px solid #dee2e6; + background-color: #f8f9fa; + } + + /* Body cells */ + .issue-table tbody td { + font-size: 14px; + color: #212529; + padding: 12px 16px; + background-color: #ffffff; + vertical-align: middle; + } + + /* Row styling */ + .issue-table tbody tr { + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); + } + + /* Button for tags (Virtual / In-person) */ + .issue-table .btn-outline-primary { + font-size: 12px; + padding: 4px 10px; + border-radius: 20px; + font-weight: 500; + } + + /* Dropdown options */ + .dropdown-menu-custom { + padding: 6px 0; + display: flex; + max-width: 50Px; + flex-direction: column; + gap: 4px; + background-color: #fff; + border-radius: 8px; + box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.1); + text-align: center !important; + } + + .dropdown-menu-custom .dropdown-item { + font-size: 13px; + padding: 8px 16px; + border-radius: 6px; + transition: background-color 0.2s ease-in-out; + justify-content: center; + cursor: pointer; + } + + .dropdown-menu-custom .dropdown-item:hover { + background-color: #f1f1f1; + } + + /* Dropdown toggle button styling */ + .dropdown-toggle { + border: 1px solid #c2b36e !important; + background-color: #fff !important; + color: #4b4b4b !important; + font-size: 13px !important; + font-weight: 500; + padding: 4px 14px !important; + border-radius: 6px !important; + } + + /* Filter section above table */ + .table-controls { + display: flex; + justify-content: space-between; + align-items: center; + padding-bottom: 12px; + } + + /* Pagination styling (if you're customizing) */ + .pagination { + margin-top: 16px; + justify-content: center; + } + + .pagination .page-item .page-link { + border-radius: 6px; + margin: 0 4px; + font-size: 14px; + color: #4b4b4b; + } + + .pagination .page-item.active .page-link { + background-color: #d4c684; + border-color: #d4c684; + color: #fff; + } + \ No newline at end of file diff --git a/src/components/BMDashboard/Issues/IssuesList.jsx b/src/components/BMDashboard/Issues/IssuesList.jsx new file mode 100644 index 0000000000..1b0829b901 --- /dev/null +++ b/src/components/BMDashboard/Issues/IssuesList.jsx @@ -0,0 +1,322 @@ +import { useState, useEffect } from 'react'; +import DatePicker from 'react-datepicker'; +import Select from 'react-select'; +import 'react-datepicker/dist/react-datepicker.css'; +import { Table, Button, Dropdown, Form, Row, Col, Container } from 'react-bootstrap'; +import './IssuesList.css'; +import { useDispatch, useSelector } from 'react-redux'; +import { fetchBMProjects } from 'actions/bmdashboard/projectActions'; +import { deleteIssue, fetchOpenIssues, updateIssue } from 'actions/bmdashboard/issueChartActions'; + +export default function IssueList() { + const [tagFilter, setTagFilter] = useState(null); + const [selectedProjects, setSelectedProjects] = useState([]); + const [dateRange, setDateRange] = useState([null, null]); + const [startDate, endDate] = dateRange; + const [issues, setIssues] = useState([]); + const [editingId, setEditingId] = useState(null); + const [editedName, setEditedName] = useState(''); + + const [dropdownOpenId, setDropdownOpenId] = useState(null); + const closeDropdown = () => setDropdownOpenId(null); + + const handleTagClick = tag => setTagFilter(tag); + const dispatch = useDispatch(); + + const { issues: rawIssues } = useSelector(state => state.bmIssues); + const projects = useSelector(state => state.bmProjects); + const projectMap = Object.fromEntries(projects.map(p => [p._id, p.name])); + + useEffect(() => { + dispatch(fetchOpenIssues()); + dispatch(fetchBMProjects()); + }, [dispatch]); + + const getDaysSinceCreated = createdDateStr => { + const created = new Date(createdDateStr); + const now = new Date(); + const diffTime = now - created; // difference in milliseconds + const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24)); // convert to days + return diffDays; + }; + + useEffect(() => { + if (Array.isArray(rawIssues) && rawIssues.length > 0) { + const processed = rawIssues.map(issue => ({ + id: issue._id, + name: issue.issueTitle?.[0] || 'Untitled', + tag: issue.tag || '', + date: new Date(issue.createdDate.split('T')[0]) || null, + project: projectMap[issue.projectId] || 'Unknown Project', + openSince: getDaysSinceCreated(issue.createdDate.split('T')[0]), + cost: issue.cost, + person: issue.project, + })); + setIssues(processed); + } + }, [rawIssues]); + + const uniqueProjects = [...new Set(issues.map(issue => issue.project))]; + const projectOptions = uniqueProjects.map(project => ({ + label: project, + value: project, + })); + + const handleRename = issueId => { + const issue = issues.find(i => i.id === issueId); + if (issue) { + setEditingId(issueId); + setEditedName(issue.name); + } + closeDropdown(); + }; + + const handleDelete = issueId => { + dispatch(deleteIssue(issueId)); + closeDropdown(); + }; + + const handleCloseIssue = issueId => { + dispatch(updateIssue(issueId, { status: 'close' })); + closeDropdown(); + }; + + const handleNameChange = e => { + setEditedName(e.target.value); + }; + + const handleReset = () => { + setTagFilter(null); + setSelectedProjects([]); + setDateRange([null, null]); + }; + + const handleNameSubmit = issueId => { + dispatch(updateIssue(issueId, { 'issueTitle.0': editedName })); + setEditingId(null); + setEditedName(''); + }; + + const filteredIssues = issues.filter(issue => { + const inDateRange = + !startDate || !endDate || (issue.date >= startDate && issue.date <= endDate); + return ( + (!tagFilter || issue.tag === tagFilter) && + (selectedProjects.length === 0 || selectedProjects.includes(issue.project)) && + inDateRange + ); + }); + + const [currentPage, setCurrentPage] = useState(1); + const [pageGroupStart, setPageGroupStart] = useState(1); + const itemsPerPage = 5; + + const totalPages = Math.ceil(filteredIssues.length / itemsPerPage); + + // Slice data for current page + const indexOfLastItem = currentPage * itemsPerPage; + const indexOfFirstItem = indexOfLastItem - itemsPerPage; + const currentItems = filteredIssues.slice(indexOfFirstItem, indexOfLastItem); + const handlePageClick = page => { + setCurrentPage(page); + + // Shift page group if needed + if (page >= pageGroupStart + 5) { + setPageGroupStart(pageGroupStart + 5); + } else if (page < pageGroupStart) { + setPageGroupStart(pageGroupStart - 5); + } + }; + + const renderPageNumbers = () => { + const pageNumbers = []; + const end = Math.min(pageGroupStart + 4, totalPages); + // eslint-disable-next-line no-plusplus + for (let i = pageGroupStart; i <= end; i++) { + pageNumbers.push( + , + ); + } + return pageNumbers; + }; + + const formatDate = date => { + return date.toISOString().slice(0, 10); // YYYY-MM-DD + }; + + const formattedRange = + startDate && endDate ? `${formatDate(startDate)} - ${formatDate(endDate)}` : ''; + + return ( + +

A List of Issues

+ + + {/* Date Filter */} + +
+ setDateRange(update)} + isClearable={false} // we'll use our own clear button + placeholderText="Filter by Date Range" + className="form-control" + value={startDate && endDate ? formattedRange : ''} + /> + +
+ + + {/* Project Filter */} + +
+