├── .eslintrc.json ├── app ├── favicon.ico ├── page.tsx ├── layout.tsx ├── globals.css ├── employeeLogin │ └── page.tsx └── login │ └── page.tsx ├── public ├── loginPic.png └── employeeLogin.svg ├── next.config.mjs ├── postcss.config.mjs ├── lib ├── utils.ts ├── types.ts ├── dummy.ts └── data.ts ├── store ├── sideBarNarrowStore.ts └── sideBarStore.ts ├── components.json ├── components ├── Departments.tsx ├── ui │ ├── sidebarTab.tsx │ ├── label.tsx │ ├── input.tsx │ ├── button.tsx │ ├── card.tsx │ ├── table.tsx │ ├── dialog.tsx │ ├── alert-dialog.tsx │ └── select.tsx ├── Employees.tsx ├── EditDialog.tsx ├── MainDisplay.tsx ├── Analytics.tsx ├── PromotionList.tsx ├── AddCSVEmployee.tsx ├── Sidebar.tsx ├── Dashboard.tsx ├── AnalyticsChart.tsx ├── AddDepartment.tsx ├── AddEmployee.tsx ├── DepartmentList.tsx └── EmployeeList.tsx ├── .gitignore ├── tsconfig.json ├── package.json ├── README.md └── tailwind.config.ts /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xditya/EmployeeManagement/frontend/app/favicon.ico -------------------------------------------------------------------------------- /public/loginPic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xditya/EmployeeManagement/frontend/public/loginPic.png -------------------------------------------------------------------------------- /next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {}; 3 | 4 | export default nextConfig; 5 | -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { type ClassValue, clsx } from "clsx"; 2 | import { twMerge } from "tailwind-merge"; 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)); 6 | } 7 | -------------------------------------------------------------------------------- /store/sideBarNarrowStore.ts: -------------------------------------------------------------------------------- 1 | import { create } from "zustand"; 2 | 3 | interface SidebarNarrowState { 4 | isNarrow: boolean; 5 | setIsNarrow: (value: boolean) => void; 6 | } 7 | 8 | const useSidebarNarrowStore = create((set) => ({ 9 | isNarrow: false, 10 | setIsNarrow: (value: boolean) => { 11 | set({ isNarrow: value }); 12 | }, 13 | })); 14 | 15 | export default useSidebarNarrowStore; -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "app/globals.css", 9 | "baseColor": "slate", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/page.tsx: -------------------------------------------------------------------------------- 1 | import Sidebar from "@/components/Sidebar"; 2 | import EditDialog from "@/components/EditDialog"; 3 | import Image from "next/image"; 4 | import MainDisplay from "@/components/MainDisplay"; 5 | export default function Home() { 6 | return ( 7 |
8 | {/* 9 | */} 10 | 11 | 12 |
13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /lib/types.ts: -------------------------------------------------------------------------------- 1 | interface EmployeeSchema { 2 | id: number; 3 | name: string; 4 | email: string; 5 | contactNumber: string; 6 | dateOfJoin: string; 7 | yearsOfExperience: number; 8 | departmentId: number; 9 | department?: string; 10 | } 11 | 12 | interface DepartmentSchema { 13 | departmentId: number; 14 | departmentName: string; 15 | location: string; 16 | managerId: number; 17 | } 18 | 19 | export type { EmployeeSchema, DepartmentSchema }; 20 | -------------------------------------------------------------------------------- /store/sideBarStore.ts: -------------------------------------------------------------------------------- 1 | import { create } from "zustand"; 2 | 3 | // Define an interface for the store state 4 | interface SidebarState { 5 | tabSelected: string; 6 | setTabSelected: (tab: string) => void; 7 | } 8 | 9 | const useSidebarStore = create((set) => ({ 10 | tabSelected: "dashboard", // Initial state with default value 11 | setTabSelected: (tab: string) => { 12 | set({ tabSelected: tab }); 13 | }, 14 | })); 15 | 16 | export default useSidebarStore; 17 | -------------------------------------------------------------------------------- /components/Departments.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import AddDepartment from "./AddDepartment"; 3 | import DepartmentList from "./DepartmentList"; 4 | import PromotionList from "./PromotionList"; 5 | 6 | const Departments = () => { 7 | return ( 8 |
9 |

Departments

10 | 11 | 12 |
13 | ); 14 | }; 15 | 16 | export default Departments; 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | .env -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import { Inter } from "next/font/google"; 3 | import "./globals.css"; 4 | 5 | const inter = Inter({ subsets: ["latin"] }); 6 | 7 | export const metadata: Metadata = { 8 | title: "EMMA", 9 | description: "Employee Management Made Accessible<", 10 | }; 11 | 12 | export default function RootLayout({ 13 | children, 14 | }: Readonly<{ 15 | children: React.ReactNode; 16 | }>) { 17 | return ( 18 | 19 | 20 | {children} 21 | 22 | 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /components/ui/sidebarTab.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import useSidebarStore from "@/store/sideBarStore"; 3 | import Link from "next/link"; 4 | import React from "react"; 5 | 6 | const SidebarTab = (tab:string) => { 7 | const { setTabSelected } = useSidebarStore(); 8 | 9 | return ( 10 | setTabSelected(tab)} 14 | > 15 | {tab} 16 | 17 | ); 18 | }; 19 | 20 | export default SidebarTab; 21 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowJs": true, 5 | "skipLibCheck": true, 6 | "strict": true, 7 | "noEmit": true, 8 | "esModuleInterop": true, 9 | "module": "esnext", 10 | "moduleResolution": "bundler", 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "jsx": "preserve", 14 | "incremental": true, 15 | "plugins": [ 16 | { 17 | "name": "next" 18 | } 19 | ], 20 | "paths": { 21 | "@/*": ["./*"] 22 | } 23 | }, 24 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 25 | "exclude": ["node_modules"] 26 | } 27 | -------------------------------------------------------------------------------- /components/Employees.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Button } from "./ui/button"; 3 | import { AddEmployee } from "./AddEmployee"; 4 | import { AddCSVEmployee } from "./AddCSVEmployee"; 5 | import EmployeeList from "./EmployeeList"; 6 | import PromotionList from "./PromotionList"; 7 | 8 | const Employees = () => { 9 | return ( 10 |
11 |

Employee

12 | 13 | 14 | 15 |

Promoted List

16 | 17 |
18 | ); 19 | }; 20 | 21 | export default Employees; 22 | -------------------------------------------------------------------------------- /components/EditDialog.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { 3 | Dialog, 4 | DialogContent, 5 | DialogDescription, 6 | DialogHeader, 7 | DialogTitle, 8 | DialogTrigger, 9 | } from "@/components/ui/dialog"; 10 | 11 | const EditDialog = () => { 12 | return ( 13 | 14 | Open 15 | 16 | 17 | Are you absolutely sure? 18 | 19 | This action cannot be undone. This will permanently delete your 20 | account and remove your data from our servers. 21 | 22 | 23 | 24 | 25 | ); 26 | }; 27 | 28 | export default EditDialog; 29 | -------------------------------------------------------------------------------- /components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import * as LabelPrimitive from "@radix-ui/react-label"; 5 | import { cva, type VariantProps } from "class-variance-authority"; 6 | 7 | import { cn } from "@/lib/utils"; 8 | 9 | const labelVariants = cva( 10 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70", 11 | ); 12 | 13 | const Label = React.forwardRef< 14 | React.ElementRef, 15 | React.ComponentPropsWithoutRef & 16 | VariantProps 17 | >(({ className, ...props }, ref) => ( 18 | 23 | )); 24 | Label.displayName = LabelPrimitive.Root.displayName; 25 | 26 | export { Label }; 27 | -------------------------------------------------------------------------------- /components/ui/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { cn } from "@/lib/utils"; 4 | 5 | export interface InputProps 6 | extends React.InputHTMLAttributes {} 7 | 8 | const Input = React.forwardRef( 9 | ({ className, type, ...props }, ref) => { 10 | return ( 11 | 20 | ); 21 | }, 22 | ); 23 | Input.displayName = "Input"; 24 | 25 | export { Input }; 26 | -------------------------------------------------------------------------------- /components/MainDisplay.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import React from "react"; 3 | import useSidebarStore from "@/store/sideBarStore"; 4 | import useSidebarNarrowStore from "@/store/sideBarNarrowStore"; 5 | import Dashboard from "./Dashboard"; 6 | import Employees from "./Employees"; 7 | import Departments from "./Departments"; 8 | import Analytics from "./Analytics"; 9 | 10 | const MainDisplay = () => { 11 | const { isNarrow, setIsNarrow } = useSidebarNarrowStore(); 12 | 13 | const { tabSelected } = useSidebarStore(); 14 | 15 | let displayContent; 16 | switch (tabSelected) { 17 | case "dashboard": 18 | displayContent = ; 19 | break; 20 | case "employees": 21 | displayContent = ; 22 | break; 23 | case "departments": 24 | displayContent = ; 25 | break; 26 | case "analytics": 27 | displayContent = ; 28 | break; 29 | default: 30 | displayContent = ``; 31 | } 32 | return ( 33 |
34 | {displayContent} 35 |
36 | ); 37 | }; 38 | 39 | export default MainDisplay; 40 | -------------------------------------------------------------------------------- /components/Analytics.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import AnalyticsChart from "./AnalyticsChart"; 3 | import { DepartmentSchema, EmployeeSchema } from "@/lib/types"; 4 | import { getDepartmentFromId, getDepartments, getEmployees } from "@/lib/data"; 5 | 6 | const Analytics = () => { 7 | const [employees,setEmployees] = useState([]); 8 | const [departments,setDepartments] = useState([]) 9 | useEffect(() => { 10 | let isMounted = true; 11 | 12 | const fetchData = async () => { 13 | try { 14 | const fetchedEmployees: EmployeeSchema[] = await getEmployees(); 15 | setEmployees(fetchedEmployees) 16 | const fetchedDepartments :DepartmentSchema[] = await getDepartments(); 17 | setDepartments(fetchedDepartments) 18 | console.log("Employees:", fetchedEmployees); 19 | console.log("Departments:",fetchedDepartments) 20 | } catch (error) { 21 | console.error("Error fetching :", error); 22 | 23 | } 24 | }; 25 | 26 | fetchData(); 27 | 28 | return () => { 29 | isMounted = false; 30 | }; 31 | }, []); 32 | return
33 |

Analytics

34 | 35 | {/* */} 36 |
37 | }; 38 | 39 | export default Analytics; 40 | -------------------------------------------------------------------------------- /lib/dummy.ts: -------------------------------------------------------------------------------- 1 | // import faker from 'faker'; 2 | // import { EmployeeSchema, DepartmentSchema } from './types'; 3 | 4 | // // Generate dummy employee data 5 | // const generateDummyEmployees = (count: number): EmployeeSchema[] => { 6 | // const employees: EmployeeSchema[] = []; 7 | // for (let i = 0; i < count; i++) { 8 | // const employee: EmployeeSchema = { 9 | // id: i + 1, 10 | // name: faker.name.findName(), 11 | // email: faker.internet.email(), 12 | // contactNumber: faker.phone.phoneNumber(), 13 | // dateOfJoin: faker.date.past().toISOString(), 14 | // yearsOfExperience: faker.random.number({ min: 0, max: 20 }), 15 | // departmentId: faker.random.number({ min: 1, max: 5 }), 16 | // }; 17 | // employees.push(employee); 18 | // } 19 | // return employees; 20 | // }; 21 | 22 | 23 | // const generateDummyDepartments = (): DepartmentSchema[] => { 24 | // const departments: DepartmentSchema[] = []; 25 | // for (let i = 0; i < 5; i++) { 26 | // const department: DepartmentSchema = { 27 | // departmentId: i + 1, 28 | // departmentName: faker.commerce.department(), 29 | // location: faker.address.city(), 30 | // managerId: faker.random.number({ min: 1, max: 100 }), 31 | // }; 32 | // departments.push(department); 33 | // } 34 | // return departments; 35 | // }; 36 | 37 | 38 | 39 | 40 | // export default {generateDummyDepartments, generateDummyEmployees} 41 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "employee-frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@faker-js/faker": "^8.4.1", 13 | "@radix-ui/react-alert-dialog": "^1.0.5", 14 | "@radix-ui/react-dialog": "^1.0.5", 15 | "@radix-ui/react-label": "^2.0.2", 16 | "@radix-ui/react-select": "^2.0.0", 17 | "@radix-ui/react-slot": "^1.0.2", 18 | "chart.js": "^4.4.2", 19 | "class-variance-authority": "^0.7.0", 20 | "clsx": "^2.1.1", 21 | "faker": "^5.5.3", 22 | "faker-js": "^1.0.0", 23 | "lucide-react": "^0.378.0", 24 | "next": "14.2.3", 25 | "papaparse": "^5.4.1", 26 | "prettier": "^3.2.5", 27 | "react": "^18", 28 | "react-chartjs-2": "^5.2.0", 29 | "react-dom": "^18", 30 | "react-firebase-hooks": "^5.1.1", 31 | "react-hot-toast": "^2.4.1", 32 | "react-icons": "^5.2.1", 33 | "tailwind-merge": "^2.3.0", 34 | "tailwindcss-animate": "^1.0.7", 35 | "toast": "^0.5.4", 36 | "zustand": "^4.5.2" 37 | }, 38 | "devDependencies": { 39 | "@types/node": "^20", 40 | "@types/papaparse": "^5.3.14", 41 | "@types/react": "^18", 42 | "@types/react-dom": "^18", 43 | "eslint": "^8", 44 | "eslint-config-next": "14.2.3", 45 | "postcss": "^8", 46 | "tailwindcss": "^3.4.1", 47 | "typescript": "^5" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | :root { 7 | --background: 0 0% 100%; 8 | --foreground: 222.2 84% 4.9%; 9 | 10 | --card: 0 0% 100%; 11 | --card-foreground: 222.2 84% 4.9%; 12 | 13 | --popover: 0 0% 100%; 14 | --popover-foreground: 222.2 84% 4.9%; 15 | 16 | --primary: 222.2 47.4% 11.2%; 17 | --primary-foreground: 210 40% 98%; 18 | 19 | --secondary: 210 40% 96.1%; 20 | --secondary-foreground: 222.2 47.4% 11.2%; 21 | 22 | --muted: 210 40% 96.1%; 23 | --muted-foreground: 215.4 16.3% 46.9%; 24 | 25 | --accent: 210 40% 96.1%; 26 | --accent-foreground: 222.2 47.4% 11.2%; 27 | 28 | --destructive: 0 84.2% 60.2%; 29 | --destructive-foreground: 210 40% 98%; 30 | 31 | --border: 214.3 31.8% 91.4%; 32 | --input: 214.3 31.8% 91.4%; 33 | --ring: 222.2 84% 4.9%; 34 | 35 | --radius: 0.5rem; 36 | } 37 | 38 | .dark { 39 | --background: 222.2 84% 4.9%; 40 | --foreground: 210 40% 98%; 41 | 42 | --card: 222.2 84% 4.9%; 43 | --card-foreground: 210 40% 98%; 44 | 45 | --popover: 222.2 84% 4.9%; 46 | --popover-foreground: 210 40% 98%; 47 | 48 | --primary: 210 40% 98%; 49 | --primary-foreground: 222.2 47.4% 11.2%; 50 | 51 | --secondary: 217.2 32.6% 17.5%; 52 | --secondary-foreground: 210 40% 98%; 53 | 54 | --muted: 217.2 32.6% 17.5%; 55 | --muted-foreground: 215 20.2% 65.1%; 56 | 57 | --accent: 217.2 32.6% 17.5%; 58 | --accent-foreground: 210 40% 98%; 59 | 60 | --destructive: 0 62.8% 30.6%; 61 | --destructive-foreground: 210 40% 98%; 62 | 63 | --border: 217.2 32.6% 17.5%; 64 | --input: 217.2 32.6% 17.5%; 65 | --ring: 212.7 26.8% 83.9%; 66 | } 67 | } 68 | 69 | @layer base { 70 | * { 71 | @apply border-border; 72 | } 73 | body { 74 | @apply bg-background text-foreground; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /components/PromotionList.tsx: -------------------------------------------------------------------------------- 1 | import { EmployeeSchema } from '@/lib/types' 2 | import React, { useEffect, useState } from 'react' 3 | import {getDepartmentFromId, getPromotionList} from '@/lib/data' 4 | import { 5 | Table, 6 | TableBody, 7 | TableCaption, 8 | TableCell, 9 | TableHead, 10 | TableHeader, 11 | TableRow, 12 | } from "@/components/ui/table"; 13 | const PromotionList = () => { 14 | const [promotedEmployees,setPromotedEmployees] = useState([]) 15 | 16 | useEffect(()=>{ 17 | let isMounted = true; 18 | const fetchPromotedEmployees = async()=>{ 19 | const promotedEmployees = await getPromotionList(); 20 | if (isMounted) { 21 | const updatedEmployees = await Promise.all( 22 | promotedEmployees.map(async (employee : EmployeeSchema) => { 23 | const department = await getDepartmentFromId(employee.departmentId); 24 | return { ...employee, department }; 25 | }) 26 | ); 27 | setPromotedEmployees(updatedEmployees); 28 | } 29 | } 30 | fetchPromotedEmployees() 31 | 32 | }) 33 | return ( 34 |
35 | 36 | 37 | 38 | Employee ID 39 | Employee Name 40 | Department 41 | 42 | 43 | 44 | {promotedEmployees.map((employee, index) => ( 45 | 46 | {employee.id} 47 | {employee.name} 48 | {employee.department} 49 | 50 | ))} 51 | 52 | 53 |
54 |
55 | ) 56 | } 57 | 58 | export default PromotionList -------------------------------------------------------------------------------- /components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Slot } from "@radix-ui/react-slot"; 3 | import { cva, type VariantProps } from "class-variance-authority"; 4 | 5 | import { cn } from "@/lib/utils"; 6 | 7 | const buttonVariants = cva( 8 | "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", 9 | { 10 | variants: { 11 | variant: { 12 | default: "bg-primary text-primary-foreground hover:bg-primary/90", 13 | destructive: 14 | "bg-destructive text-destructive-foreground hover:bg-destructive/90", 15 | outline: 16 | "border border-input bg-background hover:bg-accent hover:text-accent-foreground", 17 | secondary: 18 | "bg-secondary text-secondary-foreground hover:bg-secondary/80", 19 | ghost: "hover:bg-accent hover:text-accent-foreground", 20 | link: "text-primary underline-offset-4 hover:underline", 21 | }, 22 | size: { 23 | default: "h-10 px-4 py-2", 24 | sm: "h-9 rounded-md px-3", 25 | lg: "h-11 rounded-md px-8", 26 | icon: "h-10 w-10", 27 | }, 28 | }, 29 | defaultVariants: { 30 | variant: "default", 31 | size: "default", 32 | }, 33 | }, 34 | ); 35 | 36 | export interface ButtonProps 37 | extends React.ButtonHTMLAttributes, 38 | VariantProps { 39 | asChild?: boolean; 40 | } 41 | 42 | const Button = React.forwardRef( 43 | ({ className, variant, size, asChild = false, ...props }, ref) => { 44 | const Comp = asChild ? Slot : "button"; 45 | return ( 46 | 51 | ); 52 | }, 53 | ); 54 | Button.displayName = "Button"; 55 | 56 | export { Button, buttonVariants }; 57 | -------------------------------------------------------------------------------- /components/ui/card.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | const Card = React.forwardRef< 6 | HTMLDivElement, 7 | React.HTMLAttributes 8 | >(({ className, ...props }, ref) => ( 9 |
17 | )) 18 | Card.displayName = "Card" 19 | 20 | const CardHeader = React.forwardRef< 21 | HTMLDivElement, 22 | React.HTMLAttributes 23 | >(({ className, ...props }, ref) => ( 24 |
29 | )) 30 | CardHeader.displayName = "CardHeader" 31 | 32 | const CardTitle = React.forwardRef< 33 | HTMLParagraphElement, 34 | React.HTMLAttributes 35 | >(({ className, ...props }, ref) => ( 36 |

44 | )) 45 | CardTitle.displayName = "CardTitle" 46 | 47 | const CardDescription = React.forwardRef< 48 | HTMLParagraphElement, 49 | React.HTMLAttributes 50 | >(({ className, ...props }, ref) => ( 51 |

56 | )) 57 | CardDescription.displayName = "CardDescription" 58 | 59 | const CardContent = React.forwardRef< 60 | HTMLDivElement, 61 | React.HTMLAttributes 62 | >(({ className, ...props }, ref) => ( 63 |

64 | )) 65 | CardContent.displayName = "CardContent" 66 | 67 | const CardFooter = React.forwardRef< 68 | HTMLDivElement, 69 | React.HTMLAttributes 70 | >(({ className, ...props }, ref) => ( 71 |
76 | )) 77 | CardFooter.displayName = "CardFooter" 78 | 79 | export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } 80 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EMMA - Employee Management Made Accessible 2 | Web application providing comprehensive functionalities to manage employees and departments efficiently, with CRUD operations and analytics. 3 | 4 | [Live Deployment](https://emma-manager.netlify.app) 5 | 6 | # Backend API 7 | [Switch to REST API Codes](https://github.com/xditya/EmployeeManagement/tree/main) 8 | 9 | # Features 10 | 1. **Employee CRUD Operations**: REST API endpoints for adding, retrieving, updating, and deleting employee records. 11 | 2. **Department CRUD Operations**: REST API endpoints for managing department details, including addition, retrieval, modification, and deletion. 12 | 3. **Employee-Department Assignment**: REST API endpoints to assign employees to specific departments, ensuring one-to-one association. 13 | 4. **Employee Promotion**: REST API endpoints to handle employee promotions based on experience (minimum 5 years), updating roles to manager positions accordingly. 14 | 5. **Realtime Analytics**: Graphical analysis of employees and departments. 15 | 16 | ## The Team 17 | - [Aditya S.](https://xditya.me) ([@xditya](https://github.com/xditya)) 18 | - Ferwin Lopez ([@fer-win](https://github.com/fer-win)) 19 | - Gowri S. ([@gxrii](https://github.com/gxrii)) 20 | - V. Vany Suria ([@vanysuria](https://github.com/vanysuria)) 21 | 22 | 23 | # Screenshots 24 | ![image](https://github.com/xditya/EmployeeManagement/assets/58950863/79efe7cd-3b64-4174-a825-c2809f4626b5) 25 | ![image](https://github.com/xditya/EmployeeManagement/assets/58950863/3f86a14e-91d0-438b-b356-f300f706f486) 26 | ![image](https://github.com/xditya/EmployeeManagement/assets/58950863/533f8a12-fd46-4761-9536-aa13d5657461) 27 | ![image](https://github.com/xditya/EmployeeManagement/assets/58950863/691ac054-0c6d-46a8-aa4e-b3c1506d8d63) 28 | ![image](https://github.com/xditya/EmployeeManagement/assets/58950863/710107c5-fb41-45f6-bf83-44a3eeff4dab) 29 | ![image](https://github.com/xditya/EmployeeManagement/assets/58950863/3db90b1e-5316-4ddc-818b-ed13b1ec2e7c) 30 | ![image](https://github.com/xditya/EmployeeManagement/assets/58950863/a6575b86-ee3b-420b-9198-2510c639572e) 31 | ![image](https://github.com/xditya/EmployeeManagement/assets/58950863/2b772c11-c26b-4968-b4b2-bba8b54d2438) 32 | ![image](https://github.com/xditya/EmployeeManagement/assets/58950863/4516aa05-c187-4512-949a-645774a144a3) 33 | ![image](https://github.com/xditya/EmployeeManagement/assets/58950863/af8a6730-1664-4d63-a6b5-7bdc62a03ab7) 34 | -------------------------------------------------------------------------------- /tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "tailwindcss"; 2 | 3 | const config = { 4 | darkMode: ["class"], 5 | content: [ 6 | "./pages/**/*.{ts,tsx}", 7 | "./components/**/*.{ts,tsx}", 8 | "./app/**/*.{ts,tsx}", 9 | "./src/**/*.{ts,tsx}", 10 | ], 11 | prefix: "", 12 | theme: { 13 | container: { 14 | center: true, 15 | padding: "2rem", 16 | screens: { 17 | "2xl": "1400px", 18 | }, 19 | }, 20 | extend: { 21 | colors: { 22 | border: "hsl(var(--border))", 23 | input: "hsl(var(--input))", 24 | ring: "hsl(var(--ring))", 25 | background: "hsl(var(--background))", 26 | foreground: "hsl(var(--foreground))", 27 | primary: { 28 | DEFAULT: "hsl(var(--primary))", 29 | foreground: "hsl(var(--primary-foreground))", 30 | }, 31 | secondary: { 32 | DEFAULT: "hsl(var(--secondary))", 33 | foreground: "hsl(var(--secondary-foreground))", 34 | }, 35 | destructive: { 36 | DEFAULT: "hsl(var(--destructive))", 37 | foreground: "hsl(var(--destructive-foreground))", 38 | }, 39 | muted: { 40 | DEFAULT: "hsl(var(--muted))", 41 | foreground: "hsl(var(--muted-foreground))", 42 | }, 43 | accent: { 44 | DEFAULT: "hsl(var(--accent))", 45 | foreground: "hsl(var(--accent-foreground))", 46 | }, 47 | popover: { 48 | DEFAULT: "hsl(var(--popover))", 49 | foreground: "hsl(var(--popover-foreground))", 50 | }, 51 | card: { 52 | DEFAULT: "hsl(var(--card))", 53 | foreground: "hsl(var(--card-foreground))", 54 | }, 55 | }, 56 | borderRadius: { 57 | lg: "var(--radius)", 58 | md: "calc(var(--radius) - 2px)", 59 | sm: "calc(var(--radius) - 4px)", 60 | }, 61 | keyframes: { 62 | "accordion-down": { 63 | from: { height: "0" }, 64 | to: { height: "var(--radix-accordion-content-height)" }, 65 | }, 66 | "accordion-up": { 67 | from: { height: "var(--radix-accordion-content-height)" }, 68 | to: { height: "0" }, 69 | }, 70 | }, 71 | animation: { 72 | "accordion-down": "accordion-down 0.2s ease-out", 73 | "accordion-up": "accordion-up 0.2s ease-out", 74 | }, 75 | }, 76 | }, 77 | plugins: [require("tailwindcss-animate")], 78 | } satisfies Config; 79 | 80 | export default config; 81 | -------------------------------------------------------------------------------- /components/AddCSVEmployee.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useRef } from "react"; 2 | import { Button } from "@/components/ui/button"; 3 | import { 4 | Dialog, 5 | DialogContent, 6 | DialogDescription, 7 | DialogFooter, 8 | DialogHeader, 9 | DialogTitle, 10 | DialogTrigger, 11 | } from "@/components/ui/dialog"; 12 | import { FaFileArrowDown } from "react-icons/fa6"; 13 | import Papa from "papaparse"; 14 | import { createEmployees } from "@/lib/data"; 15 | import { EmployeeSchema } from "@/lib/types"; 16 | 17 | export function AddCSVEmployee() { 18 | const fileInputRef = useRef(null); 19 | const [fileKey, setFileKey] = useState(0); // Key to force re-rendering 20 | 21 | // Function to handle file input change 22 | const handleFileInputChange = (event:any) => { 23 | const file = event.target.files[0]; 24 | if (file) { 25 | Papa.parse(file, { 26 | complete: handleCSVData, 27 | header: true, 28 | }); 29 | } 30 | }; 31 | 32 | // Function to parse CSV data 33 | const handleCSVData = async(result:any) => { 34 | const employeesData = result.data.map((row:any, index:any) => ({ 35 | id: row.id, 36 | name: row.name, 37 | email: row.email, 38 | contactNumber: row.contactNumber, 39 | dateOfJoin: row.dateOfJoin, 40 | yearsOfExperience: parseInt(row.yearsOfExperience), 41 | departmentId: parseInt(row.departmentId), 42 | })); 43 | 44 | // Log the parsed data 45 | console.log("Parsed CSV Data:", employeesData); 46 | 47 | // Now, you can use the `employeesData` array to do whatever you need 48 | // For example, you can call a function to create employees 49 | // createEmployees(employeesData); 50 | const res = await createEmployees(employeesData); 51 | if (res) { 52 | console.log("Employees List Added"); 53 | } 54 | }; 55 | 56 | // Function to handle import button click 57 | const handleImportButtonClick = () => { 58 | // Click the hidden file input element 59 | if (fileInputRef.current) { 60 | fileInputRef?.current?.click(); 61 | } 62 | }; 63 | 64 | return ( 65 | <> 66 | 67 | 68 | 71 | 72 | 73 | 74 | Add Employees using CSV 75 | 76 | Choose the CSV file which contains the Employees Data 77 | 78 | 79 |
80 | 88 | 89 | 92 | 93 |
94 |
95 |
96 | 97 | ); 98 | } 99 | -------------------------------------------------------------------------------- /components/ui/table.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { cn } from "@/lib/utils"; 4 | 5 | const Table = React.forwardRef< 6 | HTMLTableElement, 7 | React.HTMLAttributes 8 | >(({ className, ...props }, ref) => ( 9 |
10 | 15 | 16 | )); 17 | Table.displayName = "Table"; 18 | 19 | const TableHeader = React.forwardRef< 20 | HTMLTableSectionElement, 21 | React.HTMLAttributes 22 | >(({ className, ...props }, ref) => ( 23 | 24 | )); 25 | TableHeader.displayName = "TableHeader"; 26 | 27 | const TableBody = React.forwardRef< 28 | HTMLTableSectionElement, 29 | React.HTMLAttributes 30 | >(({ className, ...props }, ref) => ( 31 | 36 | )); 37 | TableBody.displayName = "TableBody"; 38 | 39 | const TableFooter = React.forwardRef< 40 | HTMLTableSectionElement, 41 | React.HTMLAttributes 42 | >(({ className, ...props }, ref) => ( 43 | tr]:last:border-b-0", 47 | className, 48 | )} 49 | {...props} 50 | /> 51 | )); 52 | TableFooter.displayName = "TableFooter"; 53 | 54 | const TableRow = React.forwardRef< 55 | HTMLTableRowElement, 56 | React.HTMLAttributes 57 | >(({ className, ...props }, ref) => ( 58 | 66 | )); 67 | TableRow.displayName = "TableRow"; 68 | 69 | const TableHead = React.forwardRef< 70 | HTMLTableCellElement, 71 | React.ThHTMLAttributes 72 | >(({ className, ...props }, ref) => ( 73 |
81 | )); 82 | TableHead.displayName = "TableHead"; 83 | 84 | const TableCell = React.forwardRef< 85 | HTMLTableCellElement, 86 | React.TdHTMLAttributes 87 | >(({ className, ...props }, ref) => ( 88 | 93 | )); 94 | TableCell.displayName = "TableCell"; 95 | 96 | const TableCaption = React.forwardRef< 97 | HTMLTableCaptionElement, 98 | React.HTMLAttributes 99 | >(({ className, ...props }, ref) => ( 100 |
105 | )); 106 | TableCaption.displayName = "TableCaption"; 107 | 108 | export { 109 | Table, 110 | TableHeader, 111 | TableBody, 112 | TableFooter, 113 | TableHead, 114 | TableRow, 115 | TableCell, 116 | TableCaption, 117 | }; 118 | -------------------------------------------------------------------------------- /components/Sidebar.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import React, { useState } from "react"; 3 | import { MdNotifications } from "react-icons/md"; 4 | import { Button } from "@/components/ui/button"; 5 | import SidebarTab from "./ui/sidebarTab"; 6 | import useSidebarStore from "@/store/sideBarStore"; 7 | import useSidebarNarrowStore from "@/store/sideBarNarrowStore"; 8 | import { BiSolidDashboard } from "react-icons/bi"; 9 | import { HiOfficeBuilding } from "react-icons/hi"; 10 | import { FaUsers } from "react-icons/fa6"; 11 | import { SiGoogleanalytics } from "react-icons/si"; 12 | import { RxDoubleArrowLeft, RxDoubleArrowRight } from "react-icons/rx"; 13 | 14 | import Link from "next/link"; 15 | const Sidebar = () => { 16 | const { tabSelected, setTabSelected } = useSidebarStore(); 17 | const { isNarrow, setIsNarrow } = useSidebarNarrowStore(); 18 | // const tabs = ["Dashboard", "Employees", "Departments", "Analytics"]; 19 | const tabs = { 20 | Dashboard: , 21 | Employees: , 22 | Departments: , 23 | Analytics: , 24 | }; 25 | const handleTabClick = (tab: string) => { 26 | console.log("tab ", tab); 27 | if (tabSelected === tab) return; 28 | setTabSelected(tab); 29 | console.log(tabSelected); 30 | }; 31 | const handleArrowClick = () => { 32 | setIsNarrow(!isNarrow); 33 | console.log(isNarrow); 34 | }; 35 | return ( 36 |
41 |
42 |
43 | {!isNarrow && ( 44 | emma
Employee Management
Made Accessible
45 | 46 | )} 47 |
51 | {isNarrow ? ( 52 | 53 | ) : ( 54 | 55 | )} 56 |
57 |
58 |
59 | {} 60 | {Object.entries(tabs).map(([tabName, icon], index) => { 61 | return ( 62 |
63 | handleTabClick(tabName.toLowerCase())} 67 | > 68 | {icon} 69 | {!isNarrow && tabName} 70 | 71 |
72 | ); 73 | })} 74 |
75 |
76 |
77 | 80 |
81 |
82 | ); 83 | }; 84 | 85 | export default Sidebar; 86 | 87 | -------------------------------------------------------------------------------- /components/Dashboard.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { 3 | Card, 4 | CardContent, 5 | CardDescription, 6 | CardHeader, 7 | CardTitle, 8 | } from "@/components/ui/card"; 9 | import { DepartmentSchema, EmployeeSchema } from "@/lib/types"; 10 | import { getDepartmentFromId, getDepartments, getEmployees } from "@/lib/data"; 11 | import { Bar } from "react-chartjs-2"; 12 | 13 | const Dashboard = () => { 14 | const [employees, setEmployees] = useState([]); 15 | const [departments, setDepartments] = useState([]); 16 | 17 | useEffect(() => { 18 | const fetchData = async () => { 19 | try { 20 | const fetchedEmployees: EmployeeSchema[] = await getEmployees(); 21 | setEmployees(fetchedEmployees); 22 | const fetchedDepartments: DepartmentSchema[] = await getDepartments(); 23 | setDepartments(fetchedDepartments); 24 | console.log("Employees:", fetchedEmployees); 25 | console.log("Departments:", fetchedDepartments); 26 | } catch (error) { 27 | console.error("Error fetching:", error); 28 | } 29 | }; 30 | 31 | fetchData(); 32 | }, []); 33 | 34 | // const getDepartment = async (id: number) => { 35 | // console.log("Get ID : ", id); 36 | // const department = await getDepartmentFromId(id); 37 | // console.log("ID : ", id); 38 | // return department; 39 | // }; 40 | 41 | const lastFiveEmployees = employees.slice(-5); 42 | 43 | const departmentEmployeeCount: Record = {}; 44 | 45 | employees.forEach((employee) => { 46 | const departmentName = 47 | departments.find((dep) => dep.departmentId === employee.departmentId) 48 | ?.departmentName || "Unknown"; 49 | departmentEmployeeCount[departmentName] = 50 | (departmentEmployeeCount[departmentName] || 0) + 1; 51 | }); 52 | 53 | console.log(departmentEmployeeCount); 54 | 55 | const chartData = { 56 | labels: Object.keys(departmentEmployeeCount), 57 | datasets: [ 58 | { 59 | label: "Employee Count per Department", 60 | data: Object.values(departmentEmployeeCount), 61 | backgroundColor: "rgba(54, 162, 235, 0.5)", 62 | borderColor: "rgba(54, 162, 235, 1)", 63 | borderWidth: 1, 64 | }, 65 | ], 66 | }; 67 | 68 | return ( 69 | <> 70 |

Dashboard

71 |
72 | 73 | 74 | Employee Overview 75 | Employee count per department 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | Employee and Department Count 85 | 86 | Total number of employees and departments 87 | 88 | 89 | 90 |
91 |
92 | Total Employees:{" "} 93 | {employees.length} 94 |
95 |
96 | Total Departments:{" "} 97 | {departments.length} 98 |
99 |
100 |
101 |
102 | 103 | 104 | 105 | Recently Added Employees 106 | Last 5 employees added 107 | 108 | 109 |
    110 | {lastFiveEmployees.map((employee, index) => ( 111 |
  • {employee.name}
  • 112 | ))} 113 |
114 |
115 |
116 |
117 | 118 | ); 119 | }; 120 | 121 | export default Dashboard; 122 | -------------------------------------------------------------------------------- /lib/data.ts: -------------------------------------------------------------------------------- 1 | import { EmployeeSchema, DepartmentSchema } from "./types"; 2 | 3 | const baseUrl: string = "https://emma.deno.dev/"; 4 | 5 | async function createEmployee(employee: EmployeeSchema) { 6 | const url = baseUrl + "createEmployee"; 7 | const res = await fetch(url, { 8 | method: "POST", 9 | headers: { 10 | "Content-Type": "application/json", 11 | }, 12 | body: JSON.stringify(employee), 13 | }); 14 | return res.status == 200; 15 | } 16 | 17 | async function createEmployees(employees: EmployeeSchema[]) { 18 | const url = baseUrl + "createEmployees"; 19 | const res = await fetch(url, { 20 | method: "POST", 21 | headers: { 22 | "Content-Type": "application/json", 23 | }, 24 | body: JSON.stringify(employees), 25 | }); 26 | return res.status == 200; 27 | } 28 | 29 | async function getEmployees(): Promise { 30 | const url = baseUrl + "getEmployees"; 31 | const res = await fetch(url); 32 | const employees = await res.json(); 33 | employees.sort((a: any, b: any) => { 34 | if (a.id < b.id) return -1; 35 | if (a.id > b.id) return 1; 36 | return 0; 37 | }); 38 | return employees; 39 | } 40 | 41 | async function updateEmployee(employee: EmployeeSchema) { 42 | const url = baseUrl + "updateEmployee"; 43 | const res = await fetch(url, { 44 | method: "PUT", 45 | headers: { 46 | "Content-Type": "application/json", 47 | }, 48 | body: JSON.stringify(employee), 49 | }); 50 | return res.status == 200; 51 | } 52 | 53 | async function deleteEmployee(id: number) { 54 | const url = baseUrl + "deleteEmployee"; 55 | const res = await fetch(url, { 56 | method: "DELETE", 57 | headers: { 58 | "Content-Type": "application/json", 59 | }, 60 | body: JSON.stringify({ id }), 61 | }); 62 | return res.status == 200; 63 | } 64 | 65 | async function createDepartment(department: DepartmentSchema) { 66 | const url = baseUrl + "createDepartment"; 67 | const res = await fetch(url, { 68 | method: "POST", 69 | headers: { 70 | "Content-Type": "application/json", 71 | }, 72 | body: JSON.stringify(department), 73 | }); 74 | return res.json(); 75 | } 76 | 77 | async function getDepartments() { 78 | const url = baseUrl + "getDepartments"; 79 | const res = await fetch(url); 80 | return res.json(); 81 | } 82 | 83 | async function updateDepartment(department: DepartmentSchema) { 84 | const url = baseUrl + "updateDepartment"; 85 | const res = await fetch(url, { 86 | method: "PUT", 87 | headers: { 88 | "Content-Type": "application/json", 89 | }, 90 | body: JSON.stringify(department), 91 | }); 92 | return res.status == 200 93 | } 94 | 95 | async function deleteDepartment(departmentId: number) { 96 | const url = baseUrl + "deleteDepartment"; 97 | const res = await fetch(url, { 98 | method: "DELETE", 99 | body: JSON.stringify({ departmentId }), 100 | }); 101 | return res.status == 200; 102 | } 103 | 104 | async function getDepartmentFromId(departmentId: number) { 105 | const departments = await getDepartments(); 106 | const matches = departments?.find( 107 | (department: DepartmentSchema) => department.departmentId === departmentId 108 | ); 109 | return matches?.departmentName; 110 | } 111 | 112 | async function getIdFromDepartment(departmentName: string) { 113 | const departments = await getDepartments(); 114 | const targetDepartment = departments?.find( 115 | (department: DepartmentSchema) => 116 | department.departmentName == departmentName 117 | ); 118 | return targetDepartment?.departmentId; 119 | } 120 | 121 | async function deleteEmployeesInDepartment(departmentId: number) { 122 | const url = baseUrl + "deleteEmployeesInDepartment"; 123 | const res = await fetch(url, { 124 | method: "DELETE", 125 | body: JSON.stringify({ departmentId }), 126 | }); 127 | return res.status == 200; 128 | } 129 | 130 | async function getPromotionList(){ 131 | const url = baseUrl + 'promotable'; 132 | const res = await fetch(url); 133 | return res.json(); 134 | } 135 | export { 136 | createEmployee, 137 | createEmployees, 138 | updateEmployee, 139 | getEmployees, 140 | deleteEmployee, 141 | createDepartment, 142 | getDepartments, 143 | updateDepartment, 144 | deleteDepartment, 145 | getDepartmentFromId, 146 | getIdFromDepartment, 147 | getPromotionList, 148 | deleteEmployeesInDepartment 149 | }; 150 | -------------------------------------------------------------------------------- /components/ui/dialog.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import * as DialogPrimitive from "@radix-ui/react-dialog"; 5 | import { X } from "lucide-react"; 6 | 7 | import { cn } from "@/lib/utils"; 8 | 9 | const Dialog = DialogPrimitive.Root; 10 | 11 | const DialogTrigger = DialogPrimitive.Trigger; 12 | 13 | const DialogPortal = DialogPrimitive.Portal; 14 | 15 | const DialogClose = DialogPrimitive.Close; 16 | 17 | const DialogOverlay = React.forwardRef< 18 | React.ElementRef, 19 | React.ComponentPropsWithoutRef 20 | >(({ className, ...props }, ref) => ( 21 | 29 | )); 30 | DialogOverlay.displayName = DialogPrimitive.Overlay.displayName; 31 | 32 | const DialogContent = React.forwardRef< 33 | React.ElementRef, 34 | React.ComponentPropsWithoutRef 35 | >(({ className, children, ...props }, ref) => ( 36 | 37 | 38 | 46 | {children} 47 | 48 | 49 | Close 50 | 51 | 52 | 53 | )); 54 | DialogContent.displayName = DialogPrimitive.Content.displayName; 55 | 56 | const DialogHeader = ({ 57 | className, 58 | ...props 59 | }: React.HTMLAttributes) => ( 60 |
67 | ); 68 | DialogHeader.displayName = "DialogHeader"; 69 | 70 | const DialogFooter = ({ 71 | className, 72 | ...props 73 | }: React.HTMLAttributes) => ( 74 |
81 | ); 82 | DialogFooter.displayName = "DialogFooter"; 83 | 84 | const DialogTitle = React.forwardRef< 85 | React.ElementRef, 86 | React.ComponentPropsWithoutRef 87 | >(({ className, ...props }, ref) => ( 88 | 96 | )); 97 | DialogTitle.displayName = DialogPrimitive.Title.displayName; 98 | 99 | const DialogDescription = React.forwardRef< 100 | React.ElementRef, 101 | React.ComponentPropsWithoutRef 102 | >(({ className, ...props }, ref) => ( 103 | 108 | )); 109 | DialogDescription.displayName = DialogPrimitive.Description.displayName; 110 | 111 | export { 112 | Dialog, 113 | DialogPortal, 114 | DialogOverlay, 115 | DialogClose, 116 | DialogTrigger, 117 | DialogContent, 118 | DialogHeader, 119 | DialogFooter, 120 | DialogTitle, 121 | DialogDescription, 122 | }; 123 | -------------------------------------------------------------------------------- /components/AnalyticsChart.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import React, { useEffect, useRef } from 'react'; 3 | import Chart from 'chart.js/auto'; 4 | import { EmployeeSchema, DepartmentSchema } from '../lib/types'; 5 | 6 | const AnalyticsCharts = ({ employees, departments }: { employees: EmployeeSchema[], departments: DepartmentSchema[] }) => { 7 | const barChartRef = useRef(null); 8 | const pieChartRef = useRef(null); 9 | const lineChartRef = useRef(null); 10 | const scatterPlotRef = useRef(null); 11 | 12 | useEffect(() => { 13 | if (employees.length === 0 || departments.length === 0) return; 14 | 15 | const joinTrend: { [key: number]: number } = {}; 16 | const scatterData: any[] = []; 17 | const departmentEmployeeCount: { [key: string]: number } = {}; 18 | const experienceDistribution: { [key: number]: number } = {}; 19 | 20 | employees.forEach(employee => { 21 | const experience = Math.floor(employee.yearsOfExperience / 5) * 5; 22 | experienceDistribution[experience] = (experienceDistribution[experience] || 0) + 1; 23 | 24 | const departmentName = departments.find(dep => dep.departmentId === employee.departmentId)?.departmentName || 'Unknown'; 25 | console.log('Employee ID:', employee.id, 'Department Name:', departmentName); 26 | departmentEmployeeCount[departmentName] = (departmentEmployeeCount[departmentName] || 0) + 1; 27 | scatterData.push({ x: employee.yearsOfExperience, y: departmentName }); 28 | 29 | const joinYear = new Date(employee.dateOfJoin).getFullYear(); 30 | joinTrend[joinYear] = (joinTrend[joinYear] || 0) + 1; 31 | }); 32 | 33 | renderChart(barChartRef, 'bar', Object.keys(departmentEmployeeCount), Object.values(departmentEmployeeCount), 'Employee Count per Department'); 34 | renderChart(pieChartRef, 'pie', Object.keys(experienceDistribution), Object.values(experienceDistribution), 'Distribution of Employees by Years of Experience'); 35 | renderChart(lineChartRef, 'line', Object.keys(joinTrend), Object.values(joinTrend), 'Trend of Employee Joining Over Time'); 36 | renderChart(scatterPlotRef, 'scatter', scatterData.map(({ x, y }) => ({ x, y })), null, 'Relationship Between Years of Experience and Department'); 37 | 38 | return () => { 39 | destroyChart(barChartRef); 40 | destroyChart(pieChartRef); 41 | destroyChart(lineChartRef); 42 | destroyChart(scatterPlotRef); 43 | }; 44 | }, [employees, departments]); 45 | 46 | const renderChart = (ref: any, type: any, labels: any, data: any, title: string) => { 47 | if (!ref.current) return; 48 | 49 | const ctx = ref.current.getContext('2d'); 50 | if (!ctx) return; 51 | 52 | if (ref.current.chart) { 53 | ref.current.chart.destroy(); 54 | } 55 | 56 | const newChart = new Chart(ctx, { 57 | type: type, 58 | data: { 59 | labels: labels, 60 | datasets: [{ 61 | label: title, 62 | data: data, 63 | backgroundColor: type === 'bar' ? 'rgba(54, 162, 235, 0.5)' : type === 'pie' ? ['rgba(255, 99, 132, 0.5)', 'rgba(54, 162, 235, 0.5)', 'rgba(255, 205, 86, 0.5)', 'rgba(75, 192, 192, 0.5)', 'rgba(153, 102, 255, 0.5)'] : 'rgba(255, 99, 132, 0.5)', 64 | borderColor: type === 'bar' ? 'rgba(54, 162, 235, 1)' : type === 'pie' ? ['rgba(255, 99, 132, 1)', 'rgba(54, 162, 235, 1)', 'rgba(255, 205, 86, 1)', 'rgba(75, 192, 192, 1)', 'rgba(153, 102, 255, 1)'] : 'rgba(255, 99, 132, 1)', 65 | borderWidth: 1, 66 | fill: type === 'line' ? false : type === 'scatter' ? true : undefined, 67 | tension: type === 'line' ? 0.1 : undefined, 68 | pointRadius: type === 'scatter' ? 5 : undefined 69 | }] 70 | }, 71 | options: { 72 | responsive: true, 73 | scales: { 74 | y: { 75 | beginAtZero: true 76 | } 77 | }, 78 | plugins: { 79 | legend: { 80 | display: type === 'scatter' ? false : true, 81 | position: type === 'scatter' ? 'none' : 'top', 82 | }, 83 | title: { 84 | display: true, 85 | text: title 86 | } 87 | } 88 | } 89 | }); 90 | 91 | // Store the chart instance in the ref 92 | ref.current.chart = newChart; 93 | }; 94 | 95 | const destroyChart = (ref: any) => { 96 | if (ref.current && ref.current.chart) { 97 | ref.current.chart.destroy(); 98 | } 99 | }; 100 | 101 | return ( 102 |
103 | 104 | 105 | 106 | 107 |
108 | ); 109 | }; 110 | 111 | export default AnalyticsCharts; -------------------------------------------------------------------------------- /components/ui/alert-dialog.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"; 5 | 6 | import { cn } from "@/lib/utils"; 7 | import { buttonVariants } from "@/components/ui/button"; 8 | 9 | const AlertDialog = AlertDialogPrimitive.Root; 10 | 11 | const AlertDialogTrigger = AlertDialogPrimitive.Trigger; 12 | 13 | const AlertDialogPortal = AlertDialogPrimitive.Portal; 14 | 15 | const AlertDialogOverlay = React.forwardRef< 16 | React.ElementRef, 17 | React.ComponentPropsWithoutRef 18 | >(({ className, ...props }, ref) => ( 19 | 27 | )); 28 | AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName; 29 | 30 | const AlertDialogContent = React.forwardRef< 31 | React.ElementRef, 32 | React.ComponentPropsWithoutRef 33 | >(({ className, ...props }, ref) => ( 34 | 35 | 36 | 44 | 45 | )); 46 | AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName; 47 | 48 | const AlertDialogHeader = ({ 49 | className, 50 | ...props 51 | }: React.HTMLAttributes) => ( 52 |
59 | ); 60 | AlertDialogHeader.displayName = "AlertDialogHeader"; 61 | 62 | const AlertDialogFooter = ({ 63 | className, 64 | ...props 65 | }: React.HTMLAttributes) => ( 66 |
73 | ); 74 | AlertDialogFooter.displayName = "AlertDialogFooter"; 75 | 76 | const AlertDialogTitle = React.forwardRef< 77 | React.ElementRef, 78 | React.ComponentPropsWithoutRef 79 | >(({ className, ...props }, ref) => ( 80 | 85 | )); 86 | AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName; 87 | 88 | const AlertDialogDescription = React.forwardRef< 89 | React.ElementRef, 90 | React.ComponentPropsWithoutRef 91 | >(({ className, ...props }, ref) => ( 92 | 97 | )); 98 | AlertDialogDescription.displayName = 99 | AlertDialogPrimitive.Description.displayName; 100 | 101 | const AlertDialogAction = React.forwardRef< 102 | React.ElementRef, 103 | React.ComponentPropsWithoutRef 104 | >(({ className, ...props }, ref) => ( 105 | 110 | )); 111 | AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName; 112 | 113 | const AlertDialogCancel = React.forwardRef< 114 | React.ElementRef, 115 | React.ComponentPropsWithoutRef 116 | >(({ className, ...props }, ref) => ( 117 | 126 | )); 127 | AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName; 128 | 129 | export { 130 | AlertDialog, 131 | AlertDialogPortal, 132 | AlertDialogOverlay, 133 | AlertDialogTrigger, 134 | AlertDialogContent, 135 | AlertDialogHeader, 136 | AlertDialogFooter, 137 | AlertDialogTitle, 138 | AlertDialogDescription, 139 | AlertDialogAction, 140 | AlertDialogCancel, 141 | }; 142 | -------------------------------------------------------------------------------- /components/AddDepartment.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "@/components/ui/button"; 2 | import { 3 | Dialog, 4 | DialogContent, 5 | DialogDescription, 6 | DialogFooter, 7 | DialogHeader, 8 | DialogTitle, 9 | DialogTrigger, 10 | } from "@/components/ui/dialog"; 11 | import { 12 | Select, 13 | SelectContent, 14 | SelectGroup, 15 | SelectItem, 16 | SelectLabel, 17 | SelectTrigger, 18 | SelectValue, 19 | } from "@/components/ui/select"; 20 | import { Input } from "@/components/ui/input"; 21 | import { Label } from "@/components/ui/label"; 22 | import { useEffect, useState } from "react"; 23 | import { 24 | createDepartment, 25 | createEmployee, 26 | getDepartmentFromId, 27 | getIdFromDepartment, 28 | } from "@/lib/data"; 29 | import { DepartmentSchema, EmployeeSchema } from "@/lib/types"; 30 | 31 | export default function AddEmployee() { 32 | const [departmentName, setDepartmentName] = useState(""); 33 | const [departmentId, setDepartmentId] = useState(0); 34 | const [managerId, setManagerId] = useState(0); 35 | const [location, setLocation] = useState(""); 36 | 37 | const addDepartment = async (e: any) => { 38 | e.preventDefault(); 39 | const departmentData = { 40 | departmentName: departmentName, 41 | departmentId: departmentId, 42 | managerId: managerId, 43 | location: location, 44 | }; 45 | console.log("Department Data:", departmentData); 46 | const res = await createDepartment(departmentData as DepartmentSchema); 47 | if (res) { 48 | clearStates(); 49 | } else { 50 | console.log("Error adding Department"); 51 | } 52 | }; 53 | 54 | const clearStates = () => { 55 | setDepartmentName(""); 56 | setDepartmentId(0); 57 | setManagerId(0); 58 | setLocation(""); 59 | }; 60 | 61 | // const getId = async (department: string) => { 62 | // console.log("Get ID : ", department); 63 | // const id = await getIdFromDepartment(department); 64 | // console.log("ID : ", id); 65 | 66 | // return id; 67 | // }; 68 | 69 | // const calculateExperience = (joinDate: string) => { 70 | // const today = new Date(); 71 | // const startDate = new Date(joinDate); 72 | // const experienceInMilliseconds: number = 73 | // today.getDate() - startDate.getDate(); 74 | // const years = experienceInMilliseconds / (1000 * 60 * 60 * 24 * 365); 75 | // return Math.floor(years); 76 | // }; 77 | 78 | // useEffect(() => { 79 | // const fetchData = async () => { 80 | // const department = await getDepartmentFromId(1); 81 | // console.log("Department of 1 is :", department); 82 | // }; 83 | 84 | // fetchData(); 85 | // }); 86 | return ( 87 | 88 | 89 | 90 | 91 | 92 | 93 | Add Department 94 | 95 | Add a new Department to the database. Click Add when you're 96 | done. 97 | 98 | 99 |
100 |
101 | {/* Other input fields */} 102 |
103 | 106 | setDepartmentName(e.target.value)} 110 | className="col-span-3" 111 | /> 112 |
113 |
114 | 117 | setDepartmentId(Number(e.target.value))} 121 | className="col-span-3" 122 | /> 123 |
124 |
125 | 128 | setLocation(e.target.value)} 132 | className="col-span-3" 133 | /> 134 |
135 |
136 | 139 | setManagerId(Number(e.target.value))} 143 | className="col-span-3" 144 | /> 145 |
146 |
147 | 148 | 149 | 150 | 151 |
152 |
153 |
154 | ); 155 | } 156 | -------------------------------------------------------------------------------- /app/employeeLogin/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { useState } from "react"; 3 | 4 | import { MdArrowOutward } from "react-icons/md"; 5 | import { FaEye, FaEyeSlash } from "react-icons/fa"; 6 | import Link from "next/link"; 7 | import Image from "next/image"; 8 | 9 | import { useRouter } from "next/navigation"; 10 | import toast, { Toaster } from "react-hot-toast"; 11 | import loginPic from "@/public/employeeLogin.svg"; 12 | 13 | export default function Page() { 14 | const [error, setError] = useState(""); 15 | const router = useRouter(); 16 | const [email, setEmail] = useState(""); 17 | const [password, setPassword] = useState(""); 18 | const [eyeClick, setEyeClick] = useState(true); 19 | 20 | const handleLogin = async () => { 21 | console.log("Email is " + email); 22 | console.log("Password is " + password); 23 | const loginFailed = () => toast.error("Credentials are incorrect."); 24 | const loginSuccess = () => toast.success("Login Success"); 25 | 26 | try { 27 | if (email === "admin" && password === "password") { 28 | setEmail(""); 29 | setPassword(""); 30 | 31 | loginSuccess(); 32 | router.push("/"); 33 | setError(""); 34 | } else { 35 | setError("Login failed. "); 36 | loginFailed(); 37 | } 38 | } catch (error) { 39 | console.error(error); 40 | } 41 | }; 42 | 43 | return ( 44 |
47 | 54 |
55 |
56 | login 63 |
64 |
65 | 66 |
67 |
68 |
Welcome back
69 |
70 |
71 | {error ? ( 72 |
{error}
73 | ) : ( 74 |
Manage your workspace at ease
75 | )} 76 |
77 |
78 | 81 | { 87 | setEmail(e.target.value); 88 | }} 89 | className="inputField" 90 | /> 91 | 97 |
98 | { 104 | setPassword(e.target.value); 105 | }} 106 | className="inputField " 107 | > 108 | {eyeClick ? ( 109 | { 111 | setEyeClick(!eyeClick); 112 | }} 113 | className="absolute right-0 mr-4 mt-3 text-xl text-gray-950 opacity-50 hover:opacity-100 cursor-pointer" 114 | /> 115 | ) : ( 116 | { 118 | setEyeClick(!eyeClick); 119 | }} 120 | className="absolute right-0 mr-4 mt-3 text-xl text-gray-950 opacity-50 hover:opacity-100 cursor-pointer" 121 | /> 122 | )} 123 |
124 |
125 |
126 | {/* 130 | Forgot Password? 131 | */} 132 | 138 |
139 | 140 |
141 |

