Adding filter chips in daily Progress Report.

This commit is contained in:
Kartik Sharma 2025-12-10 12:11:54 +05:30 committed by pramod.mahajan
parent 07ba95e533
commit 6b9d7c56bc
4 changed files with 287 additions and 126 deletions

View File

@ -0,0 +1,95 @@
import React, { useMemo } from "react";
import { formatUTCToLocalTime } from "../../utils/dateUtils";
const TaskReportFilterChips = ({ filter, filterData, removeFilterChip, clearFilter }) => {
const data = filterData?.data || filterData || {};
const filterChips = useMemo(() => {
const chips = [];
const addGroup = (ids, list, label, key) => {
if (!ids || ids.length === 0) return;
const items = ids.map((id) => ({
id,
name: list?.find((i) => i.id === id)?.name || id,
}));
chips.push({ key, label, items });
};
// Building
addGroup(filter?.buildingIds, data?.buildings, "Building", "buildingIds");
// Floor
addGroup(filter?.floorIds, data?.floors, "Floor", "floorIds");
// Activities
addGroup(filter?.activityIds, data?.activities, "Activity", "activityIds");
// Date Range Chips
if (filter?.dateFrom || filter?.dateTo) {
chips.push({
key: "date",
label: "Date Range",
items: [
{
id: "date-range",
name: `${filter?.dateFrom ? formatUTCToLocalTime(filter.dateFrom) : ""}
${filter?.dateTo ? " to " + formatUTCToLocalTime(filter.dateTo) : ""}`,
},
],
});
}
return chips;
}, [filter, filterData]);
if (!filterChips.length) return null;
return (
<div className="d-flex flex-wrap align-items-center gap-2 mt-2">
{filterChips.map((chipGroup) => (
<div key={chipGroup.key} className="d-flex align-items-center flex-wrap">
<span className="fw-semibold me-2">{chipGroup.label}:</span>
{chipGroup.items.map((item) => (
<span
key={item.id}
className="d-flex align-items-center bg-light rounded px-2 py-1 me-1"
>
<span>{item.name}</span>
{/* If date chip → remove whole date range */}
{chipGroup.key === "date" ? (
<button
type="button"
className="btn-close btn-close-white btn-sm ms-2"
style={{
filter: "invert(1) grayscale(1)",
opacity: 0.7,
fontSize: "0.6rem",
}}
onClick={() => clearFilter()}
/>
) : (
<button
type="button"
className="btn-close btn-close-white btn-sm ms-2"
style={{
filter: "invert(1) grayscale(1)",
opacity: 0.7,
fontSize: "0.6rem",
}}
onClick={() => removeFilterChip(chipGroup.key, item.id)}
/>
)}
</span>
))}
</div>
))}
</div>
);
};
export default TaskReportFilterChips;

View File

