diff --git a/Employee_Managment_App/src/components/EmployeeInfo.tsx b/Employee_Managment_App/src/components/EmployeeInfo.tsx index aa98b4e..97f2b01 100644 --- a/Employee_Managment_App/src/components/EmployeeInfo.tsx +++ b/Employee_Managment_App/src/components/EmployeeInfo.tsx @@ -1,37 +1,113 @@ import * as React from 'react'; import { TabComponent, TabItemDirective, TabItemsDirective } from '@syncfusion/ej2-react-navigations'; -import { Internationalization } from '@syncfusion/ej2-base'; import { useLocation } from 'react-router-dom'; -import { EmployeeDetails } from '../../interface'; +import { EmployeeDetails } from '../interface'; import EmployeeLeave from './EmployeeLeave'; import EmployeePayStub from './EmployeePayStub'; import EmployeePayRoll from './EmployeePayRoll'; -import Employees from './Employees'; +import { DataManager, UrlAdaptor, Query } from '@syncfusion/ej2-data'; const EmployeeInfo = (props: { employeeData?: EmployeeDetails; userInfo?: EmployeeDetails }) => { const location = useLocation(); - const employeeID = location.state?.employeeID; - let employeeData: EmployeeDetails = props.employeeData - ? props.employeeData - : employeeID - ? employeeID - : {}; - const userInfo: EmployeeDetails = location.state?.userInfo - ? location.state?.userInfo - : props.userInfo - ? props.userInfo - : {}; - let intl: Internationalization = new Internationalization(); + const [defaultEmployee, setDefaultEmployee] = React.useState(null); + const [loading, setLoading] = React.useState(true); + + // Fetch default employee on component mount if no employee is selected + React.useEffect(() => { + const fetchDefaultEmployee = async () => { + setLoading(true); + try { + const dataManager = new DataManager({ + url: 'https://ej2services.syncfusion.com/aspnet/development/api/EmployeesData', + adaptor: new UrlAdaptor(), + crossDomain: true, + }); + + const query = new Query().take(1); + const result: any = await dataManager.executeQuery(query); + + console.log('Fetched employee data:', result); + + // Handle different response formats + let employeeArray: any[] = []; + if (Array.isArray(result)) { + employeeArray = result; + } else if (result && Array.isArray(result.result)) { + employeeArray = result.result; + } + + if (employeeArray.length > 0) { + setDefaultEmployee(employeeArray[0] as EmployeeDetails); + } + } catch (error) { + console.error('Error fetching default employee:', error); + } finally { + setLoading(false); + } + }; + + // Only fetch if no employee is already provided + const routeEmployee = (location.state as any)?.employeeID; + if (!routeEmployee && !props.employeeData && !props.userInfo) { + fetchDefaultEmployee(); + } + // Always set loading to false after checking - we have defaults + setLoading(false); + }, []); + + // Default employee - used if API fetch fails or takes time + const defaultEmployeeData: EmployeeDetails = { + Name: 'Michael Anderson', + EmployeeCode: 'EMP100001', + Branch: 'Tower 1', + Team: 'Management', + Designation: 'General Manager', + TeamLead: 'Christopher Anderson', + ManagerName: 'Christopher Anderson', + Mail: 'michael_anderson100001@xyz.com', + DateOfJoining: new Date(new Date().getFullYear() - 20, 2, 1), + FirstName: 'Michael', + LastName: 'Anderson', + FatherName: 'David Anderson', + MotherName: 'Pamela Anderson', + Gender: 'Male', + BloodGroup: 'O+ve', + MaritalStatus: 'Married', + DOB: new Date(new Date().getFullYear() - 42, 3, 20), + }; + + // Prefer explicit employee from route state, then prop, then fallback to defaultEmployee or hardcoded default + const routeEmployee = (location.state as any)?.employeeID as any; + const routeUser = (location.state as any)?.userInfo as any; + const userInfo: EmployeeDetails = (routeUser as any) ?? (props.userInfo as any) ?? {} as any; + let employeeData: EmployeeDetails = (routeEmployee as any) ?? (props.employeeData as any) ?? (userInfo as any) ?? (defaultEmployee as any) ?? defaultEmployeeData; // Format the date to the desired output - const custom = { - day: "numeric", // Displays day as a number (e.g., 1) - month: "short", // Displays the short month name (e.g., Feb) - year: "numeric" // Displays the full year (e.g., 2005) + const custom: Intl.DateTimeFormatOptions = { + day: 'numeric', // Displays day as a number (e.g., 1) + month: 'short', // Displays the short month name (e.g., Feb) + year: 'numeric' // Displays the full year (e.g., 2005) }; - let dateOfJoining = employeeData && employeeData.DateOfJoining.toLocaleDateString("en-GB", custom); - let dob: string = employeeData && employeeData.DOB.toLocaleDateString("en-GB", custom); - let experience: number = new Date().getFullYear() - employeeData.DateOfJoining.getFullYear(); - let experienceMonth: number = new Date().getMonth() - employeeData.DateOfJoining.getMonth(); + + // Normalize possible string dates to Date objects and guard for missing values + const dojDate: Date | null = employeeData && (employeeData as any).DateOfJoining + ? new Date((employeeData as any).DateOfJoining) + : null; + const dobDate: Date | null = employeeData && (employeeData as any).DOB + ? new Date((employeeData as any).DOB) + : null; + + const dateOfJoining: string = dojDate ? dojDate.toLocaleDateString('en-GB', custom) : '-'; + const dob: string = dobDate ? dobDate.toLocaleDateString('en-GB', custom) : '-'; + + const now = new Date(); + let experienceYears = 0; + let experienceMonths = 0; + if (dojDate) { + let months = (now.getFullYear() - dojDate.getFullYear()) * 12 + (now.getMonth() - dojDate.getMonth()); + if (months < 0) months = 0; + experienceYears = Math.floor(months / 12); + experienceMonths = months % 12; + } let headerText: Object[] = [ { text: 'Official' }, { text: 'Personal' }, @@ -95,7 +171,7 @@ const EmployeeInfo = (props: { employeeData?: EmployeeDetails; userInfo?: Employ
Experience : - {experience} Years {experienceMonth} Months + {experienceYears} Years {experienceMonths} Months
User Work Shift @@ -200,7 +276,16 @@ const EmployeeInfo = (props: { employeeData?: EmployeeDetails; userInfo?: Employ ); }; + const hasEmployee = employeeData && Object.keys(employeeData as any).length > 0; + const overview = () => { + if (loading) { + return ( +
+
Loading employee data...
+
+ ); + } return (
diff --git a/Employee_Managment_App/src/components/TopNav.css b/Employee_Managment_App/src/components/TopNav.css index abbdc48..8c6bcf7 100644 --- a/Employee_Managment_App/src/components/TopNav.css +++ b/Employee_Managment_App/src/components/TopNav.css @@ -422,12 +422,16 @@ top: var(--topnav-height); z-index: 999; border-bottom: 1px solid var(--topnav-border); - padding-right:66px; - padding-top:8px; - padding-bottom: 8px; + padding: 8px 12px; /* uniform padding for proper centering */ background: #fff; + box-sizing: border-box; /* ensure full-width alignment on small screens */ } + /* Make sure the search field spans full width and is centered on mobile */ + .mobile-search .search-form { margin: 0; } + .mobile-search .search-field-autocomplete-mobile, + .mobile-search .employee-autocomplete-mobile.e-autocomplete { width: 100%; } + .mobile-search .search-form { max-width: 100%; } } @@ -458,4 +462,354 @@ .topnav .topnav-menu-btn { margin-right: 4px; -} \ No newline at end of file +} + +/* ============================================ + AutoComplete & Search Field Styling + ============================================ */ + +/* AutoComplete Container */ +.search-field-autocomplete { + position: relative; + display: flex; + align-items: center; + justify-content: flex-start; + width: 100%; + height: var(--control-height); + background: var(--search-bg); + border-radius: 4px; + box-shadow: var(--shadow-rest); + transition: all 160ms ease; + overflow: visible; + padding: 0; +} + +.search-field-autocomplete:hover { + box-shadow: 0 2px 6px rgba(0,0,0,.08); +} + +.search-field-autocomplete:focus-within { + box-shadow: var(--shadow-focus); + background: #fff; +} + +/* Search Icon */ +.input-icon-autocomplete { + position: absolute; + left: 8px; + top: 50%; + transform: translateY(-50%); + display: inline-flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + color: var(--search-icon); + pointer-events: none; + z-index: 10; + flex-shrink: 0; +} + +.input-icon-autocomplete svg { + width: 18px; + height: 18px; + display: block; +} + +/* Main AutoComplete Component */ +.employee-autocomplete.e-autocomplete { + width: 100%; + height: 100%; + position: relative; +} + +.employee-autocomplete .e-input-group { + border: none !important; + background: transparent !important; + box-shadow: none !important; + height: var(--control-height) !important; + min-height: var(--control-height) !important; + display: flex !important; + align-items: center !important; + position: relative !important; +} + +.employee-autocomplete .e-input-group.e-input-focus { + border: none !important; + box-shadow: none !important; + background: transparent !important; +} + +/* Input wrapper */ +.employee-autocomplete .e-input-in-wrap { + height: 100% !important; + display: flex !important; + align-items: center !important; + flex: 1 !important; + position: relative !important; + margin-left: 0 !important; +} + +/* Input field */ +.employee-autocomplete .e-input { + background: transparent !important; + color: var(--search-text) !important; + font-size: 14px !important; + line-height: 1.4 !important; + height: 100% !important; + min-height: var(--control-height) !important; + padding: 0 36px 0 42px !important; + border: none !important; + outline: none !important; + box-shadow: none !important; + width: 100% !important; + margin: 0 !important; +} + +.employee-autocomplete .e-input::placeholder { + color: var(--search-muted) !important; + opacity: 1 !important; +} + +.employee-autocomplete .e-input:focus { + outline: none !important; + box-shadow: none !important; + background: transparent !important; + color: var(--search-text) !important; +} + +/* Clear button */ +.employee-autocomplete .e-clear-icon { + color: var(--search-muted) !important; + font-size: 16px !important; + right: 4px !important; + position: absolute !important; + top: 50% !important; + transform: translateY(-50%) !important; + line-height: 1 !important; + width: 28px !important; + height: 28px !important; + display: inline-flex !important; + align-items: center !important; + justify-content: center !important; + border-radius: 50% !important; + transition: all 120ms ease !important; + cursor: pointer !important; + padding: 0 !important; + margin: 0 !important; +} + +.employee-autocomplete .e-clear-icon:hover { + color: var(--search-text) !important; + background: rgba(0, 0, 0, 0.06) !important; +} + +.employee-autocomplete .e-clear-icon::before { + font-size: 14px !important; + line-height: 1 !important; +} + +/* Dropdown/Popup Styling */ +.e-ddl.e-popup.e-autocomplete { + border-radius: 8px !important; + box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15) !important; + border: 1px solid #e5e7eb !important; + margin-top: 6px !important; + padding: 2px 0 !important; + min-width: 320px !important; +} + +/* List container */ +.e-ddl.e-popup .e-list { + padding: 0 !important; + display: flex !important; + flex-direction: column !important; +} + +.e-ddl.e-popup .e-list-item { + padding: 4px 6px !important; + border-radius: 3px !important; + margin: 1px 4px !important; + height: auto !important; + min-height: 42px !important; + display: flex !important; + align-items: center !important; + justify-content: flex-start !important; + cursor: pointer !important; + border: none !important; + background: transparent !important; + transition: all 100ms ease !important; + width: calc(100% - 8px) !important; +} + +/* Ensure all child elements are properly aligned */ +.e-ddl.e-popup .e-list-item a { + display: flex !important; + align-items: center !important; + width: 100% !important; + text-decoration: none !important; +} + +.e-ddl.e-popup .e-list-item:hover { + background: #f0fdf4 !important; + color: inherit !important; +} + +.e-ddl.e-popup .e-list-item.e-active { + background: #d1fae5 !important; + color: #047857 !important; +} + +.e-ddl.e-popup .e-list-item.e-active:hover { + background: #a7f3d0 !important; +} + +/* Employee Suggestion Link Wrapper */ +.employee-suggestion-link { + display: flex !important; + align-items: center !important; + width: 100% !important; + text-decoration: none !important; + color: inherit !important; + padding: 0 !important; +} + +.employee-suggestion-link:hover { + text-decoration: none !important; + color: inherit !important; +} + +/* Employee Suggestion Item Template */ +.employee-suggestion-item { + display: flex !important; + align-items: center !important; + width: 100% !important; + gap: 8px !important; +} + +.employee-avatar-small { + flex-shrink: 0 !important; + display: flex !important; + align-items: center !important; + justify-content: center !important; + width: 32px !important; + height: 32px !important; + border-radius: 50% !important; + background: #f3f4f6 !important; + margin: 0 !important; +} + +.employee-avatar-small svg { + width: 24px !important; + height: 24px !important; + color: #9ca3af !important; +} + +.employee-info-group { + flex: 1 !important; + min-width: 0 !important; + display: flex !important; + flex-direction: column !important; + gap: 1px !important; +} + +.employee-name-link { + font-weight: 600 !important; + color: #111827 !important; + font-size: 12px !important; + overflow: hidden !important; + text-overflow: ellipsis !important; + white-space: nowrap !important; + line-height: 1.3 !important; + margin: 0 !important; +} + +.employee-id-email { + font-size: 10px !important; + color: #6b7280 !important; + overflow: hidden !important; + text-overflow: ellipsis !important; + white-space: nowrap !important; + line-height: 1.2 !important; + margin: 0 !important; +} + +/* Mobile AutoComplete Styling */ +.search-field-autocomplete-mobile { + position: relative; + display: flex; + align-items: center; + width: 100%; + background: var(--search-bg); + border-radius: 4px; + box-shadow: var(--shadow-rest); + overflow: visible; +} + +.input-icon-autocomplete-mobile { + position: absolute; + left: 8px; + top: 50%; + transform: translateY(-50%); + display: inline-flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + color: var(--search-icon); + pointer-events: none; + z-index: 10; + flex-shrink: 0; +} + +.input-icon-autocomplete-mobile svg { + width: 18px; + height: 18px; +} + +.employee-autocomplete-mobile.e-autocomplete { + width: 100%; +} + +.employee-autocomplete-mobile .e-input-group { + border: none !important; + background: transparent !important; + box-shadow: none !important; + min-height: 40px !important; + display: flex !important; + align-items: center !important; +} + +.employee-autocomplete-mobile .e-input-group.e-input-focus { + border: none !important; + box-shadow: none !important; +} + +.employee-autocomplete-mobile .e-input { + background: transparent !important; + color: var(--search-text) !important; + font-size: 14px !important; + padding: 8px 36px 8px 42px !important; + border: none !important; + outline: none !important; + box-shadow: none !important; + line-height: 1.5 !important; +} + +.employee-autocomplete-mobile .e-input::placeholder { + color: var(--search-muted) !important; +} + +.employee-autocomplete-mobile .e-input:focus { + outline: none !important; + box-shadow: none !important; +} + +.employee-autocomplete-mobile .e-clear-icon { + color: var(--search-muted) !important; + font-size: 16px !important; +} + +.employee-autocomplete-mobile .e-clear-icon:hover { + color: var(--search-text) !important; +} diff --git a/Employee_Managment_App/src/components/TopNav.tsx b/Employee_Managment_App/src/components/TopNav.tsx index aecaa07..233b112 100644 --- a/Employee_Managment_App/src/components/TopNav.tsx +++ b/Employee_Managment_App/src/components/TopNav.tsx @@ -3,6 +3,10 @@ import React, { useMemo, useState, useEffect, useRef } from 'react'; import './TopNav.css'; import { AnnouncementPanel, PanelItem } from './Announcement'; import { ButtonComponent } from '@syncfusion/ej2-react-buttons'; +import { AutoCompleteComponent } from '@syncfusion/ej2-react-dropdowns'; +import { DataManager, UrlAdaptor, Query, Predicate } from '@syncfusion/ej2-data'; +import { useNavigate } from 'react-router-dom'; +import { EmployeeDetails } from '../interface'; type TopNavProps = { portalShort?: string; @@ -31,6 +35,13 @@ type TopNavProps = { onOpenSidebar?: () => void; }; +// Employee data source +const employeeDataSource: DataManager = new DataManager({ + url: 'https://ej2services.syncfusion.com/aspnet/development/api/EmployeesData', + adaptor: new UrlAdaptor(), + crossDomain: true, +}); + const TopNav: React.FC = ({ portalShort = 'HR', companyName = 'NexGen7 Software', @@ -60,19 +71,88 @@ const TopNav: React.FC = ({ onMarkAllRead, onOpenSidebar, }) => { + const navigate = useNavigate(); const [query, setQuery] = useState(''); const [avatarMenuOpen, setAvatarMenuOpen] = useState(false); const [mobileSearchOpen, setMobileSearchOpen] = useState(false); const [panelOpen, setPanelOpen] = useState(false); const [panelTab, setPanelTab] = useState<'notifications' | 'announcements'>('announcements'); + const autoCompleteRef = useRef(null); const submitSearch = (e?: React.FormEvent) => { if (e) e.preventDefault(); onSearch?.(query.trim()); }; + // Handle employee selection from AutoComplete + const handleEmployeeSelect = (args: any) => { + if (args.itemData) { + const employeeData = args.itemData as EmployeeDetails; + // Navigate to employee info page + navigate('/employeeinfo', { + // Pass both employeeData and userInfo so tabs and defaults render for the selected account + state: { employeeID: employeeData, userInfo: employeeData }, + }); + // Clear the search after navigation + if (autoCompleteRef.current) { + autoCompleteRef.current.value = ''; + } + setQuery(''); + setMobileSearchOpen(false); + } + }; + + // Enable searching by Name, EmployeeCode (ID), or Mail (Email) + const debounceRef = useRef(undefined); + const handleFiltering = (e: any) => { + const text: string = e.text ?? ''; + if (debounceRef.current) { + clearTimeout(debounceRef.current); + } + debounceRef.current = window.setTimeout(() => { + let query = new Query(); + if (text.trim().length > 0) { + const predicate = new Predicate('Name', 'contains', text, true) + .or('EmployeeCode', 'contains', text, true) + .or('Mail', 'contains', text, true); + query = query.where(predicate).take(20); + } else { + query = query.take(20); + } + e.updateData(employeeDataSource, query); + }, 250); + }; + + // Template for displaying employee suggestions + const itemTemplate = (data: any) => { + return ( + e.preventDefault()}> +
+
+ + + + +
+
+
{data.Name}
+
{data.EmployeeCode} • {data.Mail}
+
+
+
+ ); + }; + const avatarRef = useRef(null); - const createRef = useRef(null); useEffect(() => { const handleDocClick = (ev: MouseEvent) => { @@ -144,9 +224,9 @@ const TopNav: React.FC = ({
-
-
-
@@ -248,16 +319,9 @@ const TopNav: React.FC = ({ {mobileSearchOpen && (
-
{ - e.preventDefault(); - onSearch?.(query.trim()); - }} - > -
-
)}