From f9d22607cdd75b3f47bff19faa5b998e30f7fb64 Mon Sep 17 00:00:00 2001 From: Khatai Huseynzada Date: Wed, 7 Jan 2026 18:47:37 +0400 Subject: [PATCH 1/3] update project --- .env.example | 13 + .firebaserc | 5 + .prettierrc | 10 + firebase.json | 10 + index.html | 20 +- package.json | 11 +- public/css/tailwind.css | 3 - src/App.jsx | 276 +++++++++++++- src/Routes/routes.jsx | 67 ++++ src/components/auth/AuthLayout.jsx | 78 ++++ src/components/auth/ForgotPasswordForm.jsx | 119 ++++++ src/components/auth/LoginForm.jsx | 174 +++++++++ src/components/auth/ProtectedRoute.jsx | 31 ++ src/components/auth/SignUpForm.jsx | 213 +++++++++++ src/components/auth/index.js | 5 + src/components/dashboard/BreakdownCard.jsx | 37 ++ src/components/dashboard/DashboardStats.jsx | 64 ++++ .../dashboard/DetailedDashboardStats.jsx | 105 ++++++ src/components/dashboard/StatCard.jsx | 75 ++++ src/components/dashboard/index.js | 4 + src/components/dialogs/MemberDialog.jsx | 330 +++++++++++++++++ src/components/dialogs/ProjectDialog.jsx | 313 ++++++++++++++++ src/components/dialogs/index.js | 2 + src/components/layout/Header.jsx | 82 +++++ .../layout/NotificationDropdown.jsx | 177 +++++++++ src/components/layout/Sidebar.jsx | 281 +++++++++++++++ src/components/layout/TabNavigation.jsx | 133 +++++++ src/components/layout/index.js | 8 + .../notifications/NotificationIcon.jsx | 20 ++ .../notifications/NotificationItem.jsx | 144 ++++++++ .../notifications/NotificationList.jsx | 41 +++ src/components/notifications/index.js | 3 + src/components/profile/ProfileEditForm.jsx | 168 +++++++++ src/components/profile/ProfileHeader.jsx | 66 ++++ src/components/profile/ProfileInfo.jsx | 149 ++++++++ src/components/profile/ProfileStats.jsx | 193 ++++++++++ src/components/profile/index.js | 4 + src/components/projects/ProjectCard.jsx | 167 +++++++++ src/components/projects/ProjectFilters.jsx | 96 +++++ src/components/projects/ProjectModal.jsx | 181 ++++++++++ src/components/projects/ProjectsStats.jsx | 68 ++++ src/components/projects/index.js | 4 + src/components/team/MemberCard.jsx | 168 +++++++++ src/components/team/MemberStatusBadge.jsx | 22 ++ src/components/team/TeamMemberRow.jsx | 90 +++++ src/components/team/TeamStats.jsx | 109 ++++++ src/components/team/TeamTable.jsx | 88 +++++ src/components/team/index.js | 5 + src/components/ui/Avatar.jsx | 47 +++ src/components/ui/Badge.jsx | 28 ++ src/components/ui/Button.jsx | 45 +++ src/components/ui/Card.jsx | 28 ++ src/components/ui/EmptyState.jsx | 25 ++ src/components/ui/Input.jsx | 42 +++ src/components/ui/Spinner.jsx | 26 ++ src/components/ui/Tooltip.jsx | 16 + src/components/ui/index.js | 9 + src/configs/charts-config.js | 61 ---- src/configs/index.js | 1 - src/context/AuthContext.jsx | 72 ++++ src/context/DashborardContext.jsx | 43 +++ src/context/ThemeContext.jsx | 102 ++++++ src/context/index.js | 3 + src/context/index.jsx | 85 ----- src/data/authors-table-data.js | 52 --- src/data/conversations-data.js | 29 -- src/data/index.js | 45 ++- src/data/initialNotifications.js | 230 ++++++++++++ src/data/initialProfile.js | 0 src/data/initialProjects.js | 294 +++++++++++++++ src/data/initialTeamMembers.js | 271 ++++++++++++++ src/data/orders-overview-data.js | 49 --- src/data/platform-settings-data.js | 38 -- src/data/projects-data.js | 60 ---- src/data/projects-table-data.js | 65 ---- src/data/statistics-cards-data.js | 55 --- src/data/statistics-charts-data.js | 131 ------- src/data/statistics.js | 231 ++++++++++++ src/hooks/index.js | 6 + src/hooks/useAuth.js | 184 ++++++++++ src/hooks/useLocalStorage.js | 73 ++++ src/hooks/useNotifications.js | 273 ++++++++++++++ src/hooks/useProfile.js | 222 ++++++++++++ src/hooks/useProjects.js | 249 +++++++++++++ src/hooks/useTeamMembers.js | 289 +++++++++++++++ src/index.css | 102 ++++++ src/layouts/auth.jsx | 52 --- src/layouts/dashboard.jsx | 56 --- src/layouts/index.js | 2 - src/lib/authProviders.js | 4 + src/lib/authService.js | 25 ++ src/lib/firebase.js | 16 + src/main.jsx | 41 +-- src/pages/HomePage.jsx | 45 +++ src/pages/NotificationsPage.jsx | 237 ++++++++++++ src/pages/ProfilePage.jsx | 221 ++++++++++++ src/pages/ProjectsPage.jsx | 211 +++++++++++ src/pages/TeamPage.jsx | 227 ++++++++++++ src/pages/auth/ForgotPasswordPage.jsx | 15 + src/pages/auth/LoginPage.jsx | 21 ++ src/pages/auth/SignUpPage.jsx | 15 + src/pages/auth/index.js | 5 +- src/pages/auth/sign-in.jsx | 126 ------- src/pages/auth/sign-up.jsx | 94 ----- src/pages/dashboard/home.jsx | 258 ------------- src/pages/dashboard/index.js | 4 - src/pages/dashboard/notifications.jsx | 88 ----- src/pages/dashboard/profile.jsx | 221 ------------ src/pages/dashboard/tables.jsx | 221 ------------ src/pages/index.js | 6 + src/routes.jsx | 66 ---- src/utils/api.js | 143 ++++++++ src/utils/auth.js | 153 ++++++++ src/utils/constants.js | 147 ++++++++ src/utils/formatters.js | 201 +++++++++++ src/utils/helpers.js | 338 ++++++++++++++++++ src/utils/index.js | 17 + src/utils/validators.js | 285 +++++++++++++++ src/widgets/cards/index.js | 3 - src/widgets/cards/message-card.jsx | 45 --- src/widgets/cards/profile-info-card.jsx | 79 ---- src/widgets/cards/statistics-card.jsx | 75 ---- src/widgets/charts/index.js | 1 - src/widgets/charts/statistics-chart.jsx | 70 ---- src/widgets/layout/configurator.jsx | 237 ------------ src/widgets/layout/dashboard-navbar.jsx | 196 ---------- src/widgets/layout/footer.jsx | 62 ---- src/widgets/layout/index.js | 5 - src/widgets/layout/navbar.jsx | 107 ------ src/widgets/layout/sidenav.jsx | 111 ------ 130 files changed, 9700 insertions(+), 2865 deletions(-) create mode 100644 .env.example create mode 100644 .firebaserc create mode 100644 .prettierrc create mode 100644 firebase.json delete mode 100644 public/css/tailwind.css create mode 100644 src/Routes/routes.jsx create mode 100644 src/components/auth/AuthLayout.jsx create mode 100644 src/components/auth/ForgotPasswordForm.jsx create mode 100644 src/components/auth/LoginForm.jsx create mode 100644 src/components/auth/ProtectedRoute.jsx create mode 100644 src/components/auth/SignUpForm.jsx create mode 100644 src/components/auth/index.js create mode 100644 src/components/dashboard/BreakdownCard.jsx create mode 100644 src/components/dashboard/DashboardStats.jsx create mode 100644 src/components/dashboard/DetailedDashboardStats.jsx create mode 100644 src/components/dashboard/StatCard.jsx create mode 100644 src/components/dashboard/index.js create mode 100644 src/components/dialogs/MemberDialog.jsx create mode 100644 src/components/dialogs/ProjectDialog.jsx create mode 100644 src/components/dialogs/index.js create mode 100644 src/components/layout/Header.jsx create mode 100644 src/components/layout/NotificationDropdown.jsx create mode 100644 src/components/layout/Sidebar.jsx create mode 100644 src/components/layout/TabNavigation.jsx create mode 100644 src/components/layout/index.js create mode 100644 src/components/notifications/NotificationIcon.jsx create mode 100644 src/components/notifications/NotificationItem.jsx create mode 100644 src/components/notifications/NotificationList.jsx create mode 100644 src/components/notifications/index.js create mode 100644 src/components/profile/ProfileEditForm.jsx create mode 100644 src/components/profile/ProfileHeader.jsx create mode 100644 src/components/profile/ProfileInfo.jsx create mode 100644 src/components/profile/ProfileStats.jsx create mode 100644 src/components/profile/index.js create mode 100644 src/components/projects/ProjectCard.jsx create mode 100644 src/components/projects/ProjectFilters.jsx create mode 100644 src/components/projects/ProjectModal.jsx create mode 100644 src/components/projects/ProjectsStats.jsx create mode 100644 src/components/projects/index.js create mode 100644 src/components/team/MemberCard.jsx create mode 100644 src/components/team/MemberStatusBadge.jsx create mode 100644 src/components/team/TeamMemberRow.jsx create mode 100644 src/components/team/TeamStats.jsx create mode 100644 src/components/team/TeamTable.jsx create mode 100644 src/components/team/index.js create mode 100644 src/components/ui/Avatar.jsx create mode 100644 src/components/ui/Badge.jsx create mode 100644 src/components/ui/Button.jsx create mode 100644 src/components/ui/Card.jsx create mode 100644 src/components/ui/EmptyState.jsx create mode 100644 src/components/ui/Input.jsx create mode 100644 src/components/ui/Spinner.jsx create mode 100644 src/components/ui/Tooltip.jsx create mode 100644 src/components/ui/index.js delete mode 100644 src/configs/charts-config.js delete mode 100644 src/configs/index.js create mode 100644 src/context/AuthContext.jsx create mode 100644 src/context/DashborardContext.jsx create mode 100644 src/context/ThemeContext.jsx create mode 100644 src/context/index.js delete mode 100644 src/context/index.jsx delete mode 100644 src/data/authors-table-data.js delete mode 100644 src/data/conversations-data.js create mode 100644 src/data/initialNotifications.js create mode 100644 src/data/initialProfile.js create mode 100644 src/data/initialProjects.js create mode 100644 src/data/initialTeamMembers.js delete mode 100644 src/data/orders-overview-data.js delete mode 100644 src/data/platform-settings-data.js delete mode 100644 src/data/projects-data.js delete mode 100644 src/data/projects-table-data.js delete mode 100644 src/data/statistics-cards-data.js delete mode 100644 src/data/statistics-charts-data.js create mode 100644 src/data/statistics.js create mode 100644 src/hooks/index.js create mode 100644 src/hooks/useAuth.js create mode 100644 src/hooks/useLocalStorage.js create mode 100644 src/hooks/useNotifications.js create mode 100644 src/hooks/useProfile.js create mode 100644 src/hooks/useProjects.js create mode 100644 src/hooks/useTeamMembers.js create mode 100644 src/index.css delete mode 100644 src/layouts/auth.jsx delete mode 100644 src/layouts/dashboard.jsx delete mode 100644 src/layouts/index.js create mode 100644 src/lib/authProviders.js create mode 100644 src/lib/authService.js create mode 100644 src/lib/firebase.js create mode 100644 src/pages/HomePage.jsx create mode 100644 src/pages/NotificationsPage.jsx create mode 100644 src/pages/ProfilePage.jsx create mode 100644 src/pages/ProjectsPage.jsx create mode 100644 src/pages/TeamPage.jsx create mode 100644 src/pages/auth/ForgotPasswordPage.jsx create mode 100644 src/pages/auth/LoginPage.jsx create mode 100644 src/pages/auth/SignUpPage.jsx delete mode 100644 src/pages/auth/sign-in.jsx delete mode 100644 src/pages/auth/sign-up.jsx delete mode 100644 src/pages/dashboard/home.jsx delete mode 100644 src/pages/dashboard/index.js delete mode 100644 src/pages/dashboard/notifications.jsx delete mode 100644 src/pages/dashboard/profile.jsx delete mode 100644 src/pages/dashboard/tables.jsx create mode 100644 src/pages/index.js delete mode 100644 src/routes.jsx create mode 100644 src/utils/api.js create mode 100644 src/utils/auth.js create mode 100644 src/utils/constants.js create mode 100644 src/utils/formatters.js create mode 100644 src/utils/helpers.js create mode 100644 src/utils/index.js create mode 100644 src/utils/validators.js delete mode 100644 src/widgets/cards/index.js delete mode 100644 src/widgets/cards/message-card.jsx delete mode 100644 src/widgets/cards/profile-info-card.jsx delete mode 100644 src/widgets/cards/statistics-card.jsx delete mode 100644 src/widgets/charts/index.js delete mode 100644 src/widgets/charts/statistics-chart.jsx delete mode 100644 src/widgets/layout/configurator.jsx delete mode 100644 src/widgets/layout/dashboard-navbar.jsx delete mode 100644 src/widgets/layout/footer.jsx delete mode 100644 src/widgets/layout/index.js delete mode 100644 src/widgets/layout/navbar.jsx delete mode 100644 src/widgets/layout/sidenav.jsx diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..0684435c --- /dev/null +++ b/.env.example @@ -0,0 +1,13 @@ +# API Configuration +REACT_APP_API_URL=http://localhost:3001/api + +# App Configuration +REACT_APP_NAME=Enterprise Dashboard +REACT_APP_VERSION=1.0.0 + +# Features +REACT_APP_ENABLE_AUTH=true +REACT_APP_ENABLE_NOTIFICATIONS=true + +# Other +REACT_APP_ENVIRONMENT=development \ No newline at end of file diff --git a/.firebaserc b/.firebaserc new file mode 100644 index 00000000..c965551a --- /dev/null +++ b/.firebaserc @@ -0,0 +1,5 @@ +{ + "projects": { + "default": "admin-dashboard-d8efe" + } +} diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..32a23973 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,10 @@ +{ + "semi": true, + "trailingComma": "es5", + "singleQuote": true, + "printWidth": 100, + "tabWidth": 2, + "useTabs": false, + "arrowParens": "always", + "endOfLine": "lf" +} diff --git a/firebase.json b/firebase.json new file mode 100644 index 00000000..059fe4f8 --- /dev/null +++ b/firebase.json @@ -0,0 +1,10 @@ +{ + "hosting": { + "public": "dist", + "ignore": [ + "firebase.json", + "**/.*", + "**/node_modules/**" + ] + } +} diff --git a/index.html b/index.html index 844e5941..14a96cba 100644 --- a/index.html +++ b/index.html @@ -1,6 +1,6 @@ - + - Material Tailwind Dashboard React | By Creative Tim + Dashboard | By Creative Tim - - + + - +
diff --git a/package.json b/package.json index 44f14fa6..77fa36cd 100644 --- a/package.json +++ b/package.json @@ -9,14 +9,15 @@ "preview": "vite preview" }, "dependencies": { - "@heroicons/react": "2.0.18", - "@material-tailwind/react": "2.1.4", + "@heroicons/react": "^2.0.18", + "@material-tailwind/react": "^2.1.4", "apexcharts": "3.44.0", - "prop-types": "15.8.1", + "firebase": "^12.7.0", + "prop-types": "^15.8.1", "react": "18.2.0", "react-apexcharts": "1.4.1", "react-dom": "18.2.0", - "react-router-dom": "6.17.0" + "react-router-dom": "^6.17.0" }, "devDependencies": { "@types/react": "18.2.31", @@ -29,4 +30,4 @@ "tailwindcss": "3.3.4", "vite": "4.5.0" } -} \ No newline at end of file +} diff --git a/public/css/tailwind.css b/public/css/tailwind.css deleted file mode 100644 index b5c61c95..00000000 --- a/public/css/tailwind.css +++ /dev/null @@ -1,3 +0,0 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; diff --git a/src/App.jsx b/src/App.jsx index 87826600..f5abdfb7 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,13 +1,273 @@ -import { Routes, Route, Navigate } from "react-router-dom"; -import { Dashboard, Auth } from "@/layouts"; +import React from 'react'; + +// Hooks +import { useProjects, useTeamMembers, useNotifications, useProfile } from './hooks'; + +// Layout Components +import { Header, Sidebar, MobileTabNavigation } from './components/layout'; + +// Pages +import { HomePage, ProjectsPage, ProfilePage, TeamPage, NotificationsPage } from './pages'; + +// Dialogs +import { ProjectDialog, MemberDialog } from './components/dialogs'; + +// Utils +import { downloadJSON } from './utils'; + +export function App() { + // State Management + const [activeTab, setActiveTab] = React.useState('home'); + const [sidebarOpen, setSidebarOpen] = React.useState(false); + const [isAuthenticated, setIsAuthenticated] = React.useState(false); + const [projectDialog, setProjectDialog] = React.useState({ + open: false, + mode: 'add', + data: null, + }); + const [memberDialog, setMemberDialog] = React.useState({ + open: false, + mode: 'add', + data: null, + }); + + // Custom Hooks + const projects = useProjects(); + const teamMembers = useTeamMembers(); + const notifications = useNotifications(); + const profile = useProfile(); + + // Project Handlers + const handleAddProject = React.useCallback(() => { + setProjectDialog({ open: true, mode: 'add', data: null }); + }, []); + + const handleEditProject = React.useCallback((project) => { + setProjectDialog({ open: true, mode: 'edit', data: project }); + }, []); + + const handleDeleteProject = React.useCallback( + (id) => { + if (window.confirm('Are you sure you want to delete this project?')) { + projects.deleteProject(id); + } + }, + [projects] + ); + + const handleSaveProject = React.useCallback( + (projectData) => { + if (projectDialog.mode === 'add') { + projects.addProject(projectData); + } else { + projects.updateProject(projectDialog.data.id, projectData); + } + setProjectDialog({ open: false, mode: 'add', data: null }); + }, + [projectDialog, projects] + ); + + // Team Member Handlers + const handleAddMember = React.useCallback(() => { + setMemberDialog({ open: true, mode: 'add', data: null }); + }, []); + + const handleEditMember = React.useCallback((member) => { + setMemberDialog({ open: true, mode: 'edit', data: member }); + }, []); + + const handleDeleteMember = React.useCallback( + (id) => { + teamMembers.deleteMember(id); + }, + [teamMembers] + ); + + const handleSaveMember = React.useCallback( + (memberData) => { + if (memberDialog.mode === 'add') { + teamMembers.addMember(memberData); + } else { + teamMembers.updateMember(memberDialog.data.id, memberData); + } + setMemberDialog({ open: false, mode: 'add', data: null }); + }, + [memberDialog, teamMembers] + ); + + // Auth Handlers + const handleLogin = React.useCallback(async (credentials) => { + console.log('Login:', credentials); + setIsAuthenticated(true); + return { success: true }; + }, []); + + const handleLogout = React.useCallback(() => { + if (window.confirm('Are you sure you want to logout?')) { + setIsAuthenticated(false); + console.log('Logged out'); + } + }, []); + + // Export Data + const handleExport = React.useCallback(() => { + const exportData = { + projects: projects.allProjects, + teamMembers: teamMembers.allMembers, + notifications: notifications.allNotifications, + profile: profile.profile, + exportDate: new Date().toISOString(), + }; + downloadJSON(exportData, `dashboard-export-${new Date().toISOString().split('T')[0]}.json`); + }, [projects, teamMembers, notifications, profile]); + + // Render current page + const renderPage = React.useMemo(() => { + switch (activeTab) { + case 'home': + return ( + console.log('View project:', project)} + onSort={projects.sortProjects} + sortConfig={projects.sortConfig} + teamMembersCount={teamMembers.allMembers.length} + /> + ); + + case 'projects': + return ( + console.log('View project:', project)} + onSort={projects.sortProjects} + sortConfig={projects.sortConfig} + /> + ); + + case 'profile': + return ( + + ); + + case 'team': + return ( + + ); + + case 'notifications': + return ( + + ); + + default: + return ( +
+