Admin?

142 | 145 |
146 | 147 |
148 |
149 |
150 |
151 | ); 152 | } 153 | -------------------------------------------------------------------------------- /app/login/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { useState } from "react"; 3 | 4 | import { MdArrowOutward } from "react-icons/md"; 5 | import { FaEye, FaEyeSlash } from "react-icons/fa"; 6 | import Link from "next/link"; 7 | import Image from "next/image"; 8 | 9 | import { useRouter } from "next/navigation"; 10 | import toast, { Toaster } from "react-hot-toast"; 11 | import loginPic from "@/public/loginPic.png"; 12 | 13 | export default function Page() { 14 | const [error, setError] = useState(""); 15 | const router = useRouter(); 16 | const [email, setEmail] = useState(""); 17 | const [password, setPassword] = useState(""); 18 | const [eyeClick, setEyeClick] = useState(true); 19 | 20 | const handleLogin = async () => { 21 | console.log("Email is " + email); 22 | console.log("Password is " + password); 23 | const loginFailed = () => toast.error("Credentials are incorrect."); 24 | const loginSuccess = () => toast.success("Login Success"); 25 | 26 | try { 27 | if (email === "admin" && password === "password") { 28 | setEmail(""); 29 | setPassword(""); 30 | 31 | loginSuccess(); 32 | router.push("/"); 33 | setError(""); 34 | } else { 35 | setError("Login failed. "); 36 | loginFailed(); 37 | } 38 | } catch (error) { 39 | console.error(error); 40 | } 41 | }; 42 | 43 | return ( 44 |
47 | 54 |
55 |
56 | login 63 |
64 |
65 | 66 |
67 |
68 |
69 | Employee Management System 70 |
71 |
72 |
73 | {error ? ( 74 |
{error}
75 | ) : ( 76 |
Manage all your Employees at ease.
77 | )} 78 |
79 |
80 | 83 | { 89 | setEmail(e.target.value); 90 | }} 91 | className="inputField" 92 | /> 93 | 99 |
100 | { 106 | setPassword(e.target.value); 107 | }} 108 | className="inputField " 109 | > 110 | {eyeClick ? ( 111 | { 113 | setEyeClick(!eyeClick); 114 | }} 115 | className="absolute right-0 mr-4 mt-3 text-xl text-gray-950 opacity-50 hover:opacity-100 cursor-pointer" 116 | /> 117 | ) : ( 118 | { 120 | setEyeClick(!eyeClick); 121 | }} 122 | className="absolute right-0 mr-4 mt-3 text-xl text-gray-950 opacity-50 hover:opacity-100 cursor-pointer" 123 | /> 124 | )} 125 |
126 |
127 |
128 | {/* 132 | Forgot Password? 133 | */} 134 | 140 |
141 | 142 |
143 |

