@paro.io/expert-shared-components 1.12.15 → 1.12.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +2 -2
- package/lib/components/ClientReferencesSection/DeleteButton.js +11 -11
- package/lib/components/ClientReferencesSection/ParoError.js +10 -10
- package/lib/components/ClientReferencesSection/TagsSection.js +2 -2
- package/lib/components/ClientReferencesSection/styles/BrandedTypography.js +2 -2
- package/lib/components/ClientReferencesSection/styles/Buttons.js +15 -15
- package/lib/components/ClientReferencesSection/styles/Name.js +5 -5
- package/lib/components/ClientReferencesSection/styles/NullContentConditionalColor.js +4 -4
- package/lib/components/ClientReferencesSection/styles/SectionBody.js +11 -11
- package/lib/components/ClientReferencesSection/styles/SectionTitle.js +6 -6
- package/lib/components/ClientReferencesSection/styles/Tags.js +2 -2
- package/lib/components/DiscussionThread/chat.d.ts +22 -22
- package/lib/components/DiscussionThread/chat.js +106 -106
- package/lib/components/DocumentCenter/DocumentTable.d.ts +15 -15
- package/lib/components/DocumentCenter/DocumentTable.js +350 -350
- package/lib/components/DocumentCenter/UploadFilesButton.d.ts +6 -6
- package/lib/components/DocumentCenter/UploadFilesButton.js +29 -29
- package/lib/components/EarningsTracker/ActiveProjectCard.d.ts +52 -52
- package/lib/components/EarningsTracker/ActiveProjectCard.js +161 -161
- package/lib/components/EarningsTracker/CenterCardUI.d.ts +13 -13
- package/lib/components/EarningsTracker/CenterCardUI.js +134 -134
- package/lib/components/EarningsTracker/EarningsTracker.d.ts +52 -52
- package/lib/components/EarningsTracker/EarningsTracker.js +508 -508
- package/lib/components/EarningsTracker/EditDateModal.d.ts +22 -22
- package/lib/components/EarningsTracker/EditDateModal.js +149 -149
- package/lib/components/EarningsTracker/EmailModal.d.ts +14 -14
- package/lib/components/EarningsTracker/EmailModal.js +79 -79
- package/lib/components/EarningsTracker/EndProjectModal.d.ts +56 -56
- package/lib/components/EarningsTracker/EndProjectModal.js +221 -221
- package/lib/components/EarningsTracker/LeftCardUI.d.ts +18 -18
- package/lib/components/EarningsTracker/LeftCardUI.js +189 -189
- package/lib/components/EarningsTracker/LogTimeModalAuthenticated.d.ts +52 -52
- package/lib/components/EarningsTracker/LogTimeModalAuthenticated.js +358 -358
- package/lib/components/EarningsTracker/ProgressBar.d.ts +4 -4
- package/lib/components/EarningsTracker/ProgressBar.js +66 -66
- package/lib/components/EarningsTracker/ReviewRequestModal.d.ts +17 -17
- package/lib/components/EarningsTracker/ReviewRequestModal.js +135 -135
- package/lib/components/EarningsTracker/RightCardUI.d.ts +46 -46
- package/lib/components/EarningsTracker/RightCardUI.js +231 -231
- package/lib/components/EarningsTracker/index.d.ts +1 -1
- package/lib/components/EarningsTracker/index.js +5 -5
- package/lib/components/ExpertProfileHeader/ActionButtonSection.js +6 -6
- package/lib/components/ExpertProfileHeader/ProfileSection.js +7 -7
- package/lib/components/Invoices/DecisionSection.d.ts +6 -1
- package/lib/components/Invoices/DecisionSection.js +118 -25
- package/lib/components/Invoices/DiscussionSection.d.ts +1 -0
- package/lib/components/Invoices/DiscussionSection.js +19 -1
- package/lib/components/Invoices/DisputeProjectCard.js +3 -1
- package/lib/components/Invoices/InvoiceCard.d.ts +4 -1
- package/lib/components/Invoices/InvoiceCard.js +2 -2
- package/lib/components/Invoices/ProjectHoursAdjustmentModal.d.ts +37 -0
- package/lib/components/Invoices/ProjectHoursAdjustmentModal.js +314 -0
- package/lib/components/Invoices/TestDecisionSection.d.ts +1 -0
- package/lib/components/Invoices/TestDecisionSection.js +126 -0
- package/lib/components/Invoices/index.d.ts +2 -0
- package/lib/components/Invoices/index.js +5 -1
- package/lib/components/OrganizationChart/OrganizationChart.d.ts +15 -15
- package/lib/components/OrganizationChart/OrganizationChart.js +312 -312
- package/lib/components/OrganizationChart/PersonCard.js +5 -5
- package/lib/components/OrganizationChart/utils.js +79 -79
- package/lib/components/ProjectCard/ProgressBar.js +4 -4
- package/lib/components/ProjectCard/ReviewRequestModal.js +5 -5
- package/lib/components/Reviews/Pagination.js +6 -6
- package/lib/components/ReviewsTab/RatingHeader.js +6 -6
- package/lib/components/ReviewsTab/expert-shared-components.code-workspace +20 -20
- package/lib/components/ReviewsTab/reviewRequestModal.js +5 -5
- package/lib/components/shared/Image.js +13 -13
- package/lib/components/shared/ProfileTextField.d.ts +18 -18
- package/lib/components/shared/ProfileTextField.js +16 -16
- package/lib/components/shared/StyledActionButtons.d.ts +7 -7
- package/lib/components/shared/StyledActionButtons.js +15 -15
- package/lib/components/shared/ToastNotification.d.ts +10 -10
- package/lib/components/shared/ToastNotification.js +63 -63
- package/package.json +61 -61
|
@@ -31,49 +31,130 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
31
31
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
32
32
|
});
|
|
33
33
|
};
|
|
34
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
35
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
36
|
-
};
|
|
37
34
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
38
35
|
exports.DecisionSection = void 0;
|
|
39
36
|
const react_1 = __importStar(require("react"));
|
|
40
37
|
const base_ui_1 = require("@paro.io/base-ui");
|
|
41
|
-
const
|
|
38
|
+
const utils_1 = require("../shared/utils");
|
|
39
|
+
const ProjectHoursAdjustmentModal_1 = require("./ProjectHoursAdjustmentModal");
|
|
42
40
|
const RESOLUTION_OPTIONS = [
|
|
43
41
|
{ value: 'APPROVED', label: 'Approve Dispute' },
|
|
44
42
|
{ value: 'DECLINED', label: 'Decline Dispute' },
|
|
45
43
|
{ value: 'PARTIAL', label: 'Partial Approval' },
|
|
46
44
|
];
|
|
47
|
-
const DecisionSection = ({ dispute, onUpdateDispute, }) => {
|
|
48
|
-
var _a;
|
|
49
|
-
const
|
|
45
|
+
const DecisionSection = ({ dispute, onUpdateDispute, updateInvoiceMutation, updateClientInvoiceDisputeMutation, invoiceSummary, user, getClientInvoiceSummaryByMonth, }) => {
|
|
46
|
+
var _a, _b;
|
|
47
|
+
const totalDisputeHours = (_a = dispute === null || dispute === void 0 ? void 0 : dispute.disputeProjects) === null || _a === void 0 ? void 0 : _a.reduce((acc, project) => acc + (project === null || project === void 0 ? void 0 : project.disputeHours), 0);
|
|
50
48
|
const [resolution, setResolution] = (0, react_1.useState)('');
|
|
51
|
-
const [
|
|
49
|
+
const [approvedHours, setApprovedHours] = (0, react_1.useState)(totalDisputeHours);
|
|
50
|
+
const [projectApprovedHours, setProjectApprovedHours] = (0, react_1.useState)({});
|
|
52
51
|
const [isSubmitting, setIsSubmitting] = (0, react_1.useState)(false);
|
|
53
|
-
const
|
|
52
|
+
const [showHoursModal, setShowHoursModal] = (0, react_1.useState)(false);
|
|
53
|
+
// Initialize project approved hours when resolution changes to PARTIAL
|
|
54
|
+
const handleResolutionChange = (newResolution) => {
|
|
55
|
+
var _a;
|
|
56
|
+
setResolution(newResolution);
|
|
57
|
+
if (newResolution === 'PARTIAL' && ((_a = dispute === null || dispute === void 0 ? void 0 : dispute.disputeProjects) === null || _a === void 0 ? void 0 : _a.length) > 0) {
|
|
58
|
+
// Initialize with dispute hours for each project
|
|
59
|
+
const initialProjectHours = {};
|
|
60
|
+
dispute.disputeProjects.forEach((project) => {
|
|
61
|
+
initialProjectHours[project.projectId] = project.disputeHours;
|
|
62
|
+
});
|
|
63
|
+
setProjectApprovedHours(initialProjectHours);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
setProjectApprovedHours({});
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
const handleProjectHoursChange = (projectId, hours) => {
|
|
70
|
+
setProjectApprovedHours(prev => (Object.assign(Object.assign({}, prev), { [projectId]: hours })));
|
|
71
|
+
};
|
|
72
|
+
const getTotalApprovedHours = () => {
|
|
73
|
+
var _a;
|
|
74
|
+
if (resolution === 'PARTIAL' && ((_a = dispute === null || dispute === void 0 ? void 0 : dispute.disputeProjects) === null || _a === void 0 ? void 0 : _a.length) > 0) {
|
|
75
|
+
return Object.values(projectApprovedHours).reduce((sum, hours) => sum + hours, 0);
|
|
76
|
+
}
|
|
77
|
+
return approvedHours;
|
|
78
|
+
};
|
|
79
|
+
const handleApproveClick = () => {
|
|
80
|
+
var _a;
|
|
54
81
|
if (!resolution)
|
|
55
82
|
return;
|
|
83
|
+
// Check if any dispute projects have hourly disputes that need hour adjustments
|
|
84
|
+
const hasHourlyDisputes = (_a = dispute === null || dispute === void 0 ? void 0 : dispute.disputeProjects) === null || _a === void 0 ? void 0 : _a.some((project) => project.disputeType === 'Hourly');
|
|
85
|
+
if (hasHourlyDisputes) {
|
|
86
|
+
setShowHoursModal(true);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
handleFinalSubmit();
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
const handleHoursSubmit = (submissionData) => {
|
|
93
|
+
setShowHoursModal(false);
|
|
94
|
+
handleFinalSubmit(submissionData);
|
|
95
|
+
};
|
|
96
|
+
const handleFinalSubmit = (submissionData) => __awaiter(void 0, void 0, void 0, function* () {
|
|
97
|
+
var _a;
|
|
56
98
|
setIsSubmitting(true);
|
|
57
99
|
try {
|
|
100
|
+
const finalApprovedHours = getTotalApprovedHours();
|
|
101
|
+
// Determine resolution type based on approval
|
|
102
|
+
const isFullApproval = resolution === 'APPROVED' ||
|
|
103
|
+
(resolution === 'PARTIAL' && Math.abs(finalApprovedHours - totalDisputeHours) <= 0.01);
|
|
104
|
+
const resolutionType = isFullApproval ? 'Upheld' : 'Reduced';
|
|
105
|
+
// Update dispute projects with resolution details
|
|
106
|
+
const disputeProjectUpdates = ((_a = dispute === null || dispute === void 0 ? void 0 : dispute.disputeProjects) === null || _a === void 0 ? void 0 : _a.map((project) => ({
|
|
107
|
+
projectId: project.projectId,
|
|
108
|
+
status: 'Resolved',
|
|
109
|
+
resolutionMode: 'Direct',
|
|
110
|
+
resolutionType: resolutionType
|
|
111
|
+
}))) || [];
|
|
112
|
+
const updateData = {
|
|
113
|
+
disputeId: dispute.disputeId,
|
|
114
|
+
status: "Resolved",
|
|
115
|
+
approvedAmount: finalApprovedHours * ((submissionData === null || submissionData === void 0 ? void 0 : submissionData.freelancerRate) || 0), //Calculate this dynamically based on rate
|
|
116
|
+
finalDecisionOwnerId: (user === null || user === void 0 ? void 0 : user.userId) || null,
|
|
117
|
+
projectDisputes: disputeProjectUpdates
|
|
118
|
+
};
|
|
119
|
+
// If we have adjusted hours, include them in the update
|
|
120
|
+
if (submissionData) {
|
|
121
|
+
// Call the updateInvoiceMutation with the submission data
|
|
122
|
+
yield updateInvoiceMutation({
|
|
123
|
+
variables: {
|
|
124
|
+
input: submissionData.input,
|
|
125
|
+
clientId: submissionData.clientId,
|
|
126
|
+
invoiceId: submissionData.invoiceId,
|
|
127
|
+
dateGenerated: submissionData.dateGenerated,
|
|
128
|
+
updateDescription: submissionData.updateDescription,
|
|
129
|
+
freelancerIds: submissionData.freelancerIds
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
}
|
|
58
133
|
yield onUpdateDispute({
|
|
59
134
|
variables: {
|
|
60
|
-
input:
|
|
61
|
-
disputeId: dispute.disputeId,
|
|
62
|
-
status: "Resolved", //To-Do - Fix with actual status
|
|
63
|
-
approvedAmount: amount ? parseFloat(amount) : undefined,
|
|
64
|
-
},
|
|
135
|
+
input: updateData,
|
|
65
136
|
},
|
|
66
137
|
});
|
|
67
|
-
|
|
138
|
+
(0, utils_1.showToast)('success', 'Thank you for resolving the dispute. The invoice has been updated with the new hours.');
|
|
68
139
|
}
|
|
69
140
|
catch (error) {
|
|
70
141
|
console.error('Failed to update dispute:', error);
|
|
71
|
-
|
|
142
|
+
(0, utils_1.showToast)('warning', 'Failed to update dispute');
|
|
72
143
|
}
|
|
73
144
|
finally {
|
|
74
145
|
setIsSubmitting(false);
|
|
75
146
|
}
|
|
76
147
|
});
|
|
148
|
+
const isPartialWithMultipleProjects = resolution === 'PARTIAL' && ((_b = dispute === null || dispute === void 0 ? void 0 : dispute.disputeProjects) === null || _b === void 0 ? void 0 : _b.length) > 0;
|
|
149
|
+
const isValidPartialApproval = () => {
|
|
150
|
+
if (resolution !== 'PARTIAL')
|
|
151
|
+
return true;
|
|
152
|
+
// Check each project's approved hours
|
|
153
|
+
return dispute.disputeProjects.every((project) => {
|
|
154
|
+
const approvedForProject = projectApprovedHours[project.projectId] || 0;
|
|
155
|
+
return approvedForProject >= 0 && approvedForProject <= project.disputeHours;
|
|
156
|
+
});
|
|
157
|
+
};
|
|
77
158
|
return (react_1.default.createElement("div", { className: "space-y-6" },
|
|
78
159
|
react_1.default.createElement("div", { className: "flex items-center justify-between" },
|
|
79
160
|
react_1.default.createElement("div", { className: "text-lg font-bold text-[#333333]" }, "Make Decision")),
|
|
@@ -82,16 +163,28 @@ const DecisionSection = ({ dispute, onUpdateDispute, }) => {
|
|
|
82
163
|
react_1.default.createElement("div", null,
|
|
83
164
|
react_1.default.createElement("label", { className: "block text-sm text-[#666666] font-bold mb-2" }, "Resolution:"),
|
|
84
165
|
react_1.default.createElement("div", { className: "w-full" },
|
|
85
|
-
react_1.default.createElement("select", { value: resolution, onChange: (e) =>
|
|
166
|
+
react_1.default.createElement("select", { value: resolution, onChange: (e) => handleResolutionChange(e.target.value), className: "w-full px-3 py-2 border border-[#CCCCCC] rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-[#248384] bg-white" },
|
|
86
167
|
react_1.default.createElement("option", { value: "" }, "Select Resolution"),
|
|
87
|
-
RESOLUTION_OPTIONS.map((option) => (react_1.default.createElement("option", { key: option.value, value: option.value }, option.label)))))),
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
react_1.default.createElement("
|
|
168
|
+
RESOLUTION_OPTIONS.map((option) => (react_1.default.createElement("option", { key: option.value, value: option.value }, option.label))))))),
|
|
169
|
+
isPartialWithMultipleProjects && (react_1.default.createElement("div", { className: "space-y-4" },
|
|
170
|
+
react_1.default.createElement("div", { className: "text-sm font-bold text-[#666666]" }, "Approved Hours per Project:"),
|
|
171
|
+
react_1.default.createElement("div", { className: "grid grid-cols-1 gap-4" }, dispute.disputeProjects.map((project) => (react_1.default.createElement("div", { key: project.projectId, className: "flex items-center justify-between p-4 bg-white rounded-lg border border-[#CCCCCC]" },
|
|
172
|
+
react_1.default.createElement("div", { className: "flex-1" },
|
|
173
|
+
react_1.default.createElement("div", { className: "font-medium text-sm" }, project.project.name),
|
|
174
|
+
react_1.default.createElement("div", { className: "text-xs text-gray-500" },
|
|
175
|
+
"Disputed Hours: ",
|
|
176
|
+
project.disputeHours)),
|
|
177
|
+
react_1.default.createElement("div", { className: "w-32" },
|
|
178
|
+
react_1.default.createElement("input", { type: "number", step: "0.01", min: "0", max: project.disputeHours, value: projectApprovedHours[project.projectId] || 0, onChange: (e) => handleProjectHoursChange(project.projectId, parseFloat(e.target.value) || 0), className: "w-full px-2 py-1 border border-[#CCCCCC] rounded text-sm focus:outline-none focus:ring-2 focus:ring-[#248384]", placeholder: "Hours" })))))),
|
|
179
|
+
react_1.default.createElement("div", { className: "text-sm text-gray-600" },
|
|
180
|
+
"Total Approved Hours: ",
|
|
181
|
+
getTotalApprovedHours().toFixed(2),
|
|
182
|
+
" / ",
|
|
183
|
+
totalDisputeHours,
|
|
184
|
+
" hours"))),
|
|
93
185
|
react_1.default.createElement("div", { className: "flex justify-end space-x-4" },
|
|
94
|
-
react_1.default.createElement(base_ui_1.Button, { label: "Decline Dispute", color: "error", onClick: () =>
|
|
95
|
-
react_1.default.createElement(base_ui_1.Button, { label: "Approve & Resolve", color: "primary", onClick:
|
|
186
|
+
react_1.default.createElement(base_ui_1.Button, { label: "Decline Dispute", color: "error", onClick: () => handleResolutionChange('DECLINED'), disabled: isSubmitting, size: "md" }),
|
|
187
|
+
react_1.default.createElement(base_ui_1.Button, { label: "Approve & Resolve", color: "primary", onClick: handleApproveClick, isLoading: isSubmitting, disabled: !resolution || (resolution === 'PARTIAL' && !isValidPartialApproval()), size: "md" }))),
|
|
188
|
+
react_1.default.createElement(ProjectHoursAdjustmentModal_1.ProjectHoursAdjustmentModal, { isOpen: showHoursModal, onClose: () => setShowHoursModal(false), disputeProjects: (dispute === null || dispute === void 0 ? void 0 : dispute.disputeProjects) || [], dispute: dispute, onSubmit: handleHoursSubmit, isSubmitting: isSubmitting, getClientInvoiceSummaryByMonth: getClientInvoiceSummaryByMonth, invoiceSummary: invoiceSummary, approvedHours: resolution === 'PARTIAL' ? getTotalApprovedHours() : totalDisputeHours, projectApprovedHours: isPartialWithMultipleProjects ? projectApprovedHours : {}, user: user })));
|
|
96
189
|
};
|
|
97
190
|
exports.DecisionSection = DecisionSection;
|
|
@@ -112,6 +112,20 @@ const DiscussionSection = ({ disputeId, currentUser, messages, onCreateMessage,
|
|
|
112
112
|
const getHeaderAlignment = (userTypeId) => {
|
|
113
113
|
return userTypeId !== currentUser.userTypeId ? 'justify-start' : 'justify-end';
|
|
114
114
|
};
|
|
115
|
+
const displayMessageVisibility = (visibility) => {
|
|
116
|
+
switch (visibility) {
|
|
117
|
+
case 'ALL':
|
|
118
|
+
return 'All';
|
|
119
|
+
case 'EXPERT_INTERNAL_ONLY':
|
|
120
|
+
return 'Expert Only';
|
|
121
|
+
case 'CLIENT_INTERNAL_ONLY':
|
|
122
|
+
return 'Client Only';
|
|
123
|
+
case 'INTERNAL_ONLY':
|
|
124
|
+
return 'Internal Only';
|
|
125
|
+
default:
|
|
126
|
+
return 'All';
|
|
127
|
+
}
|
|
128
|
+
};
|
|
115
129
|
return (react_1.default.createElement("div", { className: "space-y-6" },
|
|
116
130
|
react_1.default.createElement("div", { className: "flex items-center justify-between" },
|
|
117
131
|
react_1.default.createElement("div", { className: "text-lg font-bold text-[#333333]" },
|
|
@@ -144,7 +158,11 @@ const DiscussionSection = ({ disputeId, currentUser, messages, onCreateMessage,
|
|
|
144
158
|
message.sender.firstName,
|
|
145
159
|
" ",
|
|
146
160
|
message.sender.lastName),
|
|
147
|
-
react_1.default.createElement("span", { className: "text-xs text-[#666666]" }, (0, dayjs_1.default)(message.createdAt).format('MMM D, YYYY • h:mm A'))
|
|
161
|
+
react_1.default.createElement("span", { className: "text-xs text-[#666666]" }, (0, dayjs_1.default)(message.createdAt).format('MMM D, YYYY • h:mm A')),
|
|
162
|
+
isInternal && (react_1.default.createElement("span", { className: "text-xs text-[#666666]" },
|
|
163
|
+
"(",
|
|
164
|
+
displayMessageVisibility(message.visibility),
|
|
165
|
+
")"))),
|
|
148
166
|
react_1.default.createElement("div", { style: { backgroundColor: getSenderBackgroundColor(message.sender.userTypeId) }, className: "mt-1 p-3 rounded-lg" },
|
|
149
167
|
react_1.default.createElement("p", { className: `text-sm ${getHeaderAlignment(message.sender.userTypeId)}`, style: { color: getSenderTextColor(message.sender.userTypeId) } }, message.messageText)))))))) : react_1.default.createElement(react_1.default.Fragment, null),
|
|
150
168
|
react_1.default.createElement("div", { ref: threadsEndRef }),
|
|
@@ -74,6 +74,8 @@ const DisputeProjectCard = ({ project, client, disputeDate, disputeUpdatedDate }
|
|
|
74
74
|
react_1.default.createElement("span", { className: "text-sm text-[#333333]" }, (0, dayjs_1.default)(disputeUpdatedDate).format('MMM D, YYYY'))),
|
|
75
75
|
react_1.default.createElement("div", null,
|
|
76
76
|
react_1.default.createElement("span", { className: "text-sm text-[#666666] font-bold" }, "Days Left: "),
|
|
77
|
-
react_1.default.createElement("span", { className: "text-sm text-[#F44336] font-bold" },
|
|
77
|
+
react_1.default.createElement("span", { className: "text-sm text-[#F44336] font-bold" },
|
|
78
|
+
Math.max(0, 14 - (0, dayjs_1.default)().date()),
|
|
79
|
+
" days left to resolve"))))));
|
|
78
80
|
};
|
|
79
81
|
exports.DisputeProjectCard = DisputeProjectCard;
|
|
@@ -6,10 +6,13 @@ interface InvoiceCardProps {
|
|
|
6
6
|
updateClientInvoiceDisputeMutation: any;
|
|
7
7
|
documentUploadUrl: string;
|
|
8
8
|
downloadDocumentUrl: string;
|
|
9
|
+
updateInvoiceMutation?: any;
|
|
10
|
+
getClientInvoiceSummaryByMonth?: any;
|
|
11
|
+
invoiceSummary?: any;
|
|
9
12
|
isInternal?: boolean;
|
|
10
13
|
isClient?: boolean;
|
|
11
14
|
bucketName?: string;
|
|
12
15
|
reactAppUrl?: string;
|
|
13
16
|
}
|
|
14
|
-
export declare const InvoiceCard: ({ clientInvoice, createDisputeChatMessage, user, chatMessages, updateClientInvoiceDisputeMutation, documentUploadUrl, downloadDocumentUrl, isInternal, isClient, bucketName, reactAppUrl }: InvoiceCardProps) => JSX.Element;
|
|
17
|
+
export declare const InvoiceCard: ({ clientInvoice, createDisputeChatMessage, user, chatMessages, updateClientInvoiceDisputeMutation, updateInvoiceMutation, getClientInvoiceSummaryByMonth, invoiceSummary, documentUploadUrl, downloadDocumentUrl, isInternal, isClient, bucketName, reactAppUrl }: InvoiceCardProps) => JSX.Element;
|
|
15
18
|
export {};
|
|
@@ -45,7 +45,7 @@ const useStyles = (0, core_1.makeStyles)((theme) => ({
|
|
|
45
45
|
},
|
|
46
46
|
},
|
|
47
47
|
}));
|
|
48
|
-
const InvoiceCard = ({ clientInvoice, createDisputeChatMessage, user, chatMessages, updateClientInvoiceDisputeMutation, documentUploadUrl, downloadDocumentUrl, isInternal = false, isClient = false, bucketName, reactAppUrl }) => {
|
|
48
|
+
const InvoiceCard = ({ clientInvoice, createDisputeChatMessage, user, chatMessages, updateClientInvoiceDisputeMutation, updateInvoiceMutation, getClientInvoiceSummaryByMonth, invoiceSummary, documentUploadUrl, downloadDocumentUrl, isInternal = false, isClient = false, bucketName, reactAppUrl }) => {
|
|
49
49
|
const [currentInvoice, setCurrentInvoice] = (0, react_1.useState)(clientInvoice);
|
|
50
50
|
const classes = useStyles();
|
|
51
51
|
(0, react_1.useEffect)(() => {
|
|
@@ -65,6 +65,6 @@ const InvoiceCard = ({ clientInvoice, createDisputeChatMessage, user, chatMessag
|
|
|
65
65
|
react_1.default.createElement(ClientDisputeProjectCard_1.ClientDisputeProjectCard, { clientInvoice: currentInvoice, updateClientInvoiceDisputeMutation: updateClientInvoiceDisputeMutation, documentUploadUrl: documentUploadUrl, downloadDocumentUrl: downloadDocumentUrl, bucketName: bucketName }),
|
|
66
66
|
(currentInvoice === null || currentInvoice === void 0 ? void 0 : currentInvoice.chatEnabled) && disputeId && (react_1.default.createElement(DiscussionSection_1.DiscussionSection, { disputeId: disputeId, currentUser: user, messages: chatMessages, onCreateMessage: createDisputeChatMessage, isInternal: isInternal })),
|
|
67
67
|
isInternal &&
|
|
68
|
-
react_1.default.createElement(DecisionSection_1.DecisionSection, { dispute: currentInvoice, onUpdateDispute: updateClientInvoiceDisputeMutation }))))));
|
|
68
|
+
react_1.default.createElement(DecisionSection_1.DecisionSection, { dispute: currentInvoice, onUpdateDispute: updateClientInvoiceDisputeMutation, updateInvoiceMutation: updateInvoiceMutation, updateClientInvoiceDisputeMutation: updateClientInvoiceDisputeMutation, invoiceSummary: invoiceSummary, user: user, getClientInvoiceSummaryByMonth: getClientInvoiceSummaryByMonth }))))));
|
|
69
69
|
};
|
|
70
70
|
exports.InvoiceCard = InvoiceCard;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
interface DisputeProject {
|
|
2
|
+
__typename: string;
|
|
3
|
+
id: number;
|
|
4
|
+
projectId: number;
|
|
5
|
+
disputeAmount: number;
|
|
6
|
+
disputeHours: number;
|
|
7
|
+
disputeType: string;
|
|
8
|
+
disputeReasonCode: string;
|
|
9
|
+
clientExplanation: string;
|
|
10
|
+
clientDocumentLinks: string;
|
|
11
|
+
expertDocumentLinks: string;
|
|
12
|
+
resolutionMode: string | null;
|
|
13
|
+
resolutionType: string | null;
|
|
14
|
+
project: {
|
|
15
|
+
__typename: string;
|
|
16
|
+
name: string;
|
|
17
|
+
freelancerRate: number | null;
|
|
18
|
+
clientRate: number | null;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
interface ProjectHoursAdjustmentModalProps {
|
|
22
|
+
isOpen: boolean;
|
|
23
|
+
onClose: () => void;
|
|
24
|
+
disputeProjects: DisputeProject[];
|
|
25
|
+
dispute: any;
|
|
26
|
+
onSubmit: (adjustedHours: any) => void;
|
|
27
|
+
isSubmitting: boolean;
|
|
28
|
+
getClientInvoiceSummaryByMonth: any;
|
|
29
|
+
invoiceSummary: any;
|
|
30
|
+
approvedHours: number;
|
|
31
|
+
projectApprovedHours: {
|
|
32
|
+
[projectId: number]: number;
|
|
33
|
+
};
|
|
34
|
+
user: any;
|
|
35
|
+
}
|
|
36
|
+
export declare const ProjectHoursAdjustmentModal: ({ isOpen, onClose, disputeProjects, dispute, onSubmit, isSubmitting, getClientInvoiceSummaryByMonth, invoiceSummary, approvedHours, projectApprovedHours, user, }: ProjectHoursAdjustmentModalProps) => JSX.Element;
|
|
37
|
+
export {};
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
26
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
27
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
28
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
29
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
30
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
31
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
35
|
+
exports.ProjectHoursAdjustmentModal = void 0;
|
|
36
|
+
const react_1 = __importStar(require("react"));
|
|
37
|
+
const base_ui_1 = require("@paro.io/base-ui");
|
|
38
|
+
const ProjectHoursAdjustmentModal = ({ isOpen, onClose, disputeProjects, dispute, onSubmit, isSubmitting, getClientInvoiceSummaryByMonth, invoiceSummary, approvedHours, projectApprovedHours, user, }) => {
|
|
39
|
+
var _a, _b;
|
|
40
|
+
const [adjustedHours, setAdjustedHours] = (0, react_1.useState)({});
|
|
41
|
+
const [errors, setErrors] = (0, react_1.useState)({});
|
|
42
|
+
const [projectErrors, setProjectErrors] = (0, react_1.useState)({});
|
|
43
|
+
const [projectSections, setProjectSections] = (0, react_1.useState)([]);
|
|
44
|
+
const [loading, setLoading] = (0, react_1.useState)(false);
|
|
45
|
+
const [fetchError, setFetchError] = (0, react_1.useState)(null);
|
|
46
|
+
const clientId = (_a = dispute === null || dispute === void 0 ? void 0 : dispute.invoice) === null || _a === void 0 ? void 0 : _a.clientId;
|
|
47
|
+
const month = (_b = dispute === null || dispute === void 0 ? void 0 : dispute.invoice) === null || _b === void 0 ? void 0 : _b.month;
|
|
48
|
+
// Fetch project hours when modal opens
|
|
49
|
+
(0, react_1.useEffect)(() => {
|
|
50
|
+
if (isOpen && disputeProjects.length > 0) {
|
|
51
|
+
fetchInvoiceSummary();
|
|
52
|
+
fetchProjectHours();
|
|
53
|
+
}
|
|
54
|
+
}, [isOpen, disputeProjects]);
|
|
55
|
+
(0, react_1.useEffect)(() => {
|
|
56
|
+
if (invoiceSummary) {
|
|
57
|
+
fetchProjectHours();
|
|
58
|
+
}
|
|
59
|
+
}, [invoiceSummary]);
|
|
60
|
+
const fetchInvoiceSummary = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
61
|
+
setLoading(true);
|
|
62
|
+
yield Promise.all([
|
|
63
|
+
getClientInvoiceSummaryByMonth({ variables: { clientId: +clientId, month }, fetchPolicy: 'network-only' }),
|
|
64
|
+
]);
|
|
65
|
+
});
|
|
66
|
+
const fetchProjectHours = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
67
|
+
var _a;
|
|
68
|
+
setFetchError(null);
|
|
69
|
+
setLoading(true);
|
|
70
|
+
try {
|
|
71
|
+
if ((_a = invoiceSummary === null || invoiceSummary === void 0 ? void 0 : invoiceSummary.getClientInvoiceSummaryByMonth) === null || _a === void 0 ? void 0 : _a.projects) {
|
|
72
|
+
// Group tasks by project based on disputeProjects
|
|
73
|
+
const projectSectionsData = [];
|
|
74
|
+
disputeProjects.forEach(disputeProject => {
|
|
75
|
+
// Find matching project in invoice summary
|
|
76
|
+
const invoiceProject = invoiceSummary.getClientInvoiceSummaryByMonth.projects.find((proj) => {
|
|
77
|
+
// Match by project name since we don't have direct projectId mapping
|
|
78
|
+
return proj.projectName === disputeProject.project.name;
|
|
79
|
+
});
|
|
80
|
+
if (invoiceProject) {
|
|
81
|
+
projectSectionsData.push({
|
|
82
|
+
projectId: disputeProject.projectId,
|
|
83
|
+
projectName: disputeProject.project.name,
|
|
84
|
+
disputeHours: disputeProject.disputeHours,
|
|
85
|
+
tasks: invoiceProject.tasks.map((task) => (Object.assign(Object.assign({}, task), { projectName: disputeProject.project.name })))
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
setProjectSections(projectSectionsData);
|
|
90
|
+
// Initialize adjusted hours for all tasks
|
|
91
|
+
const initialAdjustedHours = {};
|
|
92
|
+
projectSectionsData.forEach(section => {
|
|
93
|
+
section.tasks.forEach(task => {
|
|
94
|
+
initialAdjustedHours[task.projectHourId] = {
|
|
95
|
+
originalHours: task.hours,
|
|
96
|
+
newHours: task.hours
|
|
97
|
+
};
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
setAdjustedHours(initialAdjustedHours);
|
|
101
|
+
setErrors({});
|
|
102
|
+
setProjectErrors({});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
console.error('Failed to fetch project hours:', error);
|
|
107
|
+
setFetchError('Failed to load project hours. Please try again.');
|
|
108
|
+
}
|
|
109
|
+
finally {
|
|
110
|
+
setLoading(false);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
const handleHourChange = (projectHourId, newHours) => {
|
|
114
|
+
var _a;
|
|
115
|
+
const numericValue = parseFloat(newHours) || 0;
|
|
116
|
+
const originalHours = ((_a = adjustedHours[projectHourId]) === null || _a === void 0 ? void 0 : _a.originalHours) || 0;
|
|
117
|
+
setAdjustedHours(prev => (Object.assign(Object.assign({}, prev), { [projectHourId]: Object.assign(Object.assign({}, prev[projectHourId]), { newHours: numericValue }) })));
|
|
118
|
+
// Clear individual hour error
|
|
119
|
+
setErrors(prev => (Object.assign(Object.assign({}, prev), { [projectHourId]: '' })));
|
|
120
|
+
// Validate individual hour
|
|
121
|
+
if (numericValue > originalHours) {
|
|
122
|
+
setErrors(prev => (Object.assign(Object.assign({}, prev), { [projectHourId]: 'New hours cannot exceed original hours' })));
|
|
123
|
+
}
|
|
124
|
+
else if (numericValue < 0) {
|
|
125
|
+
setErrors(prev => (Object.assign(Object.assign({}, prev), { [projectHourId]: 'Hours cannot be negative' })));
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
const getProjectTotalHours = (projectId, type) => {
|
|
129
|
+
const section = projectSections.find(s => s.projectId === projectId);
|
|
130
|
+
if (!section)
|
|
131
|
+
return 0;
|
|
132
|
+
return section.tasks.reduce((total, task) => {
|
|
133
|
+
const adjustment = adjustedHours[task.projectHourId];
|
|
134
|
+
if (!adjustment)
|
|
135
|
+
return total;
|
|
136
|
+
return total + (type === 'original' ? adjustment.originalHours : adjustment.newHours);
|
|
137
|
+
}, 0);
|
|
138
|
+
};
|
|
139
|
+
const getProjectReduction = (projectId) => {
|
|
140
|
+
return getProjectTotalHours(projectId, 'original') - getProjectTotalHours(projectId, 'new');
|
|
141
|
+
};
|
|
142
|
+
const validateAndSubmit = () => {
|
|
143
|
+
var _a, _b, _c, _d;
|
|
144
|
+
let hasErrors = false;
|
|
145
|
+
const newErrors = {};
|
|
146
|
+
const newProjectErrors = {};
|
|
147
|
+
// Validate each project section
|
|
148
|
+
projectSections.forEach(section => {
|
|
149
|
+
const projectId = section.projectId;
|
|
150
|
+
const disputeHours = section.disputeHours;
|
|
151
|
+
// Use project-specific approved hours if available, otherwise use dispute hours
|
|
152
|
+
const maxAllowedReduction = projectApprovedHours[projectId] !== undefined
|
|
153
|
+
? projectApprovedHours[projectId]
|
|
154
|
+
: disputeHours;
|
|
155
|
+
// Validate individual hours within this project
|
|
156
|
+
section.tasks.forEach(task => {
|
|
157
|
+
var _a, _b;
|
|
158
|
+
const projectHourId = task.projectHourId;
|
|
159
|
+
const originalHours = ((_a = adjustedHours[projectHourId]) === null || _a === void 0 ? void 0 : _a.originalHours) || 0;
|
|
160
|
+
const newHours = ((_b = adjustedHours[projectHourId]) === null || _b === void 0 ? void 0 : _b.newHours) || 0;
|
|
161
|
+
if (newHours > originalHours) {
|
|
162
|
+
newErrors[projectHourId] = 'New hours cannot exceed original hours';
|
|
163
|
+
hasErrors = true;
|
|
164
|
+
}
|
|
165
|
+
else if (newHours < 0) {
|
|
166
|
+
newErrors[projectHourId] = 'Hours cannot be negative';
|
|
167
|
+
hasErrors = true;
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
// Validate project-level hour reduction
|
|
171
|
+
const projectReduction = getProjectReduction(projectId);
|
|
172
|
+
if (Math.abs(projectReduction - maxAllowedReduction) > 0.01) {
|
|
173
|
+
const reductionType = projectApprovedHours[projectId] !== undefined ? 'approved' : 'disputed';
|
|
174
|
+
newProjectErrors[projectId] = `Hour reduction for this project (${projectReduction.toFixed(2)}) must equal ${reductionType} hours (${maxAllowedReduction})`;
|
|
175
|
+
hasErrors = true;
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
setErrors(newErrors);
|
|
179
|
+
setProjectErrors(newProjectErrors);
|
|
180
|
+
if (!hasErrors) {
|
|
181
|
+
// Generate the submission data in the required format
|
|
182
|
+
let freelancerRate = 0;
|
|
183
|
+
const inputArray = [];
|
|
184
|
+
projectSections.forEach(section => {
|
|
185
|
+
section.tasks.forEach(task => {
|
|
186
|
+
const projectHourId = task.projectHourId;
|
|
187
|
+
const adjustedHour = adjustedHours[projectHourId];
|
|
188
|
+
freelancerRate = task.freelancerRate;
|
|
189
|
+
// Generate expert name
|
|
190
|
+
const expertName = (dispute === null || dispute === void 0 ? void 0 : dispute.freelancer) ?
|
|
191
|
+
`${dispute.freelancer.firstName} ${dispute.freelancer.lastName}` : '';
|
|
192
|
+
// Generate internal user name
|
|
193
|
+
const internalUser = user ?
|
|
194
|
+
`${user.firstName} ${user.lastName}` : '';
|
|
195
|
+
inputArray.push({
|
|
196
|
+
clientRate: task.rate,
|
|
197
|
+
freelancerRate: task.freelancerRate,
|
|
198
|
+
hours: (adjustedHour === null || adjustedHour === void 0 ? void 0 : adjustedHour.newHours) || 0,
|
|
199
|
+
projectId: section.projectId,
|
|
200
|
+
projectHourId: task.projectHourId,
|
|
201
|
+
projectName: section.projectName,
|
|
202
|
+
projectDescription: task.description,
|
|
203
|
+
expertName: expertName,
|
|
204
|
+
internalUser: internalUser
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
const submissionData = {
|
|
209
|
+
input: inputArray,
|
|
210
|
+
clientId: ((_a = dispute === null || dispute === void 0 ? void 0 : dispute.invoice) === null || _a === void 0 ? void 0 : _a.clientId) || 0,
|
|
211
|
+
invoiceId: ((_b = dispute === null || dispute === void 0 ? void 0 : dispute.invoice) === null || _b === void 0 ? void 0 : _b.id) || 0,
|
|
212
|
+
dateGenerated: ((_c = dispute === null || dispute === void 0 ? void 0 : dispute.invoice) === null || _c === void 0 ? void 0 : _c.month) || '',
|
|
213
|
+
updateDescription: `Updating hours based on dispute ID#:${dispute === null || dispute === void 0 ? void 0 : dispute.disputeId}`,
|
|
214
|
+
freelancerIds: [((_d = dispute === null || dispute === void 0 ? void 0 : dispute.freelancer) === null || _d === void 0 ? void 0 : _d.id) || 0],
|
|
215
|
+
freelancerRate: freelancerRate
|
|
216
|
+
};
|
|
217
|
+
onSubmit(submissionData);
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
const formatDate = (dateString) => {
|
|
221
|
+
return new Date(dateString).toLocaleDateString();
|
|
222
|
+
};
|
|
223
|
+
const hasAnyErrors = () => {
|
|
224
|
+
return Object.values(errors).some(error => error !== '') ||
|
|
225
|
+
Object.values(projectErrors).some(error => error !== '');
|
|
226
|
+
};
|
|
227
|
+
const areAllProjectsValid = () => {
|
|
228
|
+
return projectSections.every(section => {
|
|
229
|
+
const projectReduction = getProjectReduction(section.projectId);
|
|
230
|
+
const maxAllowedReduction = projectApprovedHours[section.projectId] !== undefined
|
|
231
|
+
? projectApprovedHours[section.projectId]
|
|
232
|
+
: section.disputeHours;
|
|
233
|
+
return Math.abs(projectReduction - maxAllowedReduction) <= 0.01;
|
|
234
|
+
});
|
|
235
|
+
};
|
|
236
|
+
if (loading) {
|
|
237
|
+
return (react_1.default.createElement(base_ui_1.Modal, { open: isOpen, onClose: onClose, size: "lg", className: "z-[100]" },
|
|
238
|
+
react_1.default.createElement("div", { className: "flex justify-center items-center py-8" },
|
|
239
|
+
react_1.default.createElement("div", null, "Loading project hours..."))));
|
|
240
|
+
}
|
|
241
|
+
if (fetchError) {
|
|
242
|
+
return (react_1.default.createElement(base_ui_1.Modal, { open: isOpen, onClose: onClose, size: "lg", className: "z-[100]" },
|
|
243
|
+
react_1.default.createElement("div", { className: "py-8" },
|
|
244
|
+
react_1.default.createElement(base_ui_1.Alert, { color: "danger", icon: "danger", label: fetchError, className: "mb-4" }),
|
|
245
|
+
react_1.default.createElement("div", { className: "flex justify-end space-x-4" },
|
|
246
|
+
react_1.default.createElement(base_ui_1.Button, { label: "Retry", color: "primary", onClick: fetchProjectHours }),
|
|
247
|
+
react_1.default.createElement(base_ui_1.Button, { label: "Cancel", color: "default", onClick: onClose })))));
|
|
248
|
+
}
|
|
249
|
+
return (react_1.default.createElement(base_ui_1.Modal, { open: isOpen, onClose: onClose, size: "lg", className: "z-[100]" },
|
|
250
|
+
react_1.default.createElement("div", { className: "max-h-[80vh] overflow-y-auto" },
|
|
251
|
+
react_1.default.createElement("div", { className: "font-bold text-xl mb-6" }, "Please enter the new hours to settle the dispute for each of the project time log entries below"),
|
|
252
|
+
react_1.default.createElement("div", { className: "space-y-8" }, projectSections.map((section) => {
|
|
253
|
+
const projectReduction = getProjectReduction(section.projectId);
|
|
254
|
+
const maxAllowedReduction = projectApprovedHours[section.projectId] !== undefined
|
|
255
|
+
? projectApprovedHours[section.projectId]
|
|
256
|
+
: section.disputeHours;
|
|
257
|
+
const isProjectValid = Math.abs(projectReduction - maxAllowedReduction) <= 0.01;
|
|
258
|
+
const isPartialApproval = projectApprovedHours[section.projectId] !== undefined;
|
|
259
|
+
return (react_1.default.createElement("div", { key: section.projectId, className: "border border-gray-200 rounded-lg p-6" },
|
|
260
|
+
react_1.default.createElement("div", { className: "mb-4" },
|
|
261
|
+
react_1.default.createElement("div", { className: "font-bold text-lg mb-2" }, section.projectName),
|
|
262
|
+
react_1.default.createElement("div", { className: "bg-gray-50 p-4 rounded-lg" },
|
|
263
|
+
react_1.default.createElement("div", { className: "font-semibold text-md mb-2" }, isPartialApproval
|
|
264
|
+
? `Approved Hours for Project: ${maxAllowedReduction}`
|
|
265
|
+
: `Total Disputed Hours for Project: ${section.disputeHours}`),
|
|
266
|
+
isPartialApproval && (react_1.default.createElement("div", { className: "text-sm text-blue-600 mb-2" },
|
|
267
|
+
"Original Disputed Hours: ",
|
|
268
|
+
section.disputeHours)),
|
|
269
|
+
react_1.default.createElement("div", { className: "text-sm text-gray-600 grid grid-cols-2 gap-4" },
|
|
270
|
+
react_1.default.createElement("div", null,
|
|
271
|
+
react_1.default.createElement("div", null,
|
|
272
|
+
"Original Hours: ",
|
|
273
|
+
getProjectTotalHours(section.projectId, 'original').toFixed(2)),
|
|
274
|
+
react_1.default.createElement("div", null,
|
|
275
|
+
"Adjusted Hours: ",
|
|
276
|
+
getProjectTotalHours(section.projectId, 'new').toFixed(2))),
|
|
277
|
+
react_1.default.createElement("div", null,
|
|
278
|
+
react_1.default.createElement("div", null,
|
|
279
|
+
"Hour Reduction: ",
|
|
280
|
+
projectReduction.toFixed(2)),
|
|
281
|
+
react_1.default.createElement("div", null,
|
|
282
|
+
"Required Reduction: ",
|
|
283
|
+
maxAllowedReduction))),
|
|
284
|
+
projectErrors[section.projectId] && (react_1.default.createElement(base_ui_1.Alert, { color: "danger", icon: "danger", label: projectErrors[section.projectId], className: "mt-2" })),
|
|
285
|
+
isProjectValid && (react_1.default.createElement("div", { className: "text-green-600 text-sm mt-2 font-medium" }, "\u2713 Project hours are correctly adjusted")))),
|
|
286
|
+
react_1.default.createElement("div", { className: "overflow-x-auto" },
|
|
287
|
+
react_1.default.createElement("table", { className: "w-full border-collapse border border-gray-300" },
|
|
288
|
+
react_1.default.createElement("thead", null,
|
|
289
|
+
react_1.default.createElement("tr", { className: "bg-gray-100" },
|
|
290
|
+
react_1.default.createElement("th", { className: "border border-gray-300 px-4 py-2 text-left" }, "Date"),
|
|
291
|
+
react_1.default.createElement("th", { className: "border border-gray-300 px-4 py-2 text-left" }, "Project Hour Entry"),
|
|
292
|
+
react_1.default.createElement("th", { className: "border border-gray-300 px-4 py-2 text-left" }, "Original Hours"),
|
|
293
|
+
react_1.default.createElement("th", { className: "border border-gray-300 px-4 py-2 text-left" }, "Adjusted Hours"))),
|
|
294
|
+
react_1.default.createElement("tbody", null, section.tasks.map((task) => {
|
|
295
|
+
var _a;
|
|
296
|
+
const projectHourId = task.projectHourId;
|
|
297
|
+
const originalHours = task.hours;
|
|
298
|
+
const newHours = ((_a = adjustedHours[projectHourId]) === null || _a === void 0 ? void 0 : _a.newHours) || 0;
|
|
299
|
+
const hasError = errors[projectHourId];
|
|
300
|
+
return (react_1.default.createElement("tr", { key: projectHourId, className: hasError ? 'bg-red-50' : '' },
|
|
301
|
+
react_1.default.createElement("td", { className: "border border-gray-300 px-4 py-2" }, formatDate(task.date)),
|
|
302
|
+
react_1.default.createElement("td", { className: "border border-gray-300 px-4 py-2" }, task.description),
|
|
303
|
+
react_1.default.createElement("td", { className: "border border-gray-300 px-4 py-2" }, originalHours.toFixed(2)),
|
|
304
|
+
react_1.default.createElement("td", { className: "border border-gray-300 px-4 py-2" },
|
|
305
|
+
react_1.default.createElement("div", null,
|
|
306
|
+
react_1.default.createElement("input", { type: "number", step: "0.01", min: "0", max: originalHours, value: newHours, onChange: (e) => handleHourChange(projectHourId, e.target.value), className: `w-full px-2 py-1 border rounded focus:outline-none focus:ring-2 focus:ring-blue-500 ${hasError ? 'border-red-500' : 'border-gray-300'}` }),
|
|
307
|
+
hasError && (react_1.default.createElement("div", { className: "text-red-500 text-xs mt-1" }, hasError))))));
|
|
308
|
+
}))))));
|
|
309
|
+
})),
|
|
310
|
+
react_1.default.createElement("div", { className: "flex justify-end space-x-4 mt-6 pt-4 border-t" },
|
|
311
|
+
react_1.default.createElement(base_ui_1.Button, { label: "Cancel", color: "default", onClick: onClose, disabled: isSubmitting }),
|
|
312
|
+
react_1.default.createElement(base_ui_1.Button, { label: "Submit Adjustments", color: "primary", onClick: validateAndSubmit, isLoading: isSubmitting, disabled: hasAnyErrors() || projectSections.length === 0 || !areAllProjectsValid() })))));
|
|
313
|
+
};
|
|
314
|
+
exports.ProjectHoursAdjustmentModal = ProjectHoursAdjustmentModal;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const TestDecisionSection: () => JSX.Element;
|