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 */}
+
+
-
+
+ {/* 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