Notes
Notes - notes.io |
import { useSelector } from 'react-redux';
import { Checkbox, IconButton, Tooltip } from '@mui/material';
import LanIcon from '@mui/icons-material/Lan';
import AddIcon from '@mui/icons-material/Add';
import RemoveIcon from '@mui/icons-material/Remove';
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
import CommentOutlinedIcon from '@mui/icons-material/CommentOutlined';
import { IoDocumentOutline } from 'react-icons/io5';
import { selectDocumentList } from '../../../Shared/Redux/Slicers/SubmissionSlice';
import ApiService from '../../../Shared/Services/CyberAIService';
import '../Submission-Summary/Summary.css';
import './Documents.css';
import { getEnvVar } from '../../../Shared/Utility/UtilityMethods';
import FileDownloadOutlinedIcon from '@mui/icons-material/FileDownloadOutlined';
import { showDownloadSuccess } from '../../../Shared/Components/AlertBox';
const BASE_NETWORK_PATH = getEnvVar("REACT_APP_DOCUMENT_NETWORK_BASE_PATH") || "";
function Documents({ apiDataDocs, data, onDocReviewStatusChange }) {
const documentData = useSelector(selectDocumentList)
const { submission_id } = data.e
const { doc_ingestion_id } = data.e
const [activetab, setActivetab] = useState('All')
const [selectedDate, setSelectedDate] = useState("")
const [submissionVersion, setSubmissionVersion] = useState(1);
const [expandAll, setExpandAll] = useState(false);
const [reviewStatusByDate, setReviewStatusByDate] = useState({})
const [copyTooltipOpen, setCopyTooltipOpen] = useState(null)
const formatDate = (dateStr) => {
if (!dateStr || dateStr.length < 14) return ""
const year = +dateStr.slice(0, 4)
const month = +dateStr.slice(4, 6) - 1
const day = +dateStr.slice(6, 8)
const hour = +dateStr.slice(8, 10)
const minute = +dateStr.slice(10, 12)
const second = +dateStr.slice(12, 14)
return new Date(year, month, day, hour, minute, second)
.toLocaleString("en-US", {
year: "numeric", month: "2-digit",
day: "2-digit", hour: "2-digit",
minute: "2-digit", second: "2-digit"
})
}
const receivedDates = apiDataDocs?.emails?.map(e => e.received_date)
useEffect(() => {
if (!selectedDate && receivedDates?.length) {
setSelectedDate(receivedDates[0])
}
}, [receivedDates])
useEffect(() => {
if (!apiDataDocs?.review_status) return
const map = {}
apiDataDocs.review_status.forEach(r => {
map[r.received_date] = r.document_reviewed_status
})
setReviewStatusByDate(map)
}, [apiDataDocs])
const onDocumentReviewStatusHandler = async (event, docReceivedDate) => {
const isChecked = event.target.checked
const emailAddr = localStorage.getItem('email')
const selectedEmail = apiDataDocs?.emails?.find(e => e.received_date === docReceivedDate)
if (!selectedEmail) return console.error("No email for", docReceivedDate)
const formatReviewedDate = date => {
const pad = n => n.toString().padStart(2, '0')
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ` +
`${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}.00`
}
const updatedReviewStatus = isChecked ? "Review Complete" : "Needs Review"
setReviewStatusByDate(prev => ({
...prev,
[docReceivedDate]: updatedReviewStatus
}))
const requestBody = {
submission_id,
received_date: docReceivedDate,
document_reviewed_status: updatedReviewStatus,
reviewed_by: emailAddr,
reviewed_date: formatReviewedDate(new Date())
}
try {
const res = await ApiService.editDocumentReviewStatus(submission_id, requestBody)
if (res.status === 200 && res.data?.document_reviewed_status) {
onDocReviewStatusChange?.(res.data.document_reviewed_status)
}
} catch (err) {
console.error("Error updating Doc Review Status:", err)
}
}
const TAG_LABEL_MAP = {
"RFP": "RFP",
"Census": "Census",
"LossRun": "Loss Run",
"Output": "Output",
};
const TAG_ORDER = ["RFP", "LossRun", "Census", "Other", "Output"];
const getOrderedTags = () => {
if (!apiDataDocs?.emails?.length) return []
const allTags = apiDataDocs.emails.flatMap(email =>
[
...(email.attachments || []),
...(email.output || [])
].flatMap(doc => doc.classification || [])
);
const unique = [...new Set(allTags)]
const ordered = []
TAG_ORDER.forEach(tag => {
if (tag === "Other") {
ordered.push(...unique.filter(t => !TAG_ORDER.includes(t)))
} else if (unique.includes(tag)) {
ordered.push(tag)
}
})
return ordered
}
const missingInfo = apiDataDocs?.missing_information;
const missingDocs = apiDataDocs?.missing_documents;
const [expandedEmails, setExpandedEmails] = useState({});
const toggleExpand = (doc_id) => {
setExpandedEmails(prev => ({
...prev,
[doc_id]: !prev[doc_id]
}));
};
const grouped = [];
if (apiDataDocs?.emails?.length) {
apiDataDocs.emails.forEach((emailEntry, emailIndex) => {
const receivedDate = emailEntry.received_date;
const fileName = emailEntry.email_file;
const ext = fileName?.split('.').pop().toLowerCase() || "eml";
const emailIdentifier = emailEntry.attachment_received_date || receivedDate;
grouped.push({
doc_id: `email-${emailIdentifier}`,
name: fileName,
sub: emailEntry.email_sub,
displayName: emailEntry.email_sub,
type: ext === "msg" ? "msg" : "eml",
classification: ["Email"],
received_date: receivedDate,
is_email_file: true,
archive_blob_location: emailEntry.archive_blob_location,
attachment_received_date: emailEntry.attachment_received_date || "",
email_id: emailEntry.email_id || "",
network_path: emailEntry.network_path || "",
attachment_blob_path: emailEntry.attachment_blob_path || "", // ✅ Add this
output_blob_path: emailEntry.output_blob_path || "", // ✅ Add this
attachments: (emailEntry.attachments || []).map((att, i) => ({
...att,
doc_id: att.doc_id || `att-${emailIndex}-${i}`,
received_date: receivedDate,
is_email_file: false,
attachment_received_date: emailEntry.attachment_received_date || "",
email_id: emailEntry.email_id || "",
attachment_blob_path: emailEntry.attachment_blob_path || "", // ✅ Also add to attachment if needed
})),
});
});
}
const outputDocs = apiDataDocs?.emails?.flatMap(email =>
(email.output || []).map((outputDoc, i) => ({
...outputDoc,
received_date: email.received_date,
attachment_received_date: email.attachment_received_date || "",
email_id: email.email_id || "",
doc_id: outputDoc.doc_id || `output-${email.email_id || i}`,
classification: ['Output'],
}))
) || [];
useEffect(() => {
if (!grouped.length) return;
const map = {};
grouped.forEach(email => {
map[email.doc_id] = expandAll;
});
setExpandedEmails(map);
}, [expandAll]);
const filteredGrouped = activetab === 'All'
? grouped
: activetab === 'Output'
? []
: grouped.map(email => {
const filteredAttachments = email.attachments.filter(att =>
(att.classification || []).includes(activetab)
);
if (activetab === 'Email' && email.classification?.includes('Email')) {
return { ...email, attachments: [] };
}
return filteredAttachments.length > 0
? { ...email, attachments: filteredAttachments }
: null;
}).filter(Boolean);
const getAttachmentReceivedDate = (row) => {
if (row.attachment_received_date) return formatDate(row.attachment_received_date);
return "";
};
const getEmailId = (row) => {
if (row.email_id) return row.email_id;
return "";
};
const toUtcTimestamp = (dateStr) => {
if (!dateStr || dateStr.length < 14) return null;
const year = +dateStr.slice(0, 4);
const month = +dateStr.slice(4, 6) - 1;
const day = +dateStr.slice(6, 8);
const hour = +dateStr.slice(8, 10);
const minute = +dateStr.slice(10, 12);
const second = +dateStr.slice(12, 14);
const milli = dateStr.length > 14 ? +dateStr.slice(14) : 0;
return Date.UTC(year, month, day, hour, minute, second, milli);
};
const handleDownload = async (archiveBlobLocation, receivedDateUtc, emailId = null) => {
if (!archiveBlobLocation) return;
try {
const { data, status } = await ApiService.getBlobToken();
if (status !== 200 || !data?.token) return;
const sasUrl =
archiveBlobLocation +
(data.token.startsWith("?") ? data.token : `?${data.token}`);
const res = await fetch(sasUrl, { mode: "cors" });
const blob = await res.blob();
const blobUrl = URL.createObjectURL(blob);
const ts = toUtcTimestamp(receivedDateUtc);
const stamp = ts != null
? (() => {
const d = new Date(ts);
const pad2 = n => n.toString().padStart(2, '0');
const pad3 = n => n.toString().padStart(3, '0');
const Y = d.getFullYear();
const M = pad2(d.getMonth() + 1);
const D = pad2(d.getDate());
const h = pad2(d.getHours());
const m = pad2(d.getMinutes());
const s = pad2(d.getSeconds());
const ms = pad3(d.getMilliseconds());
return `${Y}${M}${D}${h}${m}${s}${ms}`;
})()
: receivedDateUtc;
let idToUse = emailId;
if (!idToUse) {
const selectedEmail = apiDataDocs?.emails?.find(e =>
(e.attachment_received_date && e.attachment_received_date === receivedDateUtc) ||
(!e.attachment_received_date && e.received_date === receivedDateUtc)
);
idToUse = (selectedEmail && selectedEmail.email_id) ? selectedEmail.email_id : doc_ingestion_id;
}
const filename = `${idToUse}_${stamp}.zip`;
const link = document.createElement("a");
link.href = blobUrl;
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(blobUrl);
showDownloadSuccess();
} catch (err) {
console.error("Error downloading blob:", err);
}
};
return (
<div className='recieved-documents-layout'>
<div className='documents-frame'>
<div className="tab-subtitle-div">
<div className="tab-subtitle">Received Documents</div>
<div className="missing-data-row">
{missingInfo && (
<div className="missing-box styled-box">
<span className="missing-label">Missing Info:</span>
<span className="missing-value">{missingInfo}</span>
</div>
)}
{missingDocs && (
<div className="missing-box styled-box">
<span className="missing-label">Missing Docs:</span>
<span className="missing-value">{missingDocs}</span>
</div>
)}
<div className="expand-all-container">
<Checkbox
checked={expandAll}
onChange={() => setExpandAll(prev => !prev)}
size="small"
/>
<span>Expand All</span>
</div>
</div>
</div>
<div className='document-controls-row'>
<button
className={activetab === 'All' ? 'submission-tab-active category-div all category-text' : 'category-div all category-text'}
onClick={() => setActivetab('All')}
>All</button>
{getOrderedTags(selectedDate).map(tag => {
if (tag === "Output" && outputDocs.length === 0) return null;
return (
<button
key={tag}
className={activetab === tag ? 'submission-tab-active category-div all category-text' : 'category-div all category-text'}
onClick={() => setActivetab(tag)}
>
{TAG_LABEL_MAP[tag] || tag}
</button>
);
})}
</div>
<div className="ui-table-container bordered">
<table className="ui-table">
<thead>
<tr style={{ position: "sticky", top: 0, background: "#e4f3fa" }}>
<th style={{ minWidth: 480 }}>Document Name</th>
<th style={{ minWidth: 30 }}>Type</th>
<th style={{ minWidth: 100 }}>Classification</th>
<th style={{ minWidth: 150 }}>Received Date</th>
<th style={{ minWidth: 150 }}>Attachment Received Date</th>
<th style={{ minWidth: 180 }}>App Email Id</th>
<th style={{ minWidth: 50 }}>Comment</th>
<th style={{ minWidth: 50 }}>Download</th>
<th style={{ minWidth: 50 }}>Reviewed Status</th>
</tr>
</thead>
<tbody>
{activetab === 'Output' ? (
outputDocs.map(output => (
<tr key={output.doc_id} className="attachment-row">
<td>
<div className="document-name-text attachment-name">
<IoDocumentOutline />
<span>{output.name}</span>
</div>
</td>
<td>{output.type}</td>
<td>
<div className="tag-list">
{(output.classification || []).map(tag => (
<button key={tag} className="document-tag">
{TAG_LABEL_MAP[tag] || tag}
</button>
))}
</div>
</td>
<td>{formatDate(output.received_date)}</td>
<td>{getAttachmentReceivedDate(output)}</td>
<td>{getEmailId(output)}</td>
<td className="center-cell">
<div className="center-cell-content">
{output.comment && (
<Tooltip title={output.comment} arrow placement="top">
<IconButton size="small">
<CommentOutlinedIcon fontSize="small" />
</IconButton>
</Tooltip>
)}
</div>
</td>
<td className="center-cell">
<div className="center-cell-content copy-path-container">
{output.archive_blob_location && (
<Tooltip title="Download" arrow placement="top">
<IconButton
type="button"
size="small"
onClick={() =>
handleDownload(
output.archive_blob_location,
output.attachment_received_date || output.received_date,
output.email_id
)
}
>
<FileDownloadOutlinedIcon fontSize="small" />
</IconButton>
</Tooltip>
)}
</div>
</td>
<td></td>
</tr>
))
) : (
filteredGrouped.length > 0 ? (
filteredGrouped.map(email => (
<React.Fragment key={email.doc_id}>
<tr className="email-row">
<td onClick={() => toggleExpand(email.doc_id)} className="email-toggle clickable">
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
<span className="expand-icon">
{expandedEmails[email.doc_id]
? <RemoveIcon fontSize="medium" color="primary" />
: <AddIcon fontSize="medium" color="primary" />}
</span>
<IoDocumentOutline />
<div className="document-name-text">
<div><strong>{email.displayName || email.name}</strong></div>
</div>
</div>
</td>
<td>{email.type}</td>
<td>
<button className="document-tag">Email</button>
</td>
<td>{formatDate(email.received_date)}</td>
<td>{getAttachmentReceivedDate(email)}</td>
<td>{getEmailId(email)}</td>
<td></td> {/* Comment column: empty for parent */}
<td className="center-cell">
<div className="center-cell-content copy-path-container">
<Tooltip
title={email.archive_blob_location
? "Download"
: "Documents not yet uploaded"}
arrow
placement="top"
>
<span>
<IconButton
type="button"
size="small"
disableRipple
disabled={!email.archive_blob_location}
onClick={e => {
if (!email.archive_blob_location) return;
e.preventDefault();
e.stopPropagation();
handleDownload(
email.archive_blob_location,
email.attachment_received_date || email.received_date,
email.email_id
);
}}
>
<FileDownloadOutlinedIcon fontSize="small" />
</IconButton>
</span>
</Tooltip>
</div>
</td>
<td className="center-cell">
<div className="center-cell-content">
<Checkbox
checked={
reviewStatusByDate[email.attachment_received_date || email.received_date]?.toLowerCase() === "review complete"
}
onChange={(e) => {
const uniqueKey = email.attachment_received_date || email.received_date;
onDocumentReviewStatusHandler(e, uniqueKey);
}}
size="small"
/>
</div>
</td>
</tr>
{expandedEmails[email.doc_id] && email.attachments.map(att => (
<tr key={att.doc_id} className="attachment-row">
<td>
<div className="document-name-text attachment-name">
<IoDocumentOutline />
<span>{att.name}</span>
</div>
</td>
<td>{att.type}</td>
<td>
<div className="tag-list">
{att.classification?.map(tag => (
<button
key={tag}
className={tag === "Could not process file, please check" ? "document-tag-error" : "document-tag"}
>
{TAG_LABEL_MAP[tag] || tag}
</button>
))}
</div>
</td>
<td>{formatDate(att.received_date)}</td>
<td>{getAttachmentReceivedDate(att)}</td>
<td>{getEmailId(att)}</td>
<td className="center-cell">
<div className="center-cell-content">
{att.comment && att.comment.trim() && (
<Tooltip title={att.comment} arrow placement="top">
<IconButton size="small">
<CommentOutlinedIcon fontSize="small" />
</IconButton>
</Tooltip>
)}
</div>
</td>
<td className="center-cell"></td>
<td className="center-cell"></td>
</tr>
))}
</React.Fragment>
))
) : (
<tr>
<td colSpan="10">
<div className="no-data-message">No Documents</div>
</td>
</tr>
)
)}
</tbody>
</table>
</div>
</div>
</div>
);
}
export default Documents
![]() |
Notes is a web-based application for online taking notes. You can take your notes and share with others people. If you like taking long notes, notes.io is designed for you. To date, over 8,000,000,000+ notes created and continuing...
With notes.io;
- * You can take a note from anywhere and any device with internet connection.
- * You can share the notes in social platforms (YouTube, Facebook, Twitter, instagram etc.).
- * You can quickly share your contents without website, blog and e-mail.
- * You don't need to create any Account to share a note. As you wish you can use quick, easy and best shortened notes with sms, websites, e-mail, or messaging services (WhatsApp, iMessage, Telegram, Signal).
- * Notes.io has fabulous infrastructure design for a short link and allows you to share the note as an easy and understandable link.
Fast: Notes.io is built for speed and performance. You can take a notes quickly and browse your archive.
Easy: Notes.io doesn’t require installation. Just write and share note!
Short: Notes.io’s url just 8 character. You’ll get shorten link of your note when you want to share. (Ex: notes.io/q )
Free: Notes.io works for 14 years and has been free since the day it was started.
You immediately create your first note and start sharing with the ones you wish. If you want to contact us, you can use the following communication channels;
Email: [email protected]
Twitter: http://twitter.com/notesio
Instagram: http://instagram.com/notes.io
Facebook: http://facebook.com/notesio
Regards;
Notes.io Team
