diff --git a/package-lock.json b/package-lock.json index 94d54c80..da9e50dd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47,7 +47,7 @@ "react-dom": "^16.13.1", "react-html-parser": "^2.0.2", "react-router-dom": "6.26.2", - "simple-git": "^3.25.0", + "simple-git": "^3.27.0", "uuid": "^10.0.0", "yargs": "^17.7.2" }, @@ -11822,6 +11822,7 @@ "version": "3.27.0", "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.27.0.tgz", "integrity": "sha512-ivHoFS9Yi9GY49ogc6/YAi3Fl9ROnF4VyubNylgCkA+RVqLaKWnDSzXOVzya8csELIaWaYNutsEuAhZrtOjozA==", + "license": "MIT", "dependencies": { "@kwsites/file-exists": "^1.1.1", "@kwsites/promise-deferred": "^1.1.1", diff --git a/package.json b/package.json index 05a1b08d..00e9d7ee 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,7 @@ "react-dom": "^16.13.1", "react-html-parser": "^2.0.2", "react-router-dom": "6.26.2", - "simple-git": "^3.25.0", + "simple-git": "^3.27.0", "uuid": "^10.0.0", "yargs": "^17.7.2" }, diff --git a/src/ui/components/Filtering/Filtering.css b/src/ui/components/Filtering/Filtering.css new file mode 100644 index 00000000..84f9258e --- /dev/null +++ b/src/ui/components/Filtering/Filtering.css @@ -0,0 +1,55 @@ +.filtering-container { + position: relative; + display: inline-block; + padding-bottom: 10px; +} + +.dropdown-toggle { + padding: 10px 10px; + padding-right: 10px; + border: 1px solid #ccc; + border-radius: 5px; + background-color: #fff; + color: #333; + cursor: pointer; + font-size: 14px; + text-align: left; + width: 130px; + display: inline-flex; + align-items: center; + justify-content: space-between; +} + +.dropdown-toggle:hover { + background-color: #f0f0f0; +} + +.dropdown-arrow { + border: none; + background: none; + cursor: pointer; + font-size: 15px; + margin-left: 1px; + margin-right: 10px; +} + +.dropdown-menu { + position: absolute; + background-color: #fff; + border: 1px solid #ccc; + border-radius: 5px; + margin-top: 5px; + z-index: 1000; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); +} + +.dropdown-item { + padding: 10px 15px; + cursor: pointer; + font-size: 14px; + color: #333; +} + +.dropdown-item:hover { + background-color: #f0f0f0; +} \ No newline at end of file diff --git a/src/ui/components/Filtering/Filtering.jsx b/src/ui/components/Filtering/Filtering.jsx new file mode 100644 index 00000000..aa9d26c7 --- /dev/null +++ b/src/ui/components/Filtering/Filtering.jsx @@ -0,0 +1,66 @@ +import React, { useState } from 'react'; +import './Filtering.css'; + +const Filtering = ({ onFilterChange }) => { + const [isOpen, setIsOpen] = useState(false); // State to toggle dropdown open/close + const [selectedOption, setSelectedOption] = useState('Sort by'); // Initial state + const [sortOrder, setSortOrder] = useState('asc'); // Track sort order (asc/desc) + + const toggleDropdown = () => { + setIsOpen(!isOpen); // Toggle dropdown open/close state + }; + + const toggleSortOrder = () => { + // Toggle sort order only if an option is selected + if (selectedOption !== 'Sort by') { + const newSortOrder = sortOrder === 'asc' ? 'desc' : 'asc'; + setSortOrder(newSortOrder); + onFilterChange(selectedOption, newSortOrder); // Trigger filtering with new order + } + }; + + const handleOptionClick = (option) => { + // Handle filter option selection + setSelectedOption(option); + onFilterChange(option, sortOrder); // Call the parent function with selected filter and order + setIsOpen(false); // Collapse the dropdown after selection + }; + + return ( +
+
+ {/* Make the entire button clickable for toggling dropdown */} + + + {isOpen && ( +
+
handleOptionClick('Date Modified')} className="dropdown-item"> + Date Modified +
+
handleOptionClick('Date Created')} className="dropdown-item"> + Date Created +
+
handleOptionClick('Alphabetical')} className="dropdown-item"> + Alphabetical +
+
+ )} +
+
+ ); +}; + +export default Filtering; + + + + diff --git a/src/ui/components/Pagination/Pagination.css b/src/ui/components/Pagination/Pagination.css new file mode 100644 index 00000000..fbaa650c --- /dev/null +++ b/src/ui/components/Pagination/Pagination.css @@ -0,0 +1,28 @@ +.paginationContainer { + display: flex; + justify-content: center; + padding: 1rem; + margin-top: 20px; + gap: 10px; +} + +.pageButton { + padding: 8px 12px; + font-size: 14px; + color: #333; + border: 1px solid #ccc; + background-color: #f9f9f9; + cursor: pointer; + border-radius: 5px; + transition: background-color 0.3s ease; +} + +.pageButton:hover { + background-color: #e2e6ea; +} + +.activeButton { + background-color: #007bff; + color: #fff; + border-color: #007bff; +} diff --git a/src/ui/components/Pagination/Pagination.jsx b/src/ui/components/Pagination/Pagination.jsx new file mode 100644 index 00000000..e87e43c1 --- /dev/null +++ b/src/ui/components/Pagination/Pagination.jsx @@ -0,0 +1,37 @@ +import React from 'react'; +import './Pagination.css'; + +export default function Pagination({ currentPage, totalItems = 0, itemsPerPage, onPageChange }) { + + const totalPages = Math.ceil(totalItems / itemsPerPage); + + const handlePageClick = (page) => { + if (page >= 1 && page <= totalPages) { + onPageChange(page); + } + }; + + return ( +
+ + + + Page {currentPage} of {totalPages} + + + +
+ ); +} diff --git a/src/ui/components/Search/Search.css b/src/ui/components/Search/Search.css new file mode 100644 index 00000000..db87dc8c --- /dev/null +++ b/src/ui/components/Search/Search.css @@ -0,0 +1,18 @@ +.search-bar { + width: 100%; + max-width:100%; + margin: 0 auto 20px auto; +} + +.search-input { + width: 100%; + padding: 10px; + font-size: 16px; + border: 1px solid #ccc; + border-radius: 4px; + box-sizing: border-box; +} + +.search-input:focus { + border-color: #007bff; +} diff --git a/src/ui/components/Search/Search.jsx b/src/ui/components/Search/Search.jsx new file mode 100644 index 00000000..5e1cbf6b --- /dev/null +++ b/src/ui/components/Search/Search.jsx @@ -0,0 +1,37 @@ +import React from 'react'; +import { TextField } from '@material-ui/core'; +import './Search.css'; +import InputAdornment from '@material-ui/core/InputAdornment'; +import SearchIcon from '@material-ui/icons/Search'; + + +export default function Search({ onSearch }) { + const handleSearchChange = (event) => { + const query = event.target.value; + onSearch(query); + }; + + return ( +
+ + + + ), + }} + /> +
+ ); +} + + + + diff --git a/src/ui/views/OpenPushRequests/OpenPushRequests.jsx b/src/ui/views/OpenPushRequests/OpenPushRequests.jsx index f46f9671..bed34cd3 100644 --- a/src/ui/views/OpenPushRequests/OpenPushRequests.jsx +++ b/src/ui/views/OpenPushRequests/OpenPushRequests.jsx @@ -4,13 +4,18 @@ import GridContainer from '../../components/Grid/GridContainer'; import PushesTable from './components/PushesTable'; import CustomTabs from '../../components/CustomTabs/CustomTabs'; + import { Visibility, CheckCircle, Cancel, Block } from '@material-ui/icons'; export default function Dashboard() { + + + return (
+ ), }, { tabName: 'Approved', tabIcon: CheckCircle, - tabContent: , + tabContent: , }, { tabName: 'Canceled', tabIcon: Cancel, - tabContent: , + tabContent: , }, { tabName: 'Rejected', tabIcon: Block, - tabContent: , + tabContent: , }, ]} /> diff --git a/src/ui/views/OpenPushRequests/components/PushesTable.jsx b/src/ui/views/OpenPushRequests/components/PushesTable.jsx index a01f169e..11a263cf 100644 --- a/src/ui/views/OpenPushRequests/components/PushesTable.jsx +++ b/src/ui/views/OpenPushRequests/components/PushesTable.jsx @@ -13,18 +13,26 @@ import Paper from '@material-ui/core/Paper'; import styles from '../../../assets/jss/material-dashboard-react/views/dashboardStyle'; import { getPushes } from '../../../services/git-push'; import { KeyboardArrowRight } from '@material-ui/icons'; +import Search from '../../../components/Search/Search'; // Import the Search component +import Pagination from '../../../components/Pagination/Pagination'; // Import Pagination component export default function PushesTable(props) { const useStyles = makeStyles(styles); const classes = useStyles(); const [data, setData] = useState([]); - const [, setAuth] = useState(true); + const [filteredData, setFilteredData] = useState([]); // State for filtered data const [isLoading, setIsLoading] = useState(false); const [isError, setIsError] = useState(false); const navigate = useNavigate(); - + const [, setAuth] = useState(true); + const [currentPage, setCurrentPage] = useState(1); // State for current page + const itemsPerPage = 5; + const [searchTerm, setSearchTerm] = useState(''); // Define searchTerm state const openPush = (push) => navigate(`/admin/push/${push}`, { replace: true }); + + + useEffect(() => { const query = {}; @@ -35,94 +43,161 @@ export default function PushesTable(props) { getPushes(setIsLoading, setData, setAuth, setIsError, query); }, [props]); + + // useEffect(() => { + // setFilteredData(data); // Initialize filtered data with full data on load + // }, [data]); + + useEffect(() => { + // Initialize filtered data with full data on load + const filtered = filterByStatus(data); + setFilteredData(filtered); + }, [props]); + + const filterByStatus = (data) => { + if (props.authorised) { + return data.filter(item => item.status === 'approved'); + } + if (props.rejected) { + return data.filter(item => item.status === 'rejected'); + } + if (props.canceled) { + return data.filter(item => item.status === 'canceled'); + } + if (props.blocked) { + return data.filter(item => item.status === 'pending'); + } + return data; + }; + + + // Apply search to the filtered data + useEffect(() => { + const filtered = filterByStatus(data); // Apply status filter first + if (searchTerm) { + const lowerCaseTerm = searchTerm.toLowerCase(); + const searchFiltered = filtered.filter((item) => + item.repo.toLowerCase().includes(lowerCaseTerm) || + item.commitTo.toLowerCase().includes(lowerCaseTerm) || + + item.commitData[0].message.toLowerCase().includes(lowerCaseTerm) + ); + setFilteredData(searchFiltered); + } else { + setFilteredData(filtered); // Reset to filtered data after clearing search + } + setCurrentPage(1); // Reset pagination on search + }, [searchTerm, props]); // Trigger on search or tab change + + // Handler function for search input + const handleSearch = (searchTerm) => { + setSearchTerm(searchTerm); // Update search term state + }; + + + const handlePageChange = (page) => { + setCurrentPage(page); // Update current page + }; + + // Logic for pagination (getting items for the current page) + const indexOfLastItem = currentPage * itemsPerPage; + const indexOfFirstItem = indexOfLastItem - itemsPerPage; + const currentItems = filteredData.slice(indexOfFirstItem, indexOfLastItem); + + // Change page + const paginate = (pageNumber) => setCurrentPage(pageNumber); + if (isLoading) return
Loading...
; if (isError) return
Something went wrong ...
; return (
+ {/* Use the Search component */} + + - +
- Timestamp - Repository - Branch - Commit SHA - Committer - Author - Author E-mail - Commit Message - No. of Commits - + Timestamp + Repository + Branch + Commit SHA + Committer + Author + Author E-mail + Commit Message + No. of Commits + - {[...data].reverse().map((row) => { + {currentItems.reverse().map((row) => { const repoFullName = row.repo.replace('.git', ''); const repoBranch = row.branch.replace('refs/heads/', ''); return ( - + {moment .unix(row.commitData[0].commitTs || row.commitData[0].commitTimestamp) .toString()} - - + + {repoFullName} - + {repoBranch} - + {row.commitTo.substring(0, 8)} - + {row.commitData[0].committer} - + {row.commitData[0].author} - + {row.commitData[0].authorEmail ? ( {row.commitData[0].authorEmail} ) : ( 'No data...' - )}{' '} + )} - {row.commitData[0].message} - {row.commitData.length} - - @@ -131,6 +206,18 @@ export default function PushesTable(props) {
+ {/* Pagination Component */} +
); } + + + + diff --git a/src/ui/views/RepoList/Components/RepoOverview.jsx b/src/ui/views/RepoList/Components/RepoOverview.jsx index a431dc72..d5e9fbad 100644 --- a/src/ui/views/RepoList/Components/RepoOverview.jsx +++ b/src/ui/views/RepoList/Components/RepoOverview.jsx @@ -5,6 +5,7 @@ import GridContainer from '../../../components/Grid/GridContainer'; import GridItem from '../../../components/Grid/GridItem'; import { CodeReviewIcon, LawIcon, PeopleIcon } from '@primer/octicons-react'; + const colors = { '1C Enterprise': '#814CCC', '2-Dimensional Array': '#38761D', @@ -671,4 +672,4 @@ export default function Repositories(props) { ); -} +} \ No newline at end of file diff --git a/src/ui/views/RepoList/Components/Repositories.jsx b/src/ui/views/RepoList/Components/Repositories.jsx index 4970858e..4be87b36 100644 --- a/src/ui/views/RepoList/Components/Repositories.jsx +++ b/src/ui/views/RepoList/Components/Repositories.jsx @@ -12,17 +12,24 @@ import NewRepo from './NewRepo'; import RepoOverview from './RepoOverview'; import { UserContext } from '../../../../context'; import PropTypes from 'prop-types'; +import Search from '../../../components/Search/Search'; +import Pagination from '../../../components/Pagination/Pagination'; +import Filtering from '../../../components/Filtering/Filtering'; + export default function Repositories(props) { const useStyles = makeStyles(styles); const classes = useStyles(); const [data, setData] = useState([]); + const [filteredData, setFilteredData] = useState([]); const [, setAuth] = useState(true); const [isLoading, setIsLoading] = useState(false); const [isError, setIsError] = useState(false); + const [currentPage, setCurrentPage] = useState(1); + const itemsPerPage = 5; const navigate = useNavigate(); - const openRepo = (repo) => navigate(`/admin/repo/${repo}`, { replace: true }); const { user } = useContext(UserContext); + const openRepo = (repo) => navigate(`/admin/repo/${repo}`, { replace: true }); useEffect(() => { const query = {}; @@ -30,14 +37,62 @@ export default function Repositories(props) { if (!k) continue; query[k] = props[k]; } - getRepos(setIsLoading, setData, setAuth, setIsError, query); + getRepos(setIsLoading, (data) => { + setData(data); + setFilteredData(data); + }, setAuth, setIsError, query); }, [props]); const refresh = async (repo) => { - console.log('refresh:', repo); - setData([...data, repo]); + const updatedData = [...data, repo]; + setData(updatedData); + setFilteredData(updatedData); }; + const handleSearch = (query) => { + setCurrentPage(1); + if (!query) { + setFilteredData(data); + } else { + const lowercasedQuery = query.toLowerCase(); + setFilteredData( + data.filter(repo => + repo.name.toLowerCase().includes(lowercasedQuery) || + repo.project.toLowerCase().includes(lowercasedQuery) + ) + ); + } + }; + + // New function for handling filter changes + const handleFilterChange = (filterOption, sortOrder) => { + const sortedData = [...data]; + switch (filterOption) { + case 'dateModified': + sortedData.sort((a, b) => new Date(a.lastModified) - new Date(b.lastModified)); + break; + case 'dateCreated': + sortedData.sort((a, b) => new Date(a.dateCreated) - new Date(b.dateCreated)); + break; + case 'alphabetical': + sortedData.sort((a, b) => a.name.localeCompare(b.name)); + break; + default: + break; + } + + if (sortOrder === 'desc') { + sortedData.reverse(); + } + + setFilteredData(sortedData); + }; + + + const handlePageChange = (page) => setCurrentPage(page); + const startIdx = (currentPage - 1) * itemsPerPage; + const paginatedData = filteredData.slice(startIdx, startIdx + itemsPerPage); + if (isLoading) return
Loading...
; if (isError) return
Something went wrong ...
; @@ -54,8 +109,14 @@ export default function Repositories(props) { key='x' classes={classes} openRepo={openRepo} - data={data} + data={paginatedData} repoButton={addrepoButton} + onSearch={handleSearch} + currentPage={currentPage} + totalItems={filteredData.length} + itemsPerPage={itemsPerPage} + onPageChange={handlePageChange} + onFilterChange={handleFilterChange} // Pass handleFilterChange as prop /> ); } @@ -65,6 +126,11 @@ GetGridContainerLayOut.propTypes = { openRepo: PropTypes.func.isRequired, data: PropTypes.array, repoButton: PropTypes.object, + onSearch: PropTypes.func.isRequired, + currentPage: PropTypes.number.isRequired, + totalItems: PropTypes.number.isRequired, + itemsPerPage: PropTypes.number.isRequired, + onPageChange: PropTypes.func.isRequired, }; function GetGridContainerLayOut(props) { @@ -72,6 +138,9 @@ function GetGridContainerLayOut(props) { {props.repoButton} + + + {/* Include the Filtering component */} @@ -86,6 +155,15 @@ function GetGridContainerLayOut(props) { + + + ); } + diff --git a/src/ui/views/User/User.jsx b/src/ui/views/User/User.jsx index c8b46ebe..8362300f 100644 --- a/src/ui/views/User/User.jsx +++ b/src/ui/views/User/User.jsx @@ -8,7 +8,6 @@ import Button from '../../components/CustomButtons/Button'; import FormLabel from '@material-ui/core/FormLabel'; import { getUser, updateUser, getUserLoggedIn } from '../../services/user'; import { makeStyles } from '@material-ui/core/styles'; - import { LogoGithubIcon } from '@primer/octicons-react'; import CloseRounded from '@material-ui/icons/CloseRounded'; import { Check, Save } from '@material-ui/icons'; diff --git a/src/ui/views/UserList/Components/UserList.jsx b/src/ui/views/UserList/Components/UserList.jsx index b1273cb5..f5e26430 100644 --- a/src/ui/views/UserList/Components/UserList.jsx +++ b/src/ui/views/UserList/Components/UserList.jsx @@ -2,7 +2,6 @@ import React, { useState, useEffect } from 'react'; import { makeStyles } from '@material-ui/core/styles'; import GridItem from '../../../components/Grid/GridItem'; import GridContainer from '../../../components/Grid/GridContainer'; -import { useNavigate } from 'react-router-dom'; import Button from '@material-ui/core/Button'; import Table from '@material-ui/core/Table'; import TableBody from '@material-ui/core/TableBody'; @@ -13,19 +12,23 @@ import TableRow from '@material-ui/core/TableRow'; import Paper from '@material-ui/core/Paper'; import styles from '../../../assets/jss/material-dashboard-react/views/dashboardStyle'; import { getUsers } from '../../../services/user'; - +import Pagination from '../../../components/Pagination/Pagination'; import { CloseRounded, Check, KeyboardArrowRight } from '@material-ui/icons'; +import Search from '../../../components/Search/Search'; + +const useStyles = makeStyles(styles); export default function UserList(props) { - const useStyles = makeStyles(styles); + const classes = useStyles(); const [data, setData] = useState([]); const [, setAuth] = useState(true); const [isLoading, setIsLoading] = useState(false); const [isError, setIsError] = useState(false); - const navigate = useNavigate(); + const [currentPage, setCurrentPage] = useState(1); + const itemsPerPage = 5; + const [searchQuery, setSearchQuery] = useState(''); - const openUser = (username) => navigate(`/admin/user/${username}`, { replace: true }); useEffect(() => { const query = {}; @@ -40,9 +43,32 @@ export default function UserList(props) { if (isLoading) return
Loading...
; if (isError) return
Something went wrong...
; + + const filteredUsers = data.filter(user => + user.displayName && user.displayName.toLowerCase().includes(searchQuery.toLowerCase()) || + user.username && user.username.toLowerCase().includes(searchQuery.toLowerCase()) +); + + const indexOfLastItem = currentPage * itemsPerPage; + const indexOfFirstItem = indexOfLastItem - itemsPerPage; + const currentItems = filteredUsers.slice(indexOfFirstItem, indexOfLastItem); + const totalItems = filteredUsers.length; + + + const handlePageChange = (page) => { + setCurrentPage(page); + }; + + + const handleSearch = (query) => { + setSearchQuery(query); + setCurrentPage(1); + }; + return ( + @@ -56,7 +82,7 @@ export default function UserList(props) { - {data.map((row) => ( + {currentItems.map((row) => ( {row.displayName} {row.title} @@ -64,29 +90,15 @@ export default function UserList(props) { {row.email} - + {row.gitAccount} - {row.admin ? ( - - - - ) : ( - - )} + {row.admin ? : } - @@ -95,7 +107,14 @@ export default function UserList(props) {
+
); } + diff --git a/website/docs/configuration/reference.mdx b/website/docs/configuration/reference.mdx index 6a7eceed..0ee6fb79 100644 --- a/website/docs/configuration/reference.mdx +++ b/website/docs/configuration/reference.mdx @@ -509,4 +509,4 @@ description: JSON schema reference documentation for GitProxy ---------------------------------------------------------------------------------------------------------------------------- -Generated using [json-schema-for-humans](https://github.com/coveooss/json-schema-for-humans) on 2024-10-22 at 16:45:32 +0100 +Generated using [json-schema-for-humans](https://github.com/coveooss/json-schema-for-humans) on 2024-11-10 at 00:03:58 +0530