Adding Dropdown in Employee.

This commit is contained in:
Kartik Sharma 2025-12-06 10:11:58 +05:30
parent dbf4f5e9c8
commit 0e75a3e1c9
3 changed files with 298 additions and 278 deletions

View File

@ -17,6 +17,8 @@ import {
import showToast from "../../services/toastService";
import { useDocumentContext } from "./Documents";
import { isPending } from "@reduxjs/toolkit";
import { AppFormController, AppFormProvider } from "../../hooks/appHooks/useAppForm";
import SelectField from "../common/Forms/SelectField";
const toBase64 = (file) =>
new Promise((resolve, reject) => {
@ -72,9 +74,12 @@ const ManageDocument = ({ closeModal, Document_Entity, Entity }) => {
handleSubmit,
watch,
setValue,
control,
reset,
formState: { errors },
} = methods;
const { mutate: UploadDocument, isPending: isUploading } = useUploadDocument(
() => {
showToast("Document Uploaded Successfully", "success");
@ -88,33 +93,33 @@ const ManageDocument = ({ closeModal, Document_Entity, Entity }) => {
}
);
const onSubmit = (data) => {
const normalizeAttachment = (attachment) => {
if (!attachment) return null;
return {
...attachment,
fileSize: Math.ceil(attachment.fileSize / 1024),
const normalizeAttachment = (attachment) => {
if (!attachment) return null;
return {
...attachment,
fileSize: Math.ceil(attachment.fileSize / 1024),
};
};
};
const payload = {
...data,
attachment: normalizeAttachment(data.attachment),
};
if (ManageDoc?.document) {
const DocumentPayload = {
...payload,
id: DocData.id,
tags: MergedTagsWithExistenStatus(data?.tags, DocData?.tags),
const payload = {
...data,
attachment: normalizeAttachment(data.attachment),
};
UpdateDocument({ documentId: DocData?.id, DocumentPayload });
} else {
const DocumentPayload = { ...payload, entityId: Entity };
UploadDocument(DocumentPayload);
}
};
if (ManageDoc?.document) {
const DocumentPayload = {
...payload,
id: DocData.id,
tags: MergedTagsWithExistenStatus(data?.tags, DocData?.tags),
};
UpdateDocument({ documentId: DocData?.id, DocumentPayload });
} else {
const DocumentPayload = { ...payload, entityId: Entity };
UploadDocument(DocumentPayload);
}
};
const {
data: DocData,
@ -134,7 +139,7 @@ const ManageDocument = ({ closeModal, Document_Entity, Entity }) => {
const { DocumentTypes, isLoading: isTypeLoading } = useDocumentTypes(
categoryId || null
);
const {data:DocumentTags} = useDocumentTags()
const { data: DocumentTags } = useDocumentTags()
// Update schema whenever document type changes
useEffect(() => {
@ -144,7 +149,7 @@ const ManageDocument = ({ closeModal, Document_Entity, Entity }) => {
(t) => String(t.id) === String(documentTypeId)
);
if (!type) return;
setSelectedType(type)
setSelectedType(type)
const newSchema = DocumentPayloadSchema({
isMandatory: type.isMandatory ?? false,
regexExpression: type.regexExpression ?? null,
@ -200,10 +205,10 @@ const ManageDocument = ({ closeModal, Document_Entity, Entity }) => {
t === "application/pdf"
? ".pdf"
: t === "image/jpeg"
? ".jpg,.jpeg"
: t === "image/png"
? ".png"
: ""
? ".jpg,.jpeg"
: t === "image/png"
? ".png"
: ""
)
.join(",") || "";
@ -231,200 +236,209 @@ const ManageDocument = ({ closeModal, Document_Entity, Entity }) => {
const isPending = isUploading || isUpdating;
return (
<div className="p-2">
<p className="fw-bold fs-6">Upload New Document</p>
<FormProvider key={documentTypeId} {...methods}>
<form onSubmit={handleSubmit(onSubmit)} className="text-start">
{/* Category */}
<div className="mb-2">
<Label htmlFor="documentCategoryId" required>Document Category</Label>
<select
{...register("documentCategoryId")}
className="form-select form-select-sm"
>
{isLoading && (
<option disabled value="">
Loading...
</option>
)}
{!isLoading && <option value="">Select Category</option>}
{DocumentCategories?.map((type) => (
<option key={type.id} value={type.id}>
{type.name}
</option>
))}
</select>
{errors.documentCategoryId && (
<div className="danger-text">
{errors.documentCategoryId.message}
</div>
)}
</div>
{/* Type */}
{categoryId && (
<div className="mb-2">
<Label htmlFor="documentTypeId" required>Document Type</Label>
<select
{...register("documentTypeId")}
className="form-select form-select-sm"
>
{isTypeLoading && (
<option disabled value="">
Loading...
</option>
<AppFormProvider {...methods}>
<div className="p-2">
<h5 className="">Upload New Document</h5>
<FormProvider key={documentTypeId} {...methods}>
<form onSubmit={handleSubmit(onSubmit)} className="text-start">
{/* Category */}
<div className="col-12 col-md-12 mb-2 mb-md-4">
<AppFormController
name="documentCategoryId"
control={control}
render={({ field }) => (
<SelectField
label="Document Category"
options={DocumentCategories ?? []}
placeholder="Select Category"
required
labelKeyKey="name"
valueKeyKey="id"
value={field.value}
onChange={field.onChange}
isLoading={isLoading}
className="m-0"
/>
)}
{DocumentTypes?.map((type) => (
<option key={type.id} value={type.id}>
{type.name}
</option>
))}
</select>
{errors.documentTypeId && (
<div className="danger-text">
{errors.documentTypeId.message}
</div>
/>
{errors.documentCategoryId && (
<small className="danger-text">
{errors.documentCategoryId.message}
</small>
)}
</div>
)}
{/* Document ID */}
<div className="mb-2">
<label
htmlFor="documentId"
required={selectedType?.isMandatory ?? false}
>
Document ID
</label>
<input
type="text"
className="form-control form-control-sm"
{...register("documentId")}
/>
{errors.documentId && (
<div className="danger-text">{errors.documentId.message}</div>
)}
</div>
{/* Document Name */}
<div className="mb-2">
<Label htmlFor="name" required>
Document Name
</Label>
<input
type="text"
className="form-control form-control-sm"
{...register("name")}
/>
{errors.name && (
<div className="danger-text">{errors.name.message}</div>
)}
</div>
{/* Upload */}
<div className="row my-2">
<div className="col-md-12">
<Label htmlFor="attachment" required>Upload Document</Label>
<div
className="border border-secondary border-dashed rounded p-4 text-center bg-textMuted position-relative"
style={{ cursor: "pointer" }}
onClick={() => document.getElementById("attachment").click()}
>
<i className="bx bx-cloud-upload d-block bx-lg"></i>
<span className="text-muted d-block">
Click to select or click here to browse
</span>
<small className="text-muted">
({selectedType?.allowedContentType || "PDF/JPG/PNG"}, max{" "}
{selectedType?.maxSizeAllowedInMB ?? 25}MB)
</small>
<input
type="file"
id="attachment"
accept={selectedType?.allowedContentType}
style={{ display: "none" }}
onChange={(e) => {
onFileChange(e);
e.target.value = ""; // reset input
}}
{/* Type */}
{categoryId && (
<div className="col-12 col-md-12 mb-2 mb-md-4">
<AppFormController
name="documentTypeId"
control={control}
render={({ field }) => (
<SelectField
label="Document Type"
options={DocumentTypes ?? []}
placeholder="Select Document Type"
required
labelKeyKey="name"
valueKeyKey="id"
value={field.value}
onChange={field.onChange}
isLoading={isTypeLoading}
className="m-0"
/>
)}
/>
{errors.documentTypeId && (
<small className="danger-text">
{errors.documentTypeId.message}
</small>
)}
</div>
{errors.attachment && (
<small className="danger-text">
{errors.attachment.message
? errors.attachment.message
: errors.attachment.fileName?.message ||
)}
{/* Document ID */}
<div className="mb-4">
<label
htmlFor="documentId"
required={selectedType?.isMandatory ?? false}
>
Document ID
</label>
<input
type="text"
className="form-control "
{...register("documentId")}
/>
{errors.documentId && (
<div className="danger-text">{errors.documentId.message}</div>
)}
</div>
{/* Document Name */}
<div className="mb-2">
<Label htmlFor="name" required>
Document Name
</Label>
<input
type="text"
className="form-control "
{...register("name")}
/>
{errors.name && (
<div className="danger-text">{errors.name.message}</div>
)}
</div>
{/* Upload */}
<div className="row my-4">
<div className="col-md-12">
<Label htmlFor="attachment" required>Upload Document</Label>
<div
className="border border-secondary border-dashed rounded p-4 text-center bg-textMuted position-relative"
style={{ cursor: "pointer" }}
onClick={() => document.getElementById("attachment").click()}
>
<i className="bx bx-cloud-upload d-block bx-lg"></i>
<span className="text-muted d-block">
Click to select or click here to browse
</span>
<small className="text-muted">
({selectedType?.allowedContentType || "PDF/JPG/PNG"}, max{" "}
{selectedType?.maxSizeAllowedInMB ?? 25}MB)
</small>
<input
type="file"
id="attachment"
accept={selectedType?.allowedContentType}
style={{ display: "none" }}
onChange={(e) => {
onFileChange(e);
e.target.value = ""; // reset input
}}
/>
</div>
{errors.attachment && (
<small className="danger-text">
{errors.attachment.message
? errors.attachment.message
: errors.attachment.fileName?.message ||
errors.attachment.base64Data?.message ||
errors.attachment.contentType?.message ||
errors.attachment.fileSize?.message}
</small>
)}
</small>
)}
{file?.base64Data && (
<div className="d-flex justify-content-between text-start p-1 mt-2">
<div>
<span className="mb-0 text-secondary small d-block">
{file.fileName}
</span>
<span className="text-body-secondary small d-block">
{(file.fileSize / 1024).toFixed(1)} KB
</span>
{file?.base64Data && (
<div className="d-flex justify-content-between text-start p-1 mt-2">
<div>
<span className="mb-0 text-secondary small d-block">
{file.fileName}
</span>
<span className="text-body-secondary small d-block">
{(file.fileSize / 1024).toFixed(1)} KB
</span>
</div>
<i
className="bx bx-trash bx-sm cursor-pointer text-danger"
onClick={removeFile}
></i>
</div>
<i
className="bx bx-trash bx-sm cursor-pointer text-danger"
onClick={removeFile}
></i>
</div>
)}
</div>
</div>
<div className="mb-4">
<TagInput name="tags" label="Tags" placeholder="Tags.." options={DocumentTags} />
{errors.tags && (
<small className="danger-text">{errors.tags.message}</small>
)}
</div>
</div>
<div className="mb-2">
<TagInput name="tags" label="Tags" placeholder="Tags.." options={DocumentTags} />
{errors.tags && (
<small className="danger-text">{errors.tags.message}</small>
)}
</div>
{/* Description */}
<div className="mb-2">
<Label htmlFor="description" required>Description</Label>
<textarea
rows="2"
className="form-control"
{...register("description")}
></textarea>
{errors.description && (
<div className="danger-text">{errors.description.message}</div>
)}
</div>
{/* Description */}
<div className="mb-4">
<Label htmlFor="description" required>Description</Label>
<textarea
rows="2"
className="form-control"
{...register("description")}
></textarea>
{errors.description && (
<div className="danger-text">{errors.description.message}</div>
)}
</div>
{/* Buttons */}
<div className="d-flex justify-content-end gap-3 mt-4">
<button
type="reset"
className="btn btn-label-secondary btn-sm"
disabled={isPending}
onClick={closeModal}
>
Cancel
</button>
<button
type="submit"
className="btn btn-primary btn-sm"
disabled={isPending}
>
{isPending ? "Please Wait..." : " Submit"}
</button>
</div>
</form>
</FormProvider>
</div>
{/* Buttons */}
<div className="d-flex justify-content-end gap-3 mt-4">
<button
type="reset"
className="btn btn-label-secondary btn-sm"
disabled={isPending}
onClick={closeModal}
>
Cancel
</button>
<button
type="submit"
className="btn btn-primary btn-sm"
disabled={isPending}
>
{isPending ? "Please Wait..." : " Submit"}
</button>
</div>
</form>
</FormProvider>
</div>
</AppFormProvider>
);
};

View File

@ -517,39 +517,35 @@ const ManageEmployee = ({ employeeId, onClosed }) => {
</div>
</div>
<div className="row mb-3">
<div className="col-sm-4">
<Label className="form-text text-start" required>
Official Designation
</Label>
<div className="input-group">
<select
className="form-select"
{...register("jobRoleId")}
id="jobRoleId"
aria-label=""
>
<option disabled value="">
Select Role
</option>
{[...job_role]
.sort((a, b) => a?.name?.localeCompare(b.name))
.map((item) => (
<option value={item?.id} key={item.id}>
{item?.name}{" "}
</option>
))}
</select>
</div>
<div className="col-sm-4 text-start">
<AppFormController
name="jobRoleId"
control={control}
render={({ field }) => (
<SelectField
label="Official Designation"
required
options={[...job_role].sort((a, b) =>
a?.name?.localeCompare(b?.name)
)}
placeholder="Select Role"
labelKeyKey="name"
valueKeyKey="id"
value={field.value}
onChange={field.onChange}
className="m-0"
/>
)}
/>
{errors.jobRoleId && (
<div
className="danger-text text-start"
style={{ fontSize: "12px" }}
>
<div className="danger-text text-start" style={{ fontSize: "12px" }}>
{errors.jobRoleId.message}
</div>
)}
</div>
<div className="col-sm-4">
<div className="col-sm-4 mt-n1">
<Label className="form-text text-start" required>
Emergency Contact Person
</Label>
@ -570,7 +566,7 @@ const ManageEmployee = ({ employeeId, onClosed }) => {
</div>
)}
</div>
<div className="col-sm-4">
<div className="col-sm-4 mt-n1">
<Label className="form-text text-start" required>
Emergency Phone Number
</Label>

View File

@ -10,6 +10,8 @@ import { useForm, Controller } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import showToast from "../../services/toastService";
import { AppFormController } from "../../hooks/appHooks/useAppForm";
import SelectField from "../common/Forms/SelectField";
export const ProjectPermissionSchema = z.object({
employeeId: z.string().min(1, "Employee is required"),
@ -46,26 +48,26 @@ const ProjectPermission = () => {
);
useEffect(() => {
if (!selectedEmployee) return;
if (!selectedEmployee) return;
const enabledPerms =
const enabledPerms =
selectedEmpPermissions?.permissions
?.filter((perm) => perm.isEnabled)
?.map((perm) => perm.id) || [];
setValue("selectedPermissions", enabledPerms, { shouldValidate: true });
}, [selectedEmpPermissions, setValue, selectedEmployee]);
const selectedPermissions = watch("selectedPermissions") || [];
const existingEnabledIds =
selectedEmpPermissions?.permissions
?.filter((perm) => perm.isEnabled)
?.map((perm) => perm.id) || [];
?.filter((p) => p.isEnabled)
?.map((p) => p.id) || [];
setValue("selectedPermissions", enabledPerms, { shouldValidate: true });
}, [selectedEmpPermissions, setValue, selectedEmployee]);
const selectedPermissions = watch("selectedPermissions") || [];
const existingEnabledIds =
selectedEmpPermissions?.permissions
?.filter((p) => p.isEnabled)
?.map((p) => p.id) || [];
const hasChanges =
selectedPermissions.length !== existingEnabledIds.length ||
selectedPermissions.some((id) => !existingEnabledIds.includes(id));
const hasChanges =
selectedPermissions.length !== existingEnabledIds.length ||
selectedPermissions.some((id) => !existingEnabledIds.includes(id));
const { mutate: updatePermission, isPending } =
useUpdateProjectLevelEmployeePermission();
@ -115,35 +117,42 @@ const hasChanges =
<form className="row" onSubmit={handleSubmit(onSubmit)}>
<div className="d-flex justify-content-between align-items-end gap-2 mb-3">
<div className="text-start d-flex align-items-center gap-2">
<div className="d-block">
{/* <div className="d-block">
<label className="form-label">Select Employee</label>
</div>
<div className="d-block">
{" "}
<select
className="form-select form-select-sm"
{...register("employeeId")}
disabled={isPending}
>
{loading ? (
<option value="">Loading...</option>
) : (
<>
<option value="">-- Select Employee --</option>
{[...employees]
?.sort((a, b) =>
`${a?.firstName} ${a?.firstName}`?.localeCompare(
`${b?.firstName} ${b?.lastName}`
</div> */}
<div className="d-block flex-grow-1">
<AppFormController
name="employeeId"
control={control}
render={({ field }) => (
<SelectField
label="Select Employee"
options={
employees
?.sort((a, b) =>
`${a?.firstName} ${a?.lastName}`.localeCompare(
`${b?.firstName} ${b?.lastName}`
)
)
)
?.map((emp) => (
<option key={emp.id} value={emp.id}>
{emp.firstName} {emp.lastName}
</option>
))}
</>
?.map((emp) => ({
id: emp.id,
name: `${emp.firstName} ${emp.lastName}`,
})) ?? []
}
placeholder="-- Select Employee --"
required
labelKeyKey="name"
valueKeyKey="id"
value={field.value}
onChange={field.onChange}
isLoading={loading}
disabled={isPending}
className="m-0"
/>
)}
</select>
/>
{errors.employeeId && (
<div className="d-block text-danger small">
{errors.employeeId.message}
@ -152,6 +161,7 @@ const hasChanges =
</div>
</div>
<div className="mt-3 text-end">
{hasChanges && (
<button