diff --git a/src/components/DocumentPreviewButton.tsx b/src/components/DocumentPreviewButton.tsx index 0d5f21b2..fe372ade 100644 --- a/src/components/DocumentPreviewButton.tsx +++ b/src/components/DocumentPreviewButton.tsx @@ -1,69 +1,88 @@ -import React, { useState } from "react"; +import React, { useState } from 'react'; +import ViewExternalDocumentsModal from '../pages/viewExternalDocuments'; const DocumentSelector = ({ item }) => { - const [isDropdownOpen, setIsDropdownOpen] = useState(false); + const [isDropdownOpen, setIsDropdownOpen] = useState(false); - const toggleDropdown = () => { - setIsDropdownOpen(!isDropdownOpen); - }; - const handleDocumentOnClick = (urls: string) => { - const url = `#/view-external-document?fileUrl=${encodeURIComponent(urls)}&backUrl=${encodeURIComponent("/")}&title=${encodeURIComponent("Documents Preview")}`; - window.open(url, "_blank"); - }; - return ( -
- + const toggleDropdown = () => { + setIsDropdownOpen(!isDropdownOpen); + }; + const handleDocumentOnClick = (urls: string) => { + const url = `#/view-external-document?fileUrl=${encodeURIComponent( + urls + )}&backUrl=${encodeURIComponent('/')}&title=${encodeURIComponent( + 'Documents Preview' + )}`; + window.open(url, '_blank'); + }; + const [show, setShow] = useState(false); + const [fileUrl, setFileUrl] = useState(''); + const viewExternalDocumentsModal = (url: any) => { + setFileUrl(url); + setShow(true); + }; + const onClose = () => { + setShow(false); + }; + return ( +
+ - {isDropdownOpen && ( - - )} -
- ); + {isDropdownOpen && ( + + )} + +
+ ); }; -export default DocumentSelector; \ No newline at end of file +export default DocumentSelector; diff --git a/src/pages/ApplicationCycle/myApplication.tsx b/src/pages/ApplicationCycle/myApplication.tsx index 4cde7d53..6f3c90a3 100644 --- a/src/pages/ApplicationCycle/myApplication.tsx +++ b/src/pages/ApplicationCycle/myApplication.tsx @@ -1,324 +1,478 @@ -import { useEffect, useState } from "react"; -import { useAppDispatch, useAppSelector } from "../../hooks/hooks"; -import { toast, ToastContainer } from "react-toastify"; -import { AiFillFilePdf } from "react-icons/ai" +import { useEffect, useState } from 'react'; +import { useAppDispatch, useAppSelector } from '../../hooks/hooks'; +import { toast, ToastContainer } from 'react-toastify'; +import { AiFillFilePdf } from 'react-icons/ai'; import { - getApplicantCyclesApplications, - getCyclesApplicationAttributes, - getCyclesStages -} from "../../redux/actions/applications"; -import { Link } from "react-router-dom"; + getApplicantCyclesApplications, + getCyclesApplicationAttributes, + getCyclesStages, +} from '../../redux/actions/applications'; +import { Link } from 'react-router-dom'; +import ViewExternalDocumentsModal from '../../pages/viewExternalDocuments'; export const MyApplication = () => { - const dispatch = useAppDispatch(); - const [isLoading, setIsLoading] = useState(true); - const [application, setApplication] = useState(null); - const [attributes, setAttributes] = useState(null); - const [stages, setStages] = useState(null); - - useEffect(() => { - const fetchApplications = async () => { - try { - const response = await getApplicantCyclesApplications(); - if (response?.data?.getTraineeCyclesApplications) { - setApplication(response.data.getTraineeCyclesApplications); - console.log("Appl", response.data.getTraineeCyclesApplications) - } else { - throw new Error("No applications found"); - } - } catch (error) { - toast.error("No applications found!"); - } finally { - setIsLoading(false); - } - }; - - if (!application) { - fetchApplications(); + const dispatch = useAppDispatch(); + const [isLoading, setIsLoading] = useState(true); + const [application, setApplication] = useState(null); + const [attributes, setAttributes] = useState(null); + const [stages, setStages] = useState(null); + const [show, setShow] = useState(false); + const [fileUrl, setFileUrl] = useState(''); + const viewExternalDocumentsModal = (url: any) => { + setFileUrl(url); + setShow(true); + }; + const onClose = () => { + setShow(false); + }; + useEffect(() => { + const fetchApplications = async () => { + try { + const response = await getApplicantCyclesApplications(); + if (response?.data?.getTraineeCyclesApplications) { + setApplication(response.data.getTraineeCyclesApplications); + console.log('Appl', response.data.getTraineeCyclesApplications); } else { - const fetchAttributesAndStages = async () => { - try { - setIsLoading(true); - if (application?._id) { - const attributesResponse = await getCyclesApplicationAttributes(application._id); - if (attributesResponse?.data?.getApplicationsAttributes) { - setAttributes(attributesResponse.data.getApplicationsAttributes); - } - const stagesResponse = await getCyclesStages(application._id); - if (stagesResponse?.data?.getApplicationStages) { - console.log(stagesResponse.data.getApplicationStages); - setStages(stagesResponse.data.getApplicationStages); - } - } - } catch (error) { - toast.error("Something went wrong while fetching details!"); - } finally { - setIsLoading(false); - } - }; + throw new Error('No applications found'); + } + } catch (error) { + toast.error('No applications found!'); + } finally { + setIsLoading(false); + } + }; - fetchAttributesAndStages(); + if (!application) { + fetchApplications(); + } else { + const fetchAttributesAndStages = async () => { + try { + setIsLoading(true); + if (application?._id) { + const attributesResponse = await getCyclesApplicationAttributes( + application._id + ); + if (attributesResponse?.data?.getApplicationsAttributes) { + setAttributes(attributesResponse.data.getApplicationsAttributes); + } + const stagesResponse = await getCyclesStages(application._id); + if (stagesResponse?.data?.getApplicationStages) { + console.log(stagesResponse.data.getApplicationStages); + setStages(stagesResponse.data.getApplicationStages); + } + } + } catch (error) { + toast.error('Something went wrong while fetching details!'); + } finally { + setIsLoading(false); } - }, [application]); + }; - const formatDate = (timestamp) => { - const date = new Date(Number(timestamp)); - console.log("DDD", date) - if (isNaN(date.getTime())) return ''; + fetchAttributesAndStages(); + } + }, [application]); - return new Intl.DateTimeFormat('en-US', { - day: '2-digit', - month: 'short', - year: 'numeric', - }).format(date); - }; + const formatDate = (timestamp) => { + const date = new Date(Number(timestamp)); + console.log('DDD', date); + if (isNaN(date.getTime())) return ''; - if (isLoading) { - return ( - <> -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- - ); - } + return new Intl.DateTimeFormat('en-US', { + day: '2-digit', + month: 'short', + year: 'numeric', + }).format(date); + }; - if (!application) { - return ( -
-
-
-

- No applications found -

-
-
-
- ); - } - console.log("APPJJA", application) + if (isLoading) { + return ( + <> +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + ); + } + if (!application) { return ( - <> - -
-

- My Application -

-
-
-
-

- Personal Information -

-
    - {application?.firstName &&
  • Firstname: {application.firstName}
  • } - {application?.lastName &&
  • Lastname: {application.lastName}
  • } - {application?.email &&
  • Email: {application.email}
  • } - {application?.cycle_id?.name &&
  • Application Cycle: {application.cycle_id.name}
  • } - {application?.cycle_id?.createdAt && ( -
  • Published date: {formatDate(application.cycle_id.createdAt)}
  • - )} - {application?.createdAt && ( -
  • Application date: {formatDate(application.createdAt)}
  • - )} -
-
-
- {attributes?.Address && ( -

- Address -

)} -
    - {attributes?.province &&
  • Province: {attributes.province}
  • } - {attributes?.district &&
  • District: {attributes.district}
  • } - {attributes?.sector &&
  • Sector: {attributes.sector}
  • } - {attributes?.Address &&
  • Address: {attributes.Address}
  • } -
-
-
- {(application?.coverLetterUrl || application?.resumeUrl || application?.idDocumentUrl) && ( -
-

- Supporting Documents -

-
    - {application?.idDocumentUrl && ( -
  • - - ID Card - - - - View ID Card - -
  • - )} - {application?.resumeUrl && ( -
  • - - Resume - - - - View Resume - -
  • - )} - {application?.coverLetterUrl && ( -
  • - - Cover Letter - - - - View Cover Letter - -
  • - )} -
-
- )} -
-

Application Stages

-

- Current Stage: - {application?.applicationPhase === "Rejected" ? ( - Rejected - ) : ( - {application?.applicationPhase} - )} -

+
+
+
+

+ No applications found +

+
+
+
+ ); + } + console.log('APPJJA', application); -
-

Stages History

- {!stages?.dismissed && !stages?.interview && !stages?.shortlist && ( -

- No stages history found -

- )} -
    - {stages?.dismissed && ( -
  • - Rejected + return ( + <> + +
    +

    + My Application +

    +
    +
    +
    +

    + Personal Information +

    +
      + {application?.firstName && ( +
    • + Firstname: {application.firstName} +
    • + )} + {application?.lastName && ( +
    • + Lastname: {application.lastName} +
    • + )} + {application?.email && ( +
    • + Email: {application.email} +
    • + )} + {application?.cycle_id?.name && ( +
    • + Application Cycle:{' '} + {application.cycle_id.name} +
    • + )} + {application?.cycle_id?.createdAt && ( +
    • + Published date:{' '} + {formatDate(application.cycle_id.createdAt)} +
    • + )} + {application?.createdAt && ( +
    • + Application date:{' '} + {formatDate(application.createdAt)} +
    • + )} +
    +
    +
    + {attributes?.Address && ( +

    + Address +

    + )} +
      + {attributes?.province && ( +
    • + Province: {attributes.province} +
    • + )} + {attributes?.district && ( +
    • + District: {attributes.district} +
    • + )} + {attributes?.sector && ( +
    • + Sector: {attributes.sector} +
    • + )} + {attributes?.Address && ( +
    • + Address: {attributes.Address} +
    • + )} +
    +
    +
    + {(application?.coverLetterUrl || + application?.resumeUrl || + application?.idDocumentUrl) && ( +
    +

    + Supporting Documents +

    +
      + {application?.idDocumentUrl && ( +
    • + + ID Card + + +
    • + )} + {application?.resumeUrl && ( +
    • + + Resume + + +
    • + )} + {application?.coverLetterUrl && ( +
    • + + Cover Letter + + +
    • + )} +
    +
    + )} +
    +

    + Application Stages +

    +

    + Current Stage: + {application?.applicationPhase === 'Rejected' ? ( + Rejected + ) : ( + + {' '} + {application?.applicationPhase} + + )} +

    - {stages.dismissed?.status && ( -

    Status: {stages.dismissed.status}

    - )} +
    +

    Stages History

    + {!stages?.dismissed && + !stages?.interview && + !stages?.shortlist && ( +

    No stages history found

    + )} +
      + {stages?.dismissed && ( +
    • + + Rejected + - {stages.dismissed?.stageDismissedFrom && ( -

      Stage dismissed from: {stages.dismissed.stageDismissedFrom}

      - )} + {stages.dismissed?.status && ( +

      + Status: {stages.dismissed.status} +

      + )} - {stages.dismissed?.comments && ( -

      Comments: {stages.dismissed.comments}

      - )} + {stages.dismissed?.stageDismissedFrom && ( +

      + Stage dismissed from:{' '} + {stages.dismissed.stageDismissedFrom} +

      + )} - {stages.dismissed?.createdAt && ( -

      Date: {formatDate(stages.dismissed.createdAt)}

      - )} -
    • - )} + {stages.dismissed?.comments && ( +

      + Comments: {stages.dismissed.comments} +

      + )} - {stages?.admitted && ( -
    • - Admitted - {stages?.allStages?.history - .filter((history: any) => history.stage === "Admitted") - .map((history: any, index: any) => ( - -   ({formatDate(history.enteredAt)} - {formatDate(history.exitedAt)}) - - ))} - {stages.admitted?.status &&

      Status: {stages.admitted.status}

      } - {stages.admitted?.stageDismissedFrom &&

      Score: {stages.admitted.stageDismissedFrom}

      } - {stages.admitted?.comments &&

      Comments: {stages.admitted.comments}

      } - {stages.admitted?.createdAt &&

      Date: {formatDate(stages.admitted.createdAt)}

      } -
    • - )} - {stages?.interview && ( -
    • - Interview Assessment - {stages?.allStages?.history - .filter((history: any) => history.stage === "Technical Assessment") - .map((history: any, index: any) => ( - -   ({formatDate(history.enteredAt)} - {formatDate(history.exitedAt)}) - - ))} - {stages.interview?.interviewScore &&

      Score: {stages.interview.interviewScore}

      } - {stages.interview?.status &&

      Status: {stages.interview.status}

      } - {stages.interview?.comments &&

      Comments: {stages.interview.comments}

      } - {stages.interview?.createdAt &&

      Date: {formatDate(stages.interview.createdAt)}

      } -
    • - )} - {stages?.technical && ( -
    • - Technical Assessment - {stages?.allStages?.history - .filter((history: any) => history.stage === "Technical Assessment") - .map((history: any, index: any) => ( - -   ({formatDate(history.enteredAt)} - {formatDate(history.exitedAt)}) - - ))} - {stages.technical?.score &&

      Score: {stages.technical.score}%

      } - {stages.technical?.status &&

      Status: {stages.technical.status}

      } - {stages.technical?.comments &&

      Comments: {stages.technical.comments}

      } - {stages.technical?.createdAt &&

      Date: {formatDate(stages.technical.createdAt)}

      } -
    • - )} - {stages?.shortlist && ( -
    • - Shortlist - {stages?.allStages?.history - .filter((history: any) => history.stage === "Shortlisted") - .map((history: any, index: any) => ( - -   ({formatDate(history.enteredAt)} - {formatDate(history.exitedAt)}) - - ))} - {stages.shortlist?.status &&

      Status: {stages.shortlist.status}

      } - {stages.shortlist?.comments &&

      Comments: {stages.shortlist.comments}

      } - {stages.shortlist?.createdAt &&

      Date: {formatDate(stages.shortlist.createdAt)}

      } -
    • - )} + {stages.dismissed?.createdAt && ( +

      + Date:{' '} + {formatDate(stages.dismissed.createdAt)} +

      + )} + + )} -
    -
    -
    -
    + {stages?.admitted && ( +
  • + Admitted + {stages?.allStages?.history + .filter((history: any) => history.stage === 'Admitted') + .map((history: any, index: any) => ( + +   ({formatDate(history.enteredAt)} -{' '} + {formatDate(history.exitedAt)}) + + ))} + {stages.admitted?.status && ( +

    + Status: {stages.admitted.status} +

    + )} + {stages.admitted?.stageDismissedFrom && ( +

    + Score: {stages.admitted.stageDismissedFrom} +

    + )} + {stages.admitted?.comments && ( +

    + Comments: {stages.admitted.comments} +

    + )} + {stages.admitted?.createdAt && ( +

    + Date: {formatDate(stages.admitted.createdAt)} +

    + )} +
  • + )} + {stages?.interview && ( +
  • + Interview Assessment + {stages?.allStages?.history + .filter( + (history: any) => + history.stage === 'Technical Assessment' + ) + .map((history: any, index: any) => ( + +   ({formatDate(history.enteredAt)} -{' '} + {formatDate(history.exitedAt)}) + + ))} + {stages.interview?.interviewScore && ( +

    + Score: {stages.interview.interviewScore} +

    + )} + {stages.interview?.status && ( +

    + Status: {stages.interview.status} +

    + )} + {stages.interview?.comments && ( +

    + Comments: {stages.interview.comments} +

    + )} + {stages.interview?.createdAt && ( +

    + Date: {formatDate(stages.interview.createdAt)} +

    + )} +
  • + )} + {stages?.technical && ( +
  • + Technical Assessment + {stages?.allStages?.history + .filter( + (history: any) => + history.stage === 'Technical Assessment' + ) + .map((history: any, index: any) => ( + +   ({formatDate(history.enteredAt)} -{' '} + {formatDate(history.exitedAt)}) + + ))} + {stages.technical?.score && ( +

    + Score: {stages.technical.score}% +

    + )} + {stages.technical?.status && ( +

    + Status: {stages.technical.status} +

    + )} + {stages.technical?.comments && ( +

    + Comments: {stages.technical.comments} +

    + )} + {stages.technical?.createdAt && ( +

    + Date: {formatDate(stages.technical.createdAt)} +

    + )} +
  • + )} + {stages?.shortlist && ( +
  • + Shortlist + {stages?.allStages?.history + .filter((history: any) => history.stage === 'Shortlisted') + .map((history: any, index: any) => ( + +   ({formatDate(history.enteredAt)} -{' '} + {formatDate(history.exitedAt)}) + + ))} + {stages.shortlist?.status && ( +

    + Status: {stages.shortlist.status} +

    + )} + {stages.shortlist?.comments && ( +

    + Comments: {stages.shortlist.comments} +

    + )} + {stages.shortlist?.createdAt && ( +

    + Date: {formatDate(stages.shortlist.createdAt)} +

    + )} +
  • + )} +
- - ); +
+ +
+
+ + ); }; -export default MyApplication; \ No newline at end of file +export default MyApplication; diff --git a/src/pages/viewExternalDocuments.tsx b/src/pages/viewExternalDocuments.tsx index 190ffac8..8099c02d 100644 --- a/src/pages/viewExternalDocuments.tsx +++ b/src/pages/viewExternalDocuments.tsx @@ -1,70 +1,126 @@ -import React from 'react' -import { useLocation, Link } from 'react-router-dom' -import { MdClose } from "react-icons/md"; -import { MdErrorOutline } from 'react-icons/md' +import { MdClose, MdErrorOutline } from 'react-icons/md'; +import PropTypes from 'prop-types'; +import { useState, useEffect } from 'react'; -const ViewExternalDocuments = () => { - const { search } = useLocation() - const queryParams = new URLSearchParams(search) - const fileUrl = queryParams.get('fileUrl') - const backUrl = queryParams.get('backUrl') - const title = queryParams.get('title') +const ViewExternalDocumentsModal = ({ show, onClose, fileUrl }) => { + const [isLoading, setIsLoading] = useState(true); + const [loadError, setLoadError] = useState(false); - const hasMissingParams = !fileUrl || !backUrl || !title + const isImage = ['.jpg', '.jpeg', '.png', '.gif', '.bmp'].some((ext) => + fileUrl?.endsWith(ext) + ); + const isPdf = fileUrl?.endsWith('.pdf'); - const isPdf = fileUrl?.endsWith('.pdf') - const isImage = ['.jpg', '.jpeg', '.png', '.gif', '.bmp'].some((ext) => fileUrl?.endsWith(ext)) + useEffect(() => { + if (fileUrl) { + setIsLoading(true); + setLoadError(false); - const handleCloseTab = () => { - window.close(); - }; + if (isImage) { + const img = new Image(); + img.onload = () => setIsLoading(false); + img.onerror = () => { + setIsLoading(false); + setLoadError(true); + }; + img.src = fileUrl; + } else if (isPdf) { + const xhr = new XMLHttpRequest(); + xhr.open('HEAD', fileUrl, true); + xhr.onload = () => { + if (xhr.status === 200) { + setIsLoading(false); + } else { + setIsLoading(false); + setLoadError(true); + } + }; + xhr.onerror = () => { + setIsLoading(false); + setLoadError(true); + }; + xhr.send(); + } else { + setIsLoading(false); + setLoadError(true); + } + } + }, [fileUrl, isImage, isPdf]); - return ( -
- {hasMissingParams ? ( -
- -

Missing Information

-

- It seems some required details are missing. Please ensure you have provided a file URL, title, and back URL in the parameters. -

- - Go Back to Home - -
- ) : ( -
-
-

{title}

- -
+ if (!show) return null; + + const hasMissingParams = !fileUrl; + + return ( +
+
+ - {isPdf ? ( - - ) : isImage ? ( - {title} - ) : ( -
Unsupported file type
- )} -
+ {hasMissingParams ? ( +
+ +

+ Missing Information +

+

+ It seems some required details are missing. Please ensure you have + provided a file URL. +

+
+ ) : isLoading ? ( +
+
+
+ ) : loadError ? ( +
+ +

+ File Loading Error +

+

+ Unable to load the file. Please check the URL and try again. +

+
+ ) : ( +
+

+ Document Preview +

+ {isPdf ? ( + + ) : isImage ? ( + Image Preview + ) : ( +
+ Unsupported file type +
)} -
- ) -} +
+ )} +
+
+ ); +}; + +ViewExternalDocumentsModal.propTypes = { + show: PropTypes.bool.isRequired, + onClose: PropTypes.func.isRequired, + fileUrl: PropTypes.string, +}; -export default ViewExternalDocuments +export default ViewExternalDocumentsModal; diff --git a/src/routes/routes.tsx b/src/routes/routes.tsx index 2e26ca72..a6d645ba 100644 --- a/src/routes/routes.tsx +++ b/src/routes/routes.tsx @@ -84,7 +84,6 @@ import SingleBlogView from "../pages/Blogs/singleBlog"; import SingleBlogPage from "../pages/LandingPage/SingleBlogPage" import LandingPage from "../pages/LandingPage/LandingPage"; import Blogs from "../pages/Blogs/Blogs" -import ViewExternalDocuments from "../pages/viewExternalDocuments"; import Documents from "../pages/documents/documents"; import UpdateDocumentation from "../pages/documents/updateDocumentation"; import SingleDocumentationDetails from "../pages/documents/singleDocumentation"; @@ -101,10 +100,7 @@ function Navigation() { } /> } /> } /> - }/> - } - /> + }/> } /> } /> } />