From a7e9605ae36d61766e80bc79d0bb278efc6b6e0f Mon Sep 17 00:00:00 2001 From: Pramod Mahajan Date: Fri, 25 Apr 2025 12:26:56 +0530 Subject: [PATCH 1/9] added css for scroll bar modified --- src/index.css | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) 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 */ +} From 3160da69d2b3c744c7107e72874930014583d3c3 Mon Sep 17 00:00:00 2001 From: Pramod Mahajan Date: Fri, 25 Apr 2025 12:28:30 +0530 Subject: [PATCH 2/9] added one more css property radius --- src/components/common/IconButton.jsx | 45 ++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 src/components/common/IconButton.jsx 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; From 786a5a689e1debf457d3f1c967abdc8a7dd5e9f4 Mon Sep 17 00:00:00 2001 From: Pramod Mahajan Date: Fri, 25 Apr 2025 12:28:56 +0530 Subject: [PATCH 3/9] made custome TagInput. --- src/components/common/TagInput.jsx | 68 ++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 src/components/common/TagInput.jsx 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; From a1a3da61dbef59d47bfc1b00de640c02df1e70bb Mon Sep 17 00:00:00 2001 From: Pramod Mahajan Date: Fri, 25 Apr 2025 12:29:36 +0530 Subject: [PATCH 4/9] new directory components --- src/components/Directory/ManageDirectory.jsx | 214 +++++++++++++++++++ src/components/Project/ProjectNav.jsx | 10 +- 2 files changed, 219 insertions(+), 5 deletions(-) create mode 100644 src/components/Directory/ManageDirectory.jsx 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 ( + +
+
+
+ + + {errors.firstName && {errors.firstName.message}} +
+ +
+ + + {errors.lastName && {errors.lastName.message}} +
+
+ +
+ + + {errors.organization && {errors.organization.message}} +
+ +
+
+ + {emailFields.map((field, index) => (<> +
+ + {index === emailFields.length - 1 ? ( + + ) : ( + + )} +
+ {errors.email?.[index] && ( + + {errors.email[index]?.message} + + )} + + ))} + + +
+ +
+ + {phoneFields.map((field, index) => (<> +
+ + {index === phoneFields.length - 1 ? ( + + ) : ( + + )} +
+ {errors.phone?.[ index ] && {errors.phone[ index ]?.message}} + + ))} + +
+
+ +
+
+ + + {errors.type && {errors.type.message}} +
+
+ +
+
+ +
+ +