Employee?

144 | 147 |
148 | 149 |
150 |
151 |
152 |
153 | ); 154 | } 155 | -------------------------------------------------------------------------------- /components/ui/select.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import * as SelectPrimitive from "@radix-ui/react-select"; 5 | import { Check, ChevronDown, ChevronUp } from "lucide-react"; 6 | 7 | import { cn } from "@/lib/utils"; 8 | 9 | const Select = SelectPrimitive.Root; 10 | 11 | const SelectGroup = SelectPrimitive.Group; 12 | 13 | const SelectValue = SelectPrimitive.Value; 14 | 15 | const SelectTrigger = React.forwardRef< 16 | React.ElementRef, 17 | React.ComponentPropsWithoutRef 18 | >(({ className, children, ...props }, ref) => ( 19 | span]:line-clamp-1", 23 | className, 24 | )} 25 | {...props} 26 | > 27 | {children} 28 | 29 | 30 | 31 | 32 | )); 33 | SelectTrigger.displayName = SelectPrimitive.Trigger.displayName; 34 | 35 | const SelectScrollUpButton = React.forwardRef< 36 | React.ElementRef, 37 | React.ComponentPropsWithoutRef 38 | >(({ className, ...props }, ref) => ( 39 | 47 | 48 | 49 | )); 50 | SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName; 51 | 52 | const SelectScrollDownButton = React.forwardRef< 53 | React.ElementRef, 54 | React.ComponentPropsWithoutRef 55 | >(({ className, ...props }, ref) => ( 56 | 64 | 65 | 66 | )); 67 | SelectScrollDownButton.displayName = 68 | SelectPrimitive.ScrollDownButton.displayName; 69 | 70 | const SelectContent = React.forwardRef< 71 | React.ElementRef, 72 | React.ComponentPropsWithoutRef 73 | >(({ className, children, position = "popper", ...props }, ref) => ( 74 | 75 | 86 | 87 | 94 | {children} 95 | 96 | 97 | 98 | 99 | )); 100 | SelectContent.displayName = SelectPrimitive.Content.displayName; 101 | 102 | const SelectLabel = React.forwardRef< 103 | React.ElementRef, 104 | React.ComponentPropsWithoutRef 105 | >(({ className, ...props }, ref) => ( 106 | 111 | )); 112 | SelectLabel.displayName = SelectPrimitive.Label.displayName; 113 | 114 | const SelectItem = React.forwardRef< 115 | React.ElementRef, 116 | React.ComponentPropsWithoutRef 117 | >(({ className, children, ...props }, ref) => ( 118 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | {children} 133 | 134 | )); 135 | SelectItem.displayName = SelectPrimitive.Item.displayName; 136 | 137 | const SelectSeparator = React.forwardRef< 138 | React.ElementRef, 139 | React.ComponentPropsWithoutRef 140 | >(({ className, ...props }, ref) => ( 141 | 146 | )); 147 | SelectSeparator.displayName = SelectPrimitive.Separator.displayName; 148 | 149 | export { 150 | Select, 151 | SelectGroup, 152 | SelectValue, 153 | SelectTrigger, 154 | SelectContent, 155 | SelectLabel, 156 | SelectItem, 157 | SelectSeparator, 158 | SelectScrollUpButton, 159 | SelectScrollDownButton, 160 | }; 161 | -------------------------------------------------------------------------------- /components/AddEmployee.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "@/components/ui/button"; 2 | import { 3 | Dialog, 4 | DialogContent, 5 | DialogDescription, 6 | DialogFooter, 7 | DialogHeader, 8 | DialogTitle, 9 | DialogTrigger, 10 | } from "@/components/ui/dialog"; 11 | import { 12 | Select, 13 | SelectContent, 14 | SelectGroup, 15 | SelectItem, 16 | SelectLabel, 17 | SelectTrigger, 18 | SelectValue, 19 | } from "@/components/ui/select"; 20 | import { Input } from "@/components/ui/input"; 21 | import { Label } from "@/components/ui/label"; 22 | import { useEffect, useState } from "react"; 23 | import { 24 | createEmployee, 25 | getDepartmentFromId, 26 | getIdFromDepartment, 27 | } from "@/lib/data"; 28 | import { EmployeeSchema } from "@/lib/types"; 29 | 30 | export function AddEmployee() { 31 | const [employeeName, setEmployeeName] = useState(""); 32 | const [employeeId, setEmployeeID] = useState(); 33 | const [employeeEmail, setEmployeeEmail] = useState(""); 34 | const [employeeDepartment, setEmployeeDepartment] = useState(""); 35 | const [employeeContact, setEmployeeContact] = useState(""); 36 | const [joiningDate, setJoiningDate] = useState(""); 37 | 38 | const addEmployee = async (e: any) => { 39 | e.preventDefault(); 40 | const employeeData = { 41 | id: employeeId, 42 | name: employeeName, 43 | email: employeeEmail, 44 | contactNumber: employeeContact, 45 | dateOfJoin: new Date(joiningDate).toISOString(), 46 | yearsOfExperience: calculateExperience(joiningDate), 47 | departmentId: await getId(employeeDepartment), 48 | }; 49 | console.log("Employee Data:", employeeData); 50 | const res = await createEmployee(employeeData as EmployeeSchema); 51 | if (res) { 52 | clearStates(); 53 | } 54 | }; 55 | 56 | const clearStates = () => { 57 | setEmployeeName(""); 58 | setEmployeeID(0); 59 | setEmployeeEmail(""); 60 | setEmployeeContact(""); 61 | setEmployeeDepartment(""); 62 | setJoiningDate(""); 63 | }; 64 | 65 | const getId = async (department: string) => { 66 | console.log("Get ID : ", department); 67 | const id = await getIdFromDepartment(department); 68 | console.log("ID : ", id); 69 | 70 | return id; 71 | }; 72 | 73 | const calculateExperience = (joinDate: string) => { 74 | const today = new Date(); 75 | const startDate = new Date(joinDate); 76 | const experienceInMilliseconds: number = 77 | today.getDate() - startDate.getDate(); 78 | const years = experienceInMilliseconds / (1000 * 60 * 60 * 24 * 365); 79 | return Math.floor(years); 80 | }; 81 | 82 | useEffect(() => { 83 | const fetchData = async () => { 84 | const department = await getDepartmentFromId(1); 85 | console.log("Department of 1 is :", department); 86 | }; 87 | 88 | fetchData(); 89 | }); 90 | return ( 91 | 92 | 93 | 94 | 95 | 96 | 97 | Add Employee 98 | 99 | Make changes to your profile here. Click save when you're done. 100 | 101 | 102 |
103 |
104 | {/* Other input fields */} 105 |
106 | 109 | setEmployeeID(parseInt(e.target.value))} 113 | className="col-span-3" 114 | /> 115 |
116 |
117 | 120 | setEmployeeName(e.target.value)} 124 | className="col-span-3" 125 | /> 126 |
127 |
128 | 131 | setEmployeeEmail(e.target.value)} 135 | className="col-span-3" 136 | /> 137 |
138 |
139 | 142 | setEmployeeContact(e.target.value)} 146 | className="col-span-3" 147 | /> 148 |
149 |
150 | 153 | setJoiningDate(e.target.value)} 158 | className="col-span-3" 159 | /> 160 |
161 |
162 | 165 | 190 |
191 |
192 | 193 | 194 | 195 |
196 |
197 |
198 | ); 199 | } 200 | -------------------------------------------------------------------------------- /components/DepartmentList.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import { deleteDepartment, deleteEmployeesInDepartment, getDepartments, updateDepartment } from '@/lib/data'; 3 | import { DepartmentSchema, EmployeeSchema } from '@/lib/types'; 4 | import React, { useEffect, useState } from 'react'; 5 | import { 6 | Select, 7 | SelectContent, 8 | SelectGroup, 9 | SelectItem, 10 | SelectLabel, 11 | SelectTrigger, 12 | SelectValue, 13 | } from "@/components/ui/select"; 14 | import { 15 | Dialog, 16 | DialogContent, 17 | DialogDescription, 18 | DialogFooter, 19 | DialogHeader, 20 | DialogTitle, 21 | DialogTrigger, 22 | } from "@/components/ui/dialog"; 23 | import { Input } from "@/components/ui/input"; 24 | import { Label } from "@/components/ui/label"; 25 | import { Button } from "@/components/ui/button"; 26 | 27 | import { 28 | Table, 29 | TableBody, 30 | TableCaption, 31 | TableCell, 32 | TableHead, 33 | TableHeader, 34 | TableRow, 35 | } from "@/components/ui/table"; 36 | import { 37 | AlertDialog, 38 | AlertDialogAction, 39 | AlertDialogCancel, 40 | AlertDialogContent, 41 | AlertDialogDescription, 42 | AlertDialogFooter, 43 | AlertDialogHeader, 44 | AlertDialogTitle, 45 | } from "@/components/ui/alert-dialog"; 46 | import { FiEdit3 } from 'react-icons/fi'; 47 | import { MdOutlineDelete } from 'react-icons/md'; 48 | import { FaAngleLeft, FaAngleRight } from 'react-icons/fa6'; 49 | const DepartmentList = () => { 50 | const [departments, setDepartments] = useState([]); 51 | const [toDelete, setToDelete] = useState(false); 52 | const [toEdit,setToEdit] = useState(false); 53 | const [departmentToEdit,setDepartmentToEdit] = useState(null); 54 | const [currentPage, setCurrentPage] = useState(1); 55 | const [rowsPerPage, setRowsPerPage] = useState(10); 56 | const [departmentToDelete, setDepartmentToDelete] = 57 | useState(null); 58 | 59 | const [departmentName,setDepartmentName] = useState(""); 60 | const [departmentId,setDepartmentId] = useState(0); 61 | const [managerId,setManagerId] = useState(0); 62 | const [location,setLocation] = useState(""); 63 | 64 | 65 | 66 | useEffect(() => { 67 | const fetchDepartments = async () => { 68 | try { 69 | 70 | const fetchedDepartments = await getDepartments(); 71 | 72 | setDepartments(fetchedDepartments); 73 | } catch (error) { 74 | console.error('Error fetching departments:', error); 75 | } 76 | }; 77 | 78 | fetchDepartments(); 79 | }, []) 80 | 81 | const handlePageChange = (pageNumber: number) => { 82 | setCurrentPage((prevPage) => Math.max(1, prevPage + pageNumber)); 83 | }; 84 | 85 | const handleDelete = (department : DepartmentSchema) =>{ 86 | 87 | setToDelete(true); 88 | setDepartmentToDelete(department); 89 | 90 | } 91 | 92 | const handleEdit = (department : DepartmentSchema)=>{ 93 | console.log("Department to be edited is :",department); 94 | } 95 | 96 | const deleteSelectedDepartment = async( department : DepartmentSchema)=>{ 97 | console.log("Department to be deleted is : ",department); 98 | const res = await deleteDepartment(department.departmentId); 99 | if (res) { 100 | console.log("Department Deleted"); 101 | } else { 102 | console.error("Error Deleting Department"); 103 | } 104 | const result = await deleteEmployeesInDepartment(department.departmentId); 105 | if(result){ 106 | console.log("Participants from the department also deleted"); 107 | }else{ 108 | console.log("Uanable to delete Participants"); 109 | } 110 | setToDelete(false); 111 | 112 | } 113 | 114 | const clearStates = () => { 115 | setDepartmentId(0); 116 | setDepartmentName(""); 117 | setLocation(""); 118 | setManagerId(0); 119 | }; 120 | const updateSelectedEmployee= async (e:any)=>{ 121 | const departmentData : DepartmentSchema ={ 122 | departmentId : departmentId, 123 | departmentName : departmentName, 124 | location : location, 125 | managerId : managerId 126 | } 127 | 128 | const res = await updateDepartment(departmentData as DepartmentSchema ); 129 | if (res) { 130 | console.log("Department Updated"); 131 | 132 | clearStates(); 133 | setToEdit(false); 134 | }else{ 135 | console.log("Department not Updated"); 136 | } 137 | 138 | } 139 | return ( 140 | <> 141 | 142 | 143 | {toDelete && departmentToDelete && ( 144 | 145 | {/* onDismiss={() => setToDelete(false)}> */} 146 | 147 | 148 | Are you absolutely sure? 149 | 150 |
151 | {" "} 152 | Department Name : 153 | 154 | {departmentToDelete.departmentName} 155 | {" "} 156 |
157 | Department Id : 158 | 159 | {departmentToDelete.departmentId} 160 | {" "} 161 |
162 |
163 | This action cannot be undone. This will permanently delete the 164 | Department and remove the Department data from the servers. 165 |
166 |
167 | 168 | setToDelete(false)}> 169 | Cancel 170 | 171 | { 173 | await deleteSelectedDepartment(departmentToDelete); 174 | setToDelete(false); 175 | }} 176 | > 177 | Delete 178 | 179 | 180 |
181 |
182 | )} 183 | 184 | {toEdit && departmentToEdit && ( 185 | 186 | {/* onDismiss={() => setToEdit(false)} > */} 187 | 188 | 189 | 190 | Update Employee 191 | 192 | Make changes to the Employee Data here. Click update when done. 193 | 194 | 195 |
196 |
197 | {/* Other input fields */} 198 |
199 | 202 | setDepartmentId(parseInt(e.target.value))} 206 | className="col-span-3" 207 | /> 208 |
209 |
210 | 213 | setDepartmentName(e.target.value)} 217 | className="col-span-3" 218 | /> 219 |
220 |
221 | 224 | setLocation(e.target.value)} 228 | className="col-span-3" 229 | /> 230 |
231 |
232 | 235 | setManagerId(parseInt((e.target.value)))} 239 | className="col-span-3" 240 | /> 241 |
242 |
243 | 244 | 245 | 246 |
247 |
248 |
249 | )} 250 | 251 | 252 | 253 | 254 | Department ID 255 | Department Name 256 | Department Location 257 | Manager Id 258 | Edit/Delete 259 | 260 | 261 | 262 | {departments.map((department, index) => ( 263 | 264 | {department.departmentId} 265 | {department.departmentName} 266 | {department.location} 267 | 268 | {department.managerId} 269 | 270 | 271 |
272 |
handleEdit(department)} 274 | className="border p-1 rounded-sm hover:bg-slate-100 transition-all duration-300 ease-in-out cursor-pointer"> 275 | 276 |
277 |
handleDelete(department)} 279 | className="border p-1 rounded-sm hover:bg-slate-100 transition-all duration-300 ease-in-out cursor-pointer" 280 | > 281 | 282 |
283 |
284 |
285 |
286 | ))} 287 |
288 |
289 |
handlePageChange(-1)} 291 | className="border p-1 rounded-sm hover:bg-slate-100 transition-all duration-300 ease-in-out cursor-pointer" 292 | > 293 | 294 |
295 |
handlePageChange(1)} 297 | className="border p-1 rounded-sm hover:bg-slate-100 transition-all duration-300 ease-in-out cursor-pointer" 298 | > 299 | 300 |
301 |
302 |
303 | 304 | ); 305 | }; 306 | 307 | export default DepartmentList; 308 | -------------------------------------------------------------------------------- /components/EmployeeList.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState, useRef } from "react"; 2 | import { 3 | Table, 4 | TableBody, 5 | TableCaption, 6 | TableCell, 7 | TableHead, 8 | TableHeader, 9 | TableRow, 10 | } from "@/components/ui/table"; 11 | import { Button } from "@/components/ui/button"; 12 | import { 13 | Dialog, 14 | DialogContent, 15 | DialogDescription, 16 | DialogFooter, 17 | DialogHeader, 18 | DialogTitle, 19 | DialogTrigger, 20 | } from "@/components/ui/dialog"; 21 | import { 22 | Select, 23 | SelectContent, 24 | SelectGroup, 25 | SelectItem, 26 | SelectLabel, 27 | SelectTrigger, 28 | SelectValue, 29 | } from "@/components/ui/select"; 30 | import { Input } from "@/components/ui/input"; 31 | import { Label } from "@/components/ui/label"; 32 | import { 33 | AlertDialog, 34 | AlertDialogAction, 35 | AlertDialogCancel, 36 | AlertDialogContent, 37 | AlertDialogDescription, 38 | AlertDialogFooter, 39 | AlertDialogHeader, 40 | AlertDialogTitle, 41 | } from "@/components/ui/alert-dialog"; 42 | import { FiEdit3 } from "react-icons/fi"; 43 | import { MdOutlineDelete } from "react-icons/md"; 44 | import { deleteEmployee, getDepartmentFromId, getEmployees, getIdFromDepartment, updateEmployee } from "@/lib/data"; 45 | import { EmployeeSchema } from "@/lib/types"; 46 | import { FaAngleLeft, FaAngleRight } from "react-icons/fa6"; 47 | 48 | const EmployeeList = () => { 49 | const [employees, setEmployees] = useState([]); 50 | const [currentPage, setCurrentPage] = useState(1); 51 | const [rowsPerPage, setRowsPerPage] = useState(10); 52 | const [toDelete, setToDelete] = useState(false); 53 | const [toEdit,setToEdit] = useState(false); 54 | const [employeeToEdit,setEmployeeToEdit] = useState(null); 55 | const [employeeToDelete, setEmployeeToDelete] = 56 | useState(null); 57 | const [employeeName, setEmployeeName] = useState(""); 58 | const [employeeId, setEmployeeID] = useState(0); 59 | const [employeeEmail, setEmployeeEmail] = useState(""); 60 | const [employeeDepartment, setEmployeeDepartment] = useState(""); 61 | const [employeeContact, setEmployeeContact] = useState(""); 62 | const [joiningDate, setJoiningDate] = useState(""); 63 | 64 | 65 | useEffect(() => { 66 | let isMounted = true; 67 | 68 | const fetchData = async () => { 69 | try { 70 | const fetchedEmployees: EmployeeSchema[] = await getEmployees(); 71 | 72 | if (isMounted) { 73 | const updatedEmployees = await Promise.all( 74 | fetchedEmployees.map(async (employee) => { 75 | const department = await getDepartmentFromId(employee.departmentId); 76 | return { ...employee, department }; 77 | }) 78 | ); 79 | setEmployees(updatedEmployees); 80 | } 81 | 82 | console.log("Employees:", fetchedEmployees); 83 | } catch (error) { 84 | console.error("Error fetching employees:", error); 85 | } 86 | }; 87 | 88 | fetchData(); 89 | 90 | return () => { 91 | isMounted = false; 92 | }; 93 | }, []); 94 | 95 | const handlePageChange = (pageNumber: number) => { 96 | setCurrentPage((prevPage) => Math.max(1, prevPage + pageNumber)); 97 | }; 98 | 99 | const deleteSelectedEmployee = async (employee: EmployeeSchema) => { 100 | console.log("Employee to be deleted is :", employee); 101 | const res = await deleteEmployee(Number(employee?.id)); 102 | if (res) { 103 | console.log("Employee Deleted"); 104 | } else { 105 | console.error("Error Deleting Employee"); 106 | } 107 | setToDelete(false); 108 | }; 109 | 110 | const displayedEmployees = employees.slice( 111 | (currentPage - 1) * rowsPerPage, 112 | currentPage * rowsPerPage, 113 | ); 114 | const handleDelete = (employee: EmployeeSchema) => { 115 | setToDelete(true); 116 | setEmployeeToDelete(employee); 117 | }; 118 | 119 | const handleEdit = async(employee:EmployeeSchema) =>{ 120 | setToEdit(true); 121 | setEmployeeToEdit(employee); 122 | setEmployeeName(employee.name); 123 | setEmployeeID(employee.id); 124 | setEmployeeEmail(employee.email); 125 | setEmployeeContact(employee.contactNumber); 126 | setEmployeeDepartment(await getDepartment(employee.departmentId)); 127 | // setJoiningDate(employee.dateOfJoin.toLocaleDateString('en-GB', { year: 'numeric', month: '2-digit', day: '2-digit' })); 128 | const dateOfJoin = new Date(employee.dateOfJoin); 129 | const formattedDateString = dateOfJoin.toLocaleDateString('en-GB', { year: 'numeric', month: '2-digit', day: '2-digit' }); 130 | 131 | const parts = formattedDateString.split("/"); 132 | const formattedDate = `${parts[2]}-${parts[1]}-${parts[0]}`; 133 | setJoiningDate(formattedDate); 134 | } 135 | 136 | const getDepartment = async (id: number) => { 137 | console.log("Get ID : ", id); 138 | const department = await getDepartmentFromId(id); 139 | console.log("ID : ", id); 140 | 141 | return department; 142 | }; 143 | const clearStates = () => { 144 | setEmployeeName(""); 145 | setEmployeeID(0); 146 | setEmployeeEmail(""); 147 | setEmployeeContact(""); 148 | setEmployeeDepartment(""); 149 | setJoiningDate(""); 150 | }; 151 | const updateSelectedEmployee =async (e:any)=>{ 152 | e.preventDefault(); 153 | const employeeData: EmployeeSchema = { 154 | id: employeeId, 155 | name: employeeName, 156 | email: employeeEmail, 157 | contactNumber: employeeContact, 158 | dateOfJoin: new Date(joiningDate).toISOString(), 159 | yearsOfExperience: calculateExperience(joiningDate), 160 | departmentId: await getId(employeeDepartment), 161 | }; 162 | const res = await updateEmployee(employeeData); 163 | if (res) { 164 | console.log("Employee Updated"); 165 | 166 | clearStates(); 167 | setToEdit(false); 168 | }else{ 169 | console.log("Employee not Updated"); 170 | } 171 | } 172 | const getId = async (department: string) => { 173 | console.log("Get ID : ", department); 174 | const id = await getIdFromDepartment(department); 175 | console.log("ID : ", id); 176 | 177 | return id; 178 | }; 179 | 180 | const calculateExperience = (joinDate: string) => { 181 | const today:Date = new Date(); 182 | const startDate:Date = new Date(joinDate); 183 | const experienceInMilliseconds:number = today.getTime() - startDate.getTime(); 184 | const years = experienceInMilliseconds / (1000 * 60 * 60 * 24 * 365); 185 | return Math.floor(years); 186 | }; 187 | 188 | return ( 189 | <> 190 | {toDelete && employeeToDelete && ( 191 | 192 | {/* onDismiss={() => setToDelete(false)}> */} 193 | 194 | 195 | Are you absolutely sure? 196 | 197 |
198 | {" "} 199 | Employee Name :{" "} 200 | 201 | {employeeToDelete.name} 202 | {" "} 203 |
204 | Employee Id :{" "} 205 | 206 | {employeeToDelete.id} 207 | {" "} 208 |
209 |
210 | This action cannot be undone. This will permanently delete the 211 | Employee and remove his/her data from the servers. 212 |
213 |
214 | 215 | setToDelete(false)}> 216 | Cancel 217 | 218 | { 220 | await deleteSelectedEmployee(employeeToDelete); 221 | setToDelete(false); 222 | }} 223 | > 224 | Delete 225 | 226 | 227 |
228 |
229 | )} 230 | 231 | {toEdit && employeeToEdit && ( 232 | 233 | {/* onDismiss={() => setToEdit(false)} > */} 234 | 235 | 236 | 237 | Update Employee 238 | 239 | Make changes to the Employee Data here. Click update when done. 240 | 241 | 242 |
243 |
244 | {/* Other input fields */} 245 |
246 | 249 | setEmployeeID(parseInt(e.target.value))} 253 | className="col-span-3" 254 | /> 255 |
256 |
257 | 260 | setEmployeeName(e.target.value)} 264 | className="col-span-3" 265 | /> 266 |
267 |
268 | 271 | setEmployeeEmail(e.target.value)} 275 | className="col-span-3" 276 | /> 277 |
278 |
279 | 282 | setEmployeeContact(e.target.value)} 286 | className="col-span-3" 287 | /> 288 |
289 |
290 | 293 | setJoiningDate(e.target.value)} 298 | className="col-span-3" 299 | /> 300 |
301 |
302 | 305 | 330 |
331 |
332 | 333 | 334 | 335 |
336 |
337 |
338 | )} 339 | 340 | 341 | 342 | Employee ID 343 | Employee Name 344 | Department 345 | Years Of Experience 346 | Edit/Delete 347 | 348 | 349 | 350 | {displayedEmployees.map((employee, index) => ( 351 | 352 | {employee.id} 353 | {employee.name} 354 | {employee.department} 355 | 356 | {employee.yearsOfExperience} 357 | 358 | 359 |
360 |
handleEdit(employee)} 362 | className="border p-1 rounded-sm hover:bg-slate-100 transition-all duration-300 ease-in-out cursor-pointer"> 363 | 364 |
365 |
handleDelete(employee)} 367 | className="border p-1 rounded-sm hover:bg-slate-100 transition-all duration-300 ease-in-out cursor-pointer" 368 | > 369 | 370 |
371 |
372 |
373 |
374 | ))} 375 |
376 |
377 |
handlePageChange(-1)} 379 | className="border p-1 rounded-sm hover:bg-slate-100 transition-all duration-300 ease-in-out cursor-pointer" 380 | > 381 | 382 |
383 |
handlePageChange(1)} 385 | className="border p-1 rounded-sm hover:bg-slate-100 transition-all duration-300 ease-in-out cursor-pointer" 386 | > 387 | 388 |
389 |
390 |
391 | 392 | ); 393 | }; 394 | 395 | export default EmployeeList; -------------------------------------------------------------------------------- /public/employeeLogin.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 9 | 12 | 84 | 89 | 94 | 95 | 96 | 99 | 103 | 105 | 107 | 109 | 111 | 114 | 117 | 119 | 122 | 126 | 128 | 134 | 136 | 139 | 142 | 145 | 147 | 150 | 152 | 155 | 160 | 163 | 165 | 167 | 169 | 172 | 176 | 179 | 181 | 184 | 187 | 190 | 194 | 196 | 199 | 203 | 205 | 208 | 210 | 212 | 219 | 221 | 223 | 224 | 225 | 228 | 231 | 233 | 236 | 239 | 241 | 246 | 251 | 255 | 258 | 261 | 263 | 266 | 269 | 273 | 275 | 277 | 280 | 283 | 284 | 286 | 288 | 290 | 293 | 296 | 299 | 301 | 303 | 305 | 307 | 309 | 311 | 313 | 315 | 316 | 319 | 322 | 324 | 326 | 328 | 330 | 333 | 335 | 337 | 340 | 344 | 346 | 350 | 353 | 372 | 374 | 376 | 378 | 379 | 380 | 382 | 384 | 388 | 391 | 393 | 396 | 399 | 401 | 406 | 410 | 413 | 415 | 418 | 420 | 425 | 427 | 428 | 431 | 433 | 435 | 439 | 442 | 446 | 448 | 455 | 458 | 461 | 465 | 467 | 469 | 472 | 478 | 481 | 485 | 487 | 489 | 491 | 493 | 496 | 498 | 500 | 503 | 507 | 509 | 512 | 514 | 516 | 518 | 520 | 523 | 525 | 527 | 530 | 534 | 536 | 538 | 540 | 542 | 544 | 545 | 546 | 549 | 552 | 556 | 558 | 561 | 563 | 565 | 568 | 570 | 572 | 575 | 579 | 581 | 583 | 585 | 588 | 591 | 593 | 596 | 599 | 601 | 604 | 607 | 608 | 610 | 612 | 614 | 618 | 621 | 624 | 626 | 628 | 630 | 632 | 634 | 636 | 638 | 641 | 644 | 648 | 651 | 654 | 656 | 660 | 662 | 665 | 668 | 687 | 689 | 690 | 691 | 693 | 694 | 698 | 701 | 703 | 705 | 707 | 709 | 712 | 714 | 716 | 719 | 722 | 724 | 726 | 729 | 730 | 733 | 735 | 737 | 741 | 743 | 746 | 748 | 751 | 753 | 757 | 759 | 761 | 764 | 769 | 772 | 774 | 777 | 780 | 782 | 787 | 791 | 794 | 796 | 798 | 801 | 809 | 814 | 816 | 817 | 820 | 822 | 824 | 827 | 830 | 837 | 839 | 841 | 843 | 845 | 846 | 847 | --------------------------------------------------------------------------------