Compare commits
2 Commits
main
...
Kartik_Tas
| Author | SHA1 | Date | |
|---|---|---|---|
| a792f55340 | |||
| 451a6b115b |
223
src/components/master/CreateActivities.jsx
Normal file
223
src/components/master/CreateActivities.jsx
Normal file
@ -0,0 +1,223 @@
|
|||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import {
|
||||||
|
useCreateActivities,
|
||||||
|
useGetServices,
|
||||||
|
useGetActivityGroups,
|
||||||
|
} from "../../hooks/masterHook/useMaster";
|
||||||
|
|
||||||
|
// ✅ Schema validation
|
||||||
|
const schema = z.object({
|
||||||
|
serviceId: z.string().min(1, { message: "Service selection is required" }),
|
||||||
|
activityGroupId: z
|
||||||
|
.string()
|
||||||
|
.min(1, { message: "Activity Group selection is required" }),
|
||||||
|
activityName: z.string().min(1, { message: "Activity Name is required" }),
|
||||||
|
unitOfMeasurement: z
|
||||||
|
.string()
|
||||||
|
.min(1, { message: "Unit of Measurement is required" }),
|
||||||
|
description: z
|
||||||
|
.string()
|
||||||
|
.min(1, { message: "Description is required" })
|
||||||
|
.max(255, { message: "Description cannot exceed 255 characters" }),
|
||||||
|
});
|
||||||
|
|
||||||
|
const CreateActivities = ({ onClose }) => {
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
handleSubmit,
|
||||||
|
formState: { errors },
|
||||||
|
reset,
|
||||||
|
watch,
|
||||||
|
} = useForm({
|
||||||
|
resolver: zodResolver(schema),
|
||||||
|
defaultValues: {
|
||||||
|
serviceId: "",
|
||||||
|
activityGroupId: "",
|
||||||
|
activityName: "",
|
||||||
|
unitOfMeasurement: "",
|
||||||
|
description: "",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const [descriptionLength, setDescriptionLength] = useState(0);
|
||||||
|
const maxDescriptionLength = 255;
|
||||||
|
|
||||||
|
// ✅ Watch dropdown values
|
||||||
|
const selectedService = watch("serviceId");
|
||||||
|
const selectedActivityGroup = watch("activityGroupId");
|
||||||
|
|
||||||
|
// ✅ Mutation Hook
|
||||||
|
const createActivityMutation = useCreateActivities(() => {
|
||||||
|
resetForm();
|
||||||
|
onClose();
|
||||||
|
});
|
||||||
|
|
||||||
|
// ✅ Fetch services
|
||||||
|
const { data: services = [], isLoading: servicesLoading } = useGetServices();
|
||||||
|
|
||||||
|
// ✅ Fetch activity groups based on selected service
|
||||||
|
const { data: activityGroups = [], isLoading: groupsLoading } =
|
||||||
|
useGetActivityGroups(selectedService, {
|
||||||
|
enabled: !!selectedService,
|
||||||
|
});
|
||||||
|
|
||||||
|
const onSubmit = (data) => {
|
||||||
|
createActivityMutation.mutate({
|
||||||
|
activityName: data.activityName,
|
||||||
|
description: data.description,
|
||||||
|
serviceId: data.serviceId,
|
||||||
|
activityGroupId: data.activityGroupId,
|
||||||
|
unitOfMeasurement: data.unitOfMeasurement,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
reset({
|
||||||
|
serviceId: "",
|
||||||
|
activityGroupId: "",
|
||||||
|
activityName: "",
|
||||||
|
unitOfMeasurement: "",
|
||||||
|
description: "",
|
||||||
|
});
|
||||||
|
setDescriptionLength(0);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => resetForm();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form className="row g-2" onSubmit={handleSubmit(onSubmit)}>
|
||||||
|
{/* Service Dropdown */}
|
||||||
|
<div className="col-12 col-md-12">
|
||||||
|
<label className="form-label">Service</label>
|
||||||
|
<select
|
||||||
|
{...register("serviceId")}
|
||||||
|
className={`form-select ${errors.serviceId ? "is-invalids" : ""}`}
|
||||||
|
disabled={servicesLoading}
|
||||||
|
>
|
||||||
|
<option value="">Select a Service</option>
|
||||||
|
{services.map((service) => (
|
||||||
|
<option key={service.id} value={service.id}>
|
||||||
|
{service.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
{errors.serviceId && (
|
||||||
|
<p className="text-danger">{errors.serviceId.message}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Activity Group Dropdown */}
|
||||||
|
{selectedService && (
|
||||||
|
<div className="col-12 col-md-12">
|
||||||
|
<label className="form-label">Activity Group</label>
|
||||||
|
<select
|
||||||
|
{...register("activityGroupId")}
|
||||||
|
className={`form-select ${
|
||||||
|
errors.activityGroupId ? "is-invalids" : ""
|
||||||
|
}`}
|
||||||
|
disabled={groupsLoading}
|
||||||
|
>
|
||||||
|
<option value="">Select an Activity Group</option>
|
||||||
|
{activityGroups.map((group) => (
|
||||||
|
<option key={group.id} value={group.id}>
|
||||||
|
{group.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
{errors.activityGroupId && (
|
||||||
|
<p className="text-danger">{errors.activityGroupId.message}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Activity Name + Unit of Measurement + Description */}
|
||||||
|
{selectedActivityGroup && (
|
||||||
|
<>
|
||||||
|
{/* Activity Name */}
|
||||||
|
<div className="col-12 col-md-12">
|
||||||
|
<label className="form-label">Activity Name</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
{...register("activityName")}
|
||||||
|
className={`form-control ${
|
||||||
|
errors.activityName ? "is-invalids" : ""
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
{errors.activityName && (
|
||||||
|
<p className="text-danger">{errors.activityName.message}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Unit of Measurement */}
|
||||||
|
<div className="col-12 col-md-12">
|
||||||
|
<label className="form-label">Unit of Measurement</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
{...register("unitOfMeasurement")}
|
||||||
|
className={`form-control ${
|
||||||
|
errors.unitOfMeasurement ? "is-invalids" : ""
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
{errors.unitOfMeasurement && (
|
||||||
|
<p className="text-danger">{errors.unitOfMeasurement.message}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Description */}
|
||||||
|
<div className="col-12 col-md-12">
|
||||||
|
<label className="form-label" htmlFor="description">
|
||||||
|
Description
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
|
rows="3"
|
||||||
|
{...register("description")}
|
||||||
|
className={`form-control ${
|
||||||
|
errors.description ? "is-invalids" : ""
|
||||||
|
}`}
|
||||||
|
onChange={(e) => {
|
||||||
|
setDescriptionLength(e.target.value.length);
|
||||||
|
register("description").onChange(e);
|
||||||
|
}}
|
||||||
|
></textarea>
|
||||||
|
<div className="text-end small text-muted">
|
||||||
|
{maxDescriptionLength - descriptionLength} characters left
|
||||||
|
</div>
|
||||||
|
{errors.description && (
|
||||||
|
<p className="text-danger">{errors.description.message}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Buttons */}
|
||||||
|
<div className="col-12 text-center">
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="btn btn-sm btn-primary me-3"
|
||||||
|
disabled={createActivityMutation.isPending}
|
||||||
|
>
|
||||||
|
{createActivityMutation.isPending
|
||||||
|
? "Please Wait..."
|
||||||
|
: "Submit"}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-sm btn-label-secondary"
|
||||||
|
onClick={() => {
|
||||||
|
resetForm();
|
||||||
|
onClose();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CreateActivities;
|
||||||
159
src/components/master/CreateActivityGroup.jsx
Normal file
159
src/components/master/CreateActivityGroup.jsx
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { useCreateActivityGroup, useGetServices } from "../../hooks/masterHook/useMaster";
|
||||||
|
|
||||||
|
// ✅ Schema validation
|
||||||
|
const schema = z.object({
|
||||||
|
name: z.string().min(1, { message: "Activity Group Name is required" }),
|
||||||
|
description: z
|
||||||
|
.string()
|
||||||
|
.min(1, { message: "Description is required" })
|
||||||
|
.max(255, { message: "Description cannot exceed 255 characters" }),
|
||||||
|
serviceId: z.string().min(1, { message: "Service selection is required" }),
|
||||||
|
});
|
||||||
|
|
||||||
|
const CreateActivityGroup = ({ onClose }) => {
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
handleSubmit,
|
||||||
|
formState: { errors },
|
||||||
|
reset,
|
||||||
|
watch,
|
||||||
|
} = useForm({
|
||||||
|
resolver: zodResolver(schema),
|
||||||
|
defaultValues: {
|
||||||
|
name: "",
|
||||||
|
description: "",
|
||||||
|
serviceId: "",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const [descriptionLength, setDescriptionLength] = useState(0);
|
||||||
|
const maxDescriptionLength = 255;
|
||||||
|
|
||||||
|
// ✅ Watch serviceId value
|
||||||
|
const selectedService = watch("serviceId");
|
||||||
|
|
||||||
|
// ✅ Mutation Hook
|
||||||
|
const createActivityGroupMutation = useCreateActivityGroup(() => {
|
||||||
|
resetForm();
|
||||||
|
onClose();
|
||||||
|
});
|
||||||
|
|
||||||
|
// ✅ Fetch services for dropdown
|
||||||
|
const { data: services = [], isLoading: servicesLoading } = useGetServices();
|
||||||
|
|
||||||
|
const onSubmit = (data) => {
|
||||||
|
createActivityGroupMutation.mutate({
|
||||||
|
name: data.name,
|
||||||
|
description: data.description,
|
||||||
|
serviceId: data.serviceId, // ✅ attach serviceId
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
reset({
|
||||||
|
name: "",
|
||||||
|
description: "",
|
||||||
|
serviceId: "",
|
||||||
|
});
|
||||||
|
setDescriptionLength(0);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => resetForm();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form className="row g-2" onSubmit={handleSubmit(onSubmit)}>
|
||||||
|
{/* Service Dropdown */}
|
||||||
|
<div className="col-12 col-md-12">
|
||||||
|
<label className="form-label">Service</label>
|
||||||
|
<select
|
||||||
|
{...register("serviceId")}
|
||||||
|
className={`form-select ${errors.serviceId ? "is-invalids" : ""}`}
|
||||||
|
disabled={servicesLoading}
|
||||||
|
>
|
||||||
|
<option value="">Select a Service</option>
|
||||||
|
{services.map((service) => (
|
||||||
|
<option key={service.id} value={service.id}>
|
||||||
|
{service.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
{errors.serviceId && (
|
||||||
|
<p className="text-danger">{errors.serviceId.message}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Render rest only when service is selected */}
|
||||||
|
{selectedService && (
|
||||||
|
<>
|
||||||
|
{/* Activity Group Name */}
|
||||||
|
<div className="col-12 col-md-12">
|
||||||
|
<label className="form-label">Activity Group Name</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
{...register("name")}
|
||||||
|
className={`form-control ${errors.name ? "is-invalids" : ""}`}
|
||||||
|
/>
|
||||||
|
{errors.name && (
|
||||||
|
<p className="text-danger">{errors.name.message}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Description */}
|
||||||
|
<div className="col-12 col-md-12">
|
||||||
|
<label className="form-label" htmlFor="description">
|
||||||
|
Description
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
|
rows="3"
|
||||||
|
{...register("description")}
|
||||||
|
className={`form-control ${
|
||||||
|
errors.description ? "is-invalids" : ""
|
||||||
|
}`}
|
||||||
|
onChange={(e) => {
|
||||||
|
setDescriptionLength(e.target.value.length);
|
||||||
|
register("description").onChange(e);
|
||||||
|
}}
|
||||||
|
></textarea>
|
||||||
|
<div className="text-end small text-muted">
|
||||||
|
{maxDescriptionLength - descriptionLength} characters left
|
||||||
|
</div>
|
||||||
|
{errors.description && (
|
||||||
|
<p className="text-danger">{errors.description.message}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Buttons */}
|
||||||
|
<div className="col-12 text-center">
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="btn btn-sm btn-primary me-3"
|
||||||
|
disabled={createActivityGroupMutation.isPending}
|
||||||
|
>
|
||||||
|
{createActivityGroupMutation.isPending
|
||||||
|
? "Please Wait..."
|
||||||
|
: "Submit"}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-sm btn-label-secondary"
|
||||||
|
onClick={() => {
|
||||||
|
resetForm();
|
||||||
|
onClose();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CreateActivityGroup;
|
||||||
113
src/components/master/CreateServices.jsx
Normal file
113
src/components/master/CreateServices.jsx
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { useCreateService } from "../../hooks/masterHook/useMaster";
|
||||||
|
|
||||||
|
const schema = z.object({
|
||||||
|
name: z.string().min(1, { message: "Service Name is required" }),
|
||||||
|
description: z
|
||||||
|
.string()
|
||||||
|
.min(1, { message: "Description is required" })
|
||||||
|
.max(255, { message: "Description cannot exceed 255 characters" }),
|
||||||
|
});
|
||||||
|
|
||||||
|
const CreateServices = ({ onClose }) => {
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
handleSubmit,
|
||||||
|
formState: { errors },
|
||||||
|
reset,
|
||||||
|
} = useForm({
|
||||||
|
resolver: zodResolver(schema),
|
||||||
|
defaultValues: {
|
||||||
|
name: "",
|
||||||
|
description: "",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const [descriptionLength, setDescriptionLength] = useState(0);
|
||||||
|
const maxDescriptionLength = 255;
|
||||||
|
|
||||||
|
// ✅ Use mutation hook
|
||||||
|
const createServiceMutation = useCreateService(() => {
|
||||||
|
resetForm();
|
||||||
|
onClose();
|
||||||
|
});
|
||||||
|
|
||||||
|
const onSubmit = (data) => {
|
||||||
|
createServiceMutation.mutate({
|
||||||
|
name: data.name,
|
||||||
|
description: data.description,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
reset({
|
||||||
|
name: "",
|
||||||
|
description: "",
|
||||||
|
});
|
||||||
|
setDescriptionLength(0);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => resetForm();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form className="row g-2" onSubmit={handleSubmit(onSubmit)}>
|
||||||
|
<div className="col-12 col-md-12">
|
||||||
|
<label className="form-label">Service Name</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
{...register("name")}
|
||||||
|
className={`form-control ${errors.name ? "is-invalids" : ""}`}
|
||||||
|
/>
|
||||||
|
{errors.name && <p className="text-danger">{errors.name.message}</p>}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="col-12 col-md-12">
|
||||||
|
<label className="form-label" htmlFor="description">
|
||||||
|
Description
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
|
rows="3"
|
||||||
|
{...register("description")}
|
||||||
|
className={`form-control ${errors.description ? "is-invalids" : ""}`}
|
||||||
|
onChange={(e) => {
|
||||||
|
setDescriptionLength(e.target.value.length);
|
||||||
|
register("description").onChange(e);
|
||||||
|
}}
|
||||||
|
></textarea>
|
||||||
|
<div className="text-end small text-muted">
|
||||||
|
{maxDescriptionLength - descriptionLength} characters left
|
||||||
|
</div>
|
||||||
|
{errors.description && (
|
||||||
|
<p className="text-danger">{errors.description.message}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="col-12 text-center">
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="btn btn-sm btn-primary me-3"
|
||||||
|
disabled={createServiceMutation.isPending}
|
||||||
|
>
|
||||||
|
{createServiceMutation.isPending ? "Please Wait..." : "Submit"}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-sm btn-label-secondary"
|
||||||
|
onClick={() => {
|
||||||
|
resetForm();
|
||||||
|
onClose();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CreateServices;
|
||||||
123
src/components/master/EditServices.jsx
Normal file
123
src/components/master/EditServices.jsx
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { useUpdateService } from "../../hooks/masterHook/useMaster"; // ✅ your custom hook
|
||||||
|
|
||||||
|
// Validation schema
|
||||||
|
const schema = z.object({
|
||||||
|
name: z.string().min(1, { message: "Service Name is required" }),
|
||||||
|
description: z
|
||||||
|
.string()
|
||||||
|
.min(1, { message: "Description is required" })
|
||||||
|
.max(255, { message: "Description cannot exceed 255 characters" }),
|
||||||
|
});
|
||||||
|
|
||||||
|
const EditServices = ({ data, onClose }) => {
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
handleSubmit,
|
||||||
|
formState: { errors },
|
||||||
|
reset,
|
||||||
|
watch,
|
||||||
|
} = useForm({
|
||||||
|
resolver: zodResolver(schema),
|
||||||
|
defaultValues: {
|
||||||
|
name: data?.name || "",
|
||||||
|
description: data?.description || "",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const [descriptionLength, setDescriptionLength] = useState(
|
||||||
|
data?.description?.length || 0
|
||||||
|
);
|
||||||
|
const maxDescriptionLength = 255;
|
||||||
|
|
||||||
|
// ✅ hook for update
|
||||||
|
const { mutate: updateService, isPending } = useUpdateService(() => {
|
||||||
|
onClose(); // close modal on success
|
||||||
|
});
|
||||||
|
|
||||||
|
const onSubmit = (formData) => {
|
||||||
|
|
||||||
|
const payload= {
|
||||||
|
id: data?.id,
|
||||||
|
name: formData.name,
|
||||||
|
description: formData.description,
|
||||||
|
};
|
||||||
|
updateService({ id: data?.id, payload });
|
||||||
|
// });
|
||||||
|
};
|
||||||
|
|
||||||
|
// Reset form when modal opens with fresh data
|
||||||
|
useEffect(() => {
|
||||||
|
reset({
|
||||||
|
name: data?.name,
|
||||||
|
description: data?.description,
|
||||||
|
});
|
||||||
|
setDescriptionLength(data?.description?.length || 0);
|
||||||
|
}, [data, reset]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form className="row g-2" onSubmit={handleSubmit(onSubmit)}>
|
||||||
|
{/* Service Name */}
|
||||||
|
<div className="col-12">
|
||||||
|
<label className="form-label">Service Name</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
{...register("name")}
|
||||||
|
className={`form-control ${errors.name ? "is-invalid" : ""}`}
|
||||||
|
/>
|
||||||
|
{errors.name && <p className="text-danger">{errors.name.message}</p>}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Description */}
|
||||||
|
<div className="col-12">
|
||||||
|
<label className="form-label" htmlFor="description">
|
||||||
|
Description
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
|
rows="3"
|
||||||
|
{...register("description")}
|
||||||
|
className={`form-control ${errors.description ? "is-invalid" : ""}`}
|
||||||
|
onChange={(e) => {
|
||||||
|
setDescriptionLength(e.target.value.length);
|
||||||
|
register("description").onChange(e); // ✅ keep RHF sync
|
||||||
|
}}
|
||||||
|
></textarea>
|
||||||
|
<div className="text-end small text-muted">
|
||||||
|
{maxDescriptionLength - descriptionLength} characters left
|
||||||
|
</div>
|
||||||
|
{errors.description && (
|
||||||
|
<p className="text-danger">{errors.description.message}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Buttons */}
|
||||||
|
<div className="col-12 text-center">
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="btn btn-sm btn-primary me-3"
|
||||||
|
disabled={isPending}
|
||||||
|
>
|
||||||
|
{isPending ? "Please Wait..." : "Submit"}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-sm btn-label-secondary"
|
||||||
|
onClick={() => {
|
||||||
|
reset({
|
||||||
|
name: data?.name || "",
|
||||||
|
description: data?.description || "",
|
||||||
|
});
|
||||||
|
onClose();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EditServices;
|
||||||
@ -4,8 +4,8 @@ import DeleteMaster from "./DeleteMaster";
|
|||||||
import EditRole from "./EditRole";
|
import EditRole from "./EditRole";
|
||||||
import CreateJobRole from "./CreateJobRole";
|
import CreateJobRole from "./CreateJobRole";
|
||||||
import EditJobRole from "./EditJobRole";
|
import EditJobRole from "./EditJobRole";
|
||||||
import CreateActivity from "./CreateActivity";
|
import CreateActivity from "./CreateServices";
|
||||||
import EditActivity from "./EditActivity";
|
import EditActivity from "./EditServices";
|
||||||
import ConfirmModal from "../common/ConfirmModal";
|
import ConfirmModal from "../common/ConfirmModal";
|
||||||
import { MasterRespository } from "../../repositories/MastersRepository";
|
import { MasterRespository } from "../../repositories/MastersRepository";
|
||||||
import { cacheData, getCachedData } from "../../slices/apiDataManager";
|
import { cacheData, getCachedData } from "../../slices/apiDataManager";
|
||||||
@ -20,6 +20,10 @@ import { useDeleteMasterItem } from "../../hooks/masterHook/useMaster";
|
|||||||
import ManageExpenseType from "./ManageExpenseType";
|
import ManageExpenseType from "./ManageExpenseType";
|
||||||
import ManagePaymentMode from "./ManagePaymentMode";
|
import ManagePaymentMode from "./ManagePaymentMode";
|
||||||
import ManageExpenseStatus from "./ManageExpenseStatus";
|
import ManageExpenseStatus from "./ManageExpenseStatus";
|
||||||
|
import CreateServices from "./CreateServices";
|
||||||
|
import EditServices from "./EditServices";
|
||||||
|
import CreateActivityGroup from "./CreateActivityGroup";
|
||||||
|
import CreateActivities from "./CreateActivities";
|
||||||
|
|
||||||
|
|
||||||
const MasterModal = ({ modaldata, closeModal }) => {
|
const MasterModal = ({ modaldata, closeModal }) => {
|
||||||
@ -83,8 +87,10 @@ const MasterModal = ({ modaldata, closeModal }) => {
|
|||||||
"Edit-Application Role": <EditRole master={modaldata} onClose={closeModal} />,
|
"Edit-Application Role": <EditRole master={modaldata} onClose={closeModal} />,
|
||||||
"Job Role": <CreateJobRole onClose={closeModal} />,
|
"Job Role": <CreateJobRole onClose={closeModal} />,
|
||||||
"Edit-Job Role": <EditJobRole data={item} onClose={closeModal} />,
|
"Edit-Job Role": <EditJobRole data={item} onClose={closeModal} />,
|
||||||
"Activity": <CreateActivity onClose={closeModal} />,
|
"Services": <CreateServices onClose={closeModal} />,
|
||||||
"Edit-Activity": <EditActivity activityData={item} onClose={closeModal} />,
|
"Edit-Services": <EditServices data={item} onClose={closeModal} />,
|
||||||
|
"Activity-Group": <CreateActivityGroup onClose={closeModal} />,
|
||||||
|
"Activities":<CreateActivities onClose={closeModal}/>,
|
||||||
"Work Category": <CreateWorkCategory onClose={closeModal} />,
|
"Work Category": <CreateWorkCategory onClose={closeModal} />,
|
||||||
"Edit-Work Category": <EditWorkCategory data={item} onClose={closeModal} />,
|
"Edit-Work Category": <EditWorkCategory data={item} onClose={closeModal} />,
|
||||||
"Contact Category": <CreateCategory data={item} onClose={closeModal} />,
|
"Contact Category": <CreateCategory data={item} onClose={closeModal} />,
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
export const mastersList = [
|
export const mastersList = [
|
||||||
{ id: 1, name: "Application Role" },
|
{ id: 1, name: "Application Role" },
|
||||||
{ id: 2, name: "Job Role" },
|
{ id: 2, name: "Job Role" },
|
||||||
{ id: 3, name: "Activity" },
|
{ id: 3, name: "Services" },
|
||||||
{ id: 4, name: "Work Category" },
|
{ id: 4, name: "Work Category" },
|
||||||
{ id: 5, name: "Contact Category" },
|
{ id: 5, name: "Contact Category" },
|
||||||
{ id: 6, name: "Contact Tag" },
|
{ id: 6, name: "Contact Tag" },
|
||||||
|
|||||||
@ -171,8 +171,12 @@ const fetchMasterData = async (masterType) => {
|
|||||||
return (await MasterRespository.getRoles()).data;
|
return (await MasterRespository.getRoles()).data;
|
||||||
case "Job Role":
|
case "Job Role":
|
||||||
return (await MasterRespository.getJobRole()).data;
|
return (await MasterRespository.getJobRole()).data;
|
||||||
case "Activity":
|
case "Services":
|
||||||
return (await MasterRespository.getActivites()).data;
|
return (await MasterRespository.getServices()).data;
|
||||||
|
case "Activity-Group":
|
||||||
|
return (await MasterRespository.getActivityGroups()).data;
|
||||||
|
case "Activities":
|
||||||
|
return (await MasterRespository.getActivities()).data;
|
||||||
case "Work Category":
|
case "Work Category":
|
||||||
return (await MasterRespository.getWorkCategory()).data;
|
return (await MasterRespository.getWorkCategory()).data;
|
||||||
case "Contact Category":
|
case "Contact Category":
|
||||||
@ -332,6 +336,126 @@ export const useUpdateApplicationRole = (onSuccessCallback) =>
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Services-------------------------------
|
||||||
|
|
||||||
|
export const useCreateService = (onSuccessCallback) => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: async (payload) => {
|
||||||
|
const resp = await MasterRespository.createService(payload);
|
||||||
|
return resp.data; // full API response
|
||||||
|
},
|
||||||
|
onSuccess: (data) => {
|
||||||
|
// Invalidate & refetch service list
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["masterData", "Services"] });
|
||||||
|
|
||||||
|
showToast(data?.message || "Service added successfully", "success");
|
||||||
|
|
||||||
|
if (onSuccessCallback) onSuccessCallback(data?.data); // pass back new service object
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
showToast(error.message || "Something went wrong", "error");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useUpdateService = (onSuccessCallback) => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: async ({ id, payload }) => {
|
||||||
|
const response = await MasterRespository.updateService(id, payload);
|
||||||
|
return response; // full response since it already has { success, message, data }
|
||||||
|
},
|
||||||
|
onSuccess: (data, variables) => {
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: ["masterData", "Services"],
|
||||||
|
});
|
||||||
|
|
||||||
|
showToast(data.message || "Service updated successfully.", "success");
|
||||||
|
|
||||||
|
if (onSuccessCallback) onSuccessCallback(data);
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
showToast(error?.message || "Something went wrong", "error");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
//Activity-group--------------------------------
|
||||||
|
export const useGetServices = () => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ["masterData", "Services"],
|
||||||
|
queryFn: async () => {
|
||||||
|
const resp = await MasterRepository.getServices();
|
||||||
|
return resp.data?.data || []; // only return array of services
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const useCreateActivityGroup = (onSuccessCallback) => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: async (payload) => {
|
||||||
|
const resp = await MasterRespository.createActivityGroup(payload);
|
||||||
|
return resp.data; // full API response
|
||||||
|
},
|
||||||
|
onSuccess: (data) => {
|
||||||
|
// Invalidate & refetch activity groups
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: ["masterData", "Activity-Group"],
|
||||||
|
});
|
||||||
|
|
||||||
|
showToast(data?.message || "Activity Group added successfully", "success");
|
||||||
|
|
||||||
|
if (onSuccessCallback) onSuccessCallback(data?.data);
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
showToast(error.message || "Something went wrong", "error");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
//Activities--------------------------------------
|
||||||
|
|
||||||
|
export const useGetActivityGroups = () => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ["masterData", "Activity-Group"],
|
||||||
|
queryFn: async () => {
|
||||||
|
const resp = await MasterRepository.getActivityGroup();
|
||||||
|
return resp.data?.data || []; // only return array of services
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useCreateActivities = (onSuccessCallback) => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: async (payload) => {
|
||||||
|
const resp = await MasterRespository.createActivities(payload);
|
||||||
|
return resp.data; // return full API response
|
||||||
|
},
|
||||||
|
onSuccess: (data) => {
|
||||||
|
// Invalidate & refetch activities list
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: ["masterData", "Activities"],
|
||||||
|
});
|
||||||
|
|
||||||
|
showToast(data?.message || "Activity added successfully", "success");
|
||||||
|
|
||||||
|
if (onSuccessCallback) onSuccessCallback(data?.data);
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
showToast(error.message || "Something went wrong", "error");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
// Activity------------------------------
|
// Activity------------------------------
|
||||||
export const useCreateActivity = (onSuccessCallback) =>
|
export const useCreateActivity = (onSuccessCallback) =>
|
||||||
{
|
{
|
||||||
|
|||||||
@ -23,7 +23,16 @@ const MasterPage = () => {
|
|||||||
const selectedMaster = useSelector((store) => store.localVariables.selectedMaster);
|
const selectedMaster = useSelector((store) => store.localVariables.selectedMaster);
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const { data: masterData = [], loading, error, RecallApi } = useMaster();
|
const { data: masterData = [], loading } = useMaster();
|
||||||
|
|
||||||
|
// Define button configs for Services
|
||||||
|
const masterButtonsConfig = {
|
||||||
|
Services: [
|
||||||
|
{ label: "Add Service", modalType: "Services" },
|
||||||
|
{ label: "Add Activity-Group", modalType: "serviceActivity" },
|
||||||
|
{ label: "Add Activities", modalType: "serviceUnit" },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
const openModal = () => setIsCreateModalOpen(true);
|
const openModal = () => setIsCreateModalOpen(true);
|
||||||
|
|
||||||
@ -61,7 +70,9 @@ const MasterPage = () => {
|
|||||||
};
|
};
|
||||||
const displayData = useMemo(() => {
|
const displayData = useMemo(() => {
|
||||||
if (searchTerm) return filteredResults;
|
if (searchTerm) return filteredResults;
|
||||||
return queryClient.getQueryData(["masterData", selectedMaster]) || masterData;
|
return (
|
||||||
|
queryClient.getQueryData(["masterData", selectedMaster]) || masterData
|
||||||
|
);
|
||||||
}, [searchTerm, filteredResults, selectedMaster, masterData]);
|
}, [searchTerm, filteredResults, selectedMaster, masterData]);
|
||||||
|
|
||||||
const columns = useMemo(() => {
|
const columns = useMemo(() => {
|
||||||
@ -148,43 +159,86 @@ const MasterPage = () => {
|
|||||||
></input>
|
></input>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div className={`dt-buttons btn-group flex-wrap ${!hasMasterPermission && 'd-none'}`}>
|
|
||||||
{" "}
|
|
||||||
<div className="input-group">
|
|
||||||
|
|
||||||
|
{/* Add Buttons */}
|
||||||
|
<div
|
||||||
|
className={`dt-buttons btn-group flex-wrap ${!hasMasterPermission && "d-none"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="d-flex flex-wrap gap-2">
|
||||||
|
{selectedMaster === "Services" ? (
|
||||||
|
<>
|
||||||
<button
|
<button
|
||||||
className={`btn btn-sm add-new btn-primary `}
|
className="btn btn-sm btn-primary"
|
||||||
// ${hasUserPermission('660131a4-788c-4739-a082-cbbf7879cbf2') ? "":"d-none"}
|
|
||||||
tabIndex="0"
|
|
||||||
aria-controls="DataTables_Table_0"
|
|
||||||
type="button"
|
type="button"
|
||||||
data-bs-toggle="modal"
|
data-bs-toggle="modal"
|
||||||
data-bs-target="#master-modal"
|
data-bs-target="#master-modal"
|
||||||
onClick={() => {
|
onClick={() =>
|
||||||
handleModalData(selectedMaster, "null", selectedMaster)
|
handleModalData("Services", null, selectedMaster)
|
||||||
|
}
|
||||||
}}
|
>
|
||||||
|
<i className="bx bx-plus-circle me-2"></i>
|
||||||
|
Add Service
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
className="btn btn-sm btn-primary"
|
||||||
|
type="button"
|
||||||
|
data-bs-toggle="modal"
|
||||||
|
data-bs-target="#master-modal"
|
||||||
|
onClick={() =>
|
||||||
|
handleModalData("Activity-Group", null, selectedMaster)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<i className="bx bx-plus-circle me-2"></i>
|
||||||
|
Add Activity-Group
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
className="btn btn-sm btn-primary"
|
||||||
|
type="button"
|
||||||
|
data-bs-toggle="modal"
|
||||||
|
data-bs-target="#master-modal"
|
||||||
|
onClick={() =>
|
||||||
|
handleModalData("Activities", null, selectedMaster)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<i className="bx bx-plus-circle me-2"></i>
|
||||||
|
Add Activities
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<button
|
||||||
|
className="btn btn-sm btn-primary"
|
||||||
|
type="button"
|
||||||
|
data-bs-toggle="modal"
|
||||||
|
data-bs-target="#master-modal"
|
||||||
|
onClick={() =>
|
||||||
|
handleModalData(selectedMaster, null, selectedMaster)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<span>
|
|
||||||
<i className="bx bx-plus-circle me-2"></i>
|
<i className="bx bx-plus-circle me-2"></i>
|
||||||
<span className=" d-sm-inline-block">
|
|
||||||
Add {selectedMaster}
|
Add {selectedMaster}
|
||||||
</span>
|
</button>
|
||||||
</span>
|
)}
|
||||||
</button>{" "}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<MasterTable data={displayData} columns={columns} loading={loading} handleModalData={handleModalData} />
|
<MasterTable
|
||||||
|
data={displayData}
|
||||||
|
columns={columns}
|
||||||
|
loading={loading}
|
||||||
|
handleModalData={handleModalData}
|
||||||
|
/>
|
||||||
<div style={{ width: "1%" }}></div>
|
<div style={{ width: "1%" }}></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -27,7 +27,19 @@ export const MasterRespository = {
|
|||||||
getJobRole: () => api.get("/api/roles/jobrole"),
|
getJobRole: () => api.get("/api/roles/jobrole"),
|
||||||
updateJobRole: (id, data) => api.put(`/api/roles/jobrole/${id}`, data),
|
updateJobRole: (id, data) => api.put(`/api/roles/jobrole/${id}`, data),
|
||||||
|
|
||||||
getActivites: () => api.get("api/master/activities"),
|
getServices: () => api.get("api/master/services"),
|
||||||
|
createService: (data) => api.post("api/master/service", data),
|
||||||
|
updateService: (id, data) => api.put(`api/master/service/${id}`, data),
|
||||||
|
|
||||||
|
getActivityGroups: () => api.get("api/master/activity-groups"),
|
||||||
|
createActivityGroup: (data) => api.post("api/master/activity-group", data),
|
||||||
|
updateActivityGroup:(id,data) => api.put(`api/master/activity-group/${id}`, data),
|
||||||
|
|
||||||
|
getActivities: () => api.get("api/master/activities"),
|
||||||
|
createActivities: (data) => api.post("api/master/activity", data),
|
||||||
|
updateActivities: (id, data) => api.put(`api/master/activity/${id}`, data),
|
||||||
|
|
||||||
|
// getActivites: () => api.get("api/master/activities"),
|
||||||
createActivity: (data) => api.post("api/master/activity", data),
|
createActivity: (data) => api.post("api/master/activity", data),
|
||||||
updateActivity: (id, data) =>
|
updateActivity: (id, data) =>
|
||||||
api.post(`api/master/activity/edit/${id}`, data),
|
api.post(`api/master/activity/edit/${id}`, data),
|
||||||
@ -36,6 +48,7 @@ export const MasterRespository = {
|
|||||||
// delete
|
// delete
|
||||||
"Job Role": (id) => api.delete(`/api/roles/jobrole/${id}`),
|
"Job Role": (id) => api.delete(`/api/roles/jobrole/${id}`),
|
||||||
Activity: (id) => api.delete(`/api/master/activity/delete/${id}`),
|
Activity: (id) => api.delete(`/api/master/activity/delete/${id}`),
|
||||||
|
"Services": (id) => api.delete(`/api/master/service/${id}`),
|
||||||
"Application Role": (id) => api.delete(`/api/roles/${id}`),
|
"Application Role": (id) => api.delete(`/api/roles/${id}`),
|
||||||
"Work Category": (id) => api.delete(`api/master/work-category/${id}`),
|
"Work Category": (id) => api.delete(`api/master/work-category/${id}`),
|
||||||
"Contact Category": (id) => api.delete(`/api/master/contact-category/${id}`),
|
"Contact Category": (id) => api.delete(`/api/master/contact-category/${id}`),
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user