@ -1,4 +1,4 @@
import React, { useState } from "react";
import React, { forwardRef, useEffect, useImperativeHandle, useMemo, useState } from "react";
import { useCurrentService } from "../../hooks/useProjects";
import { useSelectedProject } from "../../slices/apiDataManager";
import { FormProvider, useForm } from "react-hook-form";
@ -11,8 +11,9 @@ import { DateRangePicker1 } from "../common/DateRangePicker";
import SelectMultiple from "../common/SelectMultiple";
import { localToUtc } from "../../utils/appUtils";
import { useTaskFilter } from "../../hooks/useTasks";
import { set } from "date-fns";
const TaskReportFilterPanel = ({ handleFilter }) => {
const TaskReportFilterPanel = forwardRef(({ handleFilter, setFilterdata, clearFilter }, ref) => {
const [resetKey, setResetKey] = useState(0);
const selectedProject = useSelectedProject();
const selectedService = useCurrentService();
@ -23,10 +24,39 @@ const TaskReportFilterPanel = ({ handleFilter }) => {
defaultValues: TaskReportDefaultValue,
});
const dynamicDefaultFilter = useMemo(() => {
return {
...TaskReportDefaultValue,
buildingIds: TaskReportDefaultValue.buildingIds || [],
floorIds: TaskReportDefaultValue.floorIds || [],
activityIds: TaskReportDefaultValue.activityIds || [],
dateFrom: TaskReportDefaultValue.startDate,
dateTo: TaskReportDefaultValue.endDate,
};
}, [selectedProject]);
useImperativeHandle(ref, () => ({
resetFieldValue: (name, value) => {
// Reset specific field
if (value !== undefined) {
setValue(name, value);
} else {
reset({ ...methods.getValues(), [name]: defaultFilter[name] });
}
},
getValues: methods.getValues, // optional, to read current filter state
}));
useEffect(() => {
if (data && setFilterdata) {
setFilterdata(data);
}
}, [data, setFilterdata]);
const {
register,
reset,
handleSubmit,
setValue,
formState: { errors },
} = methods;
const closePanel = () => {
@ -52,7 +82,7 @@ const TaskReportFilterPanel = ({ handleFilter }) => {
<div className="mb-3 w-100">
<label className="fw-semibold">Choose Date Range:</label>
<DateRangePicker1
className="w-100"
className="w-100"
placeholder="DD-MM-YYYY To DD-MM-YYYY"
startField="dateFrom"
endField="dateTo"
@ -105,6 +135,6 @@ const TaskReportFilterPanel = ({ handleFilter }) => {
</form>
</FormProvider>
);
};
});
export default TaskReportFilterPanel;

View File

@ -17,8 +17,9 @@ import { useHasUserPermission } from "../../hooks/useHasUserPermission";
import Pagination from "../common/Pagination";
import { TaskReportListSkeleton } from "./TaskRepprtListSkeleton";
import HoverPopup from "../common/HoverPopup";
import TaskReportFilterChips from "./TaskReportFilterChips";
const TaskReportList = () => {
const TaskReportList = ({ filter, filterData, removeFilterChip, clearFilter }) => {
const [currentPage, setCurrentPage] = useState(1);
const [filters, setFilters] = useState({
selectedBuilding: "",
@ -29,7 +30,7 @@ const TaskReportList = () => {
const ApprovedTaskRights = useHasUserPermission(APPROVE_TASK);
const ReportTaskRights = useHasUserPermission(ASSIGN_REPORT_TASK);
const { service, openModal, closeModal, filter } = useDailyProgrssContext();
const { service, openModal, closeModal, filter: contextFilter } = useDailyProgrssContext();
const selectedProject = useSelectedProject();
const { projectNames } = useProjectName();
@ -37,7 +38,7 @@ const TaskReportList = () => {
selectedProject,
ITEMS_PER_PAGE,
currentPage,
service, filter
service, contextFilter
);
const ProgrssReportColumn = [
@ -193,124 +194,136 @@ const TaskReportList = () => {
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={{ minWidth: "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>
<div className="main-content">
<div className="col-12 mb-2 mt-2 px-4">
<TaskReportFilterChips
filter={filter}
filterData={filterData}
removeFilterChip={removeFilterChip}
clearFilter={clearFilter}
/>
<th>Assign Date</th>
<th>Team</th>
<th className="text-center">Actions</th>
</tr>
</thead>
<tbody>
{groupedTasks.length === 0 && (
</div>
<div className="mt-2 table-responsive text-nowrap">
<table className="table">
<thead>
<tr>
<td colSpan={6} className="text-center align-middle" style={{ height: "200px", borderBottom: "none" }}>
No reports available
</td>
</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={{ minWidth: "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>
{groupedTasks.map(({ date, tasks }) => (
<React.Fragment key={date}>
<tr className="table-row-header text-start">
<td colSpan={6}>
<strong>{formatUTCToLocalTime(date)}</strong>
<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>
{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>
)}
{groupedTasks.map(({ date, tasks }) => (
<React.Fragment key={date}>
<tr className="table-row-header text-start">
<td colSpan={6}>
<strong>{formatUTCToLocalTime(date)}</strong>
</td>
</tr>
))}
</React.Fragment>
))}
</tbody>
</table>
{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>
{
data?.data?.length > 0 && (
<Pagination
currentPage={currentPage}
totalPages={data.totalPages}
onPageChange={paginate}
/>
)
}
</div >
</div >
);
};

View File

@ -1,4 +1,4 @@
import React, { createContext, useContext, useEffect, useState } from "react";
import React, { createContext, useContext, useEffect, useRef, useState } from "react";
import Breadcrumb from "../../components/common/Breadcrumb";
import { useServices } from "../../hooks/masterHook/useMaster";
import TaskReportList from "../../components/DailyProgressRport/TaskReportList";
@ -13,6 +13,7 @@ import { useSelectedProject } from "../../slices/apiDataManager";
import SelectField from "../../components/common/Forms/SelectField";
import { AppFormController } from "../../hooks/appHooks/useAppForm";
import { useForm } from "react-hook-form";
import { TaskReportDefaultValue } from "../../components/DailyProgressRport/TaskRportScheam";
const DailyProgrssContext = createContext();
export const useDailyProgrssContext = () => {
@ -30,8 +31,9 @@ const DailyProgrssReport = () => {
const [service, setService] = useState("");
const [filter, setFilter] = useState('')
const { setOffcanvasContent, setShowTrigger } = useFab();
const updatedRef = useRef();
const { data, isLoading, isError, error } = useProjectAssignedServices(selectedProject);
const [filterData, setFilterdata] = useState(null);
const [modal, setModal] = useState({ type: null, data: null });
const openModal = (type, data = null) => setModal({ type, data });
@ -49,20 +51,38 @@ const DailyProgrssReport = () => {
serviceFilter: ""
}
});
const clearFilter = () => setFilter(TaskReportDefaultValue);
const handleFilter = (filterObj) => {
setFilter(filterObj)
}
useEffect(() => {
setShowTrigger(true);
setOffcanvasContent("Report Filter", <TaskReportFilterPanel handleFilter={handleFilter} />);
setOffcanvasContent("Report Filter", <TaskReportFilterPanel handleFilter={handleFilter} ref={updatedRef}
clearFilter={clearFilter}
setFilterdata={setFilterdata} />);
return () => {
setShowTrigger(false);
setOffcanvasContent("", null);
};
}, []);
const handleRemoveChip = (key, id) => {
setFilter((prev) => {
const updated = { ...prev };
if (Array.isArray(updated[key])) {
updated[key] = updated[key].filter((v) => v !== id);
setTimeout(() => updatedRef.current?.resetFieldValue(key, updated[key]), 0);
} else {
updated[key] = null;
setTimeout(() => updatedRef.current?.resetFieldValue(key, null), 0);
}
return updated;
});
};
return (
<div className="container-fluid">
<DailyProgrssContext.Provider value={contextDispatcher}>
@ -97,7 +117,7 @@ const DailyProgrssReport = () => {
]}
/>
<div className="card card-fullscreen p-5">
<div className="card page-min-h p-5">
{data?.length > 0 && (
<div className="col-sm-4 col-md-3 col-12 text-start">
<AppFormController
@ -107,15 +127,15 @@ const DailyProgrssReport = () => {
render={({ field }) => (
<SelectField
label="Services"
options={[{ id: "", name: "All Projects" }, ...(data ?? [])]}
options={[{ id: "", name: "All Projects" }, ...(data ?? [])]}
placeholder="Select Service"
labelKey="name"
labelKey="name"
valueKey="id"
isLoading={isLoading}
value={field.value}
onChange={(val) => {
field.onChange(val);
setService(val);
field.onChange(val);
setService(val);
}}
className="m-0"
/>
@ -125,7 +145,10 @@ const DailyProgrssReport = () => {
)}
<div>
<TaskReportList />
<TaskReportList filter={filter}
filterData={filterData}
removeFilterChip={handleRemoveChip}
clearFilter={clearFilter} />
</div>
</div>