@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.
Files changed (75) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +2 -2
  3. package/lib/components/ClientReferencesSection/DeleteButton.js +11 -11
  4. package/lib/components/ClientReferencesSection/ParoError.js +10 -10
  5. package/lib/components/ClientReferencesSection/TagsSection.js +2 -2
  6. package/lib/components/ClientReferencesSection/styles/BrandedTypography.js +2 -2
  7. package/lib/components/ClientReferencesSection/styles/Buttons.js +15 -15
  8. package/lib/components/ClientReferencesSection/styles/Name.js +5 -5
  9. package/lib/components/ClientReferencesSection/styles/NullContentConditionalColor.js +4 -4
  10. package/lib/components/ClientReferencesSection/styles/SectionBody.js +11 -11
  11. package/lib/components/ClientReferencesSection/styles/SectionTitle.js +6 -6
  12. package/lib/components/ClientReferencesSection/styles/Tags.js +2 -2
  13. package/lib/components/DiscussionThread/chat.d.ts +22 -22
  14. package/lib/components/DiscussionThread/chat.js +106 -106
  15. package/lib/components/DocumentCenter/DocumentTable.d.ts +15 -15
  16. package/lib/components/DocumentCenter/DocumentTable.js +350 -350
  17. package/lib/components/DocumentCenter/UploadFilesButton.d.ts +6 -6
  18. package/lib/components/DocumentCenter/UploadFilesButton.js +29 -29
  19. package/lib/components/EarningsTracker/ActiveProjectCard.d.ts +52 -52
  20. package/lib/components/EarningsTracker/ActiveProjectCard.js +161 -161
  21. package/lib/components/EarningsTracker/CenterCardUI.d.ts +13 -13
  22. package/lib/components/EarningsTracker/CenterCardUI.js +134 -134
  23. package/lib/components/EarningsTracker/EarningsTracker.d.ts +52 -52
  24. package/lib/components/EarningsTracker/EarningsTracker.js +508 -508
  25. package/lib/components/EarningsTracker/EditDateModal.d.ts +22 -22
  26. package/lib/components/EarningsTracker/EditDateModal.js +149 -149
  27. package/lib/components/EarningsTracker/EmailModal.d.ts +14 -14
  28. package/lib/components/EarningsTracker/EmailModal.js +79 -79
  29. package/lib/components/EarningsTracker/EndProjectModal.d.ts +56 -56
  30. package/lib/components/EarningsTracker/EndProjectModal.js +221 -221
  31. package/lib/components/EarningsTracker/LeftCardUI.d.ts +18 -18
  32. package/lib/components/EarningsTracker/LeftCardUI.js +189 -189
  33. package/lib/components/EarningsTracker/LogTimeModalAuthenticated.d.ts +52 -52
  34. package/lib/components/EarningsTracker/LogTimeModalAuthenticated.js +358 -358
  35. package/lib/components/EarningsTracker/ProgressBar.d.ts +4 -4
  36. package/lib/components/EarningsTracker/ProgressBar.js +66 -66
  37. package/lib/components/EarningsTracker/ReviewRequestModal.d.ts +17 -17
  38. package/lib/components/EarningsTracker/ReviewRequestModal.js +135 -135
  39. package/lib/components/EarningsTracker/RightCardUI.d.ts +46 -46
  40. package/lib/components/EarningsTracker/RightCardUI.js +231 -231
  41. package/lib/components/EarningsTracker/index.d.ts +1 -1
  42. package/lib/components/EarningsTracker/index.js +5 -5
  43. package/lib/components/ExpertProfileHeader/ActionButtonSection.js +6 -6
  44. package/lib/components/ExpertProfileHeader/ProfileSection.js +7 -7
  45. package/lib/components/Invoices/DecisionSection.d.ts +6 -1
  46. package/lib/components/Invoices/DecisionSection.js +118 -25
  47. package/lib/components/Invoices/DiscussionSection.d.ts +1 -0
  48. package/lib/components/Invoices/DiscussionSection.js +19 -1
  49. package/lib/components/Invoices/DisputeProjectCard.js +3 -1
  50. package/lib/components/Invoices/InvoiceCard.d.ts +4 -1
  51. package/lib/components/Invoices/InvoiceCard.js +2 -2
  52. package/lib/components/Invoices/ProjectHoursAdjustmentModal.d.ts +37 -0
  53. package/lib/components/Invoices/ProjectHoursAdjustmentModal.js +314 -0
  54. package/lib/components/Invoices/TestDecisionSection.d.ts +1 -0
  55. package/lib/components/Invoices/TestDecisionSection.js +126 -0
  56. package/lib/components/Invoices/index.d.ts +2 -0
  57. package/lib/components/Invoices/index.js +5 -1
  58. package/lib/components/OrganizationChart/OrganizationChart.d.ts +15 -15
  59. package/lib/components/OrganizationChart/OrganizationChart.js +312 -312
  60. package/lib/components/OrganizationChart/PersonCard.js +5 -5
  61. package/lib/components/OrganizationChart/utils.js +79 -79
  62. package/lib/components/ProjectCard/ProgressBar.js +4 -4
  63. package/lib/components/ProjectCard/ReviewRequestModal.js +5 -5
  64. package/lib/components/Reviews/Pagination.js +6 -6
  65. package/lib/components/ReviewsTab/RatingHeader.js +6 -6
  66. package/lib/components/ReviewsTab/expert-shared-components.code-workspace +20 -20
  67. package/lib/components/ReviewsTab/reviewRequestModal.js +5 -5
  68. package/lib/components/shared/Image.js +13 -13
  69. package/lib/components/shared/ProfileTextField.d.ts +18 -18
  70. package/lib/components/shared/ProfileTextField.js +16 -16
  71. package/lib/components/shared/StyledActionButtons.d.ts +7 -7
  72. package/lib/components/shared/StyledActionButtons.js +15 -15
  73. package/lib/components/shared/ToastNotification.d.ts +10 -10
  74. package/lib/components/shared/ToastNotification.js +63 -63
  75. 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 react_hot_toast_1 = __importDefault(require("react-hot-toast"));
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 disputeAmount = (_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.disputeAmount), 0);
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 [amount, setAmount] = (0, react_1.useState)(disputeAmount);
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 handleSubmit = () => __awaiter(void 0, void 0, void 0, function* () {
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
- react_hot_toast_1.default.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.');
68
139
  }
69
140
  catch (error) {
70
141
  console.error('Failed to update dispute:', error);
71
- react_hot_toast_1.default.error('Failed to update dispute');
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) => setResolution(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" },
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
- resolution === 'PARTIAL' && (react_1.default.createElement("div", null,
89
- react_1.default.createElement("label", { className: "block text-sm text-[#666666] font-bold mb-2" }, "Approved Amount:"),
90
- react_1.default.createElement("div", { className: "relative" },
91
- react_1.default.createElement("span", { className: "absolute left-3 top-1/2 transform -translate-y-1/2 text-[#333333]" }, "$"),
92
- react_1.default.createElement("input", { type: "number", value: amount, onChange: (e) => setAmount(e.target.value), className: "w-full pl-8 pr-3 py-2 border border-[#CCCCCC] rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-[#248384]", placeholder: "Enter amount" }))))),
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: () => setResolution('DECLINED'), disabled: isSubmitting, size: "md" }),
95
- react_1.default.createElement(base_ui_1.Button, { label: "Approve & Resolve", color: "primary", onClick: handleSubmit, isLoading: isSubmitting, disabled: !resolution || (resolution === 'PARTIAL' && !amount), size: "md" })))));
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;
@@ -8,6 +8,7 @@ interface Message {
8
8
  };
9
9
  messageText: string;
10
10
  createdAt: string;
11
+ visibility: string;
11
12
  }
12
13
  interface DiscussionSectionProps {
13
14
  disputeId: string;
@@ -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" }, "5 days left to resolve"))))));
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;