319 lines
10 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useState, useEffect, useMemo } from "react";
import { useTaskList } from "../../hooks/useTasks";
import { useSelectedProject } from "../../slices/apiDataManager";
import { useProjectName } from "../../hooks/useProjects";
import DailyProgrssReport, {
useDailyProgrssContext,
} from "../../pages/DailyProgressReport/DailyProgrssReport";
import { useDispatch } from "react-redux";
import { setProjectId } from "../../slices/localVariablesSlice";
import {
APPROVE_TASK,
ASSIGN_REPORT_TASK,
ITEMS_PER_PAGE,
} from "../../utils/constants";
import { formatNumber, formatUTCToLocalTime } from "../../utils/dateUtils";
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
import Pagination from "../common/Pagination";
import { TaskReportListSkeleton } from "./TaskRepprtListSkeleton";
import HoverPopup from "../common/HoverPopup";
const TaskReportList = () => {
const [currentPage, setCurrentPage] = useState(1);
const [filters, setFilters] = useState({
selectedBuilding: "",
selectedFloors: [],
selectedActivities: [],
});
const dispatch = useDispatch();
const ApprovedTaskRights = useHasUserPermission(APPROVE_TASK);
const ReportTaskRights = useHasUserPermission(ASSIGN_REPORT_TASK);
const { service, openModal, closeModal, filter } = useDailyProgrssContext();
const selectedProject = useSelectedProject();
const { projectNames } = useProjectName();
const { data, isLoading, isError, error } = useTaskList(
selectedProject,
ITEMS_PER_PAGE,
currentPage,
service, filter
);
const ProgrssReportColumn = [
{
key: "activity",
label: "Activity",
getValue: (task) => task.workItem.activityMaster?.activityName || "N/A",
align: "text-start",
},
{
key: "assigned",
label: "Total Assigned",
getValue: (task) => task.plannedTask ?? "N/A",
align: "text-start",
},
{
key: "completed",
label: "Completed",
getValue: (task) => task.completedTask ?? "N/A",
align: "text-start",
},
{
key: "assignAt",
label: "Assign Date",
getValue: (task) =>
task.assignmentDate ? formatUTCToLocalTime(task.assignmentDate) : "N/A",
align: "text-start",
},
{
key: "team",
label: "Team",
getValue: (task) =>
task.teamMembers?.map((m) => `${m.firstName} ${m.lastName}`).join(", ") ||
"N/A",
align: "text-start",
},
];
const paginate = (page) => {
if (page >= 1 && page <= (data?.totalPages ?? 1)) {
setCurrentPage(page);
}
};
useEffect(() => {
if (!selectedProject && projectNames.length > 0) {
dispatch(setProjectId(projectNames[0].id));
}
}, [selectedProject, projectNames, dispatch]);
useEffect(() => {
setFilters({
selectedBuilding: "",
selectedFloors: [],
selectedActivities: [],
});
}, [selectedProject]);
// Filter and Group wise data
const filteredTasks = useMemo(() => {
if (!data?.data) return [];
return data?.data.filter((task) => {
const { selectedBuilding, selectedFloors, selectedActivities } = filters;
if (
selectedBuilding &&
task?.workItem?.workArea?.floor?.building?.name !== selectedBuilding
)
return false;
if (
selectedFloors.length > 0 &&
!selectedFloors.includes(task?.workItem?.workArea?.floor?.floorName)
)
return false;
if (
selectedActivities.length > 0 &&
!selectedActivities.includes(
task?.workItem?.activityMaster?.activityName
)
)
return false;
return true;
});
}, [data?.data, filters, currentPage]);
const groupedTasks = useMemo(() => {
const groups = {};
filteredTasks.forEach((task) => {
const date = task.assignmentDate.split("T")[0];
if (!groups[date]) groups[date] = [];
groups[date].push(task);
});
return Object.keys(groups)
.sort((a, b) => new Date(b) - new Date(a))
.map((date) => ({ date, tasks: groups[date] }));
}, [filteredTasks, paginate, currentPage, selectedProject]);
const renderTeamMembers = (task, refIndex) => (
<div
key={refIndex}
tabIndex="0"
className="d-flex align-items-center avatar-group justify-content-center"
data-bs-toggle="popover"
data-bs-trigger="focus"
data-bs-placement="left"
data-bs-html="true"
data-bs-content={`
<div class="border border-secondary rounded custom-popover p-2 px-3">
${task.teamMembers
.map(
(m) => `
<div class="d-flex align-items-center gap-2 mb-2">
<div class="avatar avatar-xs">
<span class="avatar-initial rounded-circle bg-label-primary">
${m?.firstName?.charAt(0) || ""}${m?.lastName?.charAt(0) || ""
}
</span>
</div>
<span>${m.firstName} ${m.lastName}</span>
</div>`
)
.join("")}
</div>
`}
>
{task.teamMembers.slice(0, 3).map((m) => (
<div
key={m.id}
className="avatar avatar-xs"
title={`${m.firstName} ${m.lastName}`}
>
<span className="avatar-initial rounded-circle bg-label-primary">
{m?.firstName.slice(0, 1)}
</span>
</div>
))}
{task.teamMembers.length > 3 && (
<div
className="avatar avatar-xs"
title={`${task.teamMembers.length - 3} more`}
>
<span className="avatar-initial rounded-circle bg-label-secondary">
+{task.teamMembers.length - 3}
</span>
</div>
)}
</div>
);
if (isLoading) return <TaskReportListSkeleton />;
if (isError) return <div>Loading....</div>;
return (
<div>
<div className="mt-2 table-responsive text-nowrap">
<table className="table">
<thead>
<tr>
<th className="text-start">Activity</th>
<th>
<span>
Total Pending{" "}
<HoverPopup
id="total_pending_task"
title="Total Pending Task"
content={
<div className="text-wrap" style={{ maxWidth: "200px" }}>
This shows the total pending tasks for each activity on that date.
</div>
}
>
<i className="bx bx-xs ms-1 bx-info-circle cursor-pointer"></i>
</HoverPopup>
</span>
</th>
<th>
<span>
Reported/Planned{" "}
<HoverPopup
id="reportes_and_planned_task"
title="Reported and Planned Task"
content={
<div className="text-wrap" style={{ maxWidth: "200px" }}>
This shows the reported versus planned tasks for each activity on that date.
</div>
}
>
<i className="bx bx-xs ms-1 bx-info-circle cursor-pointer"></i>
</HoverPopup>
</span>
</th>
<th>Assign Date</th>
<th>Team</th>
<th className="text-center">Actions</th>
</tr>
</thead>
<tbody>
{groupedTasks.length === 0 && (
<tr>
<td colSpan={6} className="text-center align-middle" style={{ height: "200px", borderBottom: "none" }}>
No reports available
</td>
</tr>
)}
{groupedTasks.map(({ date, tasks }) => (
<React.Fragment key={date}>
<tr className="table-row-header text-start">
<td colSpan={6}>
<strong>{formatUTCToLocalTime(date)}</strong>
</td>
</tr>
{tasks.map((task, idx) => (
<tr key={task.id || idx}>
<td className="flex-wrap text-start">
<div>
{task.workItem.activityMaster?.activityName || "No Activity Name"}
</div>
<div className="text-sm py-2">
{task.workItem.workArea?.floor?.building?.name} {" "}
{task.workItem.workArea?.floor?.floorName} {" "}
{task.workItem.workArea?.areaName}
</div>
</td>
<td>
{formatNumber(task.workItem.plannedWork)}
</td>
<td>{`${formatNumber(task.completedTask)} / ${formatNumber(task.plannedTask)}`}</td>
<td>{formatUTCToLocalTime(task.assignmentDate)}</td>
<td className="text-center">{renderTeamMembers(task, idx)}</td>
<td className="text-center">
<div className="d-flex justify-content-end gap-2">
{ReportTaskRights && !task.reportedDate && (
<button className="btn btn-xs btn-primary" onClick={() => openModal("report", task)}>
Report
</button>
)}
{ApprovedTaskRights && task.reportedDate && !task.approvedBy && (
<button
className="btn btn-xs btn-warning"
onClick={() => openModal("comments", { task, isActionAllow: true })}
>
QC
</button>
)}
<button
className="btn btn-xs btn-primary"
onClick={() => openModal("comments", { task, isActionAllow: false })}
>
Comment
</button>
</div>
</td>
</tr>
))}
</React.Fragment>
))}
</tbody>
</table>
</div>
{
data?.data?.length > 0 && (
<Pagination
currentPage={currentPage}
totalPages={data.totalPages}
onPageChange={paginate}
/>
)
}
</div >
);
};
export default TaskReportList;