@parhelia/core 0.1.10884 → 0.1.10978

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 (119) hide show
  1. package/dist/components/ui/LanguageSelector.js +10 -4
  2. package/dist/components/ui/LanguageSelector.js.map +1 -1
  3. package/dist/config/config.js +19 -28
  4. package/dist/config/config.js.map +1 -1
  5. package/dist/config/types.d.ts +0 -5
  6. package/dist/editor/ContentTree.js +1 -1
  7. package/dist/editor/ContentTree.js.map +1 -1
  8. package/dist/editor/FieldListField.js +70 -3
  9. package/dist/editor/FieldListField.js.map +1 -1
  10. package/dist/editor/MainLayout.js +77 -8
  11. package/dist/editor/MainLayout.js.map +1 -1
  12. package/dist/editor/ai/AgentDocumentList.d.ts +8 -0
  13. package/dist/editor/ai/AgentDocumentList.js +132 -0
  14. package/dist/editor/ai/AgentDocumentList.js.map +1 -0
  15. package/dist/editor/ai/AgentTerminal.js +29 -9
  16. package/dist/editor/ai/AgentTerminal.js.map +1 -1
  17. package/dist/editor/ai/ContextInfoBar.d.ts +3 -1
  18. package/dist/editor/ai/ContextInfoBar.js +3 -3
  19. package/dist/editor/ai/ContextInfoBar.js.map +1 -1
  20. package/dist/editor/client/EditorShell.js +34 -8
  21. package/dist/editor/client/EditorShell.js.map +1 -1
  22. package/dist/editor/client/editContext.d.ts +3 -0
  23. package/dist/editor/client/editContext.js.map +1 -1
  24. package/dist/editor/client/itemsRepository.d.ts +2 -2
  25. package/dist/editor/client/itemsRepository.js +19 -8
  26. package/dist/editor/client/itemsRepository.js.map +1 -1
  27. package/dist/editor/client/pageModelBuilder.js +19 -3
  28. package/dist/editor/client/pageModelBuilder.js.map +1 -1
  29. package/dist/editor/control-center/Setup.js +159 -15
  30. package/dist/editor/control-center/Setup.js.map +1 -1
  31. package/dist/editor/control-center/setup-steps/AiSetupStep/utils.d.ts +1 -0
  32. package/dist/editor/control-center/setup-steps/AiSetupStep/utils.js +19 -0
  33. package/dist/editor/control-center/setup-steps/AiSetupStep/utils.js.map +1 -1
  34. package/dist/editor/control-center/setup-steps/SetupOverview.d.ts +14 -0
  35. package/dist/editor/control-center/setup-steps/SetupOverview.js +38 -0
  36. package/dist/editor/control-center/setup-steps/SetupOverview.js.map +1 -0
  37. package/dist/editor/editor-warnings/ItemLocked.js +1 -1
  38. package/dist/editor/editor-warnings/ItemLocked.js.map +1 -1
  39. package/dist/editor/menubar/toolbar-sections/ReviewCommands.js +13 -13
  40. package/dist/editor/menubar/toolbar-sections/ReviewCommands.js.map +1 -1
  41. package/dist/editor/page-viewer/PageViewerFrame.js +0 -11
  42. package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
  43. package/dist/editor/page-viewer/pageModelSkeletonBuilder.js +11 -1
  44. package/dist/editor/page-viewer/pageModelSkeletonBuilder.js.map +1 -1
  45. package/dist/editor/page-viewer/pageViewContext.js +0 -1
  46. package/dist/editor/page-viewer/pageViewContext.js.map +1 -1
  47. package/dist/editor/reviews/CreateReviewDialog.js +308 -30
  48. package/dist/editor/reviews/CreateReviewDialog.js.map +1 -1
  49. package/dist/editor/reviews/DecisionsMatrix.d.ts +5 -1
  50. package/dist/editor/reviews/DecisionsMatrix.js +668 -44
  51. package/dist/editor/reviews/DecisionsMatrix.js.map +1 -1
  52. package/dist/editor/reviews/EditReviewSettingsDialog.d.ts +20 -0
  53. package/dist/editor/reviews/EditReviewSettingsDialog.js +208 -0
  54. package/dist/editor/reviews/EditReviewSettingsDialog.js.map +1 -0
  55. package/dist/editor/reviews/ItemTreeSelector.js +5 -3
  56. package/dist/editor/reviews/ItemTreeSelector.js.map +1 -1
  57. package/dist/editor/reviews/PagesPanel.d.ts +3 -0
  58. package/dist/editor/reviews/PagesPanel.js +92 -18
  59. package/dist/editor/reviews/PagesPanel.js.map +1 -1
  60. package/dist/editor/reviews/PreconfiguredReviewerSelector.d.ts +9 -0
  61. package/dist/editor/reviews/PreconfiguredReviewerSelector.js +55 -0
  62. package/dist/editor/reviews/PreconfiguredReviewerSelector.js.map +1 -0
  63. package/dist/editor/reviews/ReviewDetail.js +108 -28
  64. package/dist/editor/reviews/ReviewDetail.js.map +1 -1
  65. package/dist/editor/reviews/ReviewersPanel.d.ts +3 -1
  66. package/dist/editor/reviews/ReviewersPanel.js +142 -22
  67. package/dist/editor/reviews/ReviewersPanel.js.map +1 -1
  68. package/dist/editor/reviews/ReviewsList.js +65 -7
  69. package/dist/editor/reviews/ReviewsList.js.map +1 -1
  70. package/dist/editor/reviews/useMultiReview.d.ts +5 -0
  71. package/dist/editor/reviews/useMultiReview.js +75 -7
  72. package/dist/editor/reviews/useMultiReview.js.map +1 -1
  73. package/dist/editor/services/agentService.d.ts +38 -0
  74. package/dist/editor/services/agentService.js +84 -0
  75. package/dist/editor/services/agentService.js.map +1 -1
  76. package/dist/editor/services/aiService.d.ts +2 -0
  77. package/dist/editor/services/aiService.js.map +1 -1
  78. package/dist/editor/services/contentService.d.ts +2 -2
  79. package/dist/editor/services/contentService.js +12 -4
  80. package/dist/editor/services/contentService.js.map +1 -1
  81. package/dist/editor/services/reviewsService.d.ts +8 -1
  82. package/dist/editor/services/reviewsService.js +32 -4
  83. package/dist/editor/services/reviewsService.js.map +1 -1
  84. package/dist/editor/services/suggestedEditsService.d.ts +1 -1
  85. package/dist/editor/services/suggestedEditsService.js +10 -2
  86. package/dist/editor/services/suggestedEditsService.js.map +1 -1
  87. package/dist/editor/sidebar/ComponentTree.js +43 -32
  88. package/dist/editor/sidebar/ComponentTree.js.map +1 -1
  89. package/dist/editor/sidebar/Sidebar.js +1 -1
  90. package/dist/editor/sidebar/Sidebar.js.map +1 -1
  91. package/dist/editor/sidebar/SidebarView.d.ts +4 -1
  92. package/dist/editor/sidebar/SidebarView.js +23 -10
  93. package/dist/editor/sidebar/SidebarView.js.map +1 -1
  94. package/dist/editor/ui/IconSelectorDialog.js +8 -4
  95. package/dist/editor/ui/IconSelectorDialog.js.map +1 -1
  96. package/dist/editor/ui/ItemNameDialogNew.js +14 -0
  97. package/dist/editor/ui/ItemNameDialogNew.js.map +1 -1
  98. package/dist/editor/utils.js +21 -4
  99. package/dist/editor/utils.js.map +1 -1
  100. package/dist/editor/views/SingleEditView.js +1 -1
  101. package/dist/editor/views/SingleEditView.js.map +1 -1
  102. package/dist/setup/services/setupWizardService.d.ts +7 -2
  103. package/dist/setup/services/setupWizardService.js +7 -14
  104. package/dist/setup/services/setupWizardService.js.map +1 -1
  105. package/dist/setup/utils/modelPricing.d.ts +7 -0
  106. package/dist/setup/utils/modelPricing.js +106 -0
  107. package/dist/setup/utils/modelPricing.js.map +1 -0
  108. package/dist/setup/wizard/steps/AgentsStep.js +6 -15
  109. package/dist/setup/wizard/steps/AgentsStep.js.map +1 -1
  110. package/dist/setup/wizard/steps/ModelsStep.js +182 -79
  111. package/dist/setup/wizard/steps/ModelsStep.js.map +1 -1
  112. package/dist/splash-screen/NewPage.js +1 -1
  113. package/dist/splash-screen/NewPage.js.map +1 -1
  114. package/dist/styles.css +33 -14
  115. package/dist/types.d.ts +17 -5
  116. package/package.json +2 -2
  117. package/dist/editor/control-center/parhelia-setup/Overview.d.ts +0 -1
  118. package/dist/editor/control-center/parhelia-setup/Overview.js +0 -91
  119. package/dist/editor/control-center/parhelia-setup/Overview.js.map +0 -1
