diff --git a/src/components/Directory/ManageDirectory.jsx b/src/components/Directory/ManageDirectory.jsx
new file mode 100644
index 00000000..6c6b41e4
--- /dev/null
+++ b/src/components/Directory/ManageDirectory.jsx
@@ -0,0 +1,214 @@
+import React, { useEffect } from "react";
+import { useForm, useFieldArray, FormProvider } from "react-hook-form";
+import { zodResolver } from "@hookform/resolvers/zod";
+import TagInput from "../common/TagInput";
+import { z } from "zod";
+
+export const directorySchema = z.object({
+ firstName: z.string().min(1, "First Name is required"),
+ lastName: z.string().min(1, "Last Name is required"),
+ organization: z.string().min(1, "Organization name is required"),
+ type: z.string().min(1, "Type is required"),
+ address: z.string().optional(),
+ description: z.string().min(1, { message: "Description is required" }),
+ email: z
+ .array(z.string().email("Invalid email"))
+ .nonempty("At least one email required"),
+ phone: z
+ .array(z.string().regex(/^\d{10}$/, "Phone must be 10 digits"))
+ .nonempty("At least one phone number is required"),
+ tags: z.array(z.string()).optional(),
+});
+
+
+
+const ManageDirectory = () => {
+ const methods = useForm({
+ resolver: zodResolver(directorySchema),
+ defaultValues: {
+ firstName: "",
+ lastName: "",
+ organization: "",
+ type: "",
+ address: "",
+ description: "",
+ email: [""],
+ phone: [""],
+ tags: [],
+ },
+ });
+
+ const {
+ register,
+ handleSubmit,
+ control,
+ getValues,
+ trigger,
+ formState: { errors },
+ } = methods;
+
+ const {
+ fields: emailFields,
+ append: appendEmail,
+ remove: removeEmail,
+ } = useFieldArray({ control, name: "email" });
+
+ const {
+ fields: phoneFields,
+ append: appendPhone,
+ remove: removePhone,
+ } = useFieldArray({ control, name: "phone" });
+
+ useEffect(() => {
+ if (emailFields.length === 0) appendEmail("");
+ if (phoneFields.length === 0) appendPhone("");
+ }, [emailFields.length, phoneFields.length]);
+
+ const onSubmit = (data) => {
+ console.log("Submitted:\n" + JSON.stringify(data, null, 2));
+ };
+
+ const handleAddEmail = async () => {
+ const emails = getValues("email");
+ const lastIndex = emails.length - 1;
+ const valid = await trigger(`email.${lastIndex}`);
+ if (valid) appendEmail("");
+ };
+
+ const handleAddPhone = async () => {
+ const phones = getValues("phone");
+ const lastIndex = phones.length - 1;
+ const valid = await trigger(`phone.${lastIndex}`);
+ if (valid) appendPhone("");
+ };
+
+ return (
+
+
+
+ );
+};
+
+export default ManageDirectory;
diff --git a/src/components/Project/ProjectNav.jsx b/src/components/Project/ProjectNav.jsx
index 6081958f..b5203ea4 100644
--- a/src/components/Project/ProjectNav.jsx
+++ b/src/components/Project/ProjectNav.jsx
@@ -18,7 +18,7 @@ const ProjectNav = ({ onPillClick, activePill }) => {
onPillClick("profile");
}}
>
- Profile
+ Profile
@@ -30,7 +30,7 @@ const ProjectNav = ({ onPillClick, activePill }) => {
onPillClick("teams");
}}
>
- Teams
+ Teams
@@ -42,7 +42,7 @@ const ProjectNav = ({ onPillClick, activePill }) => {
onPillClick("infra");
}}
>
- Infrastructure
+ Infrastructure
{/*
@@ -70,7 +70,7 @@ const ProjectNav = ({ onPillClick, activePill }) => {
onPillClick("imagegallary");
}}
>
- Image Gallary
+ Image Gallary
@@ -82,7 +82,7 @@ const ProjectNav = ({ onPillClick, activePill }) => {
onPillClick("directory");
}}
>
- Directory
+ Directory
diff --git a/src/components/common/IconButton.jsx b/src/components/common/IconButton.jsx
new file mode 100644
index 00000000..362c1db9
--- /dev/null
+++ b/src/components/common/IconButton.jsx
@@ -0,0 +1,45 @@
+import React from "react";
+
+// Light background mapping for Bootstrap theme colors
+const lightBackgrounds = {
+ primary: "#e3f2fd",
+ secondary: "#E9EEF4",
+ success: "#e6f4ea",
+ danger: "#fdecea",
+ warning: "#fff3cd",
+ info: "#e0f7fa",
+ dark: "#e9ecef",
+};
+
+const IconButton = ({
+ iconClass, // icon class string like 'bx bx-user'
+ color = "primary",
+ onClick,
+ size = 20,
+ radius=null,
+ style = {},
+ ...rest
+}) => {
+ const backgroundColor = lightBackgrounds[color] || "#f8f9fa";
+ const iconColor = `var(--bs-${color})`;
+
+ return (
+
+ );
+};
+
+export default IconButton;
diff --git a/src/components/common/TagInput.jsx b/src/components/common/TagInput.jsx
new file mode 100644
index 00000000..58f3d16f
--- /dev/null
+++ b/src/components/common/TagInput.jsx
@@ -0,0 +1,68 @@
+import React, { useState, useEffect } from "react";
+import { useFormContext } from "react-hook-form";
+
+const TagInput = ({
+ label = "Tags",
+ name = "tags",
+ placeholder = "Enter ... ",
+ color = "#e9ecef",
+}) => {
+ const [tags, setTags] = useState([]);
+ const [input, setInput] = useState("");
+ const { setValue } = useFormContext();
+
+ useEffect(() => {
+ setValue(name, tags); // sync to form when tags change
+ }, [tags, name, setValue]);
+
+ const addTag = (e) => {
+ e.preventDefault();
+ const trimmed = input.trim();
+ if (trimmed !== "" && !tags.includes(trimmed)) {
+ setTags([...tags, trimmed]);
+ setInput("");
+ }
+ };
+
+ const removeTag = (removeIndex) => {
+ const updated = tags.filter((_, i) => i !== removeIndex);
+ setTags(updated);
+ };
+
+ const backgroundColor = color || "#f8f9fa";
+ const iconColor = `var(--bs-${color})`;
+
+ return (
+ <>
+
+
+ {tags.map((tag, i) => (
+
+ {tag}
+ removeTag(i)}>
+
+ ))}
+ setInput(e.target.value)}
+ onKeyDown={(e) => (e.key === "Enter" ? addTag(e) : null)}
+ placeholder={placeholder}
+ style={{ outline: "none", minWidth: "120px" }}
+ />
+
+ >
+ );
+};
+
+export default TagInput;
diff --git a/src/data/menuData.json b/src/data/menuData.json
index 72ea7191..3995459b 100644
--- a/src/data/menuData.json
+++ b/src/data/menuData.json
@@ -24,6 +24,11 @@
"available": true,
"link": "/employees"
},
+ {
+ "text": "Directory",
+ "available": true,
+ "link": "/directory"
+ },
{
"text": "Project Status",
"available": true,
diff --git a/src/index.css b/src/index.css
index c72bd0de..142e58c2 100644
--- a/src/index.css
+++ b/src/index.css
@@ -100,3 +100,23 @@ button:focus-visible {
.checkbox-column {
flex: 1;
}
+
+/* Custom Scrollbar for Webkit browsers (Chrome, Safari, Edge) */
+::-webkit-scrollbar {
+ width: 6px; /* Width of the scrollbar */
+ height: 6px; /* Height of the scrollbar (for horizontal scrollbar) */
+}
+
+::-webkit-scrollbar-track {
+ background-color: #f1f1f1; /* Background of the scrollbar track */
+ border-radius: 2px; /* Rounded corners for the track */
+}
+
+::-webkit-scrollbar-thumb {
+ background-color: #888; /* Color of the scrollbar thumb */
+ border-radius: 10px; /* Rounded corners for the thumb */
+}
+
+::-webkit-scrollbar-thumb:hover {
+ background-color: #555; /* Color of the thumb on hover */
+}
diff --git a/src/pages/Directory/Directory.jsx b/src/pages/Directory/Directory.jsx
new file mode 100644
index 00000000..409b0091
--- /dev/null
+++ b/src/pages/Directory/Directory.jsx
@@ -0,0 +1,140 @@
+import React, { useState } from "react";
+import Breadcrumb from "../../components/common/Breadcrumb";
+import IconButton from "../../components/common/IconButton";
+import GlobalModel from "../../components/common/GlobalModel";
+import ManageDirectory from "../../components/Directory/ManageDirectory";
+
+const Directory = () => {
+ const [isOpenModal, setIsOpenModal] = useState(false);
+ const closedModel = () => setIsOpenModal(false);
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name
+ |
+
+
+ alert("User icon clicked")}
+ />
+ Email
+
+ |
+
+
+ alert("User icon clicked")}
+ />
+ Phone
+
+ |
+
+
+
+ Organization
+
+ |
+
+
+ |
+
+ Action
+ |
+
+
+
+
+
+ No projects found
+ |
+
+
+
+
+
+
+ );
+};
+
+export default Directory;
diff --git a/src/pages/project/ProjectList.jsx b/src/pages/project/ProjectList.jsx
index def125ce..9b831918 100644
--- a/src/pages/project/ProjectList.jsx
+++ b/src/pages/project/ProjectList.jsx
@@ -231,7 +231,7 @@ const ProjectList = () => {
data-bs-toggle="dropdown"
aria-expanded="false"
>
- Status
+ Status
{[
diff --git a/src/router/AppRoutes.jsx b/src/router/AppRoutes.jsx
index 456be8ad..63cb4388 100644
--- a/src/router/AppRoutes.jsx
+++ b/src/router/AppRoutes.jsx
@@ -35,6 +35,7 @@ import LegalInfoCard from "../pages/TermsAndConditions/LegalInfoCard";
// Protected Route Wrapper
import ProtectedRoute from "./ProtectedRoute";
+import Directory from "../pages/Directory/Directory";
const router = createBrowserRouter(
[
@@ -62,7 +63,8 @@ const router = createBrowserRouter(
{ path: "/employees", element: },
{ path: "/employee/:employeeId", element: },
{ path: "/employee/manage", element: },
- { path: "/employee/manage/:employeeId", element: },
+ {path: "/employee/manage/:employeeId", element: },
+ { path: "/directory", element: },
{ path: "/inventory", element: },
{ path: "/activities/attendance", element: },
{ path: "/activities/records/:projectId?", element: },