405 lines
17 KiB
React
405 lines
17 KiB
React
import React, { useEffect, useState } from "react";
|
|
import { useSelector } from "react-redux";
|
|
import Layout from "../Partials/Layout";
|
|
import { useNavigate } from "react-router-dom";
|
|
import ActiveJobMessage from "./ActiveJobMessage";
|
|
import LoadingSpinner from "../Spinners/LoadingSpinner";
|
|
|
|
import usersService from "../../services/UsersService";
|
|
|
|
function ActiveJobs(props) {
|
|
const ApiCall = new usersService()
|
|
|
|
let { userDetails } = useSelector((state) => state.userDetails);
|
|
let navigate = useNavigate()
|
|
|
|
let [messageToSend, setMessageToSend] = useState('') // State to hold the value of message to be sent
|
|
|
|
let [filesToSend, setFilesToSend] = useState([]) // State to hold the value of files to be sent
|
|
|
|
let [tab, setTab] = useState('message')
|
|
|
|
let [requestStatus, setRequestStatus] = useState({loading: false, status: false, message: ''})
|
|
|
|
// FUNCTION TO HANDLE MESSAGE CHANGE
|
|
const handleMessageChange = ({target:{value}}) => {
|
|
setMessageToSend(value)
|
|
}
|
|
|
|
// FUNCTION TO HANDLE FILE UPlOAD CHANGE
|
|
const handleFileChange = ({target:{files}}) => {
|
|
setRequestStatus({loading: false, status: false, message: ''}) // State to determine error state
|
|
|
|
if(!files[0]) { // IF NO FILE SELECTED RETURN
|
|
return
|
|
}
|
|
if(files[0].size > Number(process.env.REACT_APP_MAX_FILE_SIZE)){
|
|
setRequestStatus({loading: false, status: false, message: 'File must be <= 1mb'})
|
|
setTimeout(()=>{
|
|
setRequestStatus({loading: false, status: false, message: ''})
|
|
}, 5000)
|
|
return
|
|
}
|
|
if(filesToSend.length >= Number(process.env.REACT_APP_TOTAL_NUM_FILE)){
|
|
setRequestStatus({loading: false, status: false, message: `Total number of attachment is ${Number(process.env.REACT_APP_TOTAL_NUM_FILE)}`})
|
|
setTimeout(()=>{
|
|
setRequestStatus({loading: false, status: false, message: ''})
|
|
}, 5000)
|
|
return
|
|
}
|
|
// INCLUDE FILE IF NO ERROR
|
|
setFilesToSend(prev => ([...prev, files[0]]))
|
|
}
|
|
|
|
// FUNCTION TO CLEAR ALL TYPED MESSAGE OR FILES
|
|
const handleClearAll = ({target:{name}}) => {
|
|
if(tab == 'message'){
|
|
setMessageToSend('')
|
|
}else if(tab=='files'){
|
|
setFilesToSend([])
|
|
}else{
|
|
return
|
|
}
|
|
}
|
|
|
|
// FUNCTION TO REMOVE AND IMAGE
|
|
const handleRemoveImage = (imageToDelete) => {
|
|
setFilesToSend(prev => prev.filter(item => item.name != imageToDelete.name))
|
|
}
|
|
|
|
// FUNCTION TO SEND TASK MESSAGE
|
|
const sendTaskMessage = () => {
|
|
let reqData={message: messageToSend, msg_type: 'TEXT', contract:props.details.contract}
|
|
if(!reqData.message){
|
|
setRequestStatus({loading: false, status: false, message: 'Message is empty'})
|
|
return setTimeout(()=>{
|
|
setRequestStatus({loading: false, status: false, message: ''})
|
|
}, 5000)
|
|
}
|
|
setRequestStatus({loading: true, status: false, message: ''})
|
|
ApiCall.sendTaskMessage(reqData).then((res)=>{
|
|
if(res.status != 200 || res.data.internal_return < 0){
|
|
setRequestStatus({loading: false, status: false, message: 'Message could not be sent, try again later'})
|
|
return
|
|
}
|
|
setRequestStatus({loading: false, status: true, message: 'Message Sent Successfully'})
|
|
props.reloadActiveJobList(prev => !prev) // MAKES ACTIVE JOB MESSAGE LIST TO RELOAD
|
|
setMessageToSend('') // SENDS MESSAGE TO SEND BACK TO EMPTY STRINGS
|
|
}).catch(error => {
|
|
setRequestStatus({loading: false, status: false, message: 'Opps! something went wrong'})
|
|
}).finally(()=>{
|
|
setTimeout(()=>{
|
|
setRequestStatus({loading: false, status: false, message: ''})
|
|
}, 5000)
|
|
})
|
|
}
|
|
|
|
// FUNCTION TO SEND FILES
|
|
const sendFile = async () => {
|
|
setRequestStatus({loading: true, status: false, message: ''})
|
|
|
|
if(!filesToSend.length){ // checks if file to send is empty
|
|
setRequestStatus({loading: false, status: false, message: 'No File(s) selected'})
|
|
return setTimeout(()=>{
|
|
setRequestStatus({loading: false, status: false, message: ''})
|
|
}, 5000)
|
|
}
|
|
// let reqData = new FormData()
|
|
|
|
// for(let files of filesToSend){
|
|
// reqData.append(files.name, files)
|
|
// }
|
|
// let reqData={file_size: filesToSend[0].size, file_type: 'image/png', file_data: filesToSend[0], msg_type: 'FILE', contract:props.details.contract}
|
|
|
|
// for(let files of filesToSend){
|
|
// reqData[files.name] = files
|
|
// }
|
|
|
|
const fileToBase64 = async () =>{
|
|
try {
|
|
const base64String = await convertFileToBase64(filesToSend[0]);
|
|
return base64String;
|
|
} catch (error) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if(await !fileToBase64()){
|
|
return
|
|
}
|
|
|
|
let reqData={file_name: filesToSend[0].name, file_size: filesToSend[0].size, file_type: 'image/png', file_data: await fileToBase64(), msg_type: 'FILE', contract:props.details.contract}
|
|
|
|
console.log(reqData)
|
|
ApiCall.sendFiles(reqData).then((res)=>{
|
|
if(res.status != 200 || res.data.internal_return < 0){
|
|
setRequestStatus({loading: false, status: false, message: 'Files(s) could not be sent, try again later'})
|
|
return
|
|
}
|
|
setRequestStatus({loading: false, status: true, message: 'File(s) Uploaded Successfully'})
|
|
props.reloadActiveJobList(prev => !prev) // MAKES ACTIVE JOB MESSAGE LIST TO RELOAD
|
|
setFilesToSend([]) // SETS FILES TO SEND TO SEND BACK TO EMPTY ARRAY
|
|
}).catch(error => {
|
|
setRequestStatus({loading: false, status: false, message: 'Opps! something went wrong'})
|
|
}).finally(()=>{
|
|
setTimeout(()=>{
|
|
setRequestStatus({loading: false, status: false, message: ''})
|
|
}, 5000)
|
|
})
|
|
}
|
|
|
|
return (
|
|
<Layout>
|
|
|
|
<div className="py-[20px] bg-white px-4 rounded-2xl shadow-md lg:flex justify-between items-start space-y-4 lg:space-x-4 lg:space-y-0">
|
|
{/* job title */}
|
|
<div className="w-full lg:w-1/2">
|
|
<div className="w-full flex justify-start space-x-3 items-start">
|
|
<button
|
|
type="button"
|
|
className="min-w-[45px] h-auto text-[#374557] border border-sky-blue p-1 rounded-full"
|
|
onClick={() => navigate(props.details.pathname, {replace: true})}
|
|
>
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
width="35"
|
|
height="35"
|
|
viewBox="0 0 24 24"
|
|
fill="skyblue"
|
|
>
|
|
<path d="M19 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H19v-2z" />
|
|
</svg>
|
|
</button>
|
|
<h1 className="text-xl lg:text-2xl font-bold text-dark-gray dark:text-white tracking-wide">
|
|
{props.details?.title && props.details.title}
|
|
</h1>
|
|
</div>
|
|
|
|
<div className="w-full my-4">
|
|
<div className="pb-2 w-full flex items-center">
|
|
<p className="w-full lg:w-2/3 text-base text-slate-700 dark:text-black">
|
|
{props.details?.contract && props.details.contract}
|
|
</p>
|
|
<p className="w-full lg:w-1/3 text-base text-sky-blue">
|
|
{userDetails.firstname && userDetails.firstname}
|
|
</p>
|
|
</div>
|
|
|
|
<p className="text-base text-slate-700 dark:text-black">
|
|
<span className="font-semibold">Description: </span>
|
|
{props.details?.description && props.details.description}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
{/* end of job title */}
|
|
|
|
{/* job details */}
|
|
<div className="w-full lg:w-1/2">
|
|
<p className="text-base text-sky-blue">Delivery Detail</p>
|
|
<div className="mt-2">
|
|
<p className="text-base text-slate-700 dark:text-black">
|
|
<span className="font-semibold">Due: </span>
|
|
{props.details?.delivery_date &&
|
|
props.details.delivery_date.split(" ")[0]}
|
|
</p>
|
|
<p className="py-2 text-base text-slate-700 dark:text-black">
|
|
{props.details?.delivery_date &&
|
|
props.details.delivery_date.split(" ")[1]}
|
|
</p>
|
|
<p className="text-base text-slate-700 dark:text-black">
|
|
{props.details?.timeline_days && props.details.timeline_days} day(s)
|
|
</p>
|
|
</div>
|
|
</div>
|
|
{/* end of job details */}
|
|
</div>
|
|
|
|
<div className="my-4 py-[20px] bg-white px-4 rounded-2xl shadow-md lg:flex justify-between items-start space-y-4 lg:space-x-4 lg:space-y-0">
|
|
<div className="w-full lg:w-1/2">
|
|
<div className="">
|
|
<h1 className="text-lg font-bold text-dark-gray dark:text-white tracking-wide">Actions</h1>
|
|
<p className="my-3 py-1 text-base">
|
|
Waiting for the completion message from the client before you can approve.
|
|
</p>
|
|
</div>
|
|
|
|
{/* TEXTAREA SECTION */}
|
|
<div className="mt-5">
|
|
<div className="">
|
|
{/* <p className="relative py-2 my-2 text-lg font-bold text-slate-600 dark:text-black border-b-2 border-slate-300 tracking-wide after:absolute after:-bottom-0.5 after:content-[''] after:w-[100px] after:h-[2px] after:bg-sky-blue after:left-0">Message(s)</p> */}
|
|
<div className="my-2 flex items-center border-b border-slate-300">
|
|
<button
|
|
name='message'
|
|
onClick={(e) => setTab(e.target.name)}
|
|
className={`p-2 text-lg font-bold text-slate-600 dark:text-black border ${tab == 'message'? 'border-sky-blue':'border-slate-300'} tracking-wide transition duration-200`}>
|
|
Send Message
|
|
</button>
|
|
<button
|
|
name='files'
|
|
onClick={(e) => setTab(e.target.name)}
|
|
className={`p-2 text-lg font-bold text-slate-600 dark:text-black border ${tab == 'files'? 'border-sky-blue':'border-slate-300'} tracking-wide transition duration-200`}>
|
|
Send Files
|
|
</button>
|
|
</div>
|
|
{tab == 'message' ?
|
|
(
|
|
<textarea
|
|
className="p-4 w-full h-[300px] text-base text-slate-600 border border-slate-300 outline-none"
|
|
// rows="10"
|
|
style={{ resize: "none" }}
|
|
name='message'
|
|
onChange={handleMessageChange}
|
|
value={messageToSend}
|
|
/>
|
|
)
|
|
:
|
|
<div className="p-4 w-full h-[300px] text-base text-slate-600 border border-slate-300">
|
|
<div className="files">
|
|
<label htmlFor="file" className="h-20 btn-gradient text-base tracking-wide px-4 py-2 rounded-full text-white cursor-pointer">Select Files to Upload</label>
|
|
<input type="file" id='file' accept="image/*" style={{display: 'none'}} onChange={handleFileChange}/>
|
|
</div>
|
|
<div className="selected_file my-2">
|
|
{filesToSend.length > 0 &&
|
|
filesToSend.map((item, index)=> (
|
|
<p key={index} className="flex items-center space-x-2">
|
|
<span>{item.name}</span>
|
|
<button name='remove' onClick={()=>handleRemoveImage(item)} className="px-2 flex justify-center items-center rounded-full border border-red-500 text-red-500">x</button>
|
|
</p>
|
|
))
|
|
}
|
|
</div>
|
|
</div>
|
|
}
|
|
</div>
|
|
|
|
{/* ERROR DISPLAY AND SUBMIT BUTTON */}
|
|
<div className="w-full">
|
|
{/* error or success display */}
|
|
{requestStatus.message != "" &&
|
|
(!requestStatus.status ? (
|
|
<div
|
|
className={`relative p-4 my-4 text-[#912741] bg-[#fcd9e2] border-[#fbc6d3] mb-4 rounded-[0.475rem] text-md font-light leading-[19.5px] text-[13px]`}
|
|
>
|
|
{requestStatus.message}
|
|
</div>
|
|
) : (
|
|
requestStatus.status && (
|
|
<div
|
|
className={`relative p-4 my-4 text-green-700 bg-slate-200 border-slate-800 mb-4 rounded-[0.475rem] text-md font-light leading-[19.5px] text-[13px]`}
|
|
>
|
|
{requestStatus.message}
|
|
</div>
|
|
)
|
|
))}
|
|
</div>
|
|
{/* End of error or success display */}
|
|
|
|
{/* Buttons Sections */}
|
|
<div className="py-2 sm:flex sm:justify-end sm:items-center">
|
|
{/* <div className="w-full mb-3 sm:mb-0 sm:w-2/4">
|
|
{tab == 'files' &&
|
|
(
|
|
<button
|
|
onClick={()=>{console.log('working')}}
|
|
type="button"
|
|
className="btn-gradient text-base tracking-wide px-4 py-2 rounded-md flex justify-center items-center"
|
|
>
|
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill='white'>
|
|
<path d="M12 2L2 12h3v8h14v-8h3L12 2zm0 16v-6h-2v6H7l5-5 5 5h-3z"/>
|
|
</svg>
|
|
|
|
<span className="text-white">Upload Files</span>
|
|
</button>
|
|
)
|
|
}
|
|
</div> */}
|
|
|
|
<div className="w-full sm:w-2/4 flex justify-between items-center space-x-2">
|
|
<button
|
|
type="button"
|
|
onClick={handleClearAll}
|
|
className="border-gradient text-base tracking-wide px-4 py-3 rounded-full"
|
|
>
|
|
<span className="text-gradient">Clear</span>
|
|
</button>
|
|
{tab == 'files' ?
|
|
(
|
|
<button
|
|
onClick={sendFile}
|
|
type="button"
|
|
className="btn-gradient text-base tracking-wide px-4 py-3 rounded-full flex justify-center items-center"
|
|
>
|
|
{requestStatus.loading ?
|
|
<LoadingSpinner size='6' color='sky-blue' />
|
|
:
|
|
<>
|
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill='white'>
|
|
<path d="M12 2L2 12h3v8h14v-8h3L12 2zm0 16v-6h-2v6H7l5-5 5 5h-3z"/>
|
|
</svg>
|
|
|
|
<span className="text-white">Upload Files</span>
|
|
</>
|
|
}
|
|
</button>
|
|
)
|
|
:
|
|
(
|
|
<button
|
|
onClick={sendTaskMessage}
|
|
type="button"
|
|
className="btn-gradient text-base text-white tracking-wide px-4 py-3 rounded-full"
|
|
>
|
|
{requestStatus.loading ?
|
|
<LoadingSpinner size='6' color='sky-blue' />
|
|
:
|
|
<span className="text-white">Send</span>
|
|
}
|
|
</button>
|
|
)
|
|
}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
{/* end of Buttons Sections */}
|
|
</div>
|
|
{/* END OF TEXTAREA */}
|
|
</div>
|
|
|
|
{/* MESSAGE SECTION */}
|
|
<div className="w-full lg:w-1/2">
|
|
<p className="text-lg font-bold text-dark-gray dark:text-black tracking-wide">Message</p>
|
|
{props.activeJobMesList.loading ?
|
|
<LoadingSpinner size='16' color='sky-blue' />
|
|
:
|
|
<ActiveJobMessage activeJobMesList={props.activeJobMesList} />
|
|
}
|
|
</div>
|
|
{/* END OF MESSAGE */}
|
|
</div>
|
|
|
|
</Layout>
|
|
);
|
|
}
|
|
|
|
export default ActiveJobs;
|
|
|
|
|
|
function convertFileToBase64(file) {
|
|
return new Promise((resolve, reject) => {
|
|
const reader = new FileReader();
|
|
|
|
reader.onload = () => {
|
|
const base64String = reader.result.split(',')[1];
|
|
resolve(base64String);
|
|
};
|
|
|
|
reader.onerror = error => {
|
|
reject(error);
|
|
};
|
|
|
|
reader.readAsDataURL(file);
|
|
});
|
|
}
|
|
|