Page not found

+
+ ); + } + }, [ + activeTab, + projects, + teamMembers, + notifications, + profile, + handleAddProject, + handleEditProject, + handleDeleteProject, + handleAddMember, + handleEditMember, + handleDeleteMember, + ]); -function App() { return ( - - } /> - } /> - } /> - +
+ {/* Sidebar */} + setSidebarOpen(false)} + activeTab={activeTab} + onNavigate={setActiveTab} + unreadCount={notifications.statistics.unreadCount} + profile={profile.profile} + isAuthenticated={isAuthenticated} + onLogin={handleLogin} + onLogout={handleLogout} + /> + + {/* Main Content */} +
+ {/* Header */} +
setSidebarOpen(true)} + /> + + {/* Page Content */} +
{renderPage}
+ + {/* Mobile Navigation */} + +
+ + {/* Dialogs */} + setProjectDialog({ open: false, mode: 'add', data: null })} + onSave={handleSaveProject} + /> + + setMemberDialog({ open: false, mode: 'add', data: null })} + onSave={handleSaveMember} + /> +
); } diff --git a/src/Routes/routes.jsx b/src/Routes/routes.jsx new file mode 100644 index 00000000..dfc82a21 --- /dev/null +++ b/src/Routes/routes.jsx @@ -0,0 +1,67 @@ +import { Home, Profile, Notifications } from './pages/dashboard'; +import { ProjectsPage } from './pages/ProjectsPage'; +import { SignIn, SignUp } from './pages/auth'; +import { + HomeIcon, + UserCircleIcon, + FolderIcon, + BellIcon, + ArrowRightOnRectangleIcon, + UserPlusIcon, +} from '@heroicons/react/24/solid'; + +const icon = { + className: 'w-5 h-5 text-inherit', +}; + +export const routes = [ + { + layout: 'dashboard', + pages: [ + { + icon: , + name: 'dashboard', + path: '/home', + element: , + }, + { + icon: , + name: 'projects', + path: '/projects', + element: , + }, + { + icon: , + name: 'profile', + path: '/profile', + element: , + }, + { + icon: , + name: 'notifications', + path: '/notifications', + element: , + }, + ], + }, + { + title: 'auth pages', + layout: 'auth', + pages: [ + { + icon: , + name: 'sign in', + path: '/sign-in', + element: , + }, + { + icon: , + name: 'sign up', + path: '/sign-up', + element: , + }, + ], + }, +]; + +export default routes; diff --git a/src/components/auth/AuthLayout.jsx b/src/components/auth/AuthLayout.jsx new file mode 100644 index 00000000..9aced4e5 --- /dev/null +++ b/src/components/auth/AuthLayout.jsx @@ -0,0 +1,78 @@ +import React from 'react'; +import { Typography } from '@material-tailwind/react'; +import { ShieldCheckIcon, LockClosedIcon, CheckBadgeIcon } from '@heroicons/react/24/solid'; + +export const AuthLayout = React.memo(({ children, title, subtitle }) => { + return ( +
+ {/* Animated Background Pattern */} +
+
+
+
+ + {/* Grid Pattern Overlay */} +
+ +
+ {/* Logo & Header */} +
+
+ +
+ + {title || 'Enterprise Dashboard'} + + {subtitle && ( + + {subtitle} + + )} +
+ + {/* Content Card */} +
+ {children} +
+ + {/* Trust Indicators */} +
+
+ + + Secure SSL + +
+
+ + + Verified + +
+
+ + {/* Footer */} +
+ + © 2026 Enterprise Dashboard. All rights reserved. + + +
+
+
+ ); +}); + +AuthLayout.displayName = 'AuthLayout'; diff --git a/src/components/auth/ForgotPasswordForm.jsx b/src/components/auth/ForgotPasswordForm.jsx new file mode 100644 index 00000000..453c2600 --- /dev/null +++ b/src/components/auth/ForgotPasswordForm.jsx @@ -0,0 +1,119 @@ +import React from 'react'; +import { Button } from '../ui'; +import { EnvelopeIcon, ArrowLeftIcon } from '@heroicons/react/24/outline'; +import { Input, Typography } from '@material-tailwind/react'; + +export const ForgotPasswordForm = React.memo(({ onSubmit, onBack }) => { + const [email, setEmail] = React.useState(''); + const [error, setError] = React.useState(''); + const [success, setSuccess] = React.useState(false); + const [loading, setLoading] = React.useState(false); + + const validate = () => { + if (!email) { + setError('Email is required'); + return false; + } + if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) { + setError('Invalid email format'); + return false; + } + return true; + }; + + const handleSubmit = async (e) => { + e.preventDefault(); + setError(''); + + if (!validate()) return; + + setLoading(true); + try { + await onSubmit?.({ email }); + setSuccess(true); + } catch (err) { + setError(err.message); + } finally { + setLoading(false); + } + }; + + if (success) { + return ( +
+
+ + + +
+ +
+ + Check Your Email + + + We've sent a password reset link to {email} + +
+ + +
+ ); + } + + return ( +
+
+ + + + Forgot Password? + + + Enter your email and we'll send you a link to reset your password + +
+ + {/* Email */} +
+ } + value={email} + onChange={(e) => { + setEmail(e.target.value); + setError(''); + }} + error={!!error} + /> + {error && ( + + {error} + + )} +
+ + {/* Submit Button */} + +
+ ); +}); + +ForgotPasswordForm.displayName = 'ForgotPasswordForm'; diff --git a/src/components/auth/LoginForm.jsx b/src/components/auth/LoginForm.jsx new file mode 100644 index 00000000..11ecb721 --- /dev/null +++ b/src/components/auth/LoginForm.jsx @@ -0,0 +1,174 @@ +import React from 'react'; +import { Button } from '../ui'; +import { EnvelopeIcon, LockClosedIcon } from '@heroicons/react/24/outline'; +import { Input, Checkbox, Typography } from '@material-tailwind/react'; + +export const LoginForm = React.memo( + ({ onLogin, onForgotPassword, onSignUp, onGoogleLogin, onGithubLogin }) => { + const [formData, setFormData] = React.useState({ + email: '', + password: '', + remember: false, + }); + const [errors, setErrors] = React.useState({}); + const [loading, setLoading] = React.useState(false); + + const handleChange = (field, value) => { + setFormData((prev) => ({ ...prev, [field]: value })); + if (errors[field]) { + setErrors((prev) => ({ ...prev, [field]: '' })); + } + }; + + const validate = () => { + const newErrors = {}; + + if (!formData.email) { + newErrors.email = 'Email is required'; + } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) { + newErrors.email = 'Invalid email format'; + } + + if (!formData.password) { + newErrors.password = 'Password is required'; + } else if (formData.password.length < 6) { + newErrors.password = 'Password must be at least 6 characters'; + } + + setErrors(newErrors); + return Object.keys(newErrors).length === 0; + }; + + const handleSubmit = async (e) => { + e.preventDefault(); + + if (!validate()) return; + + setLoading(true); + try { + await onLogin?.(formData); + } catch (error) { + setErrors({ submit: error.message }); + } finally { + setLoading(false); + } + }; + + return ( +
+
+ + Welcome Back + + + Sign in to your account to continue + +
+ + {/* Email */} +
+ } + value={formData.email} + onChange={(e) => handleChange('email', e.target.value)} + error={!!errors.email} + /> + {errors.email && ( + + {errors.email} + + )} +
+ + {/* Password */} +
+ } + value={formData.password} + onChange={(e) => handleChange('password', e.target.value)} + error={!!errors.password} + /> + {errors.password && ( + + {errors.password} + + )} +
+ + {/* Remember & Forgot */} +
+ + Remember me + + } + checked={formData.remember} + onChange={(e) => handleChange('remember', e.target.checked)} + /> + +
+ + {/* Error Message */} + {errors.submit && ( +
+ + {errors.submit} + +
+ )} + + {/* Submit Button */} + + + {/* Divider */} +
+
+ + or continue with + +
+
+ + {/* Social Login */} +
+ + + +
+ + {/* Sign Up Link */} +
+ + Don't have an account?{' '} + + +
+ + ); + } +); + +LoginForm.displayName = 'LoginForm'; diff --git a/src/components/auth/ProtectedRoute.jsx b/src/components/auth/ProtectedRoute.jsx new file mode 100644 index 00000000..eb9dc218 --- /dev/null +++ b/src/components/auth/ProtectedRoute.jsx @@ -0,0 +1,31 @@ +import React from 'react'; +import { useNavigate } from 'react-router-dom'; +import { Spinner } from '../ui'; +import { useAuth } from '../../context'; + +export const ProtectedRoute = React.memo(({ children }) => { + const { isAuthenticated, loading } = useAuth(); + const navigate = useNavigate(); + + React.useEffect(() => { + if (!loading && !isAuthenticated) { + navigate('/sign-in', { replace: true }); + } + }, [isAuthenticated, loading, navigate]); + + if (loading) { + return ( +
+ +
+ ); + } + + if (!isAuthenticated) { + return null; + } + + return <>{children}; +}); + +ProtectedRoute.displayName = 'ProtectedRoute'; diff --git a/src/components/auth/SignUpForm.jsx b/src/components/auth/SignUpForm.jsx new file mode 100644 index 00000000..0911c9c0 --- /dev/null +++ b/src/components/auth/SignUpForm.jsx @@ -0,0 +1,213 @@ +import React from 'react'; +import { Button } from '../ui'; +import { UserIcon, EnvelopeIcon, LockClosedIcon, PhoneIcon } from '@heroicons/react/24/outline'; +import { Input, Checkbox, Typography } from '@material-tailwind/react'; + +export const SignUpForm = React.memo(({ onSignUp, onLogin }) => { + const [formData, setFormData] = React.useState({ + name: '', + email: '', + phone: '', + password: '', + confirmPassword: '', + agreeTerms: false, + }); + const [errors, setErrors] = React.useState({}); + const [loading, setLoading] = React.useState(false); + + const handleChange = (field, value) => { + setFormData((prev) => ({ ...prev, [field]: value })); + if (errors[field]) { + setErrors((prev) => ({ ...prev, [field]: '' })); + } + }; + + const validate = () => { + const newErrors = {}; + + if (!formData.name) { + newErrors.name = 'Name is required'; + } + + if (!formData.email) { + newErrors.email = 'Email is required'; + } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) { + newErrors.email = 'Invalid email format'; + } + + if (!formData.password) { + newErrors.password = 'Password is required'; + } else if (formData.password.length < 8) { + newErrors.password = 'Password must be at least 8 characters'; + } + + if (formData.password !== formData.confirmPassword) { + newErrors.confirmPassword = 'Passwords do not match'; + } + + if (!formData.agreeTerms) { + newErrors.agreeTerms = 'You must agree to the terms'; + } + + setErrors(newErrors); + return Object.keys(newErrors).length === 0; + }; + + const handleSubmit = async (e) => { + e.preventDefault(); + + if (!validate()) return; + + setLoading(true); + try { + await onSignUp?.(formData); + } catch (error) { + setErrors({ submit: error.message }); + } finally { + setLoading(false); + } + }; + + return ( +
+
+ + Create Account + + + Sign up to get started + +
+ + {/* Name */} +
+ } + value={formData.name} + onChange={(e) => handleChange('name', e.target.value)} + error={!!errors.name} + /> + {errors.name && ( + + {errors.name} + + )} +
+ + {/* Email */} +
+ } + value={formData.email} + onChange={(e) => handleChange('email', e.target.value)} + error={!!errors.email} + /> + {errors.email && ( + + {errors.email} + + )} +
+ + {/* Phone */} +
+ } + value={formData.phone} + onChange={(e) => handleChange('phone', e.target.value)} + /> +
+ + {/* Password */} +
+ } + value={formData.password} + onChange={(e) => handleChange('password', e.target.value)} + error={!!errors.password} + /> + {errors.password && ( + + {errors.password} + + )} +
+ + {/* Confirm Password */} +
+ } + value={formData.confirmPassword} + onChange={(e) => handleChange('confirmPassword', e.target.value)} + error={!!errors.confirmPassword} + /> + {errors.confirmPassword && ( + + {errors.confirmPassword} + + )} +
+ + {/* Terms */} +
+ + I agree to the{' '} + + Terms and Conditions + + + } + checked={formData.agreeTerms} + onChange={(e) => handleChange('agreeTerms', e.target.checked)} + /> + {errors.agreeTerms && ( + + {errors.agreeTerms} + + )} +
+ + {/* Error Message */} + {errors.submit && ( +
+ + {errors.submit} + +
+ )} + + {/* Submit Button */} + + + {/* Login Link */} +
+ + Already have an account?{' '} + + +
+
+ ); +}); + +SignUpForm.displayName = 'SignUpForm'; diff --git a/src/components/auth/index.js b/src/components/auth/index.js new file mode 100644 index 00000000..f231532b --- /dev/null +++ b/src/components/auth/index.js @@ -0,0 +1,5 @@ +export { AuthLayout } from './AuthLayout'; +export { LoginForm } from './LoginForm'; +export { SignUpForm } from './SignUpForm'; +export { ForgotPasswordForm } from './ForgotPasswordForm'; +export { ProtectedRoute } from './ProtectedRoute'; diff --git a/src/components/dashboard/BreakdownCard.jsx b/src/components/dashboard/BreakdownCard.jsx new file mode 100644 index 00000000..b25bee4b --- /dev/null +++ b/src/components/dashboard/BreakdownCard.jsx @@ -0,0 +1,37 @@ +export const BreakdownCard = ({ title, data, colors }) => { + const total = Object.values(data).reduce((sum, val) => sum + val, 0); + + return ( +
+
+

{title}

+ Cəmi: {total} +
+
+ {Object.entries(data).map(([key, value]) => { + const percentage = total > 0 ? Math.round((value / total) * 100) : 0; + return ( +
+
+
+
+ {key} +
+ {value} +
+ {/* Progress Bar */} +
+
+
+
+ ); + })} +
+
+ ); +}; diff --git a/src/components/dashboard/DashboardStats.jsx b/src/components/dashboard/DashboardStats.jsx new file mode 100644 index 00000000..56706156 --- /dev/null +++ b/src/components/dashboard/DashboardStats.jsx @@ -0,0 +1,64 @@ +export const DashboardStats = ({ projects = [], teamMembers = [], tasks = {} }) => { + const statistics = React.useMemo(() => { + const totalRevenue = projects.reduce((sum, p) => sum + (p.budget || 0), 0); + const avgCompletion = + projects.length > 0 + ? Math.round(projects.reduce((sum, p) => sum + p.completion, 0) / projects.length) + : 0; + const onlineMembers = teamMembers.filter((m) => m.status === 'online').length; + const tasksCompleted = tasks?.completed || 0; + const totalTasks = tasks?.total || 0; + const completionRate = totalTasks > 0 ? Math.round((tasksCompleted / totalTasks) * 100) : 0; + + return [ + { + id: 1, + title: 'Ümumi Gəlir', + value: `$${(totalRevenue / 1000).toFixed(0)}K`, + change: '+12.5%', + trend: 'up', + icon: CurrencyDollarIcon, + color: 'green', + description: 'keçən rübə nisbətən', + }, + { + id: 2, + title: 'Aktiv Layihələr', + value: projects.length.toString(), + change: `${avgCompletion}%`, + trend: 'up', + icon: ChartBarIcon, + color: 'blue', + description: 'orta tamamlanma dərəcəsi', + }, + { + id: 3, + title: 'Komanda Üzvləri', + value: teamMembers.length.toString(), + change: `${onlineMembers} online`, + trend: 'up', + icon: UsersIcon, + color: 'purple', + description: 'hazırda aktiv', + }, + { + id: 4, + title: 'Tamamlanan Tapşırıqlar', + value: tasksCompleted.toString(), + change: `${completionRate}%`, + trend: completionRate >= 70 ? 'up' : 'down', + icon: CheckCircleIcon, + color: 'orange', + description: 'tamamlanma faizi', + }, + ]; + }, [projects, teamMembers, tasks]); + + return ( +
+ {statistics.map((stat) => ( + + ))} +
+ ); +}; diff --git a/src/components/dashboard/DetailedDashboardStats.jsx b/src/components/dashboard/DetailedDashboardStats.jsx new file mode 100644 index 00000000..f52020fa --- /dev/null +++ b/src/components/dashboard/DetailedDashboardStats.jsx @@ -0,0 +1,105 @@ +export const DetailedDashboardStats = ({ projects = [], teamMembers = [] }) => { + const detailedStats = React.useMemo(() => { + const priorityBreakdown = projects.reduce((acc, p) => { + acc[p.priority] = (acc[p.priority] || 0) + 1; + return acc; + }, {}); + + const statusBreakdown = projects.reduce((acc, p) => { + acc[p.status] = (acc[p.status] || 0) + 1; + return acc; + }, {}); + + const departmentBreakdown = teamMembers.reduce((acc, m) => { + acc[m.department] = (acc[m.department] || 0) + 1; + return acc; + }, {}); + + const now = new Date(); + const upcomingDeadlines = projects.filter((p) => { + const deadline = new Date(p.deadline); + const diffDays = Math.ceil((deadline - now) / (1000 * 60 * 60 * 24)); + return diffDays > 0 && diffDays <= 7; + }).length; + + return { + priorityBreakdown, + statusBreakdown, + departmentBreakdown, + upcomingDeadlines, + avgProjectDuration: 4.2, + }; + }, [projects, teamMembers]); + + const additionalStats = [ + { + id: 5, + title: 'Orta Layihə Müddəti', + value: `${detailedStats.avgProjectDuration} ay`, + change: '+0.5 ay', + trend: 'up', + icon: ClockIcon, + color: 'indigo', + description: 'keçən rübə nisbətən', + }, + { + id: 6, + title: 'Yaxınlaşan Deadlinelər', + value: detailedStats.upcomingDeadlines.toString(), + change: 'Bu həftə', + trend: detailedStats.upcomingDeadlines > 3 ? 'down' : 'up', + icon: ExclamationTriangleIcon, + color: detailedStats.upcomingDeadlines > 3 ? 'red' : 'green', + description: 'diqqət tələb edir', + }, + ]; + + return ( +
+ {/* Main Stats */} + + + {/* Additional Stats */} +
+ {additionalStats.map((stat) => ( + + ))} +
+ + {/* Breakdown Cards */} +
+ + + +
+
+ ); +}; diff --git a/src/components/dashboard/StatCard.jsx b/src/components/dashboard/StatCard.jsx new file mode 100644 index 00000000..f70a85e2 --- /dev/null +++ b/src/components/dashboard/StatCard.jsx @@ -0,0 +1,75 @@ +import React from 'react'; +import { ArrowTrendingUpIcon, ArrowTrendingDownIcon } from '@heroicons/react/24/outline'; + +// Modern StatCard Component +export const StatCard = ({ title, value, change, trend, icon: Icon, color, description }) => { + const colorClasses = { + green: 'from-emerald-500 to-teal-600', + blue: 'from-blue-500 to-cyan-600', + purple: 'from-purple-500 to-pink-600', + orange: 'from-orange-500 to-amber-600', + indigo: 'from-indigo-500 to-blue-600', + red: 'from-red-500 to-rose-600', + }; + + const iconBgColors = { + green: 'bg-emerald-50', + blue: 'bg-blue-50', + purple: 'bg-purple-50', + orange: 'bg-orange-50', + indigo: 'bg-indigo-50', + red: 'bg-red-50', + }; + + const iconColors = { + green: 'text-emerald-600', + blue: 'text-blue-600', + purple: 'text-purple-600', + orange: 'text-orange-600', + indigo: 'text-indigo-600', + red: 'text-red-600', + }; + + return ( +
+ {/* Gradient Background on Hover */} +
+ +
+ {/* Header */} +
+
+ +
+ {trend && ( +
+ {trend === 'up' ? ( + + ) : ( + + )} + {change} +
+ )} +
+ + {/* Content */} +
+

