@paro.io/expert-shared-components 1.12.16 → 1.12.18
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/lib/components/FileUploader/index.d.ts +2 -1
- package/lib/components/FileUploader/index.js +2 -2
- package/lib/components/Invoices/ClientDisputeProjectCard.js +1 -0
- package/lib/components/Invoices/DecisionSection.d.ts +5 -1
- package/lib/components/Invoices/DecisionSection.js +116 -21
- package/lib/components/Invoices/DisputeProjectCard.js +3 -1
- package/lib/components/Invoices/DisputeSection.js +1 -0
- 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/package.json +1 -1
|
@@ -4,9 +4,10 @@ interface UploadFileParams {
|
|
|
4
4
|
disputeId?: number;
|
|
5
5
|
projectId: number;
|
|
6
6
|
documentUploadUrl: string;
|
|
7
|
+
bucketName: string;
|
|
7
8
|
updateClientInvoiceDisputeMutation: any;
|
|
8
9
|
previousFiles: string | string[];
|
|
9
10
|
isExpert: boolean;
|
|
10
11
|
}
|
|
11
|
-
export declare const fileUploader: ({ file, projectId, documentUploadUrl, updateClientInvoiceDisputeMutation, disputeId, previousFiles, isExpert, }: UploadFileParams) => Promise<any>;
|
|
12
|
+
export declare const fileUploader: ({ file, projectId, documentUploadUrl, updateClientInvoiceDisputeMutation, bucketName, disputeId, previousFiles, isExpert, }: UploadFileParams) => Promise<any>;
|
|
12
13
|
export {};
|
|
@@ -15,7 +15,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
15
15
|
exports.fileUploader = void 0;
|
|
16
16
|
const UploadClient_1 = __importDefault(require("../shared/UploadClient"));
|
|
17
17
|
const utils_1 = require("../shared/utils");
|
|
18
|
-
const fileUploader = (_a) => __awaiter(void 0, [_a], void 0, function* ({ file, projectId, documentUploadUrl, updateClientInvoiceDisputeMutation, disputeId, previousFiles, isExpert, }) {
|
|
18
|
+
const fileUploader = (_a) => __awaiter(void 0, [_a], void 0, function* ({ file, projectId, documentUploadUrl, updateClientInvoiceDisputeMutation, bucketName, disputeId, previousFiles, isExpert, }) {
|
|
19
19
|
const documentLinks = previousFiles ? (typeof previousFiles === 'string' ? previousFiles.split(',') : [...previousFiles]) : [];
|
|
20
20
|
try {
|
|
21
21
|
(0, utils_1.showToast)('success', 'Starting Document Upload');
|
|
@@ -24,7 +24,7 @@ const fileUploader = (_a) => __awaiter(void 0, [_a], void 0, function* ({ file,
|
|
|
24
24
|
fileName: file.name,
|
|
25
25
|
projectId,
|
|
26
26
|
documentUploadUrl,
|
|
27
|
-
bucketName:
|
|
27
|
+
bucketName: bucketName,
|
|
28
28
|
});
|
|
29
29
|
yield uploadClient.triggerMultipartUpload().then((res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
30
30
|
const resParsed = JSON.parse(res);
|
|
@@ -200,6 +200,7 @@ const ClientDisputeProjectCard = ({ clientInvoice, updateClientInvoiceDisputeMut
|
|
|
200
200
|
disputeId: clientInvoice.disputeId,
|
|
201
201
|
projectId: Number((_b = (_a = clientInvoice === null || clientInvoice === void 0 ? void 0 : clientInvoice.disputeProjects) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.projectId),
|
|
202
202
|
documentUploadUrl: documentUploadUrl,
|
|
203
|
+
bucketName: bucketName,
|
|
203
204
|
updateClientInvoiceDisputeMutation: updateClientInvoiceDisputeMutation,
|
|
204
205
|
previousFiles: (_e = (_d = (_c = clientInvoice === null || clientInvoice === void 0 ? void 0 : clientInvoice.disputeProjects) === null || _c === void 0 ? void 0 : _c[0]) === null || _d === void 0 ? void 0 : _d.clientDocumentLinks) !== null && _e !== void 0 ? _e : [],
|
|
205
206
|
isExpert: false,
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
interface DecisionSectionProps {
|
|
2
2
|
dispute: any;
|
|
3
3
|
onUpdateDispute: (variables: any) => Promise<any>;
|
|
4
|
+
updateInvoiceMutation: any;
|
|
4
5
|
user: any;
|
|
6
|
+
getClientInvoiceSummaryByMonth: any;
|
|
7
|
+
invoiceSummary: any;
|
|
8
|
+
updateClientInvoiceDisputeMutation: any;
|
|
5
9
|
}
|
|
6
|
-
export declare const DecisionSection: ({ dispute, onUpdateDispute, user }: DecisionSectionProps) => JSX.Element;
|
|
10
|
+
export declare const DecisionSection: ({ dispute, onUpdateDispute, updateInvoiceMutation, updateClientInvoiceDisputeMutation, invoiceSummary, user, getClientInvoiceSummaryByMonth, }: DecisionSectionProps) => JSX.Element;
|
|
7
11
|
export {};
|
|
@@ -36,33 +36,106 @@ exports.DecisionSection = void 0;
|
|
|
36
36
|
const react_1 = __importStar(require("react"));
|
|
37
37
|
const base_ui_1 = require("@paro.io/base-ui");
|
|
38
38
|
const utils_1 = require("../shared/utils");
|
|
39
|
+
const ProjectHoursAdjustmentModal_1 = require("./ProjectHoursAdjustmentModal");
|
|
39
40
|
const RESOLUTION_OPTIONS = [
|
|
40
41
|
{ value: 'APPROVED', label: 'Approve Dispute' },
|
|
41
42
|
{ value: 'DECLINED', label: 'Decline Dispute' },
|
|
42
43
|
{ value: 'PARTIAL', label: 'Partial Approval' },
|
|
43
44
|
];
|
|
44
|
-
const DecisionSection = ({ dispute, onUpdateDispute, user }) => {
|
|
45
|
-
var _a;
|
|
46
|
-
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);
|
|
47
48
|
const [resolution, setResolution] = (0, react_1.useState)('');
|
|
48
|
-
const [
|
|
49
|
+
const [approvedHours, setApprovedHours] = (0, react_1.useState)(totalDisputeHours);
|
|
50
|
+
const [projectApprovedHours, setProjectApprovedHours] = (0, react_1.useState)({});
|
|
49
51
|
const [isSubmitting, setIsSubmitting] = (0, react_1.useState)(false);
|
|
50
|
-
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;
|
|
51
81
|
if (!resolution)
|
|
52
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;
|
|
53
98
|
setIsSubmitting(true);
|
|
54
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
|
+
}
|
|
55
133
|
yield onUpdateDispute({
|
|
56
134
|
variables: {
|
|
57
|
-
input:
|
|
58
|
-
disputeId: dispute.disputeId,
|
|
59
|
-
status: "Resolved", //To-Do - Fix with actual status
|
|
60
|
-
approvedAmount: amount ? parseFloat(amount) : undefined,
|
|
61
|
-
finalDecisionOwnerId: (user === null || user === void 0 ? void 0 : user.userId) || null
|
|
62
|
-
},
|
|
135
|
+
input: updateData,
|
|
63
136
|
},
|
|
64
137
|
});
|
|
65
|
-
(0, utils_1.showToast)('success', 'Thank you for resolving the dispute');
|
|
138
|
+
(0, utils_1.showToast)('success', 'Thank you for resolving the dispute. The invoice has been updated with the new hours.');
|
|
66
139
|
}
|
|
67
140
|
catch (error) {
|
|
68
141
|
console.error('Failed to update dispute:', error);
|
|
@@ -72,6 +145,16 @@ const DecisionSection = ({ dispute, onUpdateDispute, user }) => {
|
|
|
72
145
|
setIsSubmitting(false);
|
|
73
146
|
}
|
|
74
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
|
+
};
|
|
75
158
|
return (react_1.default.createElement("div", { className: "space-y-6" },
|
|
76
159
|
react_1.default.createElement("div", { className: "flex items-center justify-between" },
|
|
77
160
|
react_1.default.createElement("div", { className: "text-lg font-bold text-[#333333]" }, "Make Decision")),
|
|
@@ -80,16 +163,28 @@ const DecisionSection = ({ dispute, onUpdateDispute, user }) => {
|
|
|
80
163
|
react_1.default.createElement("div", null,
|
|
81
164
|
react_1.default.createElement("label", { className: "block text-sm text-[#666666] font-bold mb-2" }, "Resolution:"),
|
|
82
165
|
react_1.default.createElement("div", { className: "w-full" },
|
|
83
|
-
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" },
|
|
84
167
|
react_1.default.createElement("option", { value: "" }, "Select Resolution"),
|
|
85
|
-
RESOLUTION_OPTIONS.map((option) => (react_1.default.createElement("option", { key: option.value, value: option.value }, option.label)))))),
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
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"))),
|
|
91
185
|
react_1.default.createElement("div", { className: "flex justify-end space-x-4" },
|
|
92
|
-
react_1.default.createElement(base_ui_1.Button, { label: "Decline Dispute", color: "error", onClick: () =>
|
|
93
|
-
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 })));
|
|
94
189
|
};
|
|
95
190
|
exports.DecisionSection = DecisionSection;
|
|
@@ -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;
|
|
@@ -80,6 +80,7 @@ const DisputeSection = ({ dispute, documentUploadUrl, downloadDocumentUrl, bucke
|
|
|
80
80
|
documentName: selectedFile.name,
|
|
81
81
|
projectId: Number((_b = (_a = dispute === null || dispute === void 0 ? void 0 : dispute.disputeProjects) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.projectId),
|
|
82
82
|
documentUploadUrl: documentUploadUrl,
|
|
83
|
+
bucketName: bucketName,
|
|
83
84
|
updateClientInvoiceDisputeMutation: updateClientInvoiceDisputeMutation,
|
|
84
85
|
disputeId: dispute === null || dispute === void 0 ? void 0 : dispute.disputeId,
|
|
85
86
|
previousFiles: isExpert ? expertDocumentLinks : clientDocumentLinks,
|
|
@@ -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, user: user }))))));
|
|
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;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.TestDecisionSection = void 0;
|
|
16
|
+
const react_1 = __importDefault(require("react"));
|
|
17
|
+
const DecisionSection_1 = require("./DecisionSection");
|
|
18
|
+
// Test data from the provided JSON
|
|
19
|
+
const testDispute = {
|
|
20
|
+
"__typename": "ClientInvoiceDispute",
|
|
21
|
+
"disputeId": 35,
|
|
22
|
+
"approvedAmount": 450,
|
|
23
|
+
"chatEnabled": true,
|
|
24
|
+
"disputeDate": "2025-05-22",
|
|
25
|
+
"disputeUpdatedDate": "2025-05-23",
|
|
26
|
+
"freelancerId": 8081,
|
|
27
|
+
"freelancer": {
|
|
28
|
+
"__typename": "User",
|
|
29
|
+
"id": 8081,
|
|
30
|
+
"email": "andpande@gmail.com",
|
|
31
|
+
"firstName": "William",
|
|
32
|
+
"lastName": "Annon"
|
|
33
|
+
},
|
|
34
|
+
"status": "Resolved",
|
|
35
|
+
"finalDecisionOwnerId": 29005,
|
|
36
|
+
"invoice": {
|
|
37
|
+
"__typename": "ClientInvoice",
|
|
38
|
+
"amount": 450,
|
|
39
|
+
"balanceDue": 450,
|
|
40
|
+
"clientId": 4433,
|
|
41
|
+
"dateGenerated": "2025-04-14",
|
|
42
|
+
"id": 175325,
|
|
43
|
+
"month": "2025-04-01"
|
|
44
|
+
},
|
|
45
|
+
"disputeProjects": [
|
|
46
|
+
{
|
|
47
|
+
"__typename": "ClientInvoiceDisputeProject",
|
|
48
|
+
"id": 37,
|
|
49
|
+
"projectId": 19158,
|
|
50
|
+
"disputeAmount": 450,
|
|
51
|
+
"disputeHours": 30,
|
|
52
|
+
"disputeType": "Hourly",
|
|
53
|
+
"disputeReasonCode": "Incorrect Hours Logged",
|
|
54
|
+
"clientExplanation": "I have a dispute over this expert for the current invoice 175325",
|
|
55
|
+
"clientDocumentLinks": "https://expert-client-dispute-files.s3.amazonaws.com/project-19158%2Ftesting.pdf,https://expert-client-dispute-files.s3.amazonaws.com/project-19158%2F_Carmelita_Resume-Graduation__1_.docx",
|
|
56
|
+
"expertDocumentLinks": "https://expert-client-dispute-files.s3.amazonaws.com/project-19158%2F0eb4b31b-b6d8-4cd1-b6ac-39b807ea4577.pdf",
|
|
57
|
+
"resolutionMode": null,
|
|
58
|
+
"resolutionType": null,
|
|
59
|
+
"project": {
|
|
60
|
+
"__typename": "Project",
|
|
61
|
+
"name": "Ongoing Ad-Hoc Support",
|
|
62
|
+
"freelancerRate": null,
|
|
63
|
+
"clientRate": null,
|
|
64
|
+
"projectHours": [
|
|
65
|
+
{
|
|
66
|
+
"__typename": "ProjectHour",
|
|
67
|
+
"id": 136605,
|
|
68
|
+
"date": "2022-02-11",
|
|
69
|
+
"description": "Ate Tacos",
|
|
70
|
+
"hours": 8
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"__typename": "ProjectHour",
|
|
74
|
+
"id": 136606,
|
|
75
|
+
"date": "2022-02-12",
|
|
76
|
+
"description": "Worked on project analysis",
|
|
77
|
+
"hours": 6
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"__typename": "ProjectHour",
|
|
81
|
+
"id": 136607,
|
|
82
|
+
"date": "2022-02-13",
|
|
83
|
+
"description": "Client meeting and documentation",
|
|
84
|
+
"hours": 4
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"__typename": "ProjectHour",
|
|
88
|
+
"id": 136608,
|
|
89
|
+
"date": "2022-02-14",
|
|
90
|
+
"description": "Code review and testing",
|
|
91
|
+
"hours": 7
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"__typename": "ProjectHour",
|
|
95
|
+
"id": 136609,
|
|
96
|
+
"date": "2022-02-15",
|
|
97
|
+
"description": "Final deliverable preparation",
|
|
98
|
+
"hours": 5
|
|
99
|
+
}
|
|
100
|
+
]
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
],
|
|
104
|
+
"lastMessageAt": "2025-05-23T10:56:53.000Z",
|
|
105
|
+
"messageCount": 5,
|
|
106
|
+
"client": {
|
|
107
|
+
"__typename": "Client",
|
|
108
|
+
"name": "Vessel Advisors"
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
const testUser = {
|
|
112
|
+
userId: 29005,
|
|
113
|
+
firstName: "Test",
|
|
114
|
+
lastName: "User"
|
|
115
|
+
};
|
|
116
|
+
const TestDecisionSection = () => {
|
|
117
|
+
const mockOnUpdateDispute = (variables) => __awaiter(void 0, void 0, void 0, function* () {
|
|
118
|
+
console.log('Mock update dispute called with:', variables);
|
|
119
|
+
return Promise.resolve({ success: true });
|
|
120
|
+
});
|
|
121
|
+
const mockUpdateInvoiceMutation = {};
|
|
122
|
+
return (react_1.default.createElement("div", { className: "p-8 max-w-4xl mx-auto" },
|
|
123
|
+
react_1.default.createElement("h1", { className: "text-2xl font-bold mb-6" }, "Test Decision Section with Hours Modal"),
|
|
124
|
+
react_1.default.createElement(DecisionSection_1.DecisionSection, { dispute: testDispute, onUpdateDispute: mockOnUpdateDispute, updateInvoiceMutation: mockUpdateInvoiceMutation, user: testUser })));
|
|
125
|
+
};
|
|
126
|
+
exports.TestDecisionSection = TestDecisionSection;
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.DiscussionSection = exports.InvoiceCard = void 0;
|
|
3
|
+
exports.ProjectHoursAdjustmentModal = exports.DecisionSection = exports.DiscussionSection = exports.InvoiceCard = void 0;
|
|
4
4
|
var InvoiceCard_1 = require("./InvoiceCard");
|
|
5
5
|
Object.defineProperty(exports, "InvoiceCard", { enumerable: true, get: function () { return InvoiceCard_1.InvoiceCard; } });
|
|
6
6
|
var DiscussionSection_1 = require("./DiscussionSection");
|
|
7
7
|
Object.defineProperty(exports, "DiscussionSection", { enumerable: true, get: function () { return DiscussionSection_1.DiscussionSection; } });
|
|
8
|
+
var DecisionSection_1 = require("./DecisionSection");
|
|
9
|
+
Object.defineProperty(exports, "DecisionSection", { enumerable: true, get: function () { return DecisionSection_1.DecisionSection; } });
|
|
10
|
+
var ProjectHoursAdjustmentModal_1 = require("./ProjectHoursAdjustmentModal");
|
|
11
|
+
Object.defineProperty(exports, "ProjectHoursAdjustmentModal", { enumerable: true, get: function () { return ProjectHoursAdjustmentModal_1.ProjectHoursAdjustmentModal; } });
|