diff --git a/package.json b/package.json index 44f14fa6..04487bad 100644 --- a/package.json +++ b/package.json @@ -11,11 +11,13 @@ "dependencies": { "@heroicons/react": "2.0.18", "@material-tailwind/react": "2.1.4", + "@reduxjs/toolkit": "^2.5.0", "apexcharts": "3.44.0", "prop-types": "15.8.1", "react": "18.2.0", "react-apexcharts": "1.4.1", "react-dom": "18.2.0", + "react-redux": "^9.2.0", "react-router-dom": "6.17.0" }, "devDependencies": { @@ -29,4 +31,4 @@ "tailwindcss": "3.3.4", "vite": "4.5.0" } -} \ No newline at end of file +} diff --git a/src/data/statistics-cards-data.js b/src/data/statistics-cards-data.js index d470e708..05725b62 100644 --- a/src/data/statistics-cards-data.js +++ b/src/data/statistics-cards-data.js @@ -9,8 +9,8 @@ export const statisticsCardsData = [ { color: "gray", icon: BanknotesIcon, - title: "Today's Money", - value: "$53k", + title: "Attendance Today", + value: "15", footer: { color: "text-green-500", value: "+55%", @@ -20,8 +20,8 @@ export const statisticsCardsData = [ { color: "gray", icon: UsersIcon, - title: "Today's Users", - value: "2,300", + title: "Active Projects", + value: "3", footer: { color: "text-green-500", value: "+3%", @@ -31,8 +31,8 @@ export const statisticsCardsData = [ { color: "gray", icon: UserPlusIcon, - title: "New Clients", - value: "3,462", + title: "New Commits Today", + value: "17", footer: { color: "text-red-500", value: "-2%", @@ -42,14 +42,38 @@ export const statisticsCardsData = [ { color: "gray", icon: ChartBarIcon, - title: "Sales", - value: "$103,430", + title: "Pull Requests", + value: "23", footer: { color: "text-green-500", value: "+5%", label: "than yesterday", }, }, + + { + color: "gray", + icon: BanknotesIcon, + title: "Work Items", + value: "15", + footer: { + color: "text-green-500", + value: "+55%", + label: "than last week", + }, + }, + + { + color: "gray", + icon: BanknotesIcon, + title: "Available Resource", + value: "15", + footer: { + color: "text-green-500", + value: "+55%", + label: "than last week", + }, + }, ]; export default statisticsCardsData; diff --git a/src/data/statistics-charts-data.js b/src/data/statistics-charts-data.js index 15a36464..014ced57 100644 --- a/src/data/statistics-charts-data.js +++ b/src/data/statistics-charts-data.js @@ -107,15 +107,15 @@ const completedTasksChart = { export const statisticsChartsData = [ { color: "white", - title: "Website View", + title: "QA Review", description: "Last Campaign Performance", footer: "campaign sent 2 days ago", chart: websiteViewsChart, }, { color: "white", - title: "Daily Sales", - description: "15% increase in today sales", + title: "Daily Commits", + description: "15% decrease in today sales", footer: "updated 4 min ago", chart: dailySalesChart, }, diff --git a/src/layouts/auth.jsx b/src/layouts/auth.jsx index c7a21165..92cc516a 100644 --- a/src/layouts/auth.jsx +++ b/src/layouts/auth.jsx @@ -5,7 +5,7 @@ import { UserPlusIcon, ArrowRightOnRectangleIcon, } from "@heroicons/react/24/solid"; -import { Navbar, Footer } from "@/widgets/layout"; +///import { Navbar, Footer } from "@/widgets/layout"; import routes from "@/routes"; export function Auth() { diff --git a/src/layouts/dashboard.jsx b/src/layouts/dashboard.jsx index 888a627a..ab9ab208 100644 --- a/src/layouts/dashboard.jsx +++ b/src/layouts/dashboard.jsx @@ -43,9 +43,9 @@ export function Dashboard() { )) )} -
+ {/*
+
*/} ); diff --git a/src/main.jsx b/src/main.jsx index a28f5a60..109c402f 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -16,13 +16,17 @@ import { BrowserRouter } from "react-router-dom"; import { ThemeProvider } from "@material-tailwind/react"; import { MaterialTailwindControllerProvider } from "@/context"; import "../public/css/tailwind.css"; +import { Provider } from "react-redux"; +import { store } from "./store/store"; ReactDOM.createRoot(document.getElementById("root")).render( - + + + diff --git a/src/pages/dashboard/commits.jsx b/src/pages/dashboard/commits.jsx new file mode 100644 index 00000000..bf33c989 --- /dev/null +++ b/src/pages/dashboard/commits.jsx @@ -0,0 +1,43 @@ +import React, { useState } from 'react'; +///import { useFetchProjectsQuery, useFetchRepositoriesQuery, useFetchCommitsQuery } from '@/store/services/azure-api-service'; + +import { useFetchProjectsQuery } from "@/store/services/azure-api-service"; + +const CommitList = () => { + const { data: projects } = useFetchProjectsQuery(); + const [selectedProject, setSelectedProject] = useState < string | null > (null); + + // const handleSelectChange = (event: React.ChangeEvent) => { + // setSelectedProject(event.target.value); + // }; + + + return ( +
+

Azure Commits

+ + + + + {/* Loading States */} + {(isLoadingProjects) && ( +

Loading...

+ )} +
+ ); +}; + +export default CommitList; diff --git a/src/pages/dashboard/home.jsx b/src/pages/dashboard/home.jsx index 2c700669..652d31c1 100644 --- a/src/pages/dashboard/home.jsx +++ b/src/pages/dashboard/home.jsx @@ -126,11 +126,10 @@ export function Home() { {projectsTableData.map( ({ img, name, members, budget, completion }, key) => { - const className = `py-3 px-5 ${ - key === projectsTableData.length - 1 - ? "" - : "border-b border-blue-gray-50" - }`; + const className = `py-3 px-5 ${key === projectsTableData.length - 1 + ? "" + : "border-b border-blue-gray-50" + }`; return ( @@ -154,9 +153,8 @@ export function Home() { alt={name} size="xs" variant="circular" - className={`cursor-pointer border-2 border-white ${ - key === 0 ? "" : "-ml-2.5" - }`} + className={`cursor-pointer border-2 border-white ${key === 0 ? "" : "-ml-2.5" + }`} /> ))} @@ -201,7 +199,7 @@ export function Home() { className="m-0 p-6" > - Orders Overview + Backlog Tasks Overview (
{React.createElement(icon, { className: `!w-5 !h-5 ${color}`, diff --git a/src/pages/dashboard/members.jsx b/src/pages/dashboard/members.jsx new file mode 100644 index 00000000..4cc88b66 --- /dev/null +++ b/src/pages/dashboard/members.jsx @@ -0,0 +1,94 @@ +import { + Card, + CardHeader, + CardBody, + Typography, + Avatar, + Chip, + Tooltip, + Progress, +} from "@material-tailwind/react"; + +import { useFetchUsersQuery } from "@/store/services/azure-organization-service"; +import { data } from "autoprefixer"; + +export function Members() { + const { data: users, error, isLoading } = useFetchUsersQuery(); + + if (isLoading) return

Loading users...

; + if (error) return

Something went wrong! {JSON.stringify(error)}

; + + return ( +
+

Azure DevOps Users

+
    + {users?.map((user) => ( +
  • + {user.displayName} ({user.principalName}) +
  • + ))} +
+
+ ); + + const className = `py-3 px-5 border-b border-blue-gray-50`; + return ( + + + + Members Zindigi + + + + + + + + {["user", "email"].map((el) => ( + + ))} + + + + {members?.map((member) => ( + + + + + + ))} + +
+ + {el} + +
+
+ {/* */} + + {member.principalName} - {member.displayName} + +
+
+ + {member.emailAddress} + +
+
+
+ ); +} \ No newline at end of file diff --git a/src/pages/dashboard/projects.jsx b/src/pages/dashboard/projects.jsx new file mode 100644 index 00000000..4c499394 --- /dev/null +++ b/src/pages/dashboard/projects.jsx @@ -0,0 +1,115 @@ +import { + Card, + CardHeader, + CardBody, + Typography, + Avatar, + Chip, + Tooltip, + Progress, +} from "@material-tailwind/react"; + +///import { EllipsisVerticalIcon } from "@heroicons/react/24/outline"; +import { useFetchProjectsQuery } from "@/store/services/azure-api-service"; + +export function Projects() { + const { data: projects } = useFetchProjectsQuery(); + + const className = `py-3 px-5 border-b border-blue-gray-50`; + return ( + + + + Project Zindigi + + + + + + + + {["project", "repository", "status", ""].map((el) => ( + + ))} + + + + {projects?.map((project) => ( + + + + + + ))} + + {/* {projects.map( + ({ name }, key) => { + const className = `py-3 px-5 ${key === projects.length - 1 + ? "" + : "border-b border-blue-gray-50" + }`; + + return ( + + + + + ); + } + )} */} + +
+ + {el} + +
+
+ {/* */} + + {project.name} + +
+
+ + Under Maintenance + +
+
+ +
+ + {name} + +
+
+
+ + Edit + +
+
+
+ ); +} \ No newline at end of file diff --git a/src/pages/dashboard/tables.jsx b/src/pages/dashboard/tables.jsx index 3d453ed7..0ba80a5b 100644 --- a/src/pages/dashboard/tables.jsx +++ b/src/pages/dashboard/tables.jsx @@ -12,19 +12,20 @@ import { EllipsisVerticalIcon } from "@heroicons/react/24/outline"; import { authorsTableData, projectsTableData } from "@/data"; export function Tables() { + return (
- Authors Table + User Status - {["author", "function", "status", "employed", ""].map((el) => ( + {["author", "role", "status", "employed", ""].map((el) => ( @@ -88,7 +88,7 @@ export function Tables() { {date} - + */} ); } @@ -115,7 +115,7 @@ export function Tables() {
{authorsTableData.map( ({ img, name, email, job, online, date }, key) => { - const className = `py-3 px-5 ${ - key === authorsTableData.length - 1 - ? "" - : "border-b border-blue-gray-50" - }`; + const className = `py-3 px-5 ${key === authorsTableData.length - 1 + ? "" + : "border-b border-blue-gray-50" + }`; return (
+ {/* Edit -
- {["companies", "members", "budget", "completion", ""].map( + {["project name", "members", , "completion progress", ""].map( (el) => ( @@ -163,21 +162,20 @@ export function Tables() { alt={name} size="xs" variant="circular" - className={`cursor-pointer border-2 border-white ${ - key === 0 ? "" : "-ml-2.5" - }`} + className={`cursor-pointer border-2 border-white ${key === 0 ? "" : "-ml-2.5" + }`} /> ))} - + */}
{projectsTableData.map( ({ img, name, members, budget, completion }, key) => { - const className = `py-3 px-5 ${ - key === projectsTableData.length - 1 - ? "" - : "border-b border-blue-gray-50" - }`; + const className = `py-3 px-5 ${key === projectsTableData.length - 1 + ? "" + : "border-b border-blue-gray-50" + }`; return (
+ {/* {budget} -
, }, + { + icon: , + name: "attendance", + path: "/attendance", + element: , + }, + { + icon: , + name: "projects", + path: "/projects", + element: , + }, { icon: , - name: "profile", - path: "/profile", - element: , + name: "members", + path: "/members", + element: , }, + // { + // icon: , + // name: "profile", + // path: "/profile", + // element: , + // }, { icon: , name: "tables", path: "/tables", element: , }, - { - icon: , - name: "notifications", - path: "/notifications", - element: , - }, + // { + // icon: , + // name: "notifications", + // path: "/notifications", + // element: , + // }, ], }, { - title: "auth pages", + title: "Monitoring", layout: "auth", pages: [ + { icon: , - name: "sign in", - path: "/sign-in", - element: , - }, - { - icon: , - name: "sign up", - path: "/sign-up", - element: , + name: "commits", + path: "/commits", + element: , }, + // { + // icon: , + // name: "sign in", + // path: "/sign-in", + // element: , + // }, + // { + // icon: , + // name: "sign up", + // path: "/sign-up", + // element: , + // }, ], }, ]; diff --git a/src/store/custom-hooks.ts b/src/store/custom-hooks.ts new file mode 100644 index 00000000..3771468e --- /dev/null +++ b/src/store/custom-hooks.ts @@ -0,0 +1,6 @@ +import { useDispatch, useSelector } from 'react-redux' +import type { RootState, AppDispatch } from './store' + +// Use throughout your app instead of plain `useDispatch` and `useSelector` +export const useAppDispatch = useDispatch.withTypes() +export const useAppSelector = useSelector.withTypes() diff --git a/src/store/services/azure-api-service.ts b/src/store/services/azure-api-service.ts new file mode 100644 index 00000000..52c4d0b8 --- /dev/null +++ b/src/store/services/azure-api-service.ts @@ -0,0 +1,52 @@ +import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; +import { Commit, Project, Repository } from '../types/types'; + +// Replace with your Azure DevOps organization name and personal access token +const organization = 'devops-ais'; +const personalAccessToken = 'D1ijCqZTiu4U5ymKx8BwCCHXhQmKzZOt1cvwiaDvza7iF7qHvS5PJQQJ99AKACAAAAAAvEZnAAASAZDO3dIe'; + + +export const azureApi = createApi({ + reducerPath: 'azureApi', + baseQuery: fetchBaseQuery({ + baseUrl: `https://dev.azure.com/${organization}/_apis/`, + prepareHeaders: (headers) => { + headers.set('Authorization', `Basic ${btoa(`:${personalAccessToken}`)}`); + return headers; + }, + }), + endpoints: (builder) => ({ + fetchProjects: builder.query({ + query: () => 'projects?api-version=7.1-preview.4', + transformResponse: (response: any) => response.value, // Extract the projects array + }), + fetchRepositories: builder.query({ + // query: (projectName) => + // `${projectName}/_apis/git/repositories?api-version=7.1-preview.1`, + query: (projectName) => { + const url = `${projectName}/_apis/git/repositories?api-version=7.1-preview.1`; + console.log('Fetching repositories URL:', url); // Log the final URL + return url; + }, + transformResponse: (response: any) => response.value, // Extract the repositories array + }), + fetchCommits: builder.query({ + // query: ({ projectName, repoId }) => + // `${projectName}/_apis/git/repositories/${repoId}/commits?searchCriteria.fromDate=${new Date() + // .toISOString() + // .split('T')[0]}&api-version=7.1-preview.1`, + query: ({ projectName, repoId }) => { + const url = `${projectName}/_apis/git/repositories/${repoId}/commits?searchCriteria.fromDate=${new Date() + .toISOString() + .split('T')[0]}&api-version=7.1-preview.1`; + console.log('Fetching commits URL:', url); // Log the final URL + return url; + }, + transformResponse: (response: any) => response.value, // Extract the commits array + }), + }), +}); + + +export const { useFetchProjectsQuery , useFetchRepositoriesQuery, + useFetchCommitsQuery,} = azureApi; \ No newline at end of file diff --git a/src/store/services/azure-organization-service.ts b/src/store/services/azure-organization-service.ts new file mode 100644 index 00000000..90d65fbf --- /dev/null +++ b/src/store/services/azure-organization-service.ts @@ -0,0 +1,26 @@ +import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; +import { User } from '../types/types'; + +// Replace with your Azure DevOps organization name and personal access token +const organization = 'devops-ais'; +const personalAccessToken = 'D1ijCqZTiu4U5ymKx8BwCCHXhQmKzZOt1cvwiaDvza7iF7qHvS5PJQQJ99AKACAAAAAAvEZnAAASAZDO3dIe'; + +// Create an RTK Query API +export const azureOrganizationApi = createApi({ + reducerPath: 'azureOrganizationApi', + baseQuery: fetchBaseQuery({ + baseUrl: `https://vssps.dev.azure.com/${organization}/_apis/`, + prepareHeaders: (headers) => { + headers.set('Authorization', `Basic ${btoa(`:${personalAccessToken}`)}`); + return headers; + }, + }), + endpoints: (builder) => ({ + fetchUsers: builder.query({ + query: () => 'graph/users?api-version=7.1-preview.1', + transformResponse: (response: any) => response.value, // Extract the users array + }), + }), +}); + +export const { useFetchUsersQuery } = azureOrganizationApi; diff --git a/src/store/store.ts b/src/store/store.ts new file mode 100644 index 00000000..e495f6ff --- /dev/null +++ b/src/store/store.ts @@ -0,0 +1,19 @@ +import { configureStore } from '@reduxjs/toolkit'; +import { azureApi } from './services/azure-api-service'; +import { azureOrganizationApi } from './services/azure-organization-service'; + +export const store = configureStore({ + reducer: { + [azureApi.reducerPath]: azureApi.reducer, + [azureOrganizationApi.reducerPath]: azureOrganizationApi.reducer, // Add azureOrganizationApi reducer + }, + middleware: (getDefaultMiddleware) => + getDefaultMiddleware() + .concat(azureApi.middleware) + .concat(azureOrganizationApi.middleware), // Add azureOrganizationApi middleware +}); + +// Infer the `RootState` and `AppDispatch` types from the store itself +export type RootState = ReturnType; +// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState} +export type AppDispatch = typeof store.dispatch; diff --git a/src/store/types/types.ts b/src/store/types/types.ts new file mode 100644 index 00000000..5ef8a9d1 --- /dev/null +++ b/src/store/types/types.ts @@ -0,0 +1,43 @@ +export interface Project { + id: string; + name: string; + description?: string; + } + + export interface User { + principalName: string; + displayName: string; + emailAddress: string; + origin: string; + originId: string; + } + + export interface Repository { + id: string; // Unique ID of the repository + name: string; // Repository name + url: string; // URL of the repository + project: { + id: string; // Project ID the repository belongs to + name: string; // Project name + }; + remoteUrl: string; // Remote URL for the repository (e.g., Git clone URL) + defaultBranch: string; // Default branch of the repository + } + + export interface Commit { + commitId: string; // Unique ID of the commit + comment: string; // Commit message + author: { + name: string; // Name of the author + email: string; // Email of the author + date: string; // Date and time of the commit (ISO format) + }; + committer: { + name: string; // Name of the committer (can differ from the author in some cases) + email: string; // Email of the committer + date: string; // Date and time of the commit by the committer + }; + url: string; // URL to view the commit in Azure DevOps + } + + \ No newline at end of file diff --git a/src/widgets/layout/dashboard-navbar.jsx b/src/widgets/layout/dashboard-navbar.jsx index d91e23f7..7accff10 100644 --- a/src/widgets/layout/dashboard-navbar.jsx +++ b/src/widgets/layout/dashboard-navbar.jsx @@ -72,7 +72,7 @@ export function DashboardNavbar() {
-
+ {/*
- - + */} + + {/* @@ -184,7 +185,7 @@ export function DashboardNavbar() { onClick={() => setOpenConfigurator(dispatch, true)} > - + */}
diff --git a/src/widgets/layout/footer.jsx b/src/widgets/layout/footer.jsx index 1ea98e53..c2ee3cb4 100644 --- a/src/widgets/layout/footer.jsx +++ b/src/widgets/layout/footer.jsx @@ -41,10 +41,10 @@ export function Footer({ brandName, brandLink, routes }) { } Footer.defaultProps = { - brandName: "Creative Tim", + brandName: "Zindigi Tim", brandLink: "https://www.creative-tim.com", routes: [ - { name: "Creative Tim", path: "https://www.creative-tim.com" }, + { name: "Zindigi Tim", path: "https://www.creative-tim.com" }, { name: "About Us", path: "https://www.creative-tim.com/presentation" }, { name: "Blog", path: "https://www.creative-tim.com/blog" }, { name: "License", path: "https://www.creative-tim.com/license" }, diff --git a/src/widgets/layout/navbar.jsx b/src/widgets/layout/navbar.jsx index ba0bd4c7..6057f364 100644 --- a/src/widgets/layout/navbar.jsx +++ b/src/widgets/layout/navbar.jsx @@ -83,7 +83,7 @@ export function Navbar({ brandName, routes, action }) { } Navbar.defaultProps = { - brandName: "Material Tailwind React", + brandName: "Zindigi Dashboard", action: (