@@ -1,60 +1,190 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { useState } from "react";
2
+ import { useState, useEffect, useMemo, useCallback } from "react";
3
3
  import { SimpleToolbar } from "../ui/SimpleToolbar";
4
4
  import { SimpleIconButton } from "../ui/SimpleIconButton";
5
5
  import { Button } from "../../components/ui/button";
6
- import { Check, X, Clock, Filter, Plus, Trash2 } from "lucide-react";
6
+ import { Checkbox } from "../../components/ui/checkbox";
7
+ import { Check, X, Clock, Filter, Plus, Trash2, MessageSquare, Lightbulb, } from "lucide-react";
7
8
  import { formatDate } from "../utils";
8
9
  import { Badge } from "../../components/ui/badge";
9
- import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, } from "../../components/ui/dialog";
10
10
  import { Popover, PopoverContent, PopoverTrigger, } from "../../components/ui/popover";
11
11
  import { Loader2 } from "lucide-react";
12
12
  import { ItemTreeSelector } from "./ItemTreeSelector";
13
13
  import { useEditContext } from "../client/editContext";
14
- export function DecisionsMatrix({ items, assignments, onMakeDecision, loading, reviewId, language, finalDecision, onSetFinalDecision, onAddPages, onRemovePages, allReviewersResponded, selectedReviewerId, currentUserEmail, }) {
14
+ import { getComments } from "../services/reviewsService";
15
+ import { getSuggestedEdits } from "../services/suggestedEditsService";
16
+ export function DecisionsMatrix({ items, assignments, onMakeDecision, loading, reviewId, language, finalDecision, onSetFinalDecision, onAddPages, onRemovePages, allReviewersResponded, selectedReviewerId, currentUserEmail, requiredApprovals = 1, }) {
15
17
  const [filter, setFilter] = useState("all");
16
- const [isFinalDecisionDialogOpen, setIsFinalDecisionDialogOpen] = useState(false);
17
18
  const [isAddPopoverOpen, setIsAddPopoverOpen] = useState(false);
18
19
  const [selectedItems, setSelectedItems] = useState([]);
20
+ const [selectedPagesForRemoval, setSelectedPagesForRemoval] = useState(new Set());
19
21
  const editContext = useEditContext();
22
+ const isLimitedPreviewUser = editContext?.user?.isLimitedPreviewUser ?? false;
23
+ const [commentsData, setCommentsData] = useState(new Map());
24
+ const [suggestionsData, setSuggestionsData] = useState(new Map());
25
+ const [loadingCounts, setLoadingCounts] = useState(false);
26
+ const [itemDetailsCache, setItemDetailsCache] = useState(new Map());
20
27
  // Filter assignments based on selection
21
- const displayAssignments = selectedReviewerId
22
- ? assignments.filter((a) => a.assignmentId === selectedReviewerId)
23
- : assignments;
24
- // Check if current user can make decisions
25
- const selectedAssignment = selectedReviewerId
26
- ? assignments.find((a) => a.assignmentId === selectedReviewerId)
27
- : null;
28
- const canMakeDecisions = selectedAssignment &&
29
- currentUserEmail &&
30
- selectedAssignment.reviewerEmail.toLowerCase() === currentUserEmail.toLowerCase();
28
+ // For limited preview users, only show their own assignment
29
+ const displayAssignments = (() => {
30
+ let filtered = assignments;
31
+ // If limited preview user, only show their own assignment
32
+ if (isLimitedPreviewUser && currentUserEmail) {
33
+ filtered = filtered.filter((a) => a.reviewerEmail.toLowerCase() === currentUserEmail.toLowerCase());
34
+ }
35
+ // Apply reviewer selection filter if specified
36
+ if (selectedReviewerId) {
37
+ filtered = filtered.filter((a) => a.assignmentId === selectedReviewerId);
38
+ }
39
+ return filtered;
40
+ })();
41
+ // Combine items from the original items list AND items that have decisions
42
+ const allItemsWithDecisions = useMemo(() => {
43
+ const itemsMap = new Map();
44
+ // Add all original items
45
+ items.forEach((item) => {
46
+ const key = `${item.itemId}-${item.language}`;
47
+ itemsMap.set(key, item);
48
+ });
49
+ // Add items from decisions that aren't already in the list
50
+ assignments.forEach((assignment) => {
51
+ assignment.decisions?.forEach((decision) => {
52
+ const key = `${decision.itemId}-${decision.itemLanguage}`;
53
+ if (!itemsMap.has(key)) {
54
+ // Check if we have cached details for this item
55
+ const cachedDetails = itemDetailsCache.get(key);
56
+ // Create a synthetic item entry for this decision
57
+ itemsMap.set(key, {
58
+ itemId: decision.itemId,
59
+ language: decision.itemLanguage,
60
+ includeSubitems: false,
61
+ name: cachedDetails?.name,
62
+ path: cachedDetails?.path,
63
+ });
64
+ }
65
+ });
66
+ });
67
+ return Array.from(itemsMap.values());
68
+ }, [items, assignments, itemDetailsCache]);
69
+ // Fetch item details for synthetic items that don't have names/paths
70
+ useEffect(() => {
71
+ const fetchItemDetails = async () => {
72
+ if (!editContext?.itemsRepository)
73
+ return;
74
+ const itemsToFetch = [];
75
+ // Find synthetic items that need details
76
+ assignments.forEach((assignment) => {
77
+ assignment.decisions?.forEach((decision) => {
78
+ const key = `${decision.itemId}-${decision.itemLanguage}`;
79
+ // Check if this item is not in the original items list
80
+ const isOriginalItem = items.some((item) => item.itemId === decision.itemId &&
81
+ item.language === decision.itemLanguage);
82
+ // Check if we already have cached details
83
+ const hasCachedDetails = itemDetailsCache.has(key);
84
+ // Only fetch if it's not an original item and we don't have cached details
85
+ if (!isOriginalItem && !hasCachedDetails) {
86
+ itemsToFetch.push({
87
+ key,
88
+ descriptor: {
89
+ id: decision.itemId,
90
+ language: decision.itemLanguage,
91
+ version: 0,
92
+ },
93
+ });
94
+ }
95
+ });
96
+ });
97
+ if (itemsToFetch.length === 0)
98
+ return;
99
+ try {
100
+ // Fetch items in batches
101
+ const fetchedItems = await Promise.all(itemsToFetch.map(({ descriptor }) => editContext.itemsRepository.getItem(descriptor)));
102
+ // Update cache with fetched details
103
+ const newCache = new Map(itemDetailsCache);
104
+ itemsToFetch.forEach(({ key }, index) => {
105
+ const fetchedItem = fetchedItems[index];
106
+ if (fetchedItem) {
107
+ newCache.set(key, {
108
+ name: fetchedItem.name,
109
+ path: fetchedItem.path,
110
+ });
111
+ }
112
+ });
113
+ if (newCache.size > itemDetailsCache.size) {
114
+ setItemDetailsCache(newCache);
115
+ }
116
+ }
117
+ catch (err) {
118
+ console.error("Failed to fetch item details:", err);
119
+ }
120
+ };
121
+ fetchItemDetails();
122
+ }, [assignments, items, itemDetailsCache, editContext?.itemsRepository]);
31
123
  const getDecision = (assignment, itemId, itemLanguage) => {
32
124
  return assignment.decisions?.find((d) => d.itemId === itemId && d.itemLanguage === itemLanguage);
33
125
  };
126
+ // Calculate approval count for an item
127
+ const getApprovalCount = (itemId, itemLanguage) => {
128
+ return assignments.filter((assignment) => {
129
+ const decision = getDecision(assignment, itemId, itemLanguage);
130
+ return decision?.decision === "Approved";
131
+ }).length;
132
+ };
133
+ // Get approval status for an item
134
+ const getItemApprovalStatus = (itemId, itemLanguage) => {
135
+ const approvalCount = getApprovalCount(itemId, itemLanguage);
136
+ const rejectionCount = assignments.filter((assignment) => {
137
+ const decision = getDecision(assignment, itemId, itemLanguage);
138
+ return decision?.decision === "Rejected";
139
+ }).length;
140
+ const totalReviewers = assignments.length;
141
+ const pendingCount = totalReviewers - approvalCount - rejectionCount;
142
+ if (approvalCount >= requiredApprovals) {
143
+ return "approved";
144
+ }
145
+ // If it's impossible to reach the threshold, mark as rejected
146
+ if (approvalCount + pendingCount < requiredApprovals) {
147
+ return "rejected";
148
+ }
149
+ return "pending";
150
+ };
34
151
  const handleLoadItem = (itemId, itemLanguage) => {
152
+ console.log("[DecisionsMatrix.handleLoadItem] Loading item from decisions matrix:", {
153
+ itemId,
154
+ itemLanguage,
155
+ isLimitedPreviewUser,
156
+ currentUserEmail,
157
+ timestamp: new Date().toISOString(),
158
+ });
35
159
  if (editContext?.loadItem) {
160
+ // Handle "*" language (All Languages mode) - use default language
161
+ const languageToLoad = itemLanguage === "*"
162
+ ? editContext?.currentItemDescriptor?.language || "en"
163
+ : itemLanguage;
36
164
  editContext.loadItem({
37
165
  id: itemId,
38
- language: itemLanguage,
166
+ language: languageToLoad,
39
167
  version: 0,
40
168
  });
41
169
  }
170
+ else {
171
+ console.error("[DecisionsMatrix.handleLoadItem] editContext.loadItem is not available");
172
+ }
42
173
  };
43
174
  const getDecisionCell = (assignment, item) => {
44
175
  const decision = getDecision(assignment, item.itemId, item.language);
45
176
  if (decision) {
46
177
  if (decision.decision === "Approved") {
47
- return (_jsxs("div", { className: "flex flex-col items-center justify-center p-2", children: [_jsx(Check, { className: "h-5 w-5 text-green-500" }), _jsx("span", { className: "text-xs text-gray-500", children: formatDate(new Date(decision.decisionDate)) })] }));
178
+ return (_jsx("div", { className: "flex items-center justify-center p-1", children: _jsxs("div", { className: "flex flex-col items-center gap-0.5", children: [_jsxs("div", { className: "flex items-center gap-1", children: [_jsx(Check, { className: "h-3.5 w-3.5 text-green-500" }), _jsx("span", { className: "text-xs leading-tight text-gray-500", children: formatDate(new Date(decision.decisionDate)) })] }), decision.itemVersion !== undefined && (_jsxs("span", { className: "text-xs leading-tight text-gray-400", children: ["v", decision.itemVersion] }))] }) }));
48
179
  }
49
180
  else {
50
- return (_jsxs("div", { className: "flex flex-col items-center justify-center p-2", children: [_jsx(X, { className: "h-5 w-5 text-red-500" }), _jsx("span", { className: "text-xs text-gray-500", children: formatDate(new Date(decision.decisionDate)) })] }));
181
+ return (_jsx("div", { className: "flex items-center justify-center p-1", children: _jsxs("div", { className: "flex items-center gap-1", children: [_jsx(X, { className: "h-3.5 w-3.5 text-red-500" }), _jsx("span", { className: "text-xs leading-tight text-gray-500", children: formatDate(new Date(decision.decisionDate)) })] }) }));
51
182
  }
52
183
  }
53
- // Show action buttons for pending decisions (if current user is the reviewer)
54
- // For now, show pending indicator
55
- return (_jsx("div", { className: "flex items-center justify-center p-2", children: _jsx(Clock, { className: "h-4 w-4 text-gray-400" }) }));
184
+ // Show pending indicator
185
+ return (_jsx("div", { className: "flex items-center justify-center p-1", children: _jsx(Clock, { className: "h-3.5 w-3.5 text-gray-400" }) }));
56
186
  };
57
- const filteredItems = items.filter((item) => {
187
+ const filteredItems = allItemsWithDecisions.filter((item) => {
58
188
  if (filter === "all")
59
189
  return true;
60
190
  if (filter === "pending") {
@@ -69,9 +199,57 @@ export function DecisionsMatrix({ items, assignments, onMakeDecision, loading, r
69
199
  return true;
70
200
  });
71
201
  const handleMakeDecision = async (assignmentId, itemId, itemLanguage, decision) => {
72
- // Find the item to get version if available
73
- const item = items.find((i) => i.itemId === itemId && i.language === itemLanguage);
74
- await onMakeDecision(assignmentId, itemId, itemLanguage, undefined, decision);
202
+ // Try to get the version of the item being decided on
203
+ let itemVersion = undefined;
204
+ // First, check if the current item matches
205
+ const currentItem = editContext?.currentItemDescriptor;
206
+ if (currentItem &&
207
+ currentItem.id === itemId &&
208
+ (currentItem.language === itemLanguage ||
209
+ itemLanguage === "*" ||
210
+ currentItem.language === "*")) {
211
+ itemVersion = currentItem.version;
212
+ }
213
+ else {
214
+ // Try to fetch the item to get its version
215
+ if (editContext?.itemsRepository) {
216
+ try {
217
+ const item = await editContext.itemsRepository.getItem({
218
+ id: itemId,
219
+ language: itemLanguage === "*" ? "en" : itemLanguage,
220
+ version: 0, // Get latest version
221
+ });
222
+ if (item) {
223
+ itemVersion = item.version;
224
+ }
225
+ }
226
+ catch (err) {
227
+ console.warn(`Failed to fetch item version for ${itemId}:`, err);
228
+ // Continue with undefined version (version-null decision)
229
+ }
230
+ }
231
+ }
232
+ await onMakeDecision(assignmentId, itemId, itemLanguage, itemVersion, decision);
233
+ };
234
+ const handleApproveCurrentPage = async () => {
235
+ const currentItem = editContext?.currentItemDescriptor;
236
+ if (!currentItem || !currentUserEmail)
237
+ return;
238
+ // Find current user's assignment
239
+ const userAssignment = assignments.find((a) => a.reviewerEmail.toLowerCase() === currentUserEmail.toLowerCase());
240
+ if (!userAssignment)
241
+ return;
242
+ await handleMakeDecision(userAssignment.assignmentId, currentItem.id, currentItem.language || "en", "Approved");
243
+ };
244
+ const handleRejectCurrentPage = async () => {
245
+ const currentItem = editContext?.currentItemDescriptor;
246
+ if (!currentItem || !currentUserEmail)
247
+ return;
248
+ // Find current user's assignment
249
+ const userAssignment = assignments.find((a) => a.reviewerEmail.toLowerCase() === currentUserEmail.toLowerCase());
250
+ if (!userAssignment)
251
+ return;
252
+ await handleMakeDecision(userAssignment.assignmentId, currentItem.id, currentItem.language || "en", "Rejected");
75
253
  };
76
254
  const handleItemsChange = (descriptors) => {
77
255
  setSelectedItems(descriptors);
@@ -88,28 +266,474 @@ export function DecisionsMatrix({ items, assignments, onMakeDecision, loading, r
88
266
  }
89
267
  };
90
268
  const handleRemovePage = async (itemId, itemLanguage) => {
91
- await onRemovePages([
92
- {
93
- id: itemId,
94
- language: itemLanguage,
95
- version: 1,
269
+ const item = allItemsWithDecisions.find((i) => i.itemId === itemId && i.language === itemLanguage);
270
+ const itemName = item?.name || item?.path || itemId;
271
+ editContext?.confirm({
272
+ header: "Remove Page",
273
+ message: `Are you sure you want to remove "${itemName}" from this review?`,
274
+ acceptLabel: "Remove",
275
+ showCancel: true,
276
+ accept: async () => {
277
+ await onRemovePages([
278
+ {
279
+ id: itemId,
280
+ language: itemLanguage,
281
+ version: 1,
282
+ },
283
+ ]);
96
284
  },
97
- ]);
285
+ });
286
+ };
287
+ const togglePageSelection = (itemId, language) => {
288
+ const key = `${itemId}-${language}`;
289
+ const newSelection = new Set(selectedPagesForRemoval);
290
+ if (newSelection.has(key)) {
291
+ newSelection.delete(key);
292
+ }
293
+ else {
294
+ newSelection.add(key);
295
+ }
296
+ setSelectedPagesForRemoval(newSelection);
297
+ };
298
+ const handleRemoveSelectedPages = async () => {
299
+ if (selectedPagesForRemoval.size === 0)
300
+ return;
301
+ const pagesToRemove = Array.from(selectedPagesForRemoval)
302
+ .map((key) => {
303
+ const [itemId, language] = key.split("-");
304
+ return { itemId, language };
305
+ })
306
+ .filter(({ itemId, language }) => itemId && language);
307
+ const itemDescriptors = pagesToRemove.map(({ itemId, language }) => ({
308
+ id: itemId,
309
+ language: language,
310
+ version: 1,
311
+ }));
312
+ const pageNames = pagesToRemove.map(({ itemId, language }) => {
313
+ const item = allItemsWithDecisions.find((i) => i.itemId === itemId && i.language === language);
314
+ return item?.name || item?.path || itemId;
315
+ });
316
+ const message = selectedPagesForRemoval.size === 1
317
+ ? `Are you sure you want to remove "${pageNames[0]}" from this review?`
318
+ : `Are you sure you want to remove ${selectedPagesForRemoval.size} pages from this review?`;
319
+ editContext?.confirm({
320
+ header: "Remove Pages",
321
+ message,
322
+ acceptLabel: "Remove",
323
+ showCancel: true,
324
+ accept: async () => {
325
+ await onRemovePages(itemDescriptors);
326
+ setSelectedPagesForRemoval(new Set());
327
+ },
328
+ });
329
+ };
330
+ const hasSelectedPages = selectedPagesForRemoval.size > 0;
331
+ // Helper function to refresh counts for a specific item
332
+ const refreshItemCounts = useCallback(async (itemId, itemLanguage) => {
333
+ const key = `${itemId}-${itemLanguage}`;
334
+ // Check if this item is in our review
335
+ const itemInReview = allItemsWithDecisions.some((item) => item.itemId === itemId &&
336
+ (item.language === itemLanguage ||
337
+ item.language === "*" ||
338
+ itemLanguage === "*"));
339
+ if (!itemInReview) {
340
+ return;
341
+ }
342
+ try {
343
+ // Fetch updated comments
344
+ const commentsResult = await getComments(itemId, itemLanguage === "*" ? "en" : itemLanguage, 0, reviewId);
345
+ if (commentsResult.data) {
346
+ setCommentsData((prev) => {
347
+ const newMap = new Map(prev);
348
+ newMap.set(key, commentsResult.data);
349
+ return newMap;
350
+ });
351
+ }
352
+ // Fetch updated suggestions
353
+ const suggestionsResult = await getSuggestedEdits(itemId, itemLanguage === "*" ? "en" : itemLanguage, 0, reviewId);
354
+ if (suggestionsResult.data) {
355
+ setSuggestionsData((prev) => {
356
+ const newMap = new Map(prev);
357
+ newMap.set(key, suggestionsResult.data);
358
+ return newMap;
359
+ });
360
+ }
361
+ }
362
+ catch (err) {
363
+ console.error(`Failed to refresh counts for ${key}:`, err);
364
+ }
365
+ }, [allItemsWithDecisions, reviewId]);
366
+ // Fetch comments and suggestions for all items
367
+ useEffect(() => {
368
+ const fetchCounts = async () => {
369
+ if (allItemsWithDecisions.length === 0) {
370
+ setCommentsData(new Map());
371
+ setSuggestionsData(new Map());
372
+ return;
373
+ }
374
+ setLoadingCounts(true);
375
+ const commentsMap = new Map();
376
+ const suggestionsMap = new Map();
377
+ try {
378
+ await Promise.all(allItemsWithDecisions.map(async (item) => {
379
+ const key = `${item.itemId}-${item.language}`;
380
+ // Fetch comments (using version 0 as default, or we could track version)
381
+ // Pass reviewId to filter server-side by review creation date
382
+ try {
383
+ const commentsResult = await getComments(item.itemId, item.language, 0, reviewId);
384
+ if (commentsResult.data) {
385
+ commentsMap.set(key, commentsResult.data);
386
+ }
387
+ }
388
+ catch (err) {
389
+ console.error(`Failed to fetch comments for ${key}:`, err);
390
+ }
391
+ // Fetch suggestions
392
+ // Pass reviewId to filter server-side by review creation date
393
+ try {
394
+ const suggestionsResult = await getSuggestedEdits(item.itemId, item.language, 0, reviewId);
395
+ if (suggestionsResult.data) {
396
+ suggestionsMap.set(key, suggestionsResult.data);
397
+ }
398
+ }
399
+ catch (err) {
400
+ console.error(`Failed to fetch suggestions for ${key}:`, err);
401
+ }
402
+ }));
403
+ setCommentsData(commentsMap);
404
+ setSuggestionsData(suggestionsMap);
405
+ }
406
+ catch (err) {
407
+ console.error("Failed to fetch counts:", err);
408
+ }
409
+ finally {
410
+ setLoadingCounts(false);
411
+ }
412
+ };
413
+ fetchCounts();
414
+ }, [allItemsWithDecisions, reviewId]);
415
+ // Listen for websocket messages to update counts in real-time
416
+ useEffect(() => {
417
+ if (!editContext?.addSocketMessageListener) {
418
+ return;
419
+ }
420
+ const unsubscribe = editContext.addSocketMessageListener((message) => {
421
+ try {
422
+ // Handle comment updates
423
+ if (message.type === "comment-updated" && message.payload?.comment) {
424
+ const comment = message.payload.comment;
425
+ const itemId = comment.mainItemId;
426
+ const itemLanguage = comment.language || "en";
427
+ // Refresh counts for this item
428
+ refreshItemCounts(itemId, itemLanguage);
429
+ }
430
+ // Handle comment deletions
431
+ if (message.type === "comment-deleted" && message.payload?.commentId) {
432
+ // Find which item(s) this comment belonged to by checking our cache
433
+ const deletedCommentId = message.payload.commentId;
434
+ const itemsToRefresh = new Set();
435
+ commentsData.forEach((comments, key) => {
436
+ if (comments.some((c) => c.id === deletedCommentId)) {
437
+ itemsToRefresh.add(key);
438
+ }
439
+ });
440
+ // If we found the comment in our cache, refresh only those items
441
+ if (itemsToRefresh.size > 0) {
442
+ itemsToRefresh.forEach((key) => {
443
+ const parts = key.split("-");
444
+ if (parts.length >= 2) {
445
+ const itemId = parts[0];
446
+ const itemLanguage = parts.slice(1).join("-"); // Handle language codes with dashes
447
+ refreshItemCounts(itemId, itemLanguage);
448
+ }
449
+ });
450
+ }
451
+ else {
452
+ // If not found in cache, refresh all items to be safe
453
+ allItemsWithDecisions.forEach((item) => {
454
+ refreshItemCounts(item.itemId, item.language);
455
+ });
456
+ }
457
+ }
458
+ // Handle suggested edit updates
459
+ if (message.type === "suggested-edit-updated" &&
460
+ message.payload?.suggestedEdit) {
461
+ const suggestion = message.payload.suggestedEdit;
462
+ const itemId = suggestion.mainItemId;
463
+ const itemLanguage = suggestion.mainItemLanguage || "en";
464
+ // Refresh counts for this item
465
+ refreshItemCounts(itemId, itemLanguage);
466
+ }
467
+ // Handle suggested edit deletions
468
+ if (message.type === "suggested-edit-deleted" && message.payload?.id) {
469
+ // Find which item(s) this suggestion belonged to by checking our cache
470
+ const deletedSuggestionId = message.payload.id;
471
+ const itemsToRefresh = new Set();
472
+ suggestionsData.forEach((suggestions, key) => {
473
+ if (suggestions.some((s) => s.id === deletedSuggestionId)) {
474
+ itemsToRefresh.add(key);
475
+ }
476
+ });
477
+ // If we found the suggestion in our cache, refresh only those items
478
+ if (itemsToRefresh.size > 0) {
479
+ itemsToRefresh.forEach((key) => {
480
+ const parts = key.split("-");
481
+ if (parts.length >= 2) {
482
+ const itemId = parts[0];
483
+ const itemLanguage = parts.slice(1).join("-"); // Handle language codes with dashes
484
+ refreshItemCounts(itemId, itemLanguage);
485
+ }
486
+ });
487
+ }
488
+ else {
489
+ // If not found in cache, refresh all items to be safe
490
+ allItemsWithDecisions.forEach((item) => {
491
+ refreshItemCounts(item.itemId, item.language);
492
+ });
493
+ }
494
+ }
495
+ }
496
+ catch (err) {
497
+ console.error("Error handling websocket message:", err);
498
+ }
499
+ });
500
+ return () => {
501
+ unsubscribe();
502
+ };
503
+ }, [
504
+ editContext?.addSocketMessageListener,
505
+ refreshItemCounts,
506
+ allItemsWithDecisions,
507
+ commentsData,
508
+ suggestionsData,
509
+ ]);
510
+ // Helper function to get comment count for an item by reviewer email
511
+ const getCommentCount = (itemId, itemLanguage, reviewerEmail) => {
512
+ const key = `${itemId}-${itemLanguage}`;
513
+ const comments = commentsData.get(key) || [];
514
+ return comments.filter((c) => c.author?.toLowerCase() === reviewerEmail.toLowerCase()).length;
98
515
  };
99
- return (_jsxs("div", { className: "flex h-full flex-col", children: [_jsxs(SimpleToolbar, { children: [_jsxs(Popover, { open: isAddPopoverOpen, onOpenChange: setIsAddPopoverOpen, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsx(SimpleIconButton, { label: "Add Pages", icon: _jsx(Plus, { className: "h-4 w-4" }), onClick: () => setIsAddPopoverOpen(true) }) }), _jsx(PopoverContent, { className: "m-2 w-[600px] p-0", align: "start", children: _jsxs("div", { className: "flex flex-col gap-2 p-2", children: [_jsx("div", { className: "text-sm font-medium", children: "Select Pages" }), _jsx(ItemTreeSelector, { selectedItems: selectedItems, onItemsChange: handleItemsChange, language: language, height: "350px" }), _jsxs("div", { className: "flex justify-end gap-2 border-t pt-2", children: [_jsx(Button, { variant: "outline", size: "sm", onClick: () => {
516
+ // Helper function to get suggestion count for an item by reviewer email
517
+ const getSuggestionCount = (itemId, itemLanguage, reviewerEmail) => {
518
+ const key = `${itemId}-${itemLanguage}`;
519
+ const suggestions = suggestionsData.get(key) || [];
520
+ return suggestions.filter((s) => s.author?.toLowerCase() === reviewerEmail.toLowerCase()).length;
521
+ };
522
+ // Helper function to get total comment count for an item
523
+ const getTotalCommentCount = (itemId, itemLanguage) => {
524
+ const key = `${itemId}-${itemLanguage}`;
525
+ const comments = commentsData.get(key) || [];
526
+ return comments.length;
527
+ };
528
+ // Helper function to get total suggestion count for an item
529
+ const getTotalSuggestionCount = (itemId, itemLanguage) => {
530
+ const key = `${itemId}-${itemLanguage}`;
531
+ const suggestions = suggestionsData.get(key) || [];
532
+ return suggestions.length;
533
+ };
534
+ const currentItem = editContext?.currentItemDescriptor;
535
+ const currentFullItem = editContext?.contentEditorItem; // FullItem has path and parentId
536
+ const userAssignment = assignments.find((a) => currentUserEmail &&
537
+ a.reviewerEmail.toLowerCase() === currentUserEmail.toLowerCase());
538
+ const currentItemDecision = userAssignment && currentItem
539
+ ? getDecision(userAssignment, currentItem.id, currentItem.language || "en")
540
+ : undefined;
541
+ // Check if current page is in the review (either directly or as a subitem)
542
+ const isCurrentPageInReview = useMemo(() => {
543
+ if (!currentItem || !currentFullItem) {
544
+ console.log("[DecisionsMatrix] No current item or full item");
545
+ return false;
546
+ }
547
+ const currentPath = currentFullItem.path;
548
+ const currentId = currentItem.id;
549
+ const currentLanguage = currentItem.language || "en";
550
+ console.log("[DecisionsMatrix] Checking if current page is in review:", {
551
+ currentItemId: currentId,
552
+ currentItemPath: currentPath,
553
+ currentItemLanguage: currentLanguage,
554
+ parentId: currentFullItem.parentId,
555
+ itemsCount: items.length,
556
+ items: items.map((i) => ({
557
+ id: i.itemId,
558
+ path: i.path,
559
+ includeSubitems: i.includeSubitems,
560
+ language: i.language,
561
+ })),
562
+ });
563
+ // Check if current item is directly in the items list
564
+ const isDirectMatch = items.some((item) => item.itemId === currentId &&
565
+ (item.language === currentLanguage ||
566
+ item.language === "*" ||
567
+ currentLanguage === "*"));
568
+ console.log("[DecisionsMatrix] Is direct match:", isDirectMatch);
569
+ if (isDirectMatch) {
570
+ console.log("[DecisionsMatrix] Current item is directly in review");
571
+ return true;
572
+ }
573
+ // Check if it's a subitem of any item with includeSubitems: true
574
+ const isSubitem = items.some((item) => {
575
+ if (!item.includeSubitems) {
576
+ return false;
577
+ }
578
+ console.log(`[DecisionsMatrix] Checking if ${currentId} is subitem of ${item.itemId}`, {
579
+ currentPath: currentPath,
580
+ itemPath: item.path,
581
+ currentParentId: currentFullItem.parentId,
582
+ itemId: item.itemId,
583
+ });
584
+ // Method 1: Check by path if both are available
585
+ if (currentPath && item.path) {
586
+ // Normalize paths - remove trailing slashes and ensure consistent format
587
+ const normalizedCurrentPath = currentPath
588
+ .replace(/\/$/, "")
589
+ .replace(/\\$/, "");
590
+ const normalizedItemPath = item.path
591
+ .replace(/\/$/, "")
592
+ .replace(/\\$/, "");
593
+ // Check if current path starts with item path and is deeper
594
+ const isSubPath = normalizedCurrentPath.startsWith(normalizedItemPath + "/") ||
595
+ normalizedCurrentPath.startsWith(normalizedItemPath + "\\");
596
+ if (isSubPath && normalizedCurrentPath !== normalizedItemPath) {
597
+ console.log(`[DecisionsMatrix] Found subitem match by path: ${normalizedCurrentPath} is under ${normalizedItemPath}`);
598
+ return true;
599
+ }
600
+ }
601
+ // Method 2: Check if current item's parent ID matches the review item ID
602
+ if (currentFullItem.parentId &&
603
+ item.itemId === currentFullItem.parentId) {
604
+ console.log(`[DecisionsMatrix] Found subitem match by parent ID: ${currentId} has parent ${currentFullItem.parentId} which matches ${item.itemId}`);
605
+ return true;
606
+ }
607
+ // Method 3: Check if item ID is in the current path
608
+ if (currentPath && item.itemId) {
609
+ const pathContainsId = currentPath.includes(`/${item.itemId}/`) ||
610
+ currentPath.includes(`\\${item.itemId}\\`) ||
611
+ currentPath.endsWith(`/${item.itemId}`) ||
612
+ currentPath.endsWith(`\\${item.itemId}`);
613
+ if (pathContainsId && currentId !== item.itemId) {
614
+ console.log(`[DecisionsMatrix] Found subitem match by ID in path: ${currentPath} contains ${item.itemId}`);
615
+ return true;
616
+ }
617
+ }
618
+ return false;
619
+ });
620
+ console.log("[DecisionsMatrix] Is in review result:", isDirectMatch || isSubitem);
621
+ return isDirectMatch || isSubitem;
622
+ }, [currentItem, currentFullItem, items]);
623
+ const showCurrentPageButtons = currentItem && userAssignment && isCurrentPageInReview;
624
+ // Debug logging
625
+ useEffect(() => {
626
+ console.log("[DecisionsMatrix] Button visibility check:", {
627
+ isLimitedPreviewUser,
628
+ hasCurrentItem: !!currentItem,
629
+ hasCurrentFullItem: !!currentFullItem,
630
+ hasUserAssignment: !!userAssignment,
631
+ isCurrentPageInReview,
632
+ showCurrentPageButtons,
633
+ currentItem: currentItem
634
+ ? {
635
+ id: currentItem.id,
636
+ path: currentItem.path,
637
+ language: currentItem.language,
638
+ }
639
+ : null,
640
+ currentFullItem: currentFullItem
641
+ ? {
642
+ id: currentFullItem.id,
643
+ path: currentFullItem.path,
644
+ parentId: currentFullItem.parentId,
645
+ }
646
+ : null,
647
+ userAssignment: userAssignment
648
+ ? {
649
+ assignmentId: userAssignment.assignmentId,
650
+ reviewerEmail: userAssignment.reviewerEmail,
651
+ }
652
+ : null,
653
+ });
654
+ }, [
655
+ isLimitedPreviewUser,
656
+ currentItem,
657
+ currentFullItem,
658
+ userAssignment,
659
+ isCurrentPageInReview,
660
+ showCurrentPageButtons,
661
+ ]);
662
+ return (_jsxs("div", { className: "flex h-full flex-col", children: [_jsxs(SimpleToolbar, { children: [!isLimitedPreviewUser && (_jsxs(Popover, { open: isAddPopoverOpen, onOpenChange: setIsAddPopoverOpen, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsx(SimpleIconButton, { label: "Add Pages", icon: _jsx(Plus, { className: "h-4 w-4" }), onClick: () => setIsAddPopoverOpen(true) }) }), _jsx(PopoverContent, { className: "m-2 w-[600px] p-0", align: "start", children: _jsxs("div", { className: "flex flex-col gap-2 p-2", children: [_jsx("div", { className: "text-sm font-medium", children: "Select Pages" }), _jsx(ItemTreeSelector, { selectedItems: selectedItems, onItemsChange: handleItemsChange, language: language, height: "350px" }), _jsxs("div", { className: "flex justify-end gap-2 border-t pt-2", children: [_jsx(Button, { variant: "outline", size: "sm", onClick: () => {
100
663
  setSelectedItems([]);
101
664
  setIsAddPopoverOpen(false);
102
- }, children: "Cancel" }), _jsxs(Button, { size: "sm", onClick: handleAddSelectedPages, disabled: selectedItems.length === 0 || loading, children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), "Add ", selectedItems.length > 0 ? `${selectedItems.length} ` : "", "Page", selectedItems.length !== 1 ? "s" : ""] })] })] }) })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Filter, { className: "h-4 w-4" }), _jsxs("select", { value: filter, onChange: (e) => setFilter(e.target.value), className: "rounded border px-2 py-1 text-xs", children: [_jsx("option", { value: "all", children: "All Items" }), _jsx("option", { value: "pending", children: "Pending" }), _jsx("option", { value: "rejected", children: "Rejected" }), _jsx("option", { value: "approved", children: "All Approved" })] })] }), allReviewersResponded && !finalDecision && (_jsx(Button, { size: "sm", onClick: () => setIsFinalDecisionDialogOpen(true), className: "ml-auto", children: "Set Final Decision" })), finalDecision && (_jsx(Badge, { variant: finalDecision === "Approved" ? "default" : "destructive", className: "ml-auto", children: finalDecision === "Approved" ? (_jsxs(_Fragment, { children: [_jsx(Check, { className: "mr-1 h-3 w-3" }), "Final: Approved"] })) : (_jsxs(_Fragment, { children: [_jsx(X, { className: "mr-1 h-3 w-3" }), "Final: Rejected"] })) }))] }), _jsx("div", { className: "flex-1 overflow-auto", children: items.length === 0 ? (_jsx("div", { className: "flex h-full items-center justify-center text-sm text-gray-500", children: "No items in review" })) : (_jsx("div", { className: "overflow-x-auto", children: _jsxs("table", { className: "w-full border-collapse text-xs", children: [_jsx("thead", { className: "sticky top-0 bg-white border-b", children: _jsxs("tr", { children: [_jsx("th", { className: "border p-2 text-left font-medium", children: "Item" }), displayAssignments.map((assignment) => (_jsx("th", { className: "border p-2 text-center font-medium min-w-[120px]", children: _jsxs("div", { className: "flex flex-col", children: [_jsx("span", { className: "font-medium", children: assignment.reviewerName }), _jsx("span", { className: "text-xs text-gray-500 font-normal", children: assignment.reviewerEmail })] }) }, assignment.assignmentId))), _jsx("th", { className: "border p-2 text-center font-medium w-16", children: "Actions" })] }) }), _jsx("tbody", { children: filteredItems.map((item, itemIndex) => (_jsxs("tr", { children: [_jsx("td", { className: "border p-2", children: _jsxs("div", { className: "flex flex-col cursor-pointer hover:bg-gray-50 rounded p-1 -m-1", onClick: () => handleLoadItem(item.itemId, item.language), title: "Click to load item", children: [_jsx("span", { className: "font-medium", children: item.name || item.path || item.itemId }), item.includeSubitems && (_jsx("span", { className: "text-xs text-gray-500", children: "(with subitems)" }))] }) }), displayAssignments.map((assignment) => {
103
- const decision = getDecision(assignment, item.itemId, item.language);
104
- const isCurrentUserReviewer = currentUserEmail &&
105
- assignment.reviewerEmail.toLowerCase() === currentUserEmail.toLowerCase();
106
- return (_jsx("td", { className: "border p-1 text-center", children: decision ? (getDecisionCell(assignment, item)) : isCurrentUserReviewer ? (_jsxs("div", { className: "flex items-center justify-center gap-1", children: [_jsx(Button, { size: "sm", variant: "outline", className: "h-6 text-xs", onClick: () => handleMakeDecision(assignment.assignmentId, item.itemId, item.language, "Approved"), disabled: loading, children: _jsx(Check, { className: "h-3 w-3" }) }), _jsx(Button, { size: "sm", variant: "outline", className: "h-6 text-xs", onClick: () => handleMakeDecision(assignment.assignmentId, item.itemId, item.language, "Rejected"), disabled: loading, children: _jsx(X, { className: "h-3 w-3" }) })] })) : (_jsx("div", { className: "flex items-center justify-center p-2", children: _jsx(Clock, { className: "h-4 w-4 text-gray-400" }) })) }, assignment.assignmentId));
107
- }), _jsx("td", { className: "border p-1 text-center", children: _jsx(Button, { size: "sm", variant: "ghost", className: "h-6 w-6 p-0", onClick: () => handleRemovePage(item.itemId, item.language), disabled: loading, title: "Remove page", children: _jsx(Trash2, { className: "h-3 w-3" }) }) })] }, `${item.itemId}-${item.language}-${itemIndex}`))) })] }) })) }), _jsx(Dialog, { open: isFinalDecisionDialogOpen, onOpenChange: setIsFinalDecisionDialogOpen, children: _jsxs(DialogContent, { children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: "Set Final Decision" }) }), _jsx("div", { className: "py-4", children: _jsx("p", { className: "text-sm text-gray-600 mb-4", children: "All reviewers have responded. Set the final decision for this review." }) }), _jsxs(DialogFooter, { children: [_jsx(Button, { variant: "outline", onClick: () => setIsFinalDecisionDialogOpen(false), children: "Cancel" }), _jsx(Button, { variant: "destructive", onClick: async () => {
108
- await onSetFinalDecision("Rejected");
109
- setIsFinalDecisionDialogOpen(false);
110
- }, disabled: loading, children: loading ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), "Setting..."] })) : (_jsxs(_Fragment, { children: [_jsx(X, { className: "mr-2 h-4 w-4" }), "Reject Review"] })) }), _jsx(Button, { onClick: async () => {
111
- await onSetFinalDecision("Approved");
112
- setIsFinalDecisionDialogOpen(false);
113
- }, disabled: loading, children: loading ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), "Setting..."] })) : (_jsxs(_Fragment, { children: [_jsx(Check, { className: "mr-2 h-4 w-4" }), "Approve Review"] })) })] })] }) })] }));
665
+ }, children: "Cancel" }), _jsxs(Button, { size: "sm", onClick: handleAddSelectedPages, disabled: selectedItems.length === 0 || loading, children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), "Add", " ", selectedItems.length > 0 ? `${selectedItems.length} ` : "", "Page", selectedItems.length !== 1 ? "s" : ""] })] })] }) })] })), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Filter, { className: "h-4 w-4" }), _jsxs("select", { value: filter, onChange: (e) => setFilter(e.target.value), className: "rounded border px-2 py-1 text-xs", children: [_jsx("option", { value: "all", children: "All Items" }), _jsx("option", { value: "pending", children: "Pending" }), _jsx("option", { value: "rejected", children: "Rejected" }), _jsx("option", { value: "approved", children: "All Approved" })] })] }), hasSelectedPages && (_jsx(SimpleIconButton, { label: "Remove Selected", icon: _jsx(Trash2, { className: "h-4 w-4" }), onClick: handleRemoveSelectedPages, disabled: loading })), showCurrentPageButtons && (_jsxs("div", { className: "ml-auto flex items-center gap-2", children: [_jsx("div", { className: "mx-1 h-6 border-l border-gray-300" }), _jsx(SimpleIconButton, { label: `Approve Current Page`, icon: _jsx(Check, { className: "h-4 w-4" }), onClick: handleApproveCurrentPage, disabled: loading, className: currentItemDecision?.decision === "Approved"
666
+ ? "text-green-600"
667
+ : "" }), _jsx(SimpleIconButton, { label: `Reject Current Page`, icon: _jsx(X, { className: "h-4 w-4" }), onClick: handleRejectCurrentPage, disabled: loading, className: currentItemDecision?.decision === "Rejected"
668
+ ? "text-red-600"
669
+ : "" })] }))] }), _jsx("div", { className: "flex-1 overflow-auto", children: allItemsWithDecisions.length === 0 ? (_jsx("div", { className: "flex h-full items-center justify-center text-sm text-gray-500", children: "No items in review" })) : (_jsx("div", { className: "overflow-x-auto", children: _jsxs("table", { className: "w-full border-collapse text-xs", children: [_jsx("thead", { className: "sticky top-0 border-b bg-white", children: _jsxs("tr", { children: [!isLimitedPreviewUser && (_jsx("th", { className: "w-12 border p-2 text-center font-medium", children: _jsx(Checkbox, { checked: filteredItems.length > 0 &&
670
+ filteredItems.every((item) => selectedPagesForRemoval.has(`${item.itemId}-${item.language}`)), onCheckedChange: (checked) => {
671
+ const newSelection = new Set();
672
+ if (checked) {
673
+ filteredItems.forEach((item) => {
674
+ newSelection.add(`${item.itemId}-${item.language}`);
675
+ });
676
+ }
677
+ setSelectedPagesForRemoval(newSelection);
678
+ }, className: "h-4 w-4" }) })), _jsx("th", { className: "border p-2 text-left font-medium", children: "Item" }), displayAssignments.map((assignment) => (_jsx("th", { className: "min-w-[120px] border p-2 text-center font-medium", children: _jsxs("div", { className: "flex flex-col", children: [_jsx("span", { className: "font-medium", children: assignment.reviewerName }), _jsx("span", { className: "text-xs font-normal text-gray-500", children: assignment.reviewerEmail })] }) }, assignment.assignmentId))), _jsx("th", { className: "min-w-[60px] border p-2 text-center font-medium", children: _jsxs("div", { className: "flex items-center justify-center gap-1.5", children: [_jsx("div", { title: "Total comments", children: _jsx(MessageSquare, { className: "h-3.5 w-3.5" }) }), _jsx("div", { title: "Total suggestions", children: _jsx(Lightbulb, { className: "h-3.5 w-3.5" }) })] }) }), _jsx("th", { className: "min-w-[120px] border p-2 text-center font-medium", children: _jsx("span", { children: "Actions" }) })] }) }), _jsx("tbody", { children: filteredItems.map((item, itemIndex) => {
679
+ const itemKey = `${item.itemId}-${item.language}`;
680
+ const isSelected = selectedPagesForRemoval.has(itemKey);
681
+ const currentItemLanguage = currentItem?.language || "en";
682
+ const itemLanguage = item.language === "*" ? currentItemLanguage : item.language;
683
+ const isCurrentItem = currentItem &&
684
+ currentItem.id === item.itemId &&
685
+ currentItemLanguage === itemLanguage;
686
+ return (_jsxs("tr", { className: isCurrentItem ? "bg-blue-50" : "", children: [!isLimitedPreviewUser && (_jsx("td", { className: "border p-1 text-center", children: _jsx(Checkbox, { checked: isSelected, onCheckedChange: () => togglePageSelection(item.itemId, item.language), onClick: (e) => e.stopPropagation(), className: "h-4 w-4" }) })), _jsx("td", { className: "border p-2", children: _jsxs("div", { className: "-m-1 flex cursor-pointer flex-col rounded p-1 hover:bg-gray-50", onClick: () => handleLoadItem(item.itemId, item.language), title: "Click to load item", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "font-medium", children: item.name || item.path || item.itemId }), item.language === "*" ? (_jsx(Badge, { variant: "outline", className: "text-xs", children: "All Languages" })) : (_jsx("span", { className: "text-xs text-gray-500", children: item.language.toUpperCase() }))] }), item.includeSubitems && (_jsx("span", { className: "text-xs text-gray-500", children: "(with subitems)" })), !isLimitedPreviewUser &&
687
+ (() => {
688
+ const approvalCount = getApprovalCount(item.itemId, item.language);
689
+ const status = getItemApprovalStatus(item.itemId, item.language);
690
+ return (_jsxs("div", { className: "mt-1 flex items-center gap-1", children: [_jsxs("span", { className: `text-xs ${status === "approved"
691
+ ? "font-semibold text-green-600"
692
+ : status === "rejected"
693
+ ? "font-semibold text-red-600"
694
+ : "text-gray-500"}`, children: [approvalCount, "/", requiredApprovals, " ", "approvals"] }), status === "approved" && (_jsx(Check, { className: "h-3 w-3 text-green-600" })), status === "rejected" && (_jsx(X, { className: "h-3 w-3 text-red-600" }))] }));
695
+ })()] }) }), displayAssignments.map((assignment) => {
696
+ return (_jsx("td", { className: "border p-1 text-center", children: getDecisionCell(assignment, item) }, assignment.assignmentId));
697
+ }), _jsx("td", { className: "border p-1 text-center", children: loadingCounts ? (_jsx(Loader2, { className: "mx-auto h-3 w-3 animate-spin" })) : (_jsx("div", { className: "flex items-center justify-center gap-2 text-xs", children: (() => {
698
+ // If a reviewer is selected, filter counts by that reviewer
699
+ // Otherwise, show total counts for all reviewers
700
+ let commentCount;
701
+ let suggestionCount;
702
+ if (selectedReviewerId) {
703
+ // Find the selected reviewer's assignment
704
+ const selectedAssignment = assignments.find((a) => a.assignmentId === selectedReviewerId);
705
+ if (selectedAssignment) {
706
+ commentCount = getCommentCount(item.itemId, item.language, selectedAssignment.reviewerEmail);
707
+ suggestionCount = getSuggestionCount(item.itemId, item.language, selectedAssignment.reviewerEmail);
708
+ }
709
+ else {
710
+ // Fallback to total counts if assignment not found
711
+ commentCount = getTotalCommentCount(item.itemId, item.language);
712
+ suggestionCount = getTotalSuggestionCount(item.itemId, item.language);
713
+ }
714
+ }
715
+ else {
716
+ // No reviewer selected, show total counts
717
+ commentCount = getTotalCommentCount(item.itemId, item.language);
718
+ suggestionCount = getTotalSuggestionCount(item.itemId, item.language);
719
+ }
720
+ return (_jsxs(_Fragment, { children: [commentCount > 0 && (_jsxs("div", { className: "flex items-center gap-0.5", title: `${commentCount} comment${commentCount !== 1 ? "s" : ""}`, children: [_jsx(MessageSquare, { className: "h-3 w-3" }), _jsx("span", { children: commentCount })] })), suggestionCount > 0 && (_jsxs("div", { className: "flex items-center gap-0.5", title: `${suggestionCount} suggestion${suggestionCount !== 1 ? "s" : ""}`, children: [_jsx(Lightbulb, { className: "h-3 w-3" }), _jsx("span", { children: suggestionCount })] })), commentCount === 0 &&
721
+ suggestionCount === 0 && (_jsx("span", { className: "text-xs text-gray-400", children: "-" }))] }));
722
+ })() })) }), _jsx("td", { className: "border p-1 text-center", children: _jsxs("div", { className: "flex items-center justify-center gap-1", children: [(() => {
723
+ // Find the current user's assignment for this item (check all assignments, not just displayed ones)
724
+ const currentUserAssignment = assignments.find((a) => currentUserEmail &&
725
+ a.reviewerEmail.toLowerCase() ===
726
+ currentUserEmail.toLowerCase());
727
+ if (currentUserAssignment) {
728
+ const decision = getDecision(currentUserAssignment, item.itemId, item.language);
729
+ return (_jsxs(_Fragment, { children: [_jsx(SimpleIconButton, { label: "Approve", icon: _jsx(Check, { className: "h-4 w-4" }), onClick: () => handleMakeDecision(currentUserAssignment.assignmentId, item.itemId, item.language, "Approved"), disabled: loading, className: decision?.decision === "Approved"
730
+ ? "text-green-600"
731
+ : "" }), _jsx(SimpleIconButton, { label: "Reject", icon: _jsx(X, { className: "h-4 w-4" }), onClick: () => handleMakeDecision(currentUserAssignment.assignmentId, item.itemId, item.language, "Rejected"), disabled: loading, className: decision?.decision === "Rejected"
732
+ ? "text-red-600"
733
+ : "" })] }));
734
+ }
735
+ return null;
736
+ })(), !isLimitedPreviewUser && (_jsx(Button, { size: "sm", variant: "ghost", className: "h-6 w-6 p-0", onClick: () => handleRemovePage(item.itemId, item.language), disabled: loading, title: "Remove page", children: _jsx(Trash2, { className: "h-3 w-3" }) }))] }) })] }, `${item.itemId}-${item.language}-${itemIndex}`));
737
+ }) })] }) })) })] }));
114
738
  }
115
739
  //# sourceMappingURL=DecisionsMatrix.js.map