added attachment views
This commit is contained in:
parent
6b9670b942
commit
3ed5999f29
@ -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>
|
||||
);
|
||||
|
66
src/components/common/ImagePreview.jsx
Normal file
66
src/components/common/ImagePreview.jsx
Normal 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;
|
50
src/components/common/ImagePreviewstyle.css
Normal file
50
src/components/common/ImagePreviewstyle.css
Normal 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;
|
||||
}
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user