Merge pull request 'Service_Project_Management_Bugs :- Changes for Jobs' (#511) from Service_Project_Management_Bugs into Service_Project_Managment
Reviewed-on: #511 Merged
This commit is contained in:
commit
3d1d8264ad
@ -1,5 +1,5 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { getNextBadgeColor } from "../../utils/appUtils";
|
import { daysLeft, getNextBadgeColor } from "../../utils/appUtils";
|
||||||
import { useServiceProjectJobs } from "../../hooks/useServiceProject";
|
import { useServiceProjectJobs } from "../../hooks/useServiceProject";
|
||||||
import { ITEMS_PER_PAGE } from "../../utils/constants";
|
import { ITEMS_PER_PAGE } from "../../utils/constants";
|
||||||
import EmployeeAvatarGroup from "../common/EmployeeAvatarGroup";
|
import EmployeeAvatarGroup from "../common/EmployeeAvatarGroup";
|
||||||
@ -37,36 +37,51 @@ const JobList = ({ filterByProject }) => {
|
|||||||
isAlwaysVisible: true,
|
isAlwaysVisible: true,
|
||||||
className: "text-start",
|
className: "text-start",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
key: "project",
|
|
||||||
label: "Project",
|
|
||||||
getValue: (e) => <div className="text-start ">{e?.project?.name}</div>,
|
|
||||||
isAlwaysVisible: true,
|
|
||||||
className: "text-start d-none d-sm-table-cell",
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
key: "employee",
|
|
||||||
label: "Team",
|
|
||||||
getValue: (e) => <EmployeeAvatarGroup employees={e.assignees} />,
|
|
||||||
isAlwaysVisible: true,
|
|
||||||
className: "text-start d-none d-sm-table-cell",
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
key: "startDate",
|
|
||||||
label: "Start Date",
|
|
||||||
getValue: (e) => formatUTCToLocalTime(e.startDate),
|
|
||||||
isAlwaysVisible: true,
|
|
||||||
className: "text-center d-none d-sm-table-cell ",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
key: "dueDate",
|
key: "dueDate",
|
||||||
label: "Due To",
|
label: "Due On",
|
||||||
getValue: (e) => formatUTCToLocalTime(e.startDate),
|
getValue: (e) => formatUTCToLocalTime(e.startDate),
|
||||||
isAlwaysVisible: true,
|
isAlwaysVisible: true,
|
||||||
className: "text-center d-none d-sm-table-cell",
|
className: "text-start d-none d-sm-table-cell",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: "status",
|
||||||
|
label: "Status",
|
||||||
|
getValue: (e) => {
|
||||||
|
const statusName = e?.status?.displayName || "N/A";
|
||||||
|
const statusColorMap = {
|
||||||
|
Assigned: "label-primary",
|
||||||
|
Pending: "label-warning",
|
||||||
|
Completed: "label-success",
|
||||||
|
Cancelled: "label-danger",
|
||||||
|
};
|
||||||
|
|
||||||
|
const badgeColor = statusColorMap[statusName] || "label-secondary";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span className={`badge bg-${badgeColor}`} style={{ fontSize: "12px" }}>
|
||||||
|
{statusName}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
isAlwaysVisible: true,
|
||||||
|
className: "text-start d-none d-sm-table-cell",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "daysLeft",
|
||||||
|
label: "Days Left",
|
||||||
|
getValue: (e) => {
|
||||||
|
const { days, color } = daysLeft(e.startDate, e.dueDate);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span className={`badge bg-${color}`} style={{ fontSize: "12px" }}>
|
||||||
|
{days !== null ? `${days} days` : "N/A"}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
isAlwaysVisible: true,
|
||||||
|
className: "text-start d-none d-sm-table-cell"
|
||||||
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -58,24 +58,7 @@ const Jobs = () => {
|
|||||||
</OffcanvasComponent>
|
</OffcanvasComponent>
|
||||||
<div className="card page-min-h my-2 px-4">
|
<div className="card page-min-h my-2 px-4">
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-12 py-2 d-flex justify-content-between ">
|
<div className="col-12 py-2 d-flex justify-content-end ">
|
||||||
<div>
|
|
||||||
{" "}
|
|
||||||
<select
|
|
||||||
className="form-select form-select-sm"
|
|
||||||
value={selectedProject}
|
|
||||||
onChange={(e) => setSelectedProject(e.target.value)}
|
|
||||||
>
|
|
||||||
<option disabled selected>
|
|
||||||
Select Poject
|
|
||||||
</option>
|
|
||||||
{data?.data?.map((project) => (
|
|
||||||
<option key={project.id} value={project.id}>
|
|
||||||
{project.name}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div className="px-2">
|
<div className="px-2">
|
||||||
<button
|
<button
|
||||||
className="btn btn-sm btn-primary"
|
className="btn btn-sm btn-primary"
|
||||||
|
|||||||
@ -134,7 +134,7 @@ const ManageJob = ({ Job }) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-12 col-md-6 mb-2 mb-md-4">
|
<div className="col-12 col-md-6 mb-2 mb-md-4">
|
||||||
<Label required>End Date</Label>
|
<Label required>Select Employee</Label>
|
||||||
<PmsEmployeeInputTag
|
<PmsEmployeeInputTag
|
||||||
control={control}
|
control={control}
|
||||||
name="assignees"
|
name="assignees"
|
||||||
|
|||||||
@ -110,7 +110,7 @@ const ManageServiceProject = ({ serviceProjectId, onClose }) => {
|
|||||||
<FormProvider {...methods}>
|
<FormProvider {...methods}>
|
||||||
<form className="px-3 py-2" onSubmit={handleSubmit(onSubmit)}>
|
<form className="px-3 py-2" onSubmit={handleSubmit(onSubmit)}>
|
||||||
<div className=" text-center">
|
<div className=" text-center">
|
||||||
<h5>{serviceProjectId ? "Update Project":"Create Project"}</h5>
|
<h5>{serviceProjectId ? "Update Project" : "Create Project"}</h5>
|
||||||
</div>
|
</div>
|
||||||
<div className="row text-start">
|
<div className="row text-start">
|
||||||
<div className="col-12 mb-2">
|
<div className="col-12 mb-2">
|
||||||
@ -150,6 +150,7 @@ const ManageServiceProject = ({ serviceProjectId, onClose }) => {
|
|||||||
type="text"
|
type="text"
|
||||||
className="form-control form-control-sm"
|
className="form-control form-control-sm"
|
||||||
{...register("name")}
|
{...register("name")}
|
||||||
|
placeholder="Enter Project Name.."
|
||||||
/>
|
/>
|
||||||
{errors?.name && (
|
{errors?.name && (
|
||||||
<span className="danger-text">{errors.name.message}</span>
|
<span className="danger-text">{errors.name.message}</span>
|
||||||
@ -163,6 +164,7 @@ const ManageServiceProject = ({ serviceProjectId, onClose }) => {
|
|||||||
type="text"
|
type="text"
|
||||||
className="form-control form-control-sm"
|
className="form-control form-control-sm"
|
||||||
{...register("shortName")}
|
{...register("shortName")}
|
||||||
|
placeholder="Enter Project Short Name.."
|
||||||
/>
|
/>
|
||||||
{errors?.shortName && (
|
{errors?.shortName && (
|
||||||
<span className="danger-text">{errors.shortName.message}</span>
|
<span className="danger-text">{errors.shortName.message}</span>
|
||||||
@ -201,12 +203,16 @@ const ManageServiceProject = ({ serviceProjectId, onClose }) => {
|
|||||||
|
|
||||||
<div className="col-12 col-md-6 mb-2">
|
<div className="col-12 col-md-6 mb-2">
|
||||||
<Label htmlFor="name" required>
|
<Label htmlFor="name" required>
|
||||||
Contact Name
|
Contact Person
|
||||||
</Label>
|
</Label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
className="form-control form-control-sm"
|
className="form-control form-control-sm"
|
||||||
{...register("contactName")}
|
{...register("contactName")}
|
||||||
|
placeholder="Enter Employee name.."
|
||||||
|
onInput={(e) => {
|
||||||
|
e.target.value = e.target.value.replace(/[^A-Za-z ]/g, "");
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
{errors?.contactName && (
|
{errors?.contactName && (
|
||||||
<span className="danger-text">{errors.contactName.message}</span>
|
<span className="danger-text">{errors.contactName.message}</span>
|
||||||
@ -220,6 +226,7 @@ const ManageServiceProject = ({ serviceProjectId, onClose }) => {
|
|||||||
type="text"
|
type="text"
|
||||||
className="form-control form-control-sm"
|
className="form-control form-control-sm"
|
||||||
{...register("contactEmail")}
|
{...register("contactEmail")}
|
||||||
|
placeholder="Enter Employee Email.."
|
||||||
/>
|
/>
|
||||||
{errors?.contactEmail && (
|
{errors?.contactEmail && (
|
||||||
<span className="danger-text">{errors.contactEmail.message}</span>
|
<span className="danger-text">{errors.contactEmail.message}</span>
|
||||||
@ -231,8 +238,13 @@ const ManageServiceProject = ({ serviceProjectId, onClose }) => {
|
|||||||
</Label>
|
</Label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
maxLength={10}
|
||||||
className="form-control form-control-sm"
|
className="form-control form-control-sm"
|
||||||
{...register("contactPhone")}
|
{...register("contactPhone")}
|
||||||
|
placeholder="Enter Employee Contact.."
|
||||||
|
onInput={(e) => {
|
||||||
|
e.target.value = e.target.value.replace(/[^0-9]/g, "");
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
{errors?.contactPhone && (
|
{errors?.contactPhone && (
|
||||||
<span className="danger-text">{errors.contactPhone.message}</span>
|
<span className="danger-text">{errors.contactPhone.message}</span>
|
||||||
@ -244,7 +256,7 @@ const ManageServiceProject = ({ serviceProjectId, onClose }) => {
|
|||||||
</Label>
|
</Label>
|
||||||
<DatePicker
|
<DatePicker
|
||||||
name="assignedDate"
|
name="assignedDate"
|
||||||
className="w-full"
|
className="w-100"
|
||||||
control={control}
|
control={control}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -258,6 +270,7 @@ const ManageServiceProject = ({ serviceProjectId, onClose }) => {
|
|||||||
className="form-control"
|
className="form-control"
|
||||||
// maxLength={maxAddressLength}
|
// maxLength={maxAddressLength}
|
||||||
{...register("address")}
|
{...register("address")}
|
||||||
|
placeholder="Enter Project Address.."
|
||||||
// onChange={(e) => {
|
// onChange={(e) => {
|
||||||
// setAddressLength(e.target.value.length);
|
// setAddressLength(e.target.value.length);
|
||||||
// }}
|
// }}
|
||||||
|
|||||||
@ -1,22 +1,47 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
import { PROJECT_STATUS } from "../../utils/constants";
|
||||||
|
|
||||||
//#region Service Project
|
//#region Service Project
|
||||||
export const projectSchema = z.object({
|
export const projectSchema = z.object({
|
||||||
name: z.string().min(1, "Name is required"),
|
name: z
|
||||||
shortName: z.string().min(1, "Short name is required"),
|
.string()
|
||||||
clientId: z.string().min(1, { message: "Client is required" }),
|
.trim()
|
||||||
|
.min(1, "Name is required"),
|
||||||
|
|
||||||
|
shortName: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.min(1, "Short name is required"),
|
||||||
|
|
||||||
|
clientId: z.string().trim().min(1, { message: "Client is required" }),
|
||||||
|
|
||||||
services: z
|
services: z
|
||||||
.array(z.string())
|
.array(z.string())
|
||||||
.min(1, { message: "At least one service required" }),
|
.min(1, { message: "At least one service required" }),
|
||||||
address: z.string().min(1, "Address is required"),
|
|
||||||
assignedDate: z.string().min(1, { message: "Date is required" }),
|
address: z.string().trim().min(1, "Address is required"),
|
||||||
statusId: z.string().min(1, "Status is required"),
|
|
||||||
contactName: z.string().min(1, "Contact name is required"),
|
assignedDate: z.string().trim().min(1, { message: "Date is required" }),
|
||||||
|
|
||||||
|
statusId: z.string().trim().min(1, "Status is required"),
|
||||||
|
|
||||||
|
contactName: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.min(1, "Contact name is required")
|
||||||
|
.regex(/^[A-Za-z\s]+$/, "Contact name can contain only letters"),
|
||||||
|
|
||||||
contactPhone: z
|
contactPhone: z
|
||||||
.string()
|
.string()
|
||||||
|
.trim()
|
||||||
|
.regex(/^\d+$/, "Phone number must contain only digits")
|
||||||
.min(7, "Invalid phone number")
|
.min(7, "Invalid phone number")
|
||||||
.max(15, "Phone number too long"),
|
.max(15, "Phone number too long"),
|
||||||
contactEmail: z.string().email("Invalid email address"),
|
|
||||||
|
contactEmail: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.email("Invalid email address"),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const defaultProjectValues = {
|
export const defaultProjectValues = {
|
||||||
@ -26,7 +51,7 @@ export const defaultProjectValues = {
|
|||||||
services: [],
|
services: [],
|
||||||
address: "",
|
address: "",
|
||||||
assignedDate: "",
|
assignedDate: "",
|
||||||
statusId: "",
|
statusId: PROJECT_STATUS.find((s) => s.label === "Active")?.id,
|
||||||
contactName: "",
|
contactName: "",
|
||||||
contactPhone: "",
|
contactPhone: "",
|
||||||
contactEmail: "",
|
contactEmail: "",
|
||||||
|
|||||||
@ -51,7 +51,7 @@ const DatePicker = ({
|
|||||||
<div className={`position-relative ${className} w-max `}>
|
<div className={`position-relative ${className} w-max `}>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
className="form-control form-control"
|
className="form-control form-control form-control-sm"
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
value={displayValue}
|
value={displayValue}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
|
|||||||
@ -238,7 +238,7 @@ export const useCreateServiceProjectJob = (onSuccessCallback) => {
|
|||||||
return await ServiceProjectRepository.CreateJob(payload);
|
return await ServiceProjectRepository.CreateJob(payload);
|
||||||
},
|
},
|
||||||
onSuccess: (data, variables) => {
|
onSuccess: (data, variables) => {
|
||||||
queryClient.invalidateQueries({ queryKey: [""] });
|
queryClient.invalidateQueries({ queryKey: ["serviceProjectJobs"] });
|
||||||
|
|
||||||
if (onSuccessCallback) onSuccessCallback();
|
if (onSuccessCallback) onSuccessCallback();
|
||||||
showToast("Job Created successfully", "success");
|
showToast("Job Created successfully", "success");
|
||||||
|
|||||||
@ -43,7 +43,7 @@ const ProjectPage = () => {
|
|||||||
const [coreProjects, setCoreProjects] = useState(() => {
|
const [coreProjects, setCoreProjects] = useState(() => {
|
||||||
const storedValue = sessionStorage.getItem('whichProjectDisplay');
|
const storedValue = sessionStorage.getItem('whichProjectDisplay');
|
||||||
return storedValue === 'true';
|
return storedValue === 'true';
|
||||||
});
|
});
|
||||||
const HasManageProject = useHasUserPermission(MANAGE_PROJECT);
|
const HasManageProject = useHasUserPermission(MANAGE_PROJECT);
|
||||||
|
|
||||||
const [selectedStatuses, setSelectedStatuses] = useState(
|
const [selectedStatuses, setSelectedStatuses] = useState(
|
||||||
@ -58,11 +58,11 @@ const ProjectPage = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const handleToggleProject = (e) => {
|
const handleToggleProject = (value) => {
|
||||||
const checked = e.target.checked;
|
setCoreProjects(value);
|
||||||
setCoreProjects(checked);
|
sessionStorage.setItem("whichProjectDisplay", String(value));
|
||||||
sessionStorage.setItem('whichProjectDisplay', String(checked));
|
};
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ProjectContext.Provider value={contextDispatcher}>
|
<ProjectContext.Provider value={contextDispatcher}>
|
||||||
@ -77,8 +77,37 @@ const handleToggleProject = (e) => {
|
|||||||
<div className="card cursor-pointer mb-5">
|
<div className="card cursor-pointer mb-5">
|
||||||
<div className="card-body p-2 pb-1">
|
<div className="card-body p-2 pb-1">
|
||||||
<div className="d-flex flex-wrap justify-content-between align-items-start">
|
<div className="d-flex flex-wrap justify-content-between align-items-start">
|
||||||
<div className="d-flex flex-wrap align-items-start">
|
{/* LEFT SIDE — DATE TOGGLE BUTTONS */}
|
||||||
<div className="flex-grow-1 me-2 mb-2">
|
<div className="mb-2">
|
||||||
|
<div className="d-inline-flex border rounded-pill overflow-hidden shadow-none">
|
||||||
|
{/* Service Project Button */}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`btn px-2 py-1 rounded-0 text-tiny ${!coreProjects ? "btn-primary text-white" : ""
|
||||||
|
}`}
|
||||||
|
onClick={() => handleToggleProject(false)}
|
||||||
|
>
|
||||||
|
Service Project
|
||||||
|
</button>
|
||||||
|
{/* Organization Project Button */}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`btn px-2 py-1 rounded-0 text-tiny ${coreProjects ? "btn-primary text-white" : ""
|
||||||
|
}`}
|
||||||
|
onClick={() => handleToggleProject(true)}
|
||||||
|
>
|
||||||
|
Infra Project
|
||||||
|
</button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{/* RIGHT SIDE — SEARCH + CARD/LIST + DROPDOWN */}
|
||||||
|
<div className="d-flex flex-wrap align-items-center justify-content-end">
|
||||||
|
|
||||||
|
{/* Search */}
|
||||||
|
<div className="me-2" style={{ minWidth: "200px" }}>
|
||||||
<input
|
<input
|
||||||
type="search"
|
type="search"
|
||||||
className="form-control form-control-sm"
|
className="form-control form-control-sm"
|
||||||
@ -91,49 +120,44 @@ const handleToggleProject = (e) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="d-flex gap-2 mb-2">
|
{/* Card/List Buttons */}
|
||||||
|
<div className="d-flex gap-2">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`btn btn-sm p-1 ${
|
className={`btn btn-sm p-1 ${!listView ? "btn-primary" : "btn-outline-primary"}`}
|
||||||
!listView ? "btn-primary" : "btn-outline-primary"
|
|
||||||
}`}
|
|
||||||
onClick={() => setListView(false)}
|
onClick={() => setListView(false)}
|
||||||
data-bs-toggle="tooltip"
|
|
||||||
data-bs-custom-class="tooltip"
|
|
||||||
title="Card View"
|
title="Card View"
|
||||||
>
|
>
|
||||||
<i className="bx bx-grid-alt fs-5"></i>
|
<i className="bx bx-grid-alt fs-5"></i>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`btn btn-sm p-1 ${
|
className={`btn btn-sm p-1 ${listView ? "btn-primary" : "btn-outline-primary"}`}
|
||||||
listView ? "btn-primary" : "btn-outline-primary"
|
|
||||||
}`}
|
|
||||||
onClick={() => setListView(true)}
|
onClick={() => setListView(true)}
|
||||||
data-bs-toggle="tooltip"
|
|
||||||
data-bs-custom-class="tooltip"
|
|
||||||
title="List View"
|
title="List View"
|
||||||
>
|
>
|
||||||
<i className="bx bx-list-ul fs-5"></i>
|
<i className="bx bx-list-ul fs-5"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="dropdown mt-1">
|
{/* Dropdown Filter */}
|
||||||
|
<div className="dropdown me-2">
|
||||||
<a
|
<a
|
||||||
className="dropdown-toggle hide-arrow cursor-pointer p-1 mt-3 "
|
className="dropdown-toggle hide-arrow cursor-pointer p-1"
|
||||||
data-bs-toggle="dropdown"
|
data-bs-toggle="dropdown"
|
||||||
aria-expanded="false"
|
aria-expanded="false"
|
||||||
data-bs-custom-class="tooltip"
|
|
||||||
title="Filter"
|
title="Filter"
|
||||||
>
|
>
|
||||||
<i className="bx bx-slider-alt ms-1"></i>
|
<i className="bx bx-slider-alt fs-5"></i>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<ul className="dropdown-menu p-2 text-capitalize">
|
<ul className="dropdown-menu p-2 text-capitalize">
|
||||||
{PROJECT_STATUS.map(({ id, label }) => (
|
{PROJECT_STATUS.map(({ id, label }) => (
|
||||||
<li key={id}>
|
<li key={id}>
|
||||||
<div className="form-check">
|
<div className="form-check">
|
||||||
<input
|
<input
|
||||||
className="form-check-input "
|
className="form-check-input"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={selectedStatuses.includes(id)}
|
checked={selectedStatuses.includes(id)}
|
||||||
onChange={() => handleStatusChange(id)}
|
onChange={() => handleStatusChange(id)}
|
||||||
@ -144,56 +168,27 @@ const handleToggleProject = (e) => {
|
|||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div className="form-check form-switch d-flex align-items-center text-nowrap ms-3">
|
|
||||||
<input
|
{HasManageProject && (
|
||||||
type="checkbox"
|
<button
|
||||||
className="form-check-input"
|
type="button"
|
||||||
id="activeEmployeeSwitch"
|
className="btn btn-primary btn-sm d-flex align-items-center"
|
||||||
checked={coreProjects}
|
onClick={() =>
|
||||||
onChange={handleToggleProject}
|
coreProjects
|
||||||
/>
|
? setMangeProject({ isOpen: true, Project: null }) // Organization Project → Infra
|
||||||
<label
|
: setManageServiceProject({ isOpen: true, Project: null }) // Service Project
|
||||||
className="form-check-label ms-2"
|
}
|
||||||
htmlFor="activeEmployeeSwitch"
|
|
||||||
>
|
>
|
||||||
{coreProjects ? "Organization Project" : "Service Project"}
|
<i className="bx bx-plus-circle me-2"></i>
|
||||||
</label>
|
New Project
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{HasManageProject && (
|
|
||||||
<div class="btn-group">
|
|
||||||
<button type="button" className="btn btn-primary">
|
|
||||||
<i className="bx bx-plus-circle me-2"></i> New Project
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="btn btn-primary dropdown-toggle dropdown-toggle-split"
|
|
||||||
data-bs-toggle="dropdown"
|
|
||||||
aria-haspopup="true"
|
|
||||||
aria-expanded="false"
|
|
||||||
>
|
|
||||||
<span class="visually-hidden">Toggle Dropdown</span>
|
|
||||||
</button>
|
|
||||||
<ul class="dropdown-menu">
|
|
||||||
<li
|
|
||||||
onClick={() =>
|
|
||||||
setManageServiceProject({ isOpen: true, Project: null })
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<a class="dropdown-item">Service Project</a>
|
|
||||||
</li>
|
|
||||||
<li
|
|
||||||
onClick={() =>
|
|
||||||
setMangeProject({ isOpen: true, Project: null })
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<a class="dropdown-item">Infra Project</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -203,3 +203,28 @@ export function getNextBadgeColor(type = "label") {
|
|||||||
colorIndex = (colorIndex + 1) % badgeColors.length;
|
colorIndex = (colorIndex + 1) % badgeColors.length;
|
||||||
return `rounded-pill text-bg-${color}`;
|
return `rounded-pill text-bg-${color}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function daysLeft(startDate, dueDate) {
|
||||||
|
if (!startDate || !dueDate) {
|
||||||
|
return { days: null, color: "label-secondary" };
|
||||||
|
}
|
||||||
|
|
||||||
|
const start = new Date(startDate);
|
||||||
|
const due = new Date(dueDate);
|
||||||
|
|
||||||
|
const today = new Date();
|
||||||
|
const diffTime = due.getTime() - today.getTime();
|
||||||
|
const days = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
||||||
|
|
||||||
|
let color = "label-primary"; // default
|
||||||
|
|
||||||
|
if (days < 0) {
|
||||||
|
color = "label-danger"; // overdue → red
|
||||||
|
} else if (days <= 15) {
|
||||||
|
color = "label-warning"; // near due → yellow
|
||||||
|
} else {
|
||||||
|
color = "label-primary"; // safe range
|
||||||
|
}
|
||||||
|
|
||||||
|
return { days, color };
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user