From 08fb9fb1398b918bd63759c3b4a3bf0c6f178e18 Mon Sep 17 00:00:00 2001 From: Vamsi Krishna Rolla Date: Fri, 9 May 2025 17:43:20 -0400 Subject: [PATCH 01/12] Code files to fetch, update & delete issues --- .../BMDashboard/Issues/IssuesList.css | 109 ++++++ .../BMDashboard/Issues/IssuesList.jsx | 322 ++++++++++++++++++ 2 files changed, 431 insertions(+) create mode 100644 src/components/BMDashboard/Issues/IssuesList.css create mode 100644 src/components/BMDashboard/Issues/IssuesList.jsx 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 */} + +
+