feat(companies): Enhance company forms with additional fields

This commit expands the company profile by adding several new fields to the create and edit forms. This allows for the collection of more detailed and comprehensive company information.

Key changes include:
- Added new fields: Establishment Date, Company Type, Business Nature, Industry, Country, State, and City.
- Introduced a new reusable `DatePicker` component, utilizing `react-datepicker`, for date input.
- Created a new `CalendarIcon` for use in the date picker.
- Updated the company Zod schema, API services, and data types to support the new fields.

Additionally, this commit includes a refactor to standardize the `MultiSelect` component by renaming the `text` prop to `label` for better consistency across form elements. Form labels are now capitalized for a uniform UI.
This commit is contained in:
AmirReza Jamali
2025-09-27 16:33:13 +03:30
parent 527d8e7661
commit bb56e1013a
22 changed files with 628 additions and 29 deletions
@@ -7,7 +7,7 @@ import MultiSelect from "@/components/FormElements/MultiSelect";
import { Select } from "@/components/FormElements/select";
import { ShowcaseSection } from "@/components/Layouts/showcase-section";
import FormFooter from "@/components/ui/FormFooter";
import { PHONE_REGEX } from "@/constants/regex";
import { REGEX } from "@/constants/regex";
import { FormErrorMessages } from "@/constants/Texts";
import { ICreateCompanyCredentials } from "@/lib/api/types/Company";
import useCreateCompanyPresenter from "./useCreateCompanyPresenter";
@@ -92,7 +92,7 @@ const CreateCompany = ({ defaultValues, editMode, id }: IProps) => {
message: FormErrorMessages.maxLength(20),
},
pattern: {
value: PHONE_REGEX,
value: REGEX.PHONE,
message: FormErrorMessages.invalidPattern,
},
})}
@@ -485,7 +485,7 @@ const CreateCompany = ({ defaultValues, editMode, id }: IProps) => {
companyCategories
? companyCategories?.data?.categories?.map((c) => {
return {
text: c.name,
label: c.name,
value: c.id,
};
})
@@ -6,7 +6,7 @@ import MultiSelect from "@/components/FormElements/MultiSelect";
import { Select } from "@/components/FormElements/select";
import { ShowcaseSection } from "@/components/Layouts/showcase-section";
import FormFooter from "@/components/ui/FormFooter";
import { USERNAME_REGEX } from "@/constants/regex";
import { REGEX } from "@/constants/regex";
import { FormErrorMessages } from "@/constants/Texts";
import { CreateCustomerCredentials } from "@/lib/api";
import useCreateCustomerPresenter from "./useCreateCustomerPresenter";
@@ -37,7 +37,7 @@ const CreateCustomer = ({ defaultValues, editMode, id }: IProps) => {
message: FormErrorMessages.maxLength(30),
},
pattern: {
value: USERNAME_REGEX,
value: REGEX.USERNAME,
message: FormErrorMessages.invalidPattern,
},
})}
@@ -60,7 +60,7 @@ const CreateCustomer = ({ defaultValues, editMode, id }: IProps) => {
companies
? companies?.map((item) => ({
value: item.id,
text: item.name,
label: item.name,
}))
: []
}
@@ -0,0 +1,176 @@
"use client";
import DatePicker from "@/components/FormElements/DatePicker/DatePicker";
import InputGroup from "@/components/FormElements/InputGroup";
import { TextAreaGroup } from "@/components/FormElements/InputGroup/text-area";
import MultiSelect from "@/components/FormElements/MultiSelect";
import { Select } from "@/components/FormElements/select";
import { ShowcaseSection } from "@/components/Layouts/showcase-section";
import FormFooter from "@/components/ui/FormFooter";
import { REGEX } from "@/constants/regex";
import { FormErrorMessages } from "@/constants/Texts";
import useCreateNotificationFormPresenter from "./useCreateNotificationFormPresenter";
const CreateNotificationForm = () => {
const {
register,
errors,
control,
handleSubmit,
recipient,
onSubmit,
onSelectTarget,
priorities,
targets,
types,
channels,
} = useCreateNotificationFormPresenter();
return (
<form onSubmit={handleSubmit(onSubmit)}>
<ShowcaseSection
title="Create Notification"
className="grid grid-cols-2 gap-5"
>
<div className="flex flex-col gap-2">
<InputGroup
required
{...register("title", {
required: { value: true, message: FormErrorMessages.required },
})}
name="title"
label="Title"
type="text"
placeholder="Enter title"
/>
{errors.title && (
<p className="mt-1 text-sm text-red-500">{errors.title.message}</p>
)}
</div>
<div className="flex flex-col gap-2">
<DatePicker
control={control}
name="schedule_at"
label="schedule at"
required
range={false}
rules={{
required: {
value: true,
message: FormErrorMessages.required,
},
}}
placeholder="Select schedule date"
/>
</div>
<div className="flex flex-col gap-2">
<Select
required
{...register("target", {
required: {
value: true,
message: FormErrorMessages.required,
},
onChange: onSelectTarget,
})}
items={targets}
label="target"
placeholder="Select target"
name="target"
/>
{errors.target && (
<p className="mt-1 text-sm text-red-500">{errors.target.message}</p>
)}
</div>
<div className="flex flex-col gap-2">
<MultiSelect
disabled={!recipient}
required={!!recipient}
{...register("recipient")}
label="recipient"
items={recipient ?? []}
placeholder="Select recipient"
name="recipient"
/>
</div>
<div className="flex flex-col gap-2">
<MultiSelect
required
{...register("channels")}
label="channels"
items={channels ?? []}
placeholder="Select channels"
name="channels"
/>
</div>
<div className="flex flex-col gap-2">
<InputGroup
required
{...register("link", {
required: { value: true, message: FormErrorMessages.required },
pattern: {
value: REGEX.URL,
message: FormErrorMessages.invalidPattern,
},
})}
name="link"
label="Link"
type="url"
placeholder="Enter link"
/>
{errors.link && (
<p className="mt-1 text-sm text-red-500">{errors.link.message}</p>
)}
</div>
<div className="flex flex-col gap-2">
<Select
required
{...register("priority", {
required: {
value: true,
message: FormErrorMessages.required,
},
})}
items={priorities}
label="Priority"
placeholder="Select priority"
name="priority"
/>
{errors.priority && (
<p className="mt-1 text-sm text-red-500">
{errors.priority.message}
</p>
)}
</div>
<div className="flex flex-col gap-2">
<Select
required
{...register("type", {
required: {
value: true,
message: FormErrorMessages.required,
},
})}
items={types}
label="type"
placeholder="Select type"
name="type"
/>
{errors.type && (
<p className="mt-1 text-sm text-red-500">{errors.type.message}</p>
)}
</div>
<div className="flex flex-col gap-2 col-span-2">
<TextAreaGroup
{...register("description")}
label="Description"
name="description"
placeholder="Enter description"
/>
</div>
<FormFooter />
</ShowcaseSection>
</form>
);
};
export default CreateNotificationForm;
@@ -0,0 +1,97 @@
"use client";
import {
NotificationChannels,
NotificationPriorities,
NotificationTargetGroups,
NotificationTypes,
} from "@/constants/enums";
import {
useCompanyFullList,
useGetAllCustomers,
useGetUsersQuery,
} from "@/hooks/queries";
import { useCreateNotificationQuery } from "@/hooks/queries/useNotificationQueries";
import { TCreateNotificationCredentials } from "@/lib/api/types/NotificationHistory";
import { ILabelValue } from "@/types/shared";
import { getEnumAsArray } from "@/utils/shared";
import { useRouter } from "next/navigation";
import { ChangeEvent, useState } from "react";
import { SubmitHandler, useForm } from "react-hook-form";
const useCreateNotificationFormPresenter = () => {
const [recipient, setRecipient] = useState<ILabelValue[] | null>(null);
const {
register,
formState: { errors },
handleSubmit,
watch,
setValue,
reset,
control,
} = useForm<TCreateNotificationCredentials>();
const router = useRouter();
const { data: companies } = useCompanyFullList();
const { data: users } = useGetUsersQuery();
const { data: customers } = useGetAllCustomers();
const { mutate } = useCreateNotificationQuery(() => {
router.back();
reset();
});
const onSubmit: SubmitHandler<TCreateNotificationCredentials> = (data) => {
mutate({ credentials: data });
};
const handleCancel = () => {
reset();
router.back();
};
const onSelectTarget = (event: ChangeEvent<HTMLSelectElement>): void => {
const type = event.target.value as NotificationTargetGroups;
const recipientMaps: Partial<
Record<NotificationTargetGroups, Array<ILabelValue> | null>
> = {
[NotificationTargetGroups.SPECIFIC_COMPANY]:
companies?.data.companies?.map((company) => ({
label: company.name,
value: company.id,
})) ?? null,
[NotificationTargetGroups.SPECIFIC_ROLE]: [
{ label: "Admin", value: "admin" },
{ label: "Analyst", value: "analyst" },
],
[NotificationTargetGroups.SPECIFIC_USERS]:
users?.data.users?.map((user) => ({
label: user.full_name,
value: user.id,
})) ?? null,
[NotificationTargetGroups.SPECIFIC_CUSTOMER]:
customers?.data.customers?.map((customer) => ({
label: customer.full_name,
value: customer.id,
})) ?? null,
};
setRecipient(recipientMaps[type] ?? null);
};
return {
handleSubmit,
watch,
priorities: getEnumAsArray(NotificationPriorities),
handleCancel,
router,
setValue,
reset,
register,
errors,
onSelectTarget,
onSubmit,
control,
recipient,
targets: getEnumAsArray(NotificationTargetGroups),
types: getEnumAsArray(NotificationTypes),
channels: getEnumAsArray(NotificationChannels),
};
};
export default useCreateNotificationFormPresenter;