@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.
- package/dist/components/ui/LanguageSelector.js +10 -4
- package/dist/components/ui/LanguageSelector.js.map +1 -1
- package/dist/config/config.js +19 -28
- package/dist/config/config.js.map +1 -1
- package/dist/config/types.d.ts +0 -5
- package/dist/editor/ContentTree.js +1 -1
- package/dist/editor/ContentTree.js.map +1 -1
- package/dist/editor/FieldListField.js +70 -3
- package/dist/editor/FieldListField.js.map +1 -1
- package/dist/editor/MainLayout.js +77 -8
- package/dist/editor/MainLayout.js.map +1 -1
- package/dist/editor/ai/AgentDocumentList.d.ts +8 -0
- package/dist/editor/ai/AgentDocumentList.js +132 -0
- package/dist/editor/ai/AgentDocumentList.js.map +1 -0
- package/dist/editor/ai/AgentTerminal.js +29 -9
- package/dist/editor/ai/AgentTerminal.js.map +1 -1
- package/dist/editor/ai/ContextInfoBar.d.ts +3 -1
- package/dist/editor/ai/ContextInfoBar.js +3 -3
- package/dist/editor/ai/ContextInfoBar.js.map +1 -1
- package/dist/editor/client/EditorShell.js +34 -8
- package/dist/editor/client/EditorShell.js.map +1 -1
- package/dist/editor/client/editContext.d.ts +3 -0
- package/dist/editor/client/editContext.js.map +1 -1
- package/dist/editor/client/itemsRepository.d.ts +2 -2
- package/dist/editor/client/itemsRepository.js +19 -8
- package/dist/editor/client/itemsRepository.js.map +1 -1
- package/dist/editor/client/pageModelBuilder.js +19 -3
- package/dist/editor/client/pageModelBuilder.js.map +1 -1
- package/dist/editor/control-center/Setup.js +159 -15
- package/dist/editor/control-center/Setup.js.map +1 -1
- package/dist/editor/control-center/setup-steps/AiSetupStep/utils.d.ts +1 -0
- package/dist/editor/control-center/setup-steps/AiSetupStep/utils.js +19 -0
- package/dist/editor/control-center/setup-steps/AiSetupStep/utils.js.map +1 -1
- package/dist/editor/control-center/setup-steps/SetupOverview.d.ts +14 -0
- package/dist/editor/control-center/setup-steps/SetupOverview.js +38 -0
- package/dist/editor/control-center/setup-steps/SetupOverview.js.map +1 -0
- package/dist/editor/editor-warnings/ItemLocked.js +1 -1
- package/dist/editor/editor-warnings/ItemLocked.js.map +1 -1
- package/dist/editor/menubar/toolbar-sections/ReviewCommands.js +13 -13
- package/dist/editor/menubar/toolbar-sections/ReviewCommands.js.map +1 -1
- package/dist/editor/page-viewer/PageViewerFrame.js +0 -11
- package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
- package/dist/editor/page-viewer/pageModelSkeletonBuilder.js +11 -1
- package/dist/editor/page-viewer/pageModelSkeletonBuilder.js.map +1 -1
- package/dist/editor/page-viewer/pageViewContext.js +0 -1
- package/dist/editor/page-viewer/pageViewContext.js.map +1 -1
- package/dist/editor/reviews/CreateReviewDialog.js +308 -30
- package/dist/editor/reviews/CreateReviewDialog.js.map +1 -1
- package/dist/editor/reviews/DecisionsMatrix.d.ts +5 -1
- package/dist/editor/reviews/DecisionsMatrix.js +668 -44
- package/dist/editor/reviews/DecisionsMatrix.js.map +1 -1
- package/dist/editor/reviews/EditReviewSettingsDialog.d.ts +20 -0
- package/dist/editor/reviews/EditReviewSettingsDialog.js +208 -0
- package/dist/editor/reviews/EditReviewSettingsDialog.js.map +1 -0
- package/dist/editor/reviews/ItemTreeSelector.js +5 -3
- package/dist/editor/reviews/ItemTreeSelector.js.map +1 -1
- package/dist/editor/reviews/PagesPanel.d.ts +3 -0
- package/dist/editor/reviews/PagesPanel.js +92 -18
- package/dist/editor/reviews/PagesPanel.js.map +1 -1
- package/dist/editor/reviews/PreconfiguredReviewerSelector.d.ts +9 -0
- package/dist/editor/reviews/PreconfiguredReviewerSelector.js +55 -0
- package/dist/editor/reviews/PreconfiguredReviewerSelector.js.map +1 -0
- package/dist/editor/reviews/ReviewDetail.js +108 -28
- package/dist/editor/reviews/ReviewDetail.js.map +1 -1
- package/dist/editor/reviews/ReviewersPanel.d.ts +3 -1
- package/dist/editor/reviews/ReviewersPanel.js +142 -22
- package/dist/editor/reviews/ReviewersPanel.js.map +1 -1
- package/dist/editor/reviews/ReviewsList.js +65 -7
- package/dist/editor/reviews/ReviewsList.js.map +1 -1
- package/dist/editor/reviews/useMultiReview.d.ts +5 -0
- package/dist/editor/reviews/useMultiReview.js +75 -7
- package/dist/editor/reviews/useMultiReview.js.map +1 -1
- package/dist/editor/services/agentService.d.ts +38 -0
- package/dist/editor/services/agentService.js +84 -0
- package/dist/editor/services/agentService.js.map +1 -1
- package/dist/editor/services/aiService.d.ts +2 -0
- package/dist/editor/services/aiService.js.map +1 -1
- package/dist/editor/services/contentService.d.ts +2 -2
- package/dist/editor/services/contentService.js +12 -4
- package/dist/editor/services/contentService.js.map +1 -1
- package/dist/editor/services/reviewsService.d.ts +8 -1
- package/dist/editor/services/reviewsService.js +32 -4
- package/dist/editor/services/reviewsService.js.map +1 -1
- package/dist/editor/services/suggestedEditsService.d.ts +1 -1
- package/dist/editor/services/suggestedEditsService.js +10 -2
- package/dist/editor/services/suggestedEditsService.js.map +1 -1
- package/dist/editor/sidebar/ComponentTree.js +43 -32
- package/dist/editor/sidebar/ComponentTree.js.map +1 -1
- package/dist/editor/sidebar/Sidebar.js +1 -1
- package/dist/editor/sidebar/Sidebar.js.map +1 -1
- package/dist/editor/sidebar/SidebarView.d.ts +4 -1
- package/dist/editor/sidebar/SidebarView.js +23 -10
- package/dist/editor/sidebar/SidebarView.js.map +1 -1
- package/dist/editor/ui/IconSelectorDialog.js +8 -4
- package/dist/editor/ui/IconSelectorDialog.js.map +1 -1
- package/dist/editor/ui/ItemNameDialogNew.js +14 -0
- package/dist/editor/ui/ItemNameDialogNew.js.map +1 -1
- package/dist/editor/utils.js +21 -4
- package/dist/editor/utils.js.map +1 -1
- package/dist/editor/views/SingleEditView.js +1 -1
- package/dist/editor/views/SingleEditView.js.map +1 -1
- package/dist/setup/services/setupWizardService.d.ts +7 -2
- package/dist/setup/services/setupWizardService.js +7 -14
- package/dist/setup/services/setupWizardService.js.map +1 -1
- package/dist/setup/utils/modelPricing.d.ts +7 -0
- package/dist/setup/utils/modelPricing.js +106 -0
- package/dist/setup/utils/modelPricing.js.map +1 -0
- package/dist/setup/wizard/steps/AgentsStep.js +6 -15
- package/dist/setup/wizard/steps/AgentsStep.js.map +1 -1
- package/dist/setup/wizard/steps/ModelsStep.js +182 -79
- package/dist/setup/wizard/steps/ModelsStep.js.map +1 -1
- package/dist/splash-screen/NewPage.js +1 -1
- package/dist/splash-screen/NewPage.js.map +1 -1
- package/dist/styles.css +33 -14
- package/dist/types.d.ts +17 -5
- package/package.json +2 -2
- package/dist/editor/control-center/parhelia-setup/Overview.d.ts +0 -1
- package/dist/editor/control-center/parhelia-setup/Overview.js +0 -91
- 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 {
|
|
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
|
-
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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:
|
|
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
|
|
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 (
|
|
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
|
|
54
|
-
|
|
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 =
|
|
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
|
-
//
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
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" })] })] }),
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|