Compare commits
10 Commits
main
...
Weidget_Da
| Author | SHA1 | Date | |
|---|---|---|---|
| 0b047ed4a7 | |||
| 87e9d88d1f | |||
| c65d8a2a2c | |||
| d1fdc03268 | |||
| cde6e416a2 | |||
| 28f9d09d1c | |||
| 303cb3ab96 | |||
| 1eeff79cf7 | |||
| 8abb9a045b | |||
| 1203b9ee64 |
6
public/assets/vendor/css/core.css
vendored
6
public/assets/vendor/css/core.css
vendored
@ -89,7 +89,7 @@
|
|||||||
);
|
);
|
||||||
--bs-root-font-size: 16px;
|
--bs-root-font-size: 16px;
|
||||||
--bs-body-font-family: var(--bs-font-sans-serif);
|
--bs-body-font-family: var(--bs-font-sans-serif);
|
||||||
--bs-body-font-size: 0.85rem;
|
--bs-body-font-size: 0.875rem;
|
||||||
--bs-body-font-weight: 400;
|
--bs-body-font-weight: 400;
|
||||||
--bs-body-line-height: 1.375;
|
--bs-body-line-height: 1.375;
|
||||||
--bs-body-color: #646e78;
|
--bs-body-color: #646e78;
|
||||||
@ -9060,7 +9060,7 @@ img[data-app-light-img][data-app-dark-img] {
|
|||||||
}
|
}
|
||||||
.table th {
|
.table th {
|
||||||
color: var(--bs-heading-color);
|
color: var(--bs-heading-color);
|
||||||
font-size: 0.8025rem;
|
font-size: 0.8125rem;
|
||||||
letter-spacing: 0.2px;
|
letter-spacing: 0.2px;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
@ -20345,7 +20345,7 @@ li:not(:first-child) .dropdown-item,
|
|||||||
}
|
}
|
||||||
|
|
||||||
.fs-6 {
|
.fs-6 {
|
||||||
font-size: 0.8375rem !important;
|
font-size: 0.9375rem !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fs-tiny {
|
.fs-tiny {
|
||||||
|
|||||||
@ -39,14 +39,12 @@ const TaskReportFilterPanel = ({ handleFilter }) => {
|
|||||||
dateTo: localToUtc(formData.dateTo),
|
dateTo: localToUtc(formData.dateTo),
|
||||||
};
|
};
|
||||||
handleFilter(filterPayload);
|
handleFilter(filterPayload);
|
||||||
closePanel();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const onClear = () => {
|
const onClear = () => {
|
||||||
setResetKey((prev) => prev + 1);
|
setResetKey((prev) => prev + 1);
|
||||||
handleFilter(TaskReportDefaultValue);
|
handleFilter(TaskReportDefaultValue);
|
||||||
reset(TaskReportDefaultValue);
|
reset(TaskReportDefaultValue);
|
||||||
closePanel();
|
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<FormProvider {...methods}>
|
<FormProvider {...methods}>
|
||||||
|
|||||||
@ -204,24 +204,34 @@ const TaskReportList = () => {
|
|||||||
<HoverPopup
|
<HoverPopup
|
||||||
id="total_pending_task"
|
id="total_pending_task"
|
||||||
title="Total Pending Task"
|
title="Total Pending Task"
|
||||||
content={<p>This shows the total pending tasks for each activity on that date.</p>}
|
content={
|
||||||
|
<div className="text-wrap" style={{ maxWidth: "200px" }}>
|
||||||
|
This shows the total pending tasks for each activity on that date.
|
||||||
|
</div>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<i className="bx bx-xs ms-1 bx-info-circle cursor-pointer"></i>
|
<i className="bx bx-xs ms-1 bx-info-circle cursor-pointer"></i>
|
||||||
</HoverPopup>
|
</HoverPopup>
|
||||||
</span>
|
</span>
|
||||||
</th>
|
</th>
|
||||||
|
|
||||||
<th>
|
<th>
|
||||||
<span>
|
<span>
|
||||||
Reported/Planned{" "}
|
Reported/Planned{" "}
|
||||||
<HoverPopup
|
<HoverPopup
|
||||||
id="reportes_and_planned_task"
|
id="reportes_and_planned_task"
|
||||||
title="Reported and Planned Task"
|
title="Reported and Planned Task"
|
||||||
content={<p>This shows the reported versus planned tasks for each activity on that date.</p>}
|
content={
|
||||||
|
<div className="text-wrap" style={{ maxWidth: "200px" }}>
|
||||||
|
This shows the reported versus planned tasks for each activity on that date.
|
||||||
|
</div>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<i className="bx bx-xs ms-1 bx-info-circle cursor-pointer"></i>
|
<i className="bx bx-xs ms-1 bx-info-circle cursor-pointer"></i>
|
||||||
</HoverPopup>
|
</HoverPopup>
|
||||||
</span>
|
</span>
|
||||||
</th>
|
</th>
|
||||||
|
|
||||||
<th>Assign Date</th>
|
<th>Assign Date</th>
|
||||||
<th>Team</th>
|
<th>Team</th>
|
||||||
<th className="text-center">Actions</th>
|
<th className="text-center">Actions</th>
|
||||||
|
|||||||
@ -100,7 +100,7 @@ const AttendanceOverview = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-white px-4 rounded shadow d-flex flex-column h-100">
|
<div className="bg-white px-4 rounded shadow d-flex flex-column h-100" style={{ minHeight: "450px" }}>
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="d-flex mt-2 justify-content-between align-items-center mb-3">
|
<div className="d-flex mt-2 justify-content-between align-items-center mb-3">
|
||||||
<div className="card-title mb-0 text-start">
|
<div className="card-title mb-0 text-start">
|
||||||
|
|||||||
@ -17,12 +17,19 @@ import ExpenseAnalysis from "./ExpenseAnalysis";
|
|||||||
import ExpenseStatus from "./ExpenseStatus";
|
import ExpenseStatus from "./ExpenseStatus";
|
||||||
import ExpenseByProject from "./ExpenseByProject";
|
import ExpenseByProject from "./ExpenseByProject";
|
||||||
import ProjectStatistics from "../Project/ProjectStatistics";
|
import ProjectStatistics from "../Project/ProjectStatistics";
|
||||||
|
import ServiceJobs from "./ServiceJobs";
|
||||||
|
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||||
|
import { REGULARIZE_ATTENDANCE, SELF_ATTENDANCE, TEAM_ATTENDANCE } from "../../utils/constants";
|
||||||
|
|
||||||
const Dashboard = () => {
|
const Dashboard = () => {
|
||||||
|
|
||||||
// Get the selected project ID from Redux store
|
// Get the selected project ID from Redux store
|
||||||
const projectId = useSelector((store) => store.localVariables.projectId);
|
const projectId = useSelector((store) => store.localVariables.projectId);
|
||||||
const isAllProjectsSelected = projectId === null;
|
const isAllProjectsSelected = projectId === null;
|
||||||
|
const canRegularize = useHasUserPermission(REGULARIZE_ATTENDANCE);
|
||||||
|
const canTeamAttendance = useHasUserPermission(TEAM_ATTENDANCE);
|
||||||
|
const canSelfAttendance = useHasUserPermission(SELF_ATTENDANCE);
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container-fluid mt-5">
|
<div className="container-fluid mt-5">
|
||||||
@ -49,7 +56,7 @@ const Dashboard = () => {
|
|||||||
<div className="col-xxl-6 col-lg-6">
|
<div className="col-xxl-6 col-lg-6">
|
||||||
<ProjectProgressChart />
|
<ProjectProgressChart />
|
||||||
</div>
|
</div>
|
||||||
{!isAllProjectsSelected && (
|
{!isAllProjectsSelected && (canRegularize || canTeamAttendance || canSelfAttendance) && (
|
||||||
<div className="col-12 col-md-6 mb-sm-0 mb-4">
|
<div className="col-12 col-md-6 mb-sm-0 mb-4">
|
||||||
<AttendanceOverview />
|
<AttendanceOverview />
|
||||||
</div>
|
</div>
|
||||||
@ -57,7 +64,9 @@ const Dashboard = () => {
|
|||||||
|
|
||||||
{!isAllProjectsSelected && (
|
{!isAllProjectsSelected && (
|
||||||
<div className="col-xxl-4 col-lg-4">
|
<div className="col-xxl-4 col-lg-4">
|
||||||
<ProjectStatistics />
|
<div className="card h-100">
|
||||||
|
<ProjectStatistics />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="col-12 col-xl-4 col-md-6">
|
<div className="col-12 col-xl-4 col-md-6">
|
||||||
@ -74,6 +83,9 @@ const Dashboard = () => {
|
|||||||
<div className="col-12 col-md-6">
|
<div className="col-12 col-md-6">
|
||||||
<ExpenseByProject />
|
<ExpenseByProject />
|
||||||
</div>
|
</div>
|
||||||
|
{/* <div className="col-12 col-md-12">
|
||||||
|
<ServiceJobs />
|
||||||
|
</div> */}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
237
src/components/Dashboard/ServiceJobs.jsx
Normal file
237
src/components/Dashboard/ServiceJobs.jsx
Normal file
@ -0,0 +1,237 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const ServiceJobs = () => {
|
||||||
|
return (
|
||||||
|
<div className="col-xxl-4 col-lg-6">
|
||||||
|
<div className="card h-100">
|
||||||
|
<div className="card-header d-flex justify-content-between">
|
||||||
|
<div className="card-title mb-0 text-start">
|
||||||
|
<h5 className="mb-1">Service Jobs</h5>
|
||||||
|
<p className="card-subtitle">All Projects</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="card-body p-0">
|
||||||
|
<div className="nav-align-top">
|
||||||
|
|
||||||
|
{/* Tabs */}
|
||||||
|
<ul className="nav nav-tabs nav-fill rounded-0 timeline-indicator-advanced" role="tablist">
|
||||||
|
<li className="nav-item">
|
||||||
|
<button className="nav-link active" data-bs-toggle="tab" data-bs-target="#tab-new">
|
||||||
|
My Jobs
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li className="nav-item">
|
||||||
|
<button className="nav-link" data-bs-toggle="tab" data-bs-target="#tab-preparing">
|
||||||
|
Assigned
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li className="nav-item">
|
||||||
|
<button className="nav-link" data-bs-toggle="tab" data-bs-target="#tab-shipping">
|
||||||
|
In Progress
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{/* Tab Content */}
|
||||||
|
<div className="tab-content border-0 mx-1 text-start">
|
||||||
|
|
||||||
|
{/* ---------------------- NEW TAB ---------------------- */}
|
||||||
|
<div className="tab-pane fade show active" id="tab-new">
|
||||||
|
{/* Entry 1 */}
|
||||||
|
<ul className="timeline mb-0">
|
||||||
|
<li className="timeline-item ps-6 border-left-dashed">
|
||||||
|
<span className="timeline-indicator-advanced timeline-indicator-success border-0 shadow-none">
|
||||||
|
<i className="bx bx-check-circle"></i>
|
||||||
|
</span>
|
||||||
|
<div className="timeline-event ps-1">
|
||||||
|
<div className="timeline-header">
|
||||||
|
<small className="text-success text-uppercase">Sender</small>
|
||||||
|
</div>
|
||||||
|
<h6 className="my-50">Myrtle Ullrich</h6>
|
||||||
|
<p className="text-body mb-0">101 Boulder, California(CA), 95959</p>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li className="timeline-item ps-6 border-transparent">
|
||||||
|
<span className="timeline-indicator-advanced timeline-indicator-primary border-0 shadow-none">
|
||||||
|
<i className="bx bx-map"></i>
|
||||||
|
</span>
|
||||||
|
<div className="timeline-event ps-1">
|
||||||
|
<div className="timeline-header">
|
||||||
|
<small className="text-primary text-uppercase">Receiver</small>
|
||||||
|
</div>
|
||||||
|
<h6 className="my-50">Barry Schowalter</h6>
|
||||||
|
<p className="text-body mb-0">939 Orange, California(CA), 92118</p>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div className="border-1 border-light border-top border-dashed my-4"></div>
|
||||||
|
|
||||||
|
{/* Entry 2 */}
|
||||||
|
<ul className="timeline mb-0">
|
||||||
|
<li className="timeline-item ps-6 border-left-dashed">
|
||||||
|
<span className="timeline-indicator-advanced timeline-indicator-success border-0 shadow-none">
|
||||||
|
<i className="bx bx-check-circle"></i>
|
||||||
|
</span>
|
||||||
|
<div className="timeline-event ps-1">
|
||||||
|
<div className="timeline-header">
|
||||||
|
<small className="text-success text-uppercase">Sender</small>
|
||||||
|
</div>
|
||||||
|
<h6 className="my-50">Veronica Herman</h6>
|
||||||
|
<p className="text-body mb-0">162 Windsor, California(CA), 95492</p>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li className="timeline-item ps-6 border-transparent">
|
||||||
|
<span className="timeline-indicator-advanced timeline-indicator-primary border-0 shadow-none">
|
||||||
|
<i className="bx bx-map"></i>
|
||||||
|
</span>
|
||||||
|
<div className="timeline-event ps-1">
|
||||||
|
<div className="timeline-header">
|
||||||
|
<small className="text-primary text-uppercase">Receiver</small>
|
||||||
|
</div>
|
||||||
|
<h6 className="my-50">Helen Jacobs</h6>
|
||||||
|
<p className="text-body mb-0">487 Sunset, California(CA), 94043</p>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ---------------------- PREPARING TAB ---------------------- */}
|
||||||
|
<div className="tab-pane fade" id="tab-preparing">
|
||||||
|
|
||||||
|
{/* Entry 1 */}
|
||||||
|
<ul className="timeline mb-0">
|
||||||
|
<li className="timeline-item ps-6 border-left-dashed">
|
||||||
|
<span className="timeline-indicator-advanced timeline-indicator-warning border-0 shadow-none">
|
||||||
|
<i className="bx bx-time-five"></i>
|
||||||
|
</span>
|
||||||
|
<div className="timeline-event ps-1">
|
||||||
|
<div className="timeline-header">
|
||||||
|
<small className="text-warning text-uppercase">Sender</small>
|
||||||
|
</div>
|
||||||
|
<h6 className="my-50">Oliver Grant</h6>
|
||||||
|
<p className="text-body mb-0">220 Pine St, California(CA), 95765</p>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li className="timeline-item ps-6 border-transparent">
|
||||||
|
<span className="timeline-indicator-advanced timeline-indicator-primary border-0 shadow-none">
|
||||||
|
<i className="bx bx-map"></i>
|
||||||
|
</span>
|
||||||
|
<div className="timeline-event ps-1">
|
||||||
|
<div className="timeline-header">
|
||||||
|
<small className="text-primary text-uppercase">Receiver</small>
|
||||||
|
</div>
|
||||||
|
<h6 className="my-50">Samantha Lee</h6>
|
||||||
|
<p className="text-body mb-0">744 Bay Area, California(CA), 94016</p>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div className="border-1 border-light border-top border-dashed my-4"></div>
|
||||||
|
|
||||||
|
{/* Entry 2 */}
|
||||||
|
<ul className="timeline mb-0">
|
||||||
|
<li className="timeline-item ps-6 border-left-dashed">
|
||||||
|
<span className="timeline-indicator-advanced timeline-indicator-warning border-0 shadow-none">
|
||||||
|
<i className="bx bx-time-five"></i>
|
||||||
|
</span>
|
||||||
|
<div className="timeline-event ps-1">
|
||||||
|
<div className="timeline-header">
|
||||||
|
<small className="text-warning text-uppercase">Sender</small>
|
||||||
|
</div>
|
||||||
|
<h6 className="my-50">Marcus Howard</h6>
|
||||||
|
<p className="text-body mb-0">58 Avenue, California(CA), 95376</p>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li className="timeline-item ps-6 border-transparent">
|
||||||
|
<span className="timeline-indicator-advanced timeline-indicator-primary border-0 shadow-none">
|
||||||
|
<i className="bx bx-map"></i>
|
||||||
|
</span>
|
||||||
|
<div className="timeline-event ps-1">
|
||||||
|
<div className="timeline-header">
|
||||||
|
<small className="text-primary text-uppercase">Receiver</small>
|
||||||
|
</div>
|
||||||
|
<h6 className="my-50">Daniel Foster</h6>
|
||||||
|
<p className="text-body mb-0">312 Marina, California(CA), 94109</p>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ---------------------- SHIPPING TAB ---------------------- */}
|
||||||
|
<div className="tab-pane fade" id="tab-shipping">
|
||||||
|
|
||||||
|
{/* Entry 1 */}
|
||||||
|
<ul className="timeline mb-0">
|
||||||
|
<li className="timeline-item ps-6 border-left-dashed">
|
||||||
|
<span className="timeline-indicator-advanced timeline-indicator-info border-0 shadow-none">
|
||||||
|
<i className="bx bx-package"></i>
|
||||||
|
</span>
|
||||||
|
<div className="timeline-event ps-1">
|
||||||
|
<div className="timeline-header">
|
||||||
|
<small className="text-info text-uppercase">Sender</small>
|
||||||
|
</div>
|
||||||
|
<h6 className="my-50">James Carter</h6>
|
||||||
|
<p className="text-body mb-0">441 Market St, California(CA), 94111</p>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li className="timeline-item ps-6 border-transparent">
|
||||||
|
<span className="timeline-indicator-advanced timeline-indicator-primary border-0 shadow-none">
|
||||||
|
<i className="bx bx-map"></i>
|
||||||
|
</span>
|
||||||
|
<div className="timeline-event ps-1">
|
||||||
|
<div className="timeline-header">
|
||||||
|
<small className="text-primary text-uppercase">Receiver</small>
|
||||||
|
</div>
|
||||||
|
<h6 className="my-50">Linda Moore</h6>
|
||||||
|
<p className="text-body mb-0">990 Willow Road, California(CA), 94025</p>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div className="border-1 border-light border-top border-dashed my-4"></div>
|
||||||
|
|
||||||
|
{/* Entry 2 */}
|
||||||
|
<ul className="timeline mb-0">
|
||||||
|
<li className="timeline-item ps-6 border-left-dashed">
|
||||||
|
<span className="timeline-indicator-advanced timeline-indicator-info border-0 shadow-none">
|
||||||
|
<i className="bx bx-package"></i>
|
||||||
|
</span>
|
||||||
|
<div className="timeline-event ps-1">
|
||||||
|
<div className="timeline-header">
|
||||||
|
<small className="text-info text-uppercase">Sender</small>
|
||||||
|
</div>
|
||||||
|
<h6 className="my-50">Sarah Bennett</h6>
|
||||||
|
<p className="text-body mb-0">882 Canyon Rd, California(CA), 94704</p>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li className="timeline-item ps-6 border-transparent">
|
||||||
|
<span className="timeline-indicator-advanced timeline-indicator-primary border-0 shadow-none">
|
||||||
|
<i className="bx bx-map"></i>
|
||||||
|
</span>
|
||||||
|
<div className="timeline-event ps-1">
|
||||||
|
<div className="timeline-header">
|
||||||
|
<small className="text-primary text-uppercase">Receiver</small>
|
||||||
|
</div>
|
||||||
|
<h6 className="my-50">George Simmons</h6>
|
||||||
|
<p className="text-body mb-0">19 Palm St, California(CA), 93001</p>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ServiceJobs;
|
||||||
@ -1,80 +1,54 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
|
|
||||||
const PreviewDocument = ({ imageUrl }) => {
|
const PreviewDocument = ({ imageUrl }) => {
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [rotation, setRotation] = useState(0);
|
const [rotation, setRotation] = useState(0);
|
||||||
const [scale, setScale] = useState(1);
|
|
||||||
|
|
||||||
const zoomIn = () => setScale((prev) => Math.min(prev + 0.2, 3));
|
|
||||||
const zoomOut = () => setScale((prev) => Math.max(prev - 0.2, 0.4));
|
|
||||||
const resetAll = () => {
|
|
||||||
setRotation(0);
|
|
||||||
setScale(1);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="d-flex justify-content-start gap-3 mb-2">
|
<div className="d-flex justify-content-start">
|
||||||
<i
|
<i
|
||||||
className="bx bx-rotate-right cursor-pointer fs-4"
|
className="bx bx-rotate-right cursor-pointer"
|
||||||
title="Rotate"
|
|
||||||
onClick={() => setRotation((prev) => prev + 90)}
|
onClick={() => setRotation((prev) => prev + 90)}
|
||||||
></i>
|
></i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="position-relative d-flex flex-column justify-content-center align-items-center"
|
||||||
|
style={{ minHeight: "80vh" }}
|
||||||
|
>
|
||||||
|
|
||||||
|
{loading && (
|
||||||
|
<div className="text-secondary text-center mb-2">Loading...</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<i
|
<div className="mb-3 d-flex justify-content-center align-items-center">
|
||||||
className="bx bx-zoom-in cursor-pointer fs-4"
|
<img
|
||||||
title="Zoom In"
|
src={imageUrl}
|
||||||
onClick={zoomIn}
|
alt="Full View"
|
||||||
></i>
|
className="img-fluid"
|
||||||
|
style={{
|
||||||
<i
|
maxHeight: "80vh",
|
||||||
className="bx bx-zoom-out cursor-pointer fs-4"
|
objectFit: "contain",
|
||||||
title="Zoom Out"
|
display: loading ? "none" : "block",
|
||||||
onClick={zoomOut}
|
transform: `rotate(${rotation}deg)`,
|
||||||
></i>
|
transition: "transform 0.3s ease",
|
||||||
|
}}
|
||||||
|
onLoad={() => setLoading(false)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div className="position-absolute bottom-0 start-0 justify-content-center gap-2">
|
||||||
className="position-relative d-flex flex-column justify-content-center align-items-center overflow-hidden"
|
<button
|
||||||
style={{ minHeight: "80vh" }}
|
className="btn btn-outline-secondary"
|
||||||
>
|
onClick={() => setRotation(0)}
|
||||||
{loading && (
|
title="Reset Rotation"
|
||||||
<div className="text-secondary text-center mb-2">
|
>
|
||||||
Loading...
|
<i className="bx bx-reset"></i> Reset
|
||||||
</div>
|
</button>
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="mb-3 d-flex justify-content-center align-items-center">
|
|
||||||
<img
|
|
||||||
src={imageUrl}
|
|
||||||
alt="Full View"
|
|
||||||
className="img-fluid"
|
|
||||||
style={{
|
|
||||||
maxHeight: "80vh",
|
|
||||||
objectFit: "contain",
|
|
||||||
display: loading ? "none" : "block",
|
|
||||||
transform: `rotate(${rotation}deg) scale(${scale})`,
|
|
||||||
transition: "transform 0.3s ease",
|
|
||||||
cursor: "grab",
|
|
||||||
}}
|
|
||||||
onLoad={() => setLoading(false)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="position-absolute bottom-0 start-0 m-2">
|
|
||||||
<button
|
|
||||||
className="btn btn-outline-secondary"
|
|
||||||
onClick={resetAll}
|
|
||||||
>
|
|
||||||
<i className="bx bx-reset"></i> Reset
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export default PreviewDocument;
|
export default PreviewDocument;
|
||||||
|
|||||||
@ -25,7 +25,9 @@ const Sidebar = () => {
|
|||||||
/>
|
/>
|
||||||
</span> */}
|
</span> */}
|
||||||
|
|
||||||
<small className="app-brand-link fw-bold navbar-brand text-green fs-6">
|
<small
|
||||||
|
className="app-brand-link fw-bold navbar-brand text-green fs-6"
|
||||||
|
>
|
||||||
<span className="app-brand-logo demo">
|
<span className="app-brand-logo demo">
|
||||||
<img src="/img/brand/marco.png" width="50" />
|
<img src="/img/brand/marco.png" width="50" />
|
||||||
</span>
|
</span>
|
||||||
@ -35,7 +37,8 @@ const Sidebar = () => {
|
|||||||
</small>
|
</small>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<small className="layout-menu-toggle menu-link text-large ms-auto">
|
<small className="layout-menu-toggle menu-link text-large ms-auto">
|
||||||
|
|
||||||
<i className="bx bx-chevron-left bx-sm d-flex align-items-center justify-content-center"></i>
|
<i className="bx bx-chevron-left bx-sm d-flex align-items-center justify-content-center"></i>
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
@ -58,7 +61,7 @@ const Sidebar = () => {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{data &&
|
{data &&
|
||||||
data?.data?.map((section) => (
|
data?.data.map((section) => (
|
||||||
<React.Fragment
|
<React.Fragment
|
||||||
key={section.id || section.header || section.items[0]?.id}
|
key={section.id || section.header || section.items[0]?.id}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -165,7 +165,7 @@ const ProjectStatistics = ({ project }) => {
|
|||||||
}, [selectedProject]);
|
}, [selectedProject]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="card h-100">
|
<>
|
||||||
<div className="card-header text-start">
|
<div className="card-header text-start">
|
||||||
<h5 className="card-action-title mb-0">
|
<h5 className="card-action-title mb-0">
|
||||||
{" "}
|
{" "}
|
||||||
@ -242,8 +242,8 @@ const ProjectStatistics = ({ project }) => {
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -10,13 +10,11 @@ import {
|
|||||||
import useMaster, { useServices } from "../../../hooks/masterHook/useMaster";
|
import useMaster, { useServices } from "../../../hooks/masterHook/useMaster";
|
||||||
import showToast from "../../../services/toastService";
|
import showToast from "../../../services/toastService";
|
||||||
import { useOrganizationEmployees } from "../../../hooks/useOrganization";
|
import { useOrganizationEmployees } from "../../../hooks/useOrganization";
|
||||||
import { useDispatch } from "react-redux";
|
|
||||||
import { changeMaster } from "../../../slices/localVariablesSlice";
|
|
||||||
|
|
||||||
const TeamEmployeeList = ({ organizationId, searchTerm, closeModal }) => {
|
const TeamEmployeeList = ({ organizationId, searchTerm, closeModal }) => {
|
||||||
const selectedProject = useSelectedProject();
|
const selectedProject = useSelectedProject();
|
||||||
const debounceSearchTerm = useDebounce(searchTerm, 500);
|
const debounceSearchTerm = useDebounce(searchTerm, 500);
|
||||||
const dispatch = useDispatch();
|
|
||||||
const {
|
const {
|
||||||
data: employeesData = [],
|
data: employeesData = [],
|
||||||
isLoading,
|
isLoading,
|
||||||
@ -47,7 +45,6 @@ const TeamEmployeeList = ({ organizationId, searchTerm, closeModal }) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(changeMaster("Job Role"));
|
|
||||||
if (employeesData?.data?.length > 0) {
|
if (employeesData?.data?.length > 0) {
|
||||||
const available = employeesData.data.filter((emp) => {
|
const available = employeesData.data.filter((emp) => {
|
||||||
const projEmp = projectEmployees.find((pe) => pe.employeeId === emp.id);
|
const projEmp = projectEmployees.find((pe) => pe.employeeId === emp.id);
|
||||||
@ -122,7 +119,7 @@ const TeamEmployeeList = ({ organizationId, searchTerm, closeModal }) => {
|
|||||||
status: true,
|
status: true,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
handleAssignEmployee({ payload, actionType: "assign" });
|
handleAssignEmployee({ payload,actionType:"assign"} );
|
||||||
|
|
||||||
setEmployees((prev) =>
|
setEmployees((prev) =>
|
||||||
prev.map((emp) => ({
|
prev.map((emp) => ({
|
||||||
@ -135,26 +132,26 @@ const TeamEmployeeList = ({ organizationId, searchTerm, closeModal }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (<div className="page-min-h d-flex justify-content-center align-items-center "><p className="text-muted">Loading employees...</p></div>);
|
return ( <div className="page-min-h d-flex justify-content-center align-items-center "><p className="text-muted">Loading employees...</p></div>) ;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isError) {
|
if (isError) {
|
||||||
return (
|
return (
|
||||||
<div className="page-min-h d-flex justify-content-center align-items-center ">
|
<div className="page-min-h d-flex justify-content-center align-items-center ">
|
||||||
{error?.status === 400 ? (
|
{error?.status === 400 ? (
|
||||||
<p className="m-0">Enter employee you want to find.</p>
|
<p className="m-0">Enter employee you want to find.</p>
|
||||||
) : (
|
) : (
|
||||||
<p className="m-0 dange-text">Something went wrong. Please try again later.</p>
|
<p className="m-0 dange-text">Something went wrong. Please try again later.</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (employees.length === 0) {
|
if (employees.length === 0) {
|
||||||
return (<div className="page-min-h d-flex justify-content-center align-items-center "><p className="text-muted">No available employees to assign.</p></div>);
|
return(<div className="page-min-h d-flex justify-content-center align-items-center "><p className="text-muted">No available employees to assign.</p></div>) ;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -186,8 +183,9 @@ const TeamEmployeeList = ({ organizationId, searchTerm, closeModal }) => {
|
|||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
handleSelectChange(index, "serviceId", e.target.value)
|
handleSelectChange(index, "serviceId", e.target.value)
|
||||||
}
|
}
|
||||||
className={`form-select form-select-sm w-auto border-none rounded-0 py-1 px-auto ${emp.errors.serviceId ? "is-invalid" : ""
|
className={`form-select form-select-sm w-auto border-none rounded-0 py-1 px-auto ${
|
||||||
}`}
|
emp.errors.serviceId ? "is-invalid" : ""
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
<option value="">Select Service</option>
|
<option value="">Select Service</option>
|
||||||
{services?.map((s) => (
|
{services?.map((s) => (
|
||||||
@ -207,8 +205,9 @@ const TeamEmployeeList = ({ organizationId, searchTerm, closeModal }) => {
|
|||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
handleSelectChange(index, "jobRole", e.target.value)
|
handleSelectChange(index, "jobRole", e.target.value)
|
||||||
}
|
}
|
||||||
className={`form-select form-select-sm w-auto border-none rounded-0 py-1 px-auto ${emp.errors.jobRole ? "is-invalid" : ""
|
className={`form-select form-select-sm w-auto border-none rounded-0 py-1 px-auto ${
|
||||||
}`}
|
emp.errors.jobRole ? "is-invalid" : ""
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
<option value="">Select Job Role</option>
|
<option value="">Select Job Role</option>
|
||||||
{jobRoles?.map((r) => (
|
{jobRoles?.map((r) => (
|
||||||
|
|||||||
@ -252,16 +252,15 @@ const ManageRecurringExpense = ({ closeModal, requestToEdit = null }) => {
|
|||||||
<HoverPopup
|
<HoverPopup
|
||||||
title="Payment Type"
|
title="Payment Type"
|
||||||
id="payment_type"
|
id="payment_type"
|
||||||
|
|
||||||
content={
|
content={
|
||||||
<div className=" w-50">
|
<div className="text-wrap" style={{ maxWidth: "200px" }}>
|
||||||
<p>
|
Choose whether the payment amount varies or remains fixed
|
||||||
Choose whether the payment amount varies or remains fixed
|
each cycle.
|
||||||
each cycle.
|
<br />
|
||||||
<br />
|
<strong>Is Variable:</strong> Amount changes per cycle.
|
||||||
<strong>Is Variable:</strong> Amount changes per cycle.
|
<br />
|
||||||
<br />
|
<strong>Fixed:</strong> Amount stays constant.
|
||||||
<strong>Fixed:</strong> Amount stays constant.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@ -476,10 +475,10 @@ const ManageRecurringExpense = ({ closeModal, requestToEdit = null }) => {
|
|||||||
title="Payment Buffer Days"
|
title="Payment Buffer Days"
|
||||||
id="payment_buffer_days"
|
id="payment_buffer_days"
|
||||||
content={
|
content={
|
||||||
<p>
|
<div className="text-wrap" style={{ maxWidth: "200px" }}>
|
||||||
Number of extra days allowed after the due date before
|
Number of extra days allowed after the due date before
|
||||||
payment is considered late.
|
payment is considered late.
|
||||||
</p>
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<i className="bx bx-info-circle bx-xs text-muted cursor-pointer"></i>
|
<i className="bx bx-info-circle bx-xs text-muted cursor-pointer"></i>
|
||||||
@ -511,10 +510,11 @@ const ManageRecurringExpense = ({ closeModal, requestToEdit = null }) => {
|
|||||||
<HoverPopup
|
<HoverPopup
|
||||||
title="End Date"
|
title="End Date"
|
||||||
id="end_date"
|
id="end_date"
|
||||||
|
|
||||||
content={
|
content={
|
||||||
<p>
|
<div className="text-wrap" style={{ maxWidth: "200px" }}>
|
||||||
The date when the last payment in the recurrence occurs.
|
The date when the last payment in the recurrence occurs.
|
||||||
</p>
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<i className="bx bx-info-circle bx-xs text-muted cursor-pointer"></i>
|
<i className="bx bx-info-circle bx-xs text-muted cursor-pointer"></i>
|
||||||
@ -592,8 +592,8 @@ const ManageRecurringExpense = ({ closeModal, requestToEdit = null }) => {
|
|||||||
{createPending || isPending
|
{createPending || isPending
|
||||||
? "Please wait...."
|
? "Please wait...."
|
||||||
: requestToEdit
|
: requestToEdit
|
||||||
? "Update"
|
? "Update"
|
||||||
: "Save as Draft"}
|
: "Save as Draft"}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@ -66,10 +66,19 @@ const Jobs = () => {
|
|||||||
<div className="col-12 col-md-6 text-start">
|
<div className="col-12 col-md-6 text-start">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`btn btn-sm ${showArchive ? "btn-secondary" : "btn-outline-secondary"}`}
|
className={`btn btn-sm ${showArchive ? "btn-primary" : "btn-outline-secondary"}`}
|
||||||
onClick={() => setShowArchive(!showArchive)}
|
onClick={() => setShowArchive(!showArchive)}
|
||||||
|
style={{ fontSize: "13px" }}
|
||||||
>
|
>
|
||||||
<i className="bx bx-archive bx-sm me-1 mt-1"></i> Archived
|
{showArchive ? (
|
||||||
|
<>
|
||||||
|
<i className="bx bx-list-ul me-1 mt-1"></i> Show Active
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<i className="bx bx-archive me-1 mt-1"></i> Show Archived
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -57,7 +57,7 @@ const ServiceProjectCard = ({ project, isCore = true }) => {
|
|||||||
DeleteProject(projectId, false);
|
DeleteProject(projectId, false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -89,7 +89,7 @@ const ServiceProjectCard = ({ project, isCore = true }) => {
|
|||||||
>
|
>
|
||||||
{project?.shortName ? project?.shortName : project?.name}
|
{project?.shortName ? project?.shortName : project?.name}
|
||||||
</h5>
|
</h5>
|
||||||
<div className="client-info text-body text-start">
|
<div className="client-info text-body">
|
||||||
<span>{project?.shortName ? project?.name : ""}</span>
|
<span>{project?.shortName ? project?.name : ""}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -58,139 +58,66 @@ const HoverPopup = ({
|
|||||||
return () => document.removeEventListener("click", handler);
|
return () => document.removeEventListener("click", handler);
|
||||||
}, [Mode, visible, dispatch, id]);
|
}, [Mode, visible, dispatch, id]);
|
||||||
|
|
||||||
// Positioning effect: respects align prop and stays inside boundary (drawer)
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!visible || !popupRef.current || !triggerRef.current) return;
|
if (!visible || !popupRef.current || !triggerRef.current) return;
|
||||||
|
|
||||||
// run in next frame so DOM/layout settles
|
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
const popup = popupRef.current;
|
const popup = popupRef.current;
|
||||||
|
const boundaryEl = (boundaryRef && boundaryRef.current) || popup.parentElement;
|
||||||
// choose boundary: provided boundaryRef or nearest positioned parent (popup.parentElement)
|
|
||||||
const boundaryEl =
|
|
||||||
(boundaryRef && boundaryRef.current) || popup.parentElement;
|
|
||||||
if (!boundaryEl) return;
|
if (!boundaryEl) return;
|
||||||
|
|
||||||
const boundaryRect = boundaryEl.getBoundingClientRect();
|
const boundaryRect = boundaryEl.getBoundingClientRect();
|
||||||
const triggerRect = triggerRef.current.getBoundingClientRect();
|
const triggerRect = triggerRef.current.getBoundingClientRect();
|
||||||
|
|
||||||
// reset styles first
|
|
||||||
popup.style.left = "";
|
popup.style.left = "";
|
||||||
popup.style.right = "";
|
popup.style.right = "";
|
||||||
popup.style.transform = "";
|
popup.style.transform = "";
|
||||||
popup.style.top = "";
|
popup.style.top = "";
|
||||||
|
|
||||||
const popupRect = popup.getBoundingClientRect();
|
const popupRect = popup.getBoundingClientRect();
|
||||||
const parentRect = boundaryRect; // alias
|
const triggerCenterX = triggerRect.left + triggerRect.width / 2 - boundaryRect.left;
|
||||||
|
let left = triggerCenterX - popupRect.width / 2;
|
||||||
|
|
||||||
// Convert trigger center to parent coordinates
|
// Clamp to boundaries
|
||||||
const triggerCenterX =
|
left = Math.max(0, Math.min(left, boundaryRect.width - popupRect.width));
|
||||||
triggerRect.left + triggerRect.width / 2 - parentRect.left;
|
popup.style.left = `${left}px`;
|
||||||
|
|
||||||
// preferred left so popup center aligns to trigger center:
|
|
||||||
const preferredLeft = triggerCenterX - popupRect.width / 2;
|
|
||||||
|
|
||||||
// Helpers to set styles in parent's coordinate system:
|
|
||||||
const setLeft = (leftPx) => {
|
|
||||||
popup.style.left = `${leftPx}px`;
|
|
||||||
popup.style.right = "auto";
|
|
||||||
popup.style.transform = "none";
|
|
||||||
};
|
|
||||||
const setRight = (rightPx) => {
|
|
||||||
popup.style.left = "auto";
|
|
||||||
popup.style.right = `${rightPx}px`;
|
|
||||||
popup.style.transform = "none";
|
|
||||||
};
|
|
||||||
|
|
||||||
// If user forced align:
|
|
||||||
if (align === "left") {
|
|
||||||
// align popup's left to parent's left (0)
|
|
||||||
setLeft(0);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (align === "right") {
|
|
||||||
// align popup's right to parent's right (0)
|
|
||||||
setRight(0);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (align === "center") {
|
|
||||||
popup.style.left = "50%";
|
|
||||||
popup.style.right = "auto";
|
|
||||||
popup.style.transform = "translateX(-50%)";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// align === "auto": try preferred centered position, but flip fully if overflow
|
|
||||||
// clamp preferredLeft to boundaries so it doesn't render partially outside
|
|
||||||
const leftIfCentered = Math.max(
|
|
||||||
0,
|
|
||||||
Math.min(preferredLeft, parentRect.width - popupRect.width)
|
|
||||||
);
|
|
||||||
|
|
||||||
// if centered fits, use it
|
|
||||||
if (leftIfCentered === preferredLeft) {
|
|
||||||
setLeft(leftIfCentered);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if centering would overflow right -> stick popup fully to left (left=0)
|
|
||||||
if (preferredLeft > parentRect.width - popupRect.width) {
|
|
||||||
// place popup so its right aligns to parent's right
|
|
||||||
// i.e., left = parent width - popup width
|
|
||||||
setLeft(parentRect.width - popupRect.width);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if centering would overflow left -> stick popup fully to left=0
|
|
||||||
if (preferredLeft < 0) {
|
|
||||||
setLeft(0);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// fallback center
|
|
||||||
setLeft(leftIfCentered);
|
|
||||||
});
|
});
|
||||||
}, [visible, align, boundaryRef]);
|
}, [visible, align, boundaryRef]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
|
||||||
className="d-inline-block position-relative" // <-- ADD THIS !!
|
|
||||||
style={{
|
|
||||||
maxWidth: "calc(700px - 100px)",
|
|
||||||
width: "100%",
|
|
||||||
wordWrap: "break-word",
|
|
||||||
overflow: "visible", // also make sure popup isn't clipped
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
className="d-inline-block"
|
className="d-inline-block position-relative" // <-- ADD THIS !!
|
||||||
ref={triggerRef}
|
style={{ overflow: "visible" }}
|
||||||
onMouseEnter={handleMouseEnter}
|
|
||||||
onMouseLeave={handleMouseLeave}
|
|
||||||
onClick={handleClick}
|
|
||||||
style={{ cursor: "pointer" }}
|
|
||||||
>
|
>
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{visible && (
|
|
||||||
<div
|
<div
|
||||||
ref={popupRef}
|
|
||||||
className={`hover-popup bg-white border rounded shadow-sm p-3 position-absolute mt-2 ${className}`}
|
ref={triggerRef}
|
||||||
style={{
|
onMouseEnter={handleMouseEnter}
|
||||||
zIndex: 2000,
|
onMouseLeave={handleMouseLeave}
|
||||||
top: "100%",
|
onClick={handleClick}
|
||||||
width: "max-content",
|
style={{ cursor: "pointer", display: "inline-block" }}
|
||||||
minWidth: "120px",
|
|
||||||
}}
|
|
||||||
onClick={(e) => e.stopPropagation()}
|
|
||||||
>
|
>
|
||||||
{title && <h6 className="fw-semibold mb-2">{title}</h6>}
|
{children}
|
||||||
<div>{content}</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
</div>
|
{visible && (
|
||||||
);
|
<div
|
||||||
|
ref={popupRef}
|
||||||
|
className={`hover-popup bg-white border rounded shadow-sm p-3 position-absolute mt-2 ${className}`}
|
||||||
|
style={{
|
||||||
|
zIndex: 2000,
|
||||||
|
top: "100%",
|
||||||
|
minWidth: "200px",
|
||||||
|
maxWidth: "300px",
|
||||||
|
wordWrap: "break-word",
|
||||||
|
}}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
{title && <h6 className="fw-semibold mb-2">{title}</h6>}
|
||||||
|
<div>{content}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -60,8 +60,8 @@ export const useCreateServiceProject = (onSuccessCallback) => {
|
|||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
showToast(
|
showToast(
|
||||||
error?.response?.data?.message ||
|
error?.response?.data?.message ||
|
||||||
error.message ||
|
error.message ||
|
||||||
"Failed to delete task",
|
"Failed to delete task",
|
||||||
"error"
|
"error"
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -85,8 +85,8 @@ export const useUpdateServiceProject = (onSuccessCallback) => {
|
|||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
showToast(
|
showToast(
|
||||||
error?.response?.data?.message ||
|
error?.response?.data?.message ||
|
||||||
error.message ||
|
error.message ||
|
||||||
"Failed to update project",
|
"Failed to update project",
|
||||||
"error"
|
"error"
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -111,8 +111,8 @@ export const useActiveInActiveServiceProject = (onSuccessCallback) => {
|
|||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
showToast(
|
showToast(
|
||||||
error?.response?.data?.message ||
|
error?.response?.data?.message ||
|
||||||
error.message ||
|
error.message ||
|
||||||
"Failed to update project",
|
"Failed to update project",
|
||||||
"error"
|
"error"
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -139,8 +139,8 @@ export const useAllocationServiceProjectTeam = (onSuccessCallback) => {
|
|||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
showToast(
|
showToast(
|
||||||
error?.response?.data?.message ||
|
error?.response?.data?.message ||
|
||||||
error.message ||
|
error.message ||
|
||||||
"Failed to update project",
|
"Failed to update project",
|
||||||
"error"
|
"error"
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -149,6 +149,11 @@ export const useAllocationServiceProjectTeam = (onSuccessCallback) => {
|
|||||||
|
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//#region Service Jobs
|
//#region Service Jobs
|
||||||
|
|
||||||
export const useServiceProjectJobs = (
|
export const useServiceProjectJobs = (
|
||||||
@ -159,14 +164,7 @@ export const useServiceProjectJobs = (
|
|||||||
isArchive
|
isArchive
|
||||||
) => {
|
) => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: [
|
queryKey: ["serviceProjectJobs", pageSize, pageNumber, isActive, project, isArchive],
|
||||||
"serviceProjectJobs",
|
|
||||||
pageSize,
|
|
||||||
pageNumber,
|
|
||||||
isActive,
|
|
||||||
project,
|
|
||||||
isArchive,
|
|
||||||
],
|
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const resp = await ServiceProjectRepository.GetJobList(
|
const resp = await ServiceProjectRepository.GetJobList(
|
||||||
pageSize,
|
pageSize,
|
||||||
@ -233,8 +231,8 @@ export const useAddCommentJob = (onSuccessCallback) => {
|
|||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
showToast(
|
showToast(
|
||||||
error?.response?.data?.message ||
|
error?.response?.data?.message ||
|
||||||
error.message ||
|
error.message ||
|
||||||
"Failed to update project",
|
"Failed to update project",
|
||||||
"error"
|
"error"
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -257,8 +255,8 @@ export const useCreateServiceProjectJob = (onSuccessCallback) => {
|
|||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
showToast(
|
showToast(
|
||||||
error?.response?.data?.message ||
|
error?.response?.data?.message ||
|
||||||
error.message ||
|
error.message ||
|
||||||
"Failed to update project",
|
"Failed to update project",
|
||||||
"error"
|
"error"
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -287,19 +285,27 @@ export const useUpdateServiceProjectJob = (onSuccessCallback) => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
showToast(
|
showToast(
|
||||||
error?.response?.data?.message ||
|
error?.response?.data?.message ||
|
||||||
error.message ||
|
error.message ||
|
||||||
"Failed to update project",
|
"Failed to update project",
|
||||||
"error"
|
"error"
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//#region Branch
|
//#region Branch
|
||||||
export const useBranches = (
|
export const useBranches = (
|
||||||
projectId,
|
projectId,
|
||||||
@ -338,8 +344,8 @@ export const useBranchTypes = () => {
|
|||||||
const resp = await ServiceProjectRepository.GetBranchTypeList();
|
const resp = await ServiceProjectRepository.GetBranchTypeList();
|
||||||
return resp.data;
|
return resp.data;
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
export const useBranchDetails = (id) => {
|
export const useBranchDetails = (id) => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
@ -348,9 +354,9 @@ export const useBranchDetails = (id) => {
|
|||||||
const resp = await ServiceProjectRepository.GetBranchDetail(id);
|
const resp = await ServiceProjectRepository.GetBranchDetail(id);
|
||||||
return resp.data;
|
return resp.data;
|
||||||
},
|
},
|
||||||
enabled: !!id,
|
enabled: !!id
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
export const useCreateBranch = (onSuccessCallBack) => {
|
export const useCreateBranch = (onSuccessCallBack) => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
@ -396,6 +402,7 @@ export const useUpdateBranch = (onSuccessCallBack) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export const useDeleteBranch = () => {
|
export const useDeleteBranch = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
@ -405,17 +412,14 @@ export const useDeleteBranch = () => {
|
|||||||
|
|
||||||
onSuccess: (_, variable) => {
|
onSuccess: (_, variable) => {
|
||||||
queryClient.invalidateQueries({ queryKey: ["branches"] });
|
queryClient.invalidateQueries({ queryKey: ["branches"] });
|
||||||
showToast(
|
showToast(`Branch ${variable.isActive ? "restored" : "deleted"} successfully`, "success");
|
||||||
`Branch ${variable.isActive ? "restored" : "deleted"} successfully`,
|
|
||||||
"success"
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
showToast(
|
showToast(
|
||||||
error?.response?.data?.message ||
|
error?.response?.data?.message ||
|
||||||
error.message ||
|
error.message ||
|
||||||
"Failed to delete branch",
|
"Failed to delete branch",
|
||||||
"error"
|
"error"
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@ -24,6 +24,9 @@ import { useProjectAccess } from "../../hooks/useProjectAccess";
|
|||||||
|
|
||||||
import "./ProjectDetails.css";
|
import "./ProjectDetails.css";
|
||||||
import ProjectOrganizations from "../../components/Project/ProjectOrganizations";
|
import ProjectOrganizations from "../../components/Project/ProjectOrganizations";
|
||||||
|
import ProjectStatistics from "../../components/Project/ProjectStatistics";
|
||||||
|
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||||
|
import { REGULARIZE_ATTENDANCE, SELF_ATTENDANCE, TEAM_ATTENDANCE } from "../../utils/constants";
|
||||||
|
|
||||||
const ProjectDetails = () => {
|
const ProjectDetails = () => {
|
||||||
const projectId = useSelectedProject();
|
const projectId = useSelectedProject();
|
||||||
@ -34,6 +37,10 @@ const ProjectDetails = () => {
|
|||||||
useProjectDetails(projectId);
|
useProjectDetails(projectId);
|
||||||
|
|
||||||
const { canView, loading: permsLoading } = useProjectAccess(projectId);
|
const { canView, loading: permsLoading } = useProjectAccess(projectId);
|
||||||
|
const canRegularize = useHasUserPermission(REGULARIZE_ATTENDANCE);
|
||||||
|
const canTeamAttendance = useHasUserPermission(TEAM_ATTENDANCE);
|
||||||
|
const canSelfAttendance = useHasUserPermission(SELF_ATTENDANCE);
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!projectId && projectNames.length > 0) {
|
if (!projectId && projectNames.length > 0) {
|
||||||
@ -82,13 +89,16 @@ const ProjectDetails = () => {
|
|||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-lg-4 col-md-5 mt-2">
|
<div className="col-lg-4 col-md-5 mt-2">
|
||||||
<AboutProject />
|
<AboutProject />
|
||||||
<ProjectOverview project={projectId} />
|
<div className="card"><ProjectStatistics project={projectId} /></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-lg-8 col-md-7 mt-2">
|
<div className="col-lg-8 col-md-7 mt-2">
|
||||||
<ProjectProgressChart ShowAllProject="false" DefaultRange="1M" />
|
<ProjectProgressChart ShowAllProject="false" DefaultRange="1M" />
|
||||||
<div className="mt-5">
|
{(canRegularize || canTeamAttendance || canSelfAttendance) && (
|
||||||
<AttendanceOverview />
|
<div className="mt-5">
|
||||||
</div>
|
<AttendanceOverview />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import React, { createContext, useContext, useEffect, useRef, useState } from "react";
|
import React, { createContext, useContext, useEffect, useState } from "react";
|
||||||
import Breadcrumb from "../../components/common/Breadcrumb";
|
import Breadcrumb from "../../components/common/Breadcrumb";
|
||||||
import {
|
import {
|
||||||
ITEMS_PER_PAGE,
|
ITEMS_PER_PAGE,
|
||||||
@ -36,7 +36,7 @@ const ProjectPage = () => {
|
|||||||
isOpen: false,
|
isOpen: false,
|
||||||
project: null,
|
project: null,
|
||||||
});
|
});
|
||||||
const dropdownRef = useRef(null);
|
|
||||||
const [projectList, setProjectList] = useState([]);
|
const [projectList, setProjectList] = useState([]);
|
||||||
const [listView, setListView] = useState(false);
|
const [listView, setListView] = useState(false);
|
||||||
const [searchTerm, setSearchTerm] = useState("");
|
const [searchTerm, setSearchTerm] = useState("");
|
||||||
@ -46,7 +46,6 @@ const ProjectPage = () => {
|
|||||||
});
|
});
|
||||||
const HasManageProject = useHasUserPermission(MANAGE_PROJECT);
|
const HasManageProject = useHasUserPermission(MANAGE_PROJECT);
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
const [open, setOpen] = useState(false);
|
|
||||||
|
|
||||||
const [selectedStatuses, setSelectedStatuses] = useState(
|
const [selectedStatuses, setSelectedStatuses] = useState(
|
||||||
PROJECT_STATUS.map((s) => s.id)
|
PROJECT_STATUS.map((s) => s.id)
|
||||||
@ -71,16 +70,6 @@ const ProjectPage = () => {
|
|||||||
sessionStorage.setItem("whichProjectDisplay", String(value));
|
sessionStorage.setItem("whichProjectDisplay", String(value));
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const handleClickOutside = (event) => {
|
|
||||||
if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
|
|
||||||
setOpen(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
document.addEventListener("mousedown", handleClickOutside);
|
|
||||||
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ProjectContext.Provider value={contextDispatcher}>
|
<ProjectContext.Provider value={contextDispatcher}>
|
||||||
<div className="container-fluid">
|
<div className="container-fluid">
|
||||||
@ -100,8 +89,9 @@ const ProjectPage = () => {
|
|||||||
{/* Service Project Button */}
|
{/* Service Project Button */}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`btn px-2 py-1 rounded-0 text-tiny ${!coreProjects ? "btn-primary text-white" : ""
|
className={`btn px-2 py-1 rounded-0 text-tiny ${
|
||||||
}`}
|
!coreProjects ? "btn-primary text-white" : ""
|
||||||
|
}`}
|
||||||
onClick={() => handleToggleProject(false)}
|
onClick={() => handleToggleProject(false)}
|
||||||
>
|
>
|
||||||
Service Project
|
Service Project
|
||||||
@ -109,8 +99,9 @@ const ProjectPage = () => {
|
|||||||
{/* Organization Project Button */}
|
{/* Organization Project Button */}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`btn px-2 py-1 rounded-0 text-tiny ${coreProjects ? "btn-primary text-white" : ""
|
className={`btn px-2 py-1 rounded-0 text-tiny ${
|
||||||
}`}
|
coreProjects ? "btn-primary text-white" : ""
|
||||||
|
}`}
|
||||||
onClick={() => handleToggleProject(true)}
|
onClick={() => handleToggleProject(true)}
|
||||||
>
|
>
|
||||||
Infra Project
|
Infra Project
|
||||||
@ -138,8 +129,9 @@ const ProjectPage = () => {
|
|||||||
<div className="d-flex gap-2">
|
<div className="d-flex gap-2">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`btn btn-sm p-1 ${!listView ? "btn-primary" : "btn-outline-primary"
|
className={`btn btn-sm p-1 ${
|
||||||
}`}
|
!listView ? "btn-primary" : "btn-outline-primary"
|
||||||
|
}`}
|
||||||
onClick={() => setListView(false)}
|
onClick={() => setListView(false)}
|
||||||
title="Card View"
|
title="Card View"
|
||||||
>
|
>
|
||||||
@ -148,8 +140,9 @@ const ProjectPage = () => {
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`btn btn-sm p-1 ${listView ? "btn-primary" : "btn-outline-primary"
|
className={`btn btn-sm p-1 ${
|
||||||
}`}
|
listView ? "btn-primary" : "btn-outline-primary"
|
||||||
|
}`}
|
||||||
onClick={() => setListView(true)}
|
onClick={() => setListView(true)}
|
||||||
title="List View"
|
title="List View"
|
||||||
>
|
>
|
||||||
@ -158,62 +151,33 @@ const ProjectPage = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Dropdown Filter */}
|
{/* Dropdown Filter */}
|
||||||
<div className="dropdown me-2 position-relative">
|
<div className="dropdown me-2">
|
||||||
<div
|
<a
|
||||||
className="cursor-pointer p-1"
|
className="dropdown-toggle hide-arrow cursor-pointer p-1"
|
||||||
onClick={() => setOpen(!open)}
|
data-bs-toggle="dropdown"
|
||||||
|
aria-expanded="false"
|
||||||
|
title="Filter"
|
||||||
>
|
>
|
||||||
<i
|
<i className="bx bx-slider-alt fs-5"></i>
|
||||||
className={`bx bx-slider-alt fs-5 ${selectedStatuses.length !== PROJECT_STATUS.length ? "text-primary" : ""
|
</a>
|
||||||
}`}
|
|
||||||
></i>
|
|
||||||
|
|
||||||
{selectedStatuses.length !== PROJECT_STATUS.length && (
|
<ul className="dropdown-menu p-2 text-capitalize">
|
||||||
<span className="badge bg-warning text-white rounded-pill position-absolute"
|
{PROJECT_STATUS.map(({ id, label }) => (
|
||||||
style={{
|
<li key={id}>
|
||||||
top: "-4px",
|
<div className="form-check">
|
||||||
right: "-4px",
|
<input
|
||||||
fontSize: "0.6rem",
|
className="form-check-input"
|
||||||
padding: "2px 5px",
|
type="checkbox"
|
||||||
}}
|
checked={selectedStatuses.includes(id)}
|
||||||
>
|
onChange={() => handleStatusChange(id)}
|
||||||
{PROJECT_STATUS.length - selectedStatuses.length}
|
/>
|
||||||
</span>
|
<label className="form-check-label">{label}</label>
|
||||||
)}
|
</div>
|
||||||
</div>
|
</li>
|
||||||
|
))}
|
||||||
{open && (
|
</ul>
|
||||||
<ul
|
|
||||||
ref={dropdownRef}
|
|
||||||
className="dropdown-menu show p-2 text-capitalize"
|
|
||||||
onMouseDown={(e) => e.stopPropagation()} // IMPORTANT
|
|
||||||
>
|
|
||||||
{PROJECT_STATUS.map(({ id, label }) => (
|
|
||||||
<li key={id}>
|
|
||||||
<div className="form-check">
|
|
||||||
<input
|
|
||||||
className="form-check-input"
|
|
||||||
type="checkbox"
|
|
||||||
checked={selectedStatuses.includes(id)}
|
|
||||||
onClick={(e) => e.stopPropagation()} // IMPORTANT
|
|
||||||
onChange={() => handleStatusChange(id)}
|
|
||||||
/>
|
|
||||||
<label
|
|
||||||
className="form-check-label"
|
|
||||||
onClick={(e) => e.stopPropagation()} // OPTIONAL
|
|
||||||
>
|
|
||||||
{label}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{HasManageProject && (
|
{HasManageProject && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@ -223,9 +187,9 @@ const ProjectPage = () => {
|
|||||||
coreProjects
|
coreProjects
|
||||||
? setMangeProject({ isOpen: true, Project: null }) // Organization Project → Infra
|
? setMangeProject({ isOpen: true, Project: null }) // Organization Project → Infra
|
||||||
: setManageServiceProject({
|
: setManageServiceProject({
|
||||||
isOpen: true,
|
isOpen: true,
|
||||||
Project: null,
|
Project: null,
|
||||||
}) // Service Project
|
}) // Service Project
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<i className="bx bx-plus-circle me-2"></i>
|
<i className="bx bx-plus-circle me-2"></i>
|
||||||
|
|||||||
@ -150,9 +150,6 @@ export function startSignalR(loggedUser) {
|
|||||||
queryClient.invalidateQueries(["serviceProjects"]);
|
queryClient.invalidateQueries(["serviceProjects"]);
|
||||||
queryClient.invalidateQueries(["serviceProject"]);
|
queryClient.invalidateQueries(["serviceProject"]);
|
||||||
}
|
}
|
||||||
if (keyword === "Project_Branch") {
|
|
||||||
queryClient.invalidateQueries(["branches"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (keyword === "Service_Project_Allocation") {
|
if (keyword === "Service_Project_Allocation") {
|
||||||
queryClient.invalidateQueries(["serviceProjectTeam"]);
|
queryClient.invalidateQueries(["serviceProjectTeam"]);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user