added attachment views

This commit is contained in:
Pramod Mahajan 2025-06-12 22:54:08 +05:30
parent 6b9670b942
commit 3ed5999f29
4 changed files with 279 additions and 151 deletions

View File

@ -8,6 +8,7 @@ import showToast from "../../services/toastService";
import Avatar from "../common/Avatar";
import { getBgClassFromHash } from "../../utils/projectStatus";
import { cacheData, getCachedData } from "../../slices/apiDataManager";
import ImagePreview from "../common/ImagePreview";
const schema = z.object({
comment: z.string().min(1, "Comment cannot be empty"),
@ -40,7 +41,9 @@ const ReportTaskComments = ({ commentsData, closeModal }) => {
useEffect(() => {
const taskList = getCachedData("taskList");
if (taskList && taskList.data && commentsData?.id) {
const currentTask = taskList.data.find(task => task.id === commentsData.id);
const currentTask = taskList.data.find(
(task) => task.id === commentsData.id
);
if (currentTask && currentTask.comments) {
setComment(currentTask.comments);
} else {
@ -99,157 +102,170 @@ const ReportTaskComments = ({ commentsData, closeModal }) => {
showToast("Successfully Sent", "success");
} catch (error) {
setloading(false);
showToast(error.response.data?.message || "Something went wrong", "error");
showToast(
error.response.data?.message || "Something went wrong",
"error"
);
}
};
return (
<div
className="modal-dialog modal-lg modal-simple report-task-comments-modal mx-sm-auto mx-1"
role="document"
>
<div className="modal-content">
<div className="modal-body p-sm-4 p-0">
<button
type="button"
className="btn-close"
onClick={closeModal}
aria-label="Close"
></button>
<h5 className=" text-center mb-2">
Activity Summary
</h5>
<div className="p-1">
<div className="modal-body p-sm-4 p-0">
<h5 className=" text-center mb-2">Activity Summary</h5>
<p className="small-text text-start my-2">
{commentsData?.workItem?.workArea?.floor?.building?.description}
</p>
<p className="small-text text-start my-2">
{commentsData?.workItem?.workArea?.floor?.building?.description}
</p>
<p className="fw-bold my-2 text-start">
Assigned By :
<span className=" ms-2">
{commentsData?.assignedBy?.firstName +
" " +
commentsData?.assignedBy?.lastName}
</span>{" "}
</p>
<p className="fw-bold my-2 text-start">
Assigned By :
<span className=" ms-2">
{commentsData?.assignedBy?.firstName +
" " +
commentsData?.assignedBy?.lastName}
</span>{" "}
</p>
<p className="fw-bold my-2 text-start">
Reported By :
<span className=" ms-2"> -
{/* {commentsData?.assignedBy?.firstName +
<p className="fw-bold my-2 text-start">
Reported By :
<span className=" ms-2">
{" "}
-
{/* {commentsData?.assignedBy?.firstName +
" " +
commentsData?.assignedBy?.lastName} */}
</span>{" "}
</p>
</span>{" "}
</p>
<p className="fw-bold my-2 text-start">
Location :
<span className="fw-normal ms-2 text-start">
{`${commentsData?.workItem?.workArea?.floor?.building?.name}`}{" "}
<i className="bx bx-chevron-right"></i>{" "}
{`${commentsData?.workItem?.workArea?.floor?.floorName} `}{" "}
<i className="bx bx-chevron-right"></i>
{`${commentsData?.workItem?.workArea?.areaName}`}
<i className="bx bx-chevron-right"></i>
{` ${commentsData?.workItem?.activityMaster?.activityName}`}
</span>
</p>
<p className="fw-bold my-2 text-start">
Planned Work: {commentsData?.plannedTask}{" "}
{
commentsData?.workItem?.activityMaster
?.unitOfMeasurement
}
</p>
<p className="fw-bold my-2 text-start">
{" "}
Completed Work : {commentsData?.completedTask}{" "}
{
commentsData?.workItem?.activityMaster
?.unitOfMeasurement
}
</p>
<div className="d-flex align-items-center flex-wrap">
<p className="fw-bold text-start m-0 me-1">Team :</p>
<div className="d-flex flex-wrap align-items-center gap-2">
{commentsData?.teamMembers?.map((member, idx) => (
<span key={idx} className="d-flex align-items-center">
<Avatar
firstName={member?.firstName}
lastName={member?.lastName}
size="xs"
/>
{member?.firstName + " " + member?.lastName}
</span>
))}
</div>
<p className="fw-bold my-2 text-start">
Location :
<span className="fw-normal ms-2 text-start">
{`${commentsData?.workItem?.workArea?.floor?.building?.name}`}{" "}
<i className="bx bx-chevron-right"></i>{" "}
{`${commentsData?.workItem?.workArea?.floor?.floorName} `}{" "}
<i className="bx bx-chevron-right"></i>
{`${commentsData?.workItem?.workArea?.areaName}`}
<i className="bx bx-chevron-right"></i>
{` ${commentsData?.workItem?.activityMaster?.activityName}`}
</span>
</p>
<p className="fw-bold my-2 text-start">
Planned Work: {commentsData?.plannedTask}{" "}
{commentsData?.workItem?.activityMaster?.unitOfMeasurement}
</p>
<p className="fw-bold my-2 text-start">
{" "}
Completed Work : {commentsData?.completedTask}{" "}
{commentsData?.workItem?.activityMaster?.unitOfMeasurement}
</p>
<div className="d-flex align-items-center flex-wrap">
<p className="fw-bold text-start m-0 me-1">Team :</p>
<div className="d-flex flex-wrap align-items-center gap-2">
{commentsData?.teamMembers?.map((member, idx) => (
<span key={idx} className="d-flex align-items-center">
<Avatar
firstName={member?.firstName}
lastName={member?.lastName}
size="xs"
/>
{member?.firstName + " " + member?.lastName}
</span>
))}
</div>
<form onSubmit={handleSubmit(onSubmit)} className="text-start">
<label className="fw-bold text-start my-1">Add comment :</label>
<textarea
{...register("comment")}
className="form-control"
id="exampleFormControlTextarea1"
placeholder="Enter comment"
/>
{errors.comment && (
<div className="danger-text">{errors.comment.message}</div>
)}
<div className="text-end my-1">
<button
type="button"
className="btn btn-sm btn-secondary"
onClick={closeModal}
data-bs-dismiss="modal"
>
Close
</button>
<button type="submit" className="btn btn-sm btn-primary ms-2" disabled={loading}>
{loading ? "Sending..." : "Comment"}
</button>
</div>
</form>
<ul
className="list-group px-0 mx-0 overflow-auto border-0"
ref={containerRef}
>
{comments &&
comments
?.slice()
.reverse()
.map((data, idx) => {
const fullName = `${data?.employee?.firstName} ${data?.employee?.lastName}`;
return (
<li
className={`list-group-item list-group-item-action border-none my-1 p-1`}
key={idx}
>
<div
className={`li-wrapper d-flex justify-content-start align-items-center my-0`}
>
<div className="avatar avatar-xs me-1">
<span
className={`avatar-initial rounded-circle bg-label-primary`}
>
{`${data?.employee?.firstName?.slice(0, 1)} ${data?.employee?.lastName?.slice(0, 1)}`}
</span>
</div>
<div className={`d-flex align-items-center justify-content-start`}>
<p className={`mb-0 text-muted me-2`}>{fullName}</p>
<p className={`text-secondary m-0`} style={{ fontSize: "10px" }}>
{moment.utc(data?.commentDate).local().fromNow()}
</p>
</div>
</div>
<p className={`ms-6 text-start mb-0 text-body`}>{data?.comment}</p>
</li>
);
})}
</ul>
</div>
{commentsData?.reportedPreSignedUrls?.length > 0 && (
<div className=" text-start">
<p className="fw-bold m-0">Attachment</p>
<ImagePreview
IsReported={true}
images={commentsData?.reportedPreSignedUrls}
/>
</div>
)}
<form onSubmit={handleSubmit(onSubmit)} className="text-start">
<label className="fw-bold text-start my-1">Add comment :</label>
<textarea
{...register("comment")}
className="form-control"
id="exampleFormControlTextarea1"
placeholder="Enter comment"
/>
{errors.comment && (
<div className="danger-text">{errors.comment.message}</div>
)}
<div className="text-end my-1">
<button
type="button"
className="btn btn-sm btn-secondary"
onClick={closeModal}
data-bs-dismiss="modal"
>
Close
</button>
<button
type="submit"
className="btn btn-sm btn-primary ms-2"
disabled={loading}
>
{loading ? "Sending..." : "Comment"}
</button>
</div>
</form>
<ul
className="list-group px-0 mx-0 overflow-auto border-0"
ref={containerRef}
>
{comments &&
comments
?.slice()
.reverse()
.map((data, idx) => {
const fullName = `${data?.employee?.firstName} ${data?.employee?.lastName}`;
return (
<li
className={`list-group-item list-group-item-action border-none my-1 p-1`}
key={idx}
>
<div
className={`li-wrapper d-flex justify-content-start align-items-center my-0`}
>
<div className="avatar avatar-xs me-1">
<span
className={`avatar-initial rounded-circle bg-label-primary`}
>
{`${data?.employee?.firstName?.slice(
0,
1
)} ${data?.employee?.lastName?.slice(0, 1)}`}
</span>
</div>
<div
className={`d-flex align-items-center justify-content-start`}
>
<p className={`mb-0 text-muted me-2`}>{fullName}</p>
<p
className={`text-secondary m-0`}
style={{ fontSize: "10px" }}
>
{moment.utc(data?.commentDate).local().fromNow()}
</p>
</div>
</div>
<p className={`ms-6 text-start mb-0 text-body`}>
{data?.comment}
</p>
{data?.preSignedUrls?.length > 0 && (
<div className="ps-6 text-start">
<small className="">Attachment</small>
<ImagePreview images={data?.preSignedUrls} IsReported={true} />
</div>
)}
</li>
);
})}
</ul>
</div>
</div>
);

View File

@ -0,0 +1,66 @@
import React, { useState } from "react";
import "./ImagePreviewstyle.css";
import GlobalModel from "./GlobalModel";
const ImagePreview = ({ images = [], IsReported = false }) => {
const [selectedImage, setSelectedImage] = useState(null);
const handleOpen = (imgSrc) => {
setSelectedImage(imgSrc);
};
const handleClose = () => {
setSelectedImage(null);
};
return (
<>
{IsReported ? (
<div className="d-flex flex-wrap gap-2">
{images.map((img, index) => (
<div
key={index}
className="img-container position-relative"
onClick={() => handleOpen(img)}
>
<img src={img} alt={`thumb-${index}`} className="img-thumbnail" />
<div className="eye-overlay d-flex justify-content-center align-items-center">
<i className="fa-solid fa-eye fs-6"></i>
</div>
</div>
))}
</div>
) : (
<div className="d-flex flex-wrap gap-2">
{images.map((img, index) => (
<div key={index} className="" onClick={() => handleOpen(img)}>
{/* <img src={img} alt={`thumb-${ index }`} className="img-thumbnail" /> */}
<i className="fa-regular fa-image fs-3 cursor-pointer"></i>
</div>
))}
</div>
)}
{selectedImage && (
<GlobalModel
isOpen={selectedImage}
size="lg"
dialogClass="modal-dialog-centered"
closeModal={() => setSelectedImage(null)}
>
<div className="mt-3 img-preview" >
<img
src={selectedImage}
alt="Previe Image"
className="w-100"
/>
</div>
</GlobalModel>
)}
</>
);
};
export default ImagePreview;

View File

@ -0,0 +1,50 @@
.img-container {
width: 70px; /* or whatever size you want */
height: 30px;
position: relative; /* to contain the absolute overlay */
overflow: hidden;
cursor: pointer;
}
.img-container img.img-thumbnail {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.eye-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.4);
opacity: 0;
transition: opacity 0.3s ease;
pointer-events: none;
color: white;
font-size: 1.5rem;
user-select: none;
}
/* center icon */
.eye-overlay i {
pointer-events: none;
}
/* fade in on hover */
.img-container:hover .eye-overlay {
opacity: 1;
pointer-events: auto; /* enable pointer events on hover */
}
.iconImage{
font-size: 30px;
}
.img-preview {
width: 100%;
height: 500px;
background-color: #E9E9E9;
text-align: center;
}

View File

@ -10,6 +10,7 @@ import DateRangePicker from "../../components/common/DateRangePicker";
import { useSearchParams } from "react-router-dom";
import moment from "moment";
import FilterIcon from "../../components/common/FilterIcon"; // Import the FilterIcon component
import GlobalModel from "../../components/common/GlobalModel";
const DailyTask = () => {
const [searchParams] = useSearchParams();
@ -170,19 +171,14 @@ const DailyTask = () => {
</div>
<div
className={`modal fade ${isModalOpenComment ? "show d-block" : ""}`}
tabIndex="-1"
role="dialog"
style={{ display: isModalOpenComment ? "block" : "none" }}
aria-hidden={!isModalOpenComment}
>
<ReportTaskComments
{isModalOpenComment && (
<GlobalModel isOpen={isModalOpenComment} size="lg" closeModal={()=>setIsModalOpenComment(false)}>
<ReportTaskComments
commentsData={comments}
closeModal={closeCommentModal}
/>
</div>
</GlobalModel>
)}
<div className="container-xxl flex-grow-1 container-p-y">
<Breadcrumb