diff --git a/src/components/Directory/DirectorySchema.js b/src/components/Directory/DirectorySchema.js new file mode 100644 index 00000000..45e2904a --- /dev/null +++ b/src/components/Directory/DirectorySchema.js @@ -0,0 +1,57 @@ +import { z } from "zod"; +export const ContactSchema = z + .object({ + name: z.string().min(1, "Name is required"), + organization: z.string().min(1, "Organization name is required"), + contactCategoryId: z.string().nullable().optional(), + address: z.string().optional(), + description: z.string().min(1, { message: "Description is required" }), + projectIds: z.array(z.string()).min(1, "Project is required"), + contactEmails: z + .array( + z.object({ + label: z.string(), + emailAddress: z.string().email("Invalid email").or(z.literal("")), + }) + ) + .optional() + .default([]), + + contactPhones: z + .array( + z.object({ + label: z.string(), + phoneNumber: z + .string() + .min(6, "Invalid Number") + .max(10, "Invalid Number") + .regex(/^[\d\s+()-]+$/, "Invalid phone number format").or(z.literal("")), + }) + ) + .optional() + .default([]), + + tags: z + .array( + z.object({ + id: z.string().nullable(), + name: z.string(), + }) + ) + .min(1, { message: "At least one tag is required" }), + bucketIds: z.array(z.string()).optional(), + }) + + .refine((data) => { + const hasValidEmail = (data.contactEmails || []).some( + (e) => e.emailAddress?.trim() !== "" + ); + const hasValidPhone = (data.contactPhones || []).some( + (p) => p.phoneNumber?.trim() !== "" + ); + + return hasValidEmail || hasValidPhone; +}, { + message: "At least one contact (email or phone) is required", + path: ["contactPhone"], +}); diff --git a/src/components/Directory/ListViewDirectory.jsx b/src/components/Directory/ListViewDirectory.jsx index d9188fd2..441aee45 100644 --- a/src/components/Directory/ListViewDirectory.jsx +++ b/src/components/Directory/ListViewDirectory.jsx @@ -23,7 +23,7 @@ const getPhoneIcon = (type) => { } }; -const ListViewDirectory = ({ contact }) => { +const ListViewDirectory = ({ contact,setSelectedContact,setIsOpenModal }) => { return ( {`${contact.name}`} @@ -40,7 +40,6 @@ const ListViewDirectory = ({ contact }) => { - {/* Phones */}
{contact.contactPhones?.map((phone, index) => ( @@ -64,7 +63,11 @@ const ListViewDirectory = ({ contact }) => { {/* Actions */} - + + { + setSelectedContact( contact ) + setIsOpenModal(true) + }}> diff --git a/src/components/Directory/ManageDirectory.jsx b/src/components/Directory/ManageDirectory.jsx index 2198c023..03101aee 100644 --- a/src/components/Directory/ManageDirectory.jsx +++ b/src/components/Directory/ManageDirectory.jsx @@ -7,76 +7,48 @@ import { } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import TagInput from "../common/TagInput"; -import { z } from "zod"; import IconButton from "../common/IconButton"; -import useMaster from "../../hooks/masterHook/useMaster"; +import useMaster, { + useContactCategory, + useContactTags, +} from "../../hooks/masterHook/useMaster"; import { useDispatch, useSelector } from "react-redux"; import { changeMaster } from "../../slices/localVariablesSlice"; import { useBuckets } from "../../hooks/useDirectory"; -import {useProjects} from "../../hooks/useProjects"; +import { useProjects } from "../../hooks/useProjects"; +import SelectMultiple from "../common/SelectMultiple"; +import {ContactSchema} from "./DirectorySchema"; -export const ContactSchema = z.object({ - Name: z.string().min(1, "Name is required"), - organization: z.string().min(1, "Organization name is required"), - ContactCategoryId: z.string().optional(), - address: z.string().optional(), - description: z.string().min( 1, {message: "Description is required"} ), - ProjectId :z.string().optional(), - ContactEmails: z - .array( - z.object({ - label: z.string(), - emailAddress: z.string().email("Invalid email"), - }) - ) - .optional() - .default([]), - ContactPhones: z - .array( - z.object({ - label: z.string(), - phoneNumber: z.string().regex(/^\d{10}$/, "Phone must be 10 digits"), - }) - ) - .optional() - .default([]), - tags: z - .array( - z.object({ - id: z.string().nullable(), - name: z.string(), - }) - ) - .optional(), - BucketIds: z.array(z.string()).optional(), -}); -const ManageDirectory = ({submitContact,onCLosed}) => { +const ManageDirectory = ({ submitContact, onCLosed }) => { const selectedMaster = useSelector( (store) => store.localVariables.selectedMaster ); const [categoryData, setCategoryData] = useState([]); const [TagsData, setTagsData] = useState([]); const { data, loading } = useMaster(); - const {buckets, loading: bucketsLoaging} = useBuckets(); - const {projects, loading: projectLoading} = useProjects(); - const [IsSubmitting,setSubmitting] = useState(false) + const { buckets, loading: bucketsLoaging } = useBuckets(); + const { projects, loading: projectLoading } = useProjects(); + const { contactCategory, loading: contactCategoryLoading } = + useContactCategory(); + const { contactTags, loading: Tagloading } = useContactTags(); + const [IsSubmitting, setSubmitting] = useState(false); const dispatch = useDispatch(); const methods = useForm({ resolver: zodResolver(ContactSchema), defaultValues: { - Name: "", + name: "", organization: "", - ContactCategoryId: null, + contactCategoryId: null, address: "", description: "", - ProjectId:null, - ContactEmails: [], - ContactPhones: [], + projectIds: [], + contactEmails: [], + contactPhones: [], tags: [], - BucketIds: [], + bucketIds: [], }, }); @@ -96,93 +68,98 @@ const ManageDirectory = ({submitContact,onCLosed}) => { fields: emailFields, append: appendEmail, remove: removeEmail, - } = useFieldArray({ control, name: "ContactEmails" }); + } = useFieldArray({ control, name: "contactEmails" }); const { fields: phoneFields, append: appendPhone, remove: removePhone, - } = useFieldArray({ control, name: "ContactPhones" }); + } = useFieldArray({ control, name: "contactPhones" }); - useEffect(() => { - if (emailFields.length === 0) appendEmail(""); - if (phoneFields.length === 0) appendPhone(""); - }, [emailFields.length, phoneFields.length]); +useEffect(() => { + if (emailFields.length === 0) { + appendEmail({ label: "Work", emailAddress: "" }); + } + if (phoneFields.length === 0) { + appendPhone({ label: "Office", phoneNumber: "" }); + } +}, [emailFields.length, phoneFields.length]); - const handleAddEmail = async () => { - const emails = getValues("ContactEmails"); + const emails = getValues("contactEmails"); const lastIndex = emails.length - 1; - const valid = await trigger(`ContactEmails.${lastIndex}.emailAddress`); + const valid = await trigger(`contactEmails.${lastIndex}.emailAddress`); if (valid) { appendEmail({ label: "Work", emailAddress: "" }); } }; const handleAddPhone = async () => { - const phones = getValues("ContactPhones"); + const phones = getValues("contactPhones"); const lastIndex = phones.length - 1; - const valid = await trigger(`ContactPhones.${lastIndex}.phoneNumber`); + const valid = await trigger(`contactPhones.${lastIndex}.phoneNumber`); if (valid) { appendPhone({ label: "Office", phoneNumber: "" }); } }; - useEffect(() => { - if (selectedMaster === "Contact Category") { - setCategoryData(data); - } else { - setTagsData(data); - } - }, [selectedMaster, data]); - - const watchBucketIds = watch("BucketIds"); + const watchBucketIds = watch("bucketIds"); const toggleBucketId = (id) => { const updated = watchBucketIds?.includes(id) ? watchBucketIds.filter((val) => val !== id) : [...watchBucketIds, id]; - setValue("BucketIds", updated, { shouldValidate: true }); + setValue("bucketIds", updated, { shouldValidate: true }); }; const handleCheckboxChange = (id) => { const updated = watchBucketIds.includes(id) ? watchBucketIds.filter((i) => i !== id) : [...watchBucketIds, id]; - setValue("BucketIds", updated, { shouldValidate: true }); + setValue("bucketIds", updated, { shouldValidate: true }); }; - - - const onSubmit = ( data ) => { - setSubmitting(true) - submitContact( data, reset, setSubmitting ) + const cleaned = { + ...data, + contactEmails: (data.contactEmails || []).filter( + (e) => e.emailAddress?.trim() !== "" + ), + contactPhones: (data.contactPhones || []).filter( + (p) => p.phoneNumber?.trim() !== "" + ), + }; + setSubmitting(true); + submitContact(cleaned, reset, setSubmitting); + }; + + const handleClosed = () => { + onCLosed(); }; return (
-
+
{" "}
Create New Contact
-
+
- {errors.Name && ( - {errors.Name.message} + {errors.name && ( + {errors.name.message} )}
-
+
{ key={field.id} className="row d-flex align-items-center mb-1" > -
+
- {errors.ContactEmails?.[index]?.label && ( + {errors.contactEmails?.[index]?.label && ( - {errors.ContactEmails[index].label.message} + {errors.contactEmails[index].label.message} )}
-
+
{index === emailFields.length - 1 ? ( @@ -247,27 +224,26 @@ const ManageDirectory = ({submitContact,onCLosed}) => { )}
- {errors.ContactEmails?.[index]?.emailAddress && ( + {errors.contactEmails?.[index]?.emailAddress && ( - {errors.ContactEmails[index].emailAddress.message} + {errors.contactEmails[index].emailAddress.message} )}
))}
-
{phoneFields.map((field, index) => (
-
+
{index === phoneFields.length - 1 ? ( @@ -308,27 +284,28 @@ const ManageDirectory = ({submitContact,onCLosed}) => { )}
- {errors.ContactPhones?.[index]?.phoneNumber && ( + {errors.contactPhones?.[index]?.phoneNumber && ( - {errors.ContactPhones[index].phoneNumber.message} + {errors.contactPhones[index].phoneNumber.message} )}
))}
+ {errors.contactPhone?.message && ( +
{errors.contactPhone.message}
+ )}
-
+
- {errors.ContactCategoryId && ( - {errors.ContactCategoryId.message} + {errors.contactCategoryId && ( + + {errors.contactCategoryId.message} + )}
-
- +
+ + {errors.projectIds && ( + + {errors.projectIds.message} + + )}
-
-
- -
+ + {errors.tags && ( + + {errors.tags.message} + + )} +
+
+
+ + +
    {bucketsLoaging &&

    Loading...

    } {buckets?.map((item) => ( -
    -
    +
  • +
    { {item.name}
    -
  • + ))} -
    +
{errors.BucketIds && ( {errors.BucketIds.message} )}
- -
- - - {errors.category && ( - {errors.category.message} - )} -
-
+