{title}

+

+ {value} +

+

{description}

+
+
+
+ ); +}; diff --git a/src/components/dashboard/index.js b/src/components/dashboard/index.js new file mode 100644 index 00000000..5ed5d98a --- /dev/null +++ b/src/components/dashboard/index.js @@ -0,0 +1,4 @@ +export { StatCard } from './StatCard'; +export { BreakdownCard } from './BreakdownCard'; +export { DashboardStats } from './DashboardStats'; +export { DetailedDashboardStats } from './DetailedDashboardStats'; diff --git a/src/components/dialogs/MemberDialog.jsx b/src/components/dialogs/MemberDialog.jsx new file mode 100644 index 00000000..e6576ffd --- /dev/null +++ b/src/components/dialogs/MemberDialog.jsx @@ -0,0 +1,330 @@ +import React from 'react'; +import { Button } from '../ui'; +import { XMarkIcon } from '@heroicons/react/24/outline'; +import { + Dialog, + DialogHeader, + DialogBody, + DialogFooter, + Input, + Select, + Option, + Textarea, + Typography, +} from '@material-tailwind/react'; + +const DEPARTMENT_OPTIONS = [ + { value: 'engineering', label: 'Engineering' }, + { value: 'product', label: 'Product' }, + { value: 'design', label: 'Design' }, + { value: 'marketing', label: 'Marketing' }, + { value: 'sales', label: 'Sales' }, + { value: 'hr', label: 'Human Resources' }, + { value: 'finance', label: 'Finance' }, +]; + +const STATUS_OPTIONS = [ + { value: 'online', label: 'Online' }, + { value: 'offline', label: 'Offline' }, + { value: 'away', label: 'Away' }, + { value: 'busy', label: 'Busy' }, +]; + +export const MemberDialog = React.memo( + ({ + open, + mode = 'add', // 'add' or 'edit' + member = null, + onClose, + onSave, + }) => { + const [formData, setFormData] = React.useState({ + name: '', + email: '', + phone: '', + role: '', + department: 'engineering', + status: 'offline', + bio: '', + location: '', + joinDate: '', + }); + + const [errors, setErrors] = React.useState({}); + const [loading, setLoading] = React.useState(false); + + // Initialize form data when dialog opens + React.useEffect(() => { + if (open) { + if (mode === 'edit' && member) { + setFormData({ + name: member.name || '', + email: member.email || '', + phone: member.phone || '', + role: member.role || '', + department: member.department || 'engineering', + status: member.status || 'offline', + bio: member.bio || '', + location: member.location || '', + joinDate: member.joinDate || '', + }); + } else { + // Reset for add mode + setFormData({ + name: '', + email: '', + phone: '', + role: '', + department: 'engineering', + status: 'offline', + bio: '', + location: '', + joinDate: new Date().toISOString().split('T')[0], + }); + } + setErrors({}); + } + }, [open, mode, member]); + + const handleChange = (field, value) => { + setFormData((prev) => ({ ...prev, [field]: value })); + // Clear error for this field + if (errors[field]) { + setErrors((prev) => { + const newErrors = { ...prev }; + delete newErrors[field]; + return newErrors; + }); + } + }; + + const validate = () => { + const newErrors = {}; + + if (!formData.name.trim()) { + newErrors.name = 'Name is required'; + } + + if (!formData.email.trim()) { + newErrors.email = 'Email is required'; + } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) { + newErrors.email = 'Invalid email format'; + } + + if (!formData.role.trim()) { + newErrors.role = 'Role is required'; + } + + if (formData.phone && !/^\+?[\d\s-()]+$/.test(formData.phone)) { + newErrors.phone = 'Invalid phone format'; + } + + setErrors(newErrors); + return Object.keys(newErrors).length === 0; + }; + + const handleSave = async () => { + if (!validate()) return; + + setLoading(true); + + try { + const memberData = { ...formData }; + + if (mode === 'edit' && member) { + memberData.id = member.id; + } + + await onSave?.(memberData); + handleClose(); + } catch (error) { + console.error('Error saving member:', error); + setErrors({ submit: error.message }); + } finally { + setLoading(false); + } + }; + + const handleClose = () => { + setFormData({ + name: '', + email: '', + phone: '', + role: '', + department: 'engineering', + status: 'offline', + bio: '', + location: '', + joinDate: '', + }); + setErrors({}); + onClose?.(); + }; + + return ( + + {/* Header */} + + + {mode === 'add' ? 'Add Team Member' : 'Edit Team Member'} + + + + + {/* Body */} + +
+ {/* Name */} +
+ handleChange('name', e.target.value)} + error={!!errors.name} + /> + {errors.name && ( + + {errors.name} + + )} +
+ + {/* Email */} +
+ handleChange('email', e.target.value)} + error={!!errors.email} + /> + {errors.email && ( + + {errors.email} + + )} +
+ + {/* Phone */} +
+ handleChange('phone', e.target.value)} + error={!!errors.phone} + /> + {errors.phone && ( + + {errors.phone} + + )} +
+ + {/* Role and Department */} +
+
+ handleChange('role', e.target.value)} + error={!!errors.role} + /> + {errors.role && ( + + {errors.role} + + )} +
+ +
+ +
+
+ + {/* Status and Join Date */} +
+
+ +
+ +
+ handleChange('joinDate', e.target.value)} + /> +
+
+ + {/* Location */} +
+ handleChange('location', e.target.value)} + /> +
+ + {/* Bio */} +
+