@tomismeta/paperclip-aperture 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +105 -0
- package/dist/manifest.js +67 -0
- package/dist/ui/index.js +1133 -0
- package/dist/worker.js +11658 -0
- package/package.json +75 -0
package/dist/ui/index.js
ADDED
|
@@ -0,0 +1,1133 @@
|
|
|
1
|
+
// src/ui/index.tsx
|
|
2
|
+
import {
|
|
3
|
+
usePluginAction,
|
|
4
|
+
usePluginData
|
|
5
|
+
} from "@paperclipai/plugin-sdk/ui";
|
|
6
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
7
|
+
|
|
8
|
+
// src/aperture/types.ts
|
|
9
|
+
function createEmptySnapshot(companyId) {
|
|
10
|
+
return {
|
|
11
|
+
companyId,
|
|
12
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13
|
+
active: null,
|
|
14
|
+
queued: [],
|
|
15
|
+
ambient: [],
|
|
16
|
+
counts: {
|
|
17
|
+
active: 0,
|
|
18
|
+
queued: 0,
|
|
19
|
+
ambient: 0,
|
|
20
|
+
total: 0
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// src/ui/index.tsx
|
|
26
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
27
|
+
var ACCENT_COLOR = "#19A1FF";
|
|
28
|
+
var ACCENT_BG_STYLE = { backgroundColor: ACCENT_COLOR };
|
|
29
|
+
function currentSurfaceBrand() {
|
|
30
|
+
return {
|
|
31
|
+
key: "focus",
|
|
32
|
+
wordmark: "Focus",
|
|
33
|
+
supportCopy: "Powered by Aperture",
|
|
34
|
+
headingEmptyState: "No focus state yet.",
|
|
35
|
+
loadingLabel: "Loading Focus\u2026"
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function useAccentColor() {
|
|
39
|
+
const ref = useRef(null);
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
ref.current?.style.setProperty("color", ACCENT_COLOR, "important");
|
|
42
|
+
});
|
|
43
|
+
return ref;
|
|
44
|
+
}
|
|
45
|
+
function Accent({ children, className }) {
|
|
46
|
+
const ref = useAccentColor();
|
|
47
|
+
return /* @__PURE__ */ jsx("span", { ref, className, children });
|
|
48
|
+
}
|
|
49
|
+
function cn(...parts) {
|
|
50
|
+
return parts.filter(Boolean).join(" ");
|
|
51
|
+
}
|
|
52
|
+
function humanizeToken(value) {
|
|
53
|
+
return value.split(/[_\-.]/g).filter(Boolean).map((part) => part.slice(0, 1).toUpperCase() + part.slice(1)).join(" ");
|
|
54
|
+
}
|
|
55
|
+
function pluginPagePath(companyPrefix) {
|
|
56
|
+
return companyPrefix ? `/${companyPrefix}/aperture` : "/aperture";
|
|
57
|
+
}
|
|
58
|
+
function formatRelativeTime(value) {
|
|
59
|
+
const timestamp = new Date(value).getTime();
|
|
60
|
+
if (Number.isNaN(timestamp)) return "recently";
|
|
61
|
+
const elapsedMs = timestamp - Date.now();
|
|
62
|
+
const elapsedMinutes = Math.round(elapsedMs / 6e4);
|
|
63
|
+
const formatter = new Intl.RelativeTimeFormat(void 0, { numeric: "auto" });
|
|
64
|
+
if (Math.abs(elapsedMinutes) < 60) return formatter.format(elapsedMinutes, "minute");
|
|
65
|
+
const elapsedHours = Math.round(elapsedMinutes / 60);
|
|
66
|
+
if (Math.abs(elapsedHours) < 24) return formatter.format(elapsedHours, "hour");
|
|
67
|
+
const elapsedDays = Math.round(elapsedHours / 24);
|
|
68
|
+
return formatter.format(elapsedDays, "day");
|
|
69
|
+
}
|
|
70
|
+
function sourceLabel(frame) {
|
|
71
|
+
return frame.source?.label ?? "Paperclip";
|
|
72
|
+
}
|
|
73
|
+
function contextValue(frame, id) {
|
|
74
|
+
const item = frame.context?.items?.find((entry) => entry.id === id);
|
|
75
|
+
return typeof item?.value === "string" && item.value.trim().length > 0 ? item.value : void 0;
|
|
76
|
+
}
|
|
77
|
+
function isBudgetOverride(frame) {
|
|
78
|
+
return frame.provenance?.factors?.includes("budget stop") ?? false;
|
|
79
|
+
}
|
|
80
|
+
function requestedAmount(frame) {
|
|
81
|
+
return contextValue(frame, "requested-amount");
|
|
82
|
+
}
|
|
83
|
+
function budgetReason(frame) {
|
|
84
|
+
return contextValue(frame, "budget-reason");
|
|
85
|
+
}
|
|
86
|
+
function modeLabel(frame) {
|
|
87
|
+
switch (frame.mode) {
|
|
88
|
+
case "approval":
|
|
89
|
+
return "approval";
|
|
90
|
+
case "choice":
|
|
91
|
+
return "choice";
|
|
92
|
+
case "form":
|
|
93
|
+
return "form";
|
|
94
|
+
default:
|
|
95
|
+
return "status";
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function urgencyLabel(frame) {
|
|
99
|
+
switch (frame.tone) {
|
|
100
|
+
case "critical":
|
|
101
|
+
return "urgent";
|
|
102
|
+
case "focused":
|
|
103
|
+
return "needs attention";
|
|
104
|
+
case "ambient":
|
|
105
|
+
return "low urgency";
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function riskLabel(frame) {
|
|
109
|
+
return `${frame.consequence} risk`;
|
|
110
|
+
}
|
|
111
|
+
function toneBadgeStyle(frame) {
|
|
112
|
+
switch (frame.tone) {
|
|
113
|
+
case "critical":
|
|
114
|
+
return {
|
|
115
|
+
className: "border-transparent",
|
|
116
|
+
style: { color: ACCENT_COLOR, backgroundColor: `${ACCENT_COLOR}1a`, borderColor: `${ACCENT_COLOR}4d` }
|
|
117
|
+
};
|
|
118
|
+
case "focused":
|
|
119
|
+
return {
|
|
120
|
+
className: "border-transparent",
|
|
121
|
+
style: { color: ACCENT_COLOR, opacity: 0.8, backgroundColor: `${ACCENT_COLOR}0d`, borderColor: `${ACCENT_COLOR}33` }
|
|
122
|
+
};
|
|
123
|
+
case "ambient":
|
|
124
|
+
return { className: "border-border bg-secondary text-muted-foreground" };
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
function postureForSnapshot(snapshot) {
|
|
128
|
+
const current = snapshot.active;
|
|
129
|
+
if (current?.tone === "critical" || current?.consequence === "high" || snapshot.counts.active + snapshot.counts.queued >= 3) {
|
|
130
|
+
return { glyph: "\u25CF", label: "busy" };
|
|
131
|
+
}
|
|
132
|
+
if (snapshot.counts.active > 0 || snapshot.counts.queued > 0 || snapshot.counts.ambient > 0) {
|
|
133
|
+
return { glyph: "\u25D0", label: "elevated" };
|
|
134
|
+
}
|
|
135
|
+
return { glyph: "\u25CB", label: "calm" };
|
|
136
|
+
}
|
|
137
|
+
function actionableCount(snapshot) {
|
|
138
|
+
return (snapshot.active ? 1 : 0) + snapshot.queued.length;
|
|
139
|
+
}
|
|
140
|
+
function judgmentLine(frame, lane) {
|
|
141
|
+
if (frame.provenance?.whyNow) return frame.provenance.whyNow;
|
|
142
|
+
if (lane === "active") {
|
|
143
|
+
if (isBudgetOverride(frame)) return "Budget controls are blocking work until a board decision lands.";
|
|
144
|
+
if (frame.mode === "approval") return "A human decision is blocking work right now.";
|
|
145
|
+
if (frame.tone === "critical") return "This surfaced because it can displace the operator now.";
|
|
146
|
+
return "This is the clearest current operator focus.";
|
|
147
|
+
}
|
|
148
|
+
if (isBudgetOverride(frame)) return "Budget review is staged behind the current focus.";
|
|
149
|
+
if (lane === "queued") return "Important enough to keep visible, but not enough to displace now.";
|
|
150
|
+
return "Useful for awareness, but not interrupting.";
|
|
151
|
+
}
|
|
152
|
+
function frameUpdatedAt(frame, snapshotUpdatedAt) {
|
|
153
|
+
return frame.timing.updatedAt || snapshotUpdatedAt;
|
|
154
|
+
}
|
|
155
|
+
function approvalIdForFrame(frame) {
|
|
156
|
+
const [kind, id] = frame.taskId.split(":");
|
|
157
|
+
return kind === "approval" && id ? id : null;
|
|
158
|
+
}
|
|
159
|
+
function entityIdFromFrame(frame) {
|
|
160
|
+
const parts = frame.taskId.split(":");
|
|
161
|
+
return parts.length >= 2 ? parts.slice(1).join(":") : null;
|
|
162
|
+
}
|
|
163
|
+
function entityTypeFromFrame(frame) {
|
|
164
|
+
const parts = frame.taskId.split(":");
|
|
165
|
+
return parts.length >= 2 ? parts[0] : null;
|
|
166
|
+
}
|
|
167
|
+
function itemHref(frame, companyPrefix) {
|
|
168
|
+
const entityType = entityTypeFromFrame(frame);
|
|
169
|
+
const entityId = entityIdFromFrame(frame);
|
|
170
|
+
if (!entityType || !entityId || !companyPrefix) return null;
|
|
171
|
+
const pluralType = entityType === "run" ? "runs" : entityType === "approval" ? "approvals" : entityType === "issue" ? "issues" : entityType === "agent" ? "agents" : null;
|
|
172
|
+
if (!pluralType) return null;
|
|
173
|
+
return `/${companyPrefix}/${pluralType}/${entityId}`;
|
|
174
|
+
}
|
|
175
|
+
function costsHref(companyPrefix) {
|
|
176
|
+
return companyPrefix ? `/${companyPrefix}/costs` : null;
|
|
177
|
+
}
|
|
178
|
+
function responseKind(frame, lane) {
|
|
179
|
+
if (approvalIdForFrame(frame) && (frame.responseSpec?.kind === "approval" || frame.mode === "approval")) {
|
|
180
|
+
return "approval";
|
|
181
|
+
}
|
|
182
|
+
if (lane !== "ambient" && (frame.responseSpec?.kind === "acknowledge" || frame.mode === "status")) {
|
|
183
|
+
return "acknowledge";
|
|
184
|
+
}
|
|
185
|
+
return "none";
|
|
186
|
+
}
|
|
187
|
+
function compactTitle(frame) {
|
|
188
|
+
return frame.title.trim().length > 0 ? frame.title : "Untitled frame";
|
|
189
|
+
}
|
|
190
|
+
function approvalTitle(record) {
|
|
191
|
+
const payload = record.payload ?? {};
|
|
192
|
+
const explicitTitle = typeof payload.title === "string" ? payload.title : typeof payload.plan === "string" ? payload.plan : typeof payload.name === "string" ? payload.name : null;
|
|
193
|
+
if (explicitTitle) return explicitTitle;
|
|
194
|
+
return `${humanizeToken(record.type)} approval`;
|
|
195
|
+
}
|
|
196
|
+
function isBudgetOverrideRecord(record) {
|
|
197
|
+
return record.type === "budget_override_required";
|
|
198
|
+
}
|
|
199
|
+
function actionableApprovalRecords(records) {
|
|
200
|
+
if (!records) return [];
|
|
201
|
+
return records.filter((record) => record.status === "pending" || record.status === "revision_requested").sort((left, right) => {
|
|
202
|
+
const leftScore = isBudgetOverrideRecord(left) ? 1 : 0;
|
|
203
|
+
const rightScore = isBudgetOverrideRecord(right) ? 1 : 0;
|
|
204
|
+
if (leftScore !== rightScore) return rightScore - leftScore;
|
|
205
|
+
return (right.updatedAt ?? right.createdAt).localeCompare(left.updatedAt ?? left.createdAt);
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
function approvalRecordToFrame(record) {
|
|
209
|
+
const payload = record.payload ?? {};
|
|
210
|
+
const budgetOverride = isBudgetOverrideRecord(record);
|
|
211
|
+
const requestedAmount2 = typeof payload.requestedAmount === "string" ? payload.requestedAmount : void 0;
|
|
212
|
+
const reason = typeof payload.reason === "string" ? payload.reason : void 0;
|
|
213
|
+
const decisionContext = typeof payload.decisionContext === "string" ? payload.decisionContext : void 0;
|
|
214
|
+
const summary = typeof payload.summary === "string" ? payload.summary : budgetOverride ? "Budget controls are blocking work until a board decision lands." : "A board decision is blocking work in Paperclip.";
|
|
215
|
+
const updatedAt = record.updatedAt ?? record.createdAt;
|
|
216
|
+
return {
|
|
217
|
+
id: `approval-bootstrap:${record.id}`,
|
|
218
|
+
taskId: `approval:${record.id}`,
|
|
219
|
+
interactionId: `approval:${record.id}:approval`,
|
|
220
|
+
source: {
|
|
221
|
+
id: "paperclip:approval",
|
|
222
|
+
kind: "paperclip",
|
|
223
|
+
label: "Paperclip approval"
|
|
224
|
+
},
|
|
225
|
+
version: 1,
|
|
226
|
+
mode: "approval",
|
|
227
|
+
tone: "focused",
|
|
228
|
+
consequence: budgetOverride ? "high" : "medium",
|
|
229
|
+
title: approvalTitle(record),
|
|
230
|
+
summary,
|
|
231
|
+
context: {
|
|
232
|
+
items: [
|
|
233
|
+
{
|
|
234
|
+
id: "approval-type",
|
|
235
|
+
label: "Type",
|
|
236
|
+
value: humanizeToken(record.type)
|
|
237
|
+
},
|
|
238
|
+
...requestedAmount2 ? [{
|
|
239
|
+
id: "requested-amount",
|
|
240
|
+
label: "Requested amount",
|
|
241
|
+
value: requestedAmount2
|
|
242
|
+
}] : [],
|
|
243
|
+
...reason ? [{
|
|
244
|
+
id: "budget-reason",
|
|
245
|
+
label: "Reason",
|
|
246
|
+
value: reason
|
|
247
|
+
}] : [],
|
|
248
|
+
...decisionContext ? [{
|
|
249
|
+
id: "decision-context",
|
|
250
|
+
label: "Decision context",
|
|
251
|
+
value: decisionContext
|
|
252
|
+
}] : []
|
|
253
|
+
]
|
|
254
|
+
},
|
|
255
|
+
responseSpec: {
|
|
256
|
+
kind: "approval",
|
|
257
|
+
actions: [
|
|
258
|
+
{ id: "approve", label: "Approve", kind: "approve", emphasis: "primary" },
|
|
259
|
+
{ id: "reject", label: "Reject", kind: "reject", emphasis: "danger" },
|
|
260
|
+
...budgetOverride ? [{ id: "request-revision", label: "Request revision", kind: "cancel", emphasis: "secondary" }] : []
|
|
261
|
+
]
|
|
262
|
+
},
|
|
263
|
+
provenance: {
|
|
264
|
+
whyNow: budgetOverride ? "Budget controls are blocking work until a board decision lands." : "Paperclip is waiting on a human approval before work can continue.",
|
|
265
|
+
factors: budgetOverride ? ["budget stop", "approval", "operator decision"] : ["approval", "operator decision"]
|
|
266
|
+
},
|
|
267
|
+
timing: {
|
|
268
|
+
createdAt: record.createdAt,
|
|
269
|
+
updatedAt
|
|
270
|
+
},
|
|
271
|
+
metadata: {
|
|
272
|
+
approvalStatus: record.status,
|
|
273
|
+
approvalType: record.type
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
function frameSortScore(frame, lane) {
|
|
278
|
+
const toneWeight = frame.tone === "critical" ? 40 : frame.tone === "focused" ? 25 : 5;
|
|
279
|
+
const consequenceWeight = frame.consequence === "high" ? 30 : frame.consequence === "medium" ? 15 : 0;
|
|
280
|
+
const modeWeight = frame.mode === "approval" ? 12 : frame.mode === "choice" ? 8 : 0;
|
|
281
|
+
const laneWeight = lane === "active" ? 20 : lane === "queued" ? 10 : 0;
|
|
282
|
+
const budgetWeight = isBudgetOverride(frame) ? 10 : 0;
|
|
283
|
+
return toneWeight + consequenceWeight + modeWeight + laneWeight + budgetWeight;
|
|
284
|
+
}
|
|
285
|
+
function mergeSnapshotWithApprovals(snapshot, companyId, approvals) {
|
|
286
|
+
const base = snapshot ?? createEmptySnapshot(companyId);
|
|
287
|
+
const approvalFrames = actionableApprovalRecords(approvals).map(approvalRecordToFrame);
|
|
288
|
+
const baseNonApprovalActive = base.active && !approvalIdForFrame(base.active) ? base.active : null;
|
|
289
|
+
const baseNonApprovalQueued = base.queued.filter((frame) => !approvalIdForFrame(frame));
|
|
290
|
+
const baseAmbient = base.ambient.filter((frame) => !approvalIdForFrame(frame));
|
|
291
|
+
if (approvalFrames.length === 0) {
|
|
292
|
+
return {
|
|
293
|
+
...base,
|
|
294
|
+
active: baseNonApprovalActive,
|
|
295
|
+
queued: baseNonApprovalQueued,
|
|
296
|
+
ambient: baseAmbient,
|
|
297
|
+
counts: {
|
|
298
|
+
active: baseNonApprovalActive ? 1 : 0,
|
|
299
|
+
queued: baseNonApprovalQueued.length,
|
|
300
|
+
ambient: baseAmbient.length,
|
|
301
|
+
total: (baseNonApprovalActive ? 1 : 0) + baseNonApprovalQueued.length + baseAmbient.length
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
const candidates = [
|
|
306
|
+
...baseNonApprovalActive ? [{ frame: baseNonApprovalActive, lane: "active" }] : [],
|
|
307
|
+
...baseNonApprovalQueued.map((frame) => ({ frame, lane: "queued" })),
|
|
308
|
+
...approvalFrames.map((frame, index) => ({ frame, lane: index === 0 ? "active" : "queued" }))
|
|
309
|
+
].sort((left, right) => {
|
|
310
|
+
const byScore = frameSortScore(right.frame, right.lane) - frameSortScore(left.frame, left.lane);
|
|
311
|
+
if (byScore !== 0) return byScore;
|
|
312
|
+
return frameUpdatedAt(right.frame, base.updatedAt).localeCompare(frameUpdatedAt(left.frame, base.updatedAt));
|
|
313
|
+
});
|
|
314
|
+
const active = candidates[0]?.frame ?? null;
|
|
315
|
+
const queued = candidates.slice(1).map((entry) => entry.frame);
|
|
316
|
+
return {
|
|
317
|
+
...base,
|
|
318
|
+
active,
|
|
319
|
+
queued,
|
|
320
|
+
ambient: baseAmbient,
|
|
321
|
+
counts: {
|
|
322
|
+
active: active ? 1 : 0,
|
|
323
|
+
queued: queued.length,
|
|
324
|
+
ambient: baseAmbient.length,
|
|
325
|
+
total: (active ? 1 : 0) + queued.length + baseAmbient.length
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
function usePendingApprovals(companyId) {
|
|
330
|
+
const [data, setData] = useState(null);
|
|
331
|
+
const [loading, setLoading] = useState(true);
|
|
332
|
+
const [error, setError] = useState(null);
|
|
333
|
+
const [refreshVersion, setRefreshVersion] = useState(0);
|
|
334
|
+
useEffect(() => {
|
|
335
|
+
if (!companyId) {
|
|
336
|
+
setData(null);
|
|
337
|
+
setLoading(false);
|
|
338
|
+
setError(null);
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
let cancelled = false;
|
|
342
|
+
async function loadApprovals() {
|
|
343
|
+
try {
|
|
344
|
+
setError(null);
|
|
345
|
+
const response = await window.fetch(`/api/companies/${companyId}/approvals?status=pending`, {
|
|
346
|
+
method: "GET",
|
|
347
|
+
credentials: "same-origin"
|
|
348
|
+
});
|
|
349
|
+
if (!response.ok) {
|
|
350
|
+
throw new Error(`Failed to load approvals (${response.status})`);
|
|
351
|
+
}
|
|
352
|
+
const approvals = await response.json();
|
|
353
|
+
if (!cancelled) {
|
|
354
|
+
setData(approvals);
|
|
355
|
+
setLoading(false);
|
|
356
|
+
}
|
|
357
|
+
} catch (fetchError) {
|
|
358
|
+
if (!cancelled) {
|
|
359
|
+
setError(fetchError instanceof Error ? fetchError : new Error(String(fetchError)));
|
|
360
|
+
setLoading(false);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
setLoading(true);
|
|
365
|
+
void loadApprovals();
|
|
366
|
+
return () => {
|
|
367
|
+
cancelled = true;
|
|
368
|
+
};
|
|
369
|
+
}, [companyId, refreshVersion]);
|
|
370
|
+
return {
|
|
371
|
+
data,
|
|
372
|
+
loading,
|
|
373
|
+
error,
|
|
374
|
+
refresh: () => {
|
|
375
|
+
setRefreshVersion((value) => value + 1);
|
|
376
|
+
}
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
var ENTITY_PATTERN = /\b([A-Z][A-Z0-9]+-\d+)\b|"([^"]+)"/g;
|
|
380
|
+
function HighlightedTitle({ text }) {
|
|
381
|
+
const parts = [];
|
|
382
|
+
let lastIndex = 0;
|
|
383
|
+
let match;
|
|
384
|
+
const re = new RegExp(ENTITY_PATTERN.source, "g");
|
|
385
|
+
while ((match = re.exec(text)) !== null) {
|
|
386
|
+
if (match.index > lastIndex) {
|
|
387
|
+
parts.push(text.slice(lastIndex, match.index));
|
|
388
|
+
}
|
|
389
|
+
const token = match[1] ?? match[2] ?? match[0];
|
|
390
|
+
parts.push(
|
|
391
|
+
/* @__PURE__ */ jsx(Accent, { children: match[1] ? token : `"${token}"` }, match.index)
|
|
392
|
+
);
|
|
393
|
+
lastIndex = re.lastIndex;
|
|
394
|
+
}
|
|
395
|
+
if (lastIndex < text.length) {
|
|
396
|
+
parts.push(text.slice(lastIndex));
|
|
397
|
+
}
|
|
398
|
+
return parts.length > 1 ? /* @__PURE__ */ jsx(Fragment, { children: parts }) : null;
|
|
399
|
+
}
|
|
400
|
+
function renderTitle(frame) {
|
|
401
|
+
const text = compactTitle(frame);
|
|
402
|
+
const highlighted = HighlightedTitle({ text });
|
|
403
|
+
return highlighted ?? text;
|
|
404
|
+
}
|
|
405
|
+
function useAttentionPolling(companyId, refreshers, intervalMs = 5e3) {
|
|
406
|
+
const refreshersRef = useRef(refreshers);
|
|
407
|
+
refreshersRef.current = refreshers;
|
|
408
|
+
useEffect(() => {
|
|
409
|
+
if (!companyId) return;
|
|
410
|
+
const timer = window.setInterval(() => {
|
|
411
|
+
for (const refresh of refreshersRef.current) refresh();
|
|
412
|
+
}, intervalMs);
|
|
413
|
+
return () => {
|
|
414
|
+
window.clearInterval(timer);
|
|
415
|
+
};
|
|
416
|
+
}, [companyId, intervalMs]);
|
|
417
|
+
}
|
|
418
|
+
function Badge(props) {
|
|
419
|
+
return /* @__PURE__ */ jsx(
|
|
420
|
+
"span",
|
|
421
|
+
{
|
|
422
|
+
className: cn(
|
|
423
|
+
"inline-flex items-center justify-center rounded-full border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap shrink-0",
|
|
424
|
+
props.className
|
|
425
|
+
),
|
|
426
|
+
style: props.style,
|
|
427
|
+
children: props.children
|
|
428
|
+
}
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
function ActionButton(props) {
|
|
432
|
+
const toneClass = props.tone === "primary" ? "bg-green-700 text-white hover:bg-green-600" : props.tone === "danger" ? "bg-destructive text-white hover:bg-destructive/90 dark:bg-destructive/60" : "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50";
|
|
433
|
+
return /* @__PURE__ */ jsx(
|
|
434
|
+
"button",
|
|
435
|
+
{
|
|
436
|
+
type: "button",
|
|
437
|
+
onClick: props.onClick,
|
|
438
|
+
disabled: props.disabled,
|
|
439
|
+
className: cn(
|
|
440
|
+
"inline-flex h-8 items-center justify-center gap-2 rounded-md px-3 text-sm font-medium",
|
|
441
|
+
"transition-[color,background-color,border-color,box-shadow,opacity]",
|
|
442
|
+
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
|
|
443
|
+
"disabled:pointer-events-none disabled:opacity-50",
|
|
444
|
+
toneClass
|
|
445
|
+
),
|
|
446
|
+
children: props.label
|
|
447
|
+
}
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
function FrameActions(props) {
|
|
451
|
+
const actionMode = responseKind(props.frame, props.lane);
|
|
452
|
+
const isPending = props.pendingId === props.frame.id;
|
|
453
|
+
if (actionMode === "none") return null;
|
|
454
|
+
return /* @__PURE__ */ jsx("div", { className: cn("flex items-center gap-2", props.compact && "justify-end"), children: actionMode === "approval" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
455
|
+
isBudgetOverride(props.frame) ? /* @__PURE__ */ jsx(
|
|
456
|
+
ActionButton,
|
|
457
|
+
{
|
|
458
|
+
label: "Request revision",
|
|
459
|
+
tone: "secondary",
|
|
460
|
+
disabled: isPending,
|
|
461
|
+
onClick: () => void props.onRequestRevision(props.frame)
|
|
462
|
+
}
|
|
463
|
+
) : null,
|
|
464
|
+
/* @__PURE__ */ jsx(
|
|
465
|
+
ActionButton,
|
|
466
|
+
{
|
|
467
|
+
label: isPending ? "Submitting\u2026" : "Approve",
|
|
468
|
+
tone: "primary",
|
|
469
|
+
disabled: isPending,
|
|
470
|
+
onClick: () => void props.onApprove(props.frame)
|
|
471
|
+
}
|
|
472
|
+
),
|
|
473
|
+
/* @__PURE__ */ jsx(
|
|
474
|
+
ActionButton,
|
|
475
|
+
{
|
|
476
|
+
label: "Reject",
|
|
477
|
+
tone: "danger",
|
|
478
|
+
disabled: isPending,
|
|
479
|
+
onClick: () => void props.onReject(props.frame)
|
|
480
|
+
}
|
|
481
|
+
)
|
|
482
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
483
|
+
/* @__PURE__ */ jsx(
|
|
484
|
+
ActionButton,
|
|
485
|
+
{
|
|
486
|
+
label: isPending ? "Saving\u2026" : "Acknowledge",
|
|
487
|
+
tone: "primary",
|
|
488
|
+
disabled: isPending,
|
|
489
|
+
onClick: () => void props.onAcknowledge(props.frame)
|
|
490
|
+
}
|
|
491
|
+
),
|
|
492
|
+
/* @__PURE__ */ jsx(
|
|
493
|
+
ActionButton,
|
|
494
|
+
{
|
|
495
|
+
label: "Dismiss",
|
|
496
|
+
tone: "secondary",
|
|
497
|
+
disabled: isPending,
|
|
498
|
+
onClick: () => void props.onDismiss(props.frame)
|
|
499
|
+
}
|
|
500
|
+
)
|
|
501
|
+
] }) });
|
|
502
|
+
}
|
|
503
|
+
function ContextItems({ frame }) {
|
|
504
|
+
const items = frame.context?.items ?? [];
|
|
505
|
+
if (items.length === 0) return null;
|
|
506
|
+
return /* @__PURE__ */ jsx("div", { className: "grid gap-2 sm:grid-cols-2", children: items.map((item) => /* @__PURE__ */ jsxs("div", { className: "border border-border bg-secondary/50 px-3 py-2", children: [
|
|
507
|
+
/* @__PURE__ */ jsx("div", { className: "text-xs font-medium text-muted-foreground", children: item.label }),
|
|
508
|
+
/* @__PURE__ */ jsx("div", { className: "mt-1 text-sm text-foreground", children: item.value ?? "Available" })
|
|
509
|
+
] }, item.id)) });
|
|
510
|
+
}
|
|
511
|
+
function NowDetails(props) {
|
|
512
|
+
const { frame, snapshotUpdatedAt } = props;
|
|
513
|
+
const href = itemHref(frame, props.companyPrefix);
|
|
514
|
+
const budgetHref = isBudgetOverride(frame) ? costsHref(props.companyPrefix) : null;
|
|
515
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-3 border-t border-border pt-4", children: [
|
|
516
|
+
frame.summary ? /* @__PURE__ */ jsx("p", { className: "text-sm leading-relaxed text-muted-foreground", children: frame.summary }) : null,
|
|
517
|
+
/* @__PURE__ */ jsxs("div", { className: "grid gap-2 sm:grid-cols-2", children: [
|
|
518
|
+
/* @__PURE__ */ jsxs("div", { className: "border border-border bg-secondary/50 px-3 py-2", children: [
|
|
519
|
+
/* @__PURE__ */ jsx("div", { className: "text-xs font-medium text-muted-foreground", children: "Judgment" }),
|
|
520
|
+
/* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-foreground/90", children: judgmentLine(frame, "active") })
|
|
521
|
+
] }),
|
|
522
|
+
/* @__PURE__ */ jsxs("div", { className: "border border-border bg-secondary/50 px-3 py-2", children: [
|
|
523
|
+
/* @__PURE__ */ jsx("div", { className: "text-xs font-medium text-muted-foreground", children: "Request" }),
|
|
524
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-1 space-y-0.5 text-sm", children: [
|
|
525
|
+
/* @__PURE__ */ jsxs("div", { className: "text-foreground/90", children: [
|
|
526
|
+
sourceLabel(frame),
|
|
527
|
+
" \xB7 ",
|
|
528
|
+
modeLabel(frame)
|
|
529
|
+
] }),
|
|
530
|
+
/* @__PURE__ */ jsxs("div", { className: "text-muted-foreground", children: [
|
|
531
|
+
riskLabel(frame),
|
|
532
|
+
" \xB7 ",
|
|
533
|
+
formatRelativeTime(frameUpdatedAt(frame, snapshotUpdatedAt))
|
|
534
|
+
] }),
|
|
535
|
+
requestedAmount(frame) ? /* @__PURE__ */ jsxs("div", { className: "text-foreground/90", children: [
|
|
536
|
+
"Amount requested: ",
|
|
537
|
+
requestedAmount(frame)
|
|
538
|
+
] }) : null,
|
|
539
|
+
budgetReason(frame) ? /* @__PURE__ */ jsx("div", { className: "text-muted-foreground", children: budgetReason(frame) }) : null
|
|
540
|
+
] })
|
|
541
|
+
] })
|
|
542
|
+
] }),
|
|
543
|
+
/* @__PURE__ */ jsx(ContextItems, { frame }),
|
|
544
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-3", children: [
|
|
545
|
+
href ? /* @__PURE__ */ jsxs(
|
|
546
|
+
"a",
|
|
547
|
+
{
|
|
548
|
+
href,
|
|
549
|
+
target: "_blank",
|
|
550
|
+
rel: "noopener noreferrer",
|
|
551
|
+
className: "inline-flex items-center gap-1 text-xs font-medium text-muted-foreground underline underline-offset-2 hover:text-foreground",
|
|
552
|
+
children: [
|
|
553
|
+
"Open in Paperclip",
|
|
554
|
+
/* @__PURE__ */ jsxs("svg", { viewBox: "0 0 16 16", className: "h-3 w-3", fill: "none", stroke: "currentColor", strokeWidth: "1.5", children: [
|
|
555
|
+
/* @__PURE__ */ jsx("path", { d: "M6.5 3.5h-3a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1v-3" }),
|
|
556
|
+
/* @__PURE__ */ jsx("path", { d: "M8.5 1.5h6v6" }),
|
|
557
|
+
/* @__PURE__ */ jsx("path", { d: "M14.5 1.5l-7 7" })
|
|
558
|
+
] })
|
|
559
|
+
]
|
|
560
|
+
}
|
|
561
|
+
) : null,
|
|
562
|
+
budgetHref ? /* @__PURE__ */ jsx(Accent, { children: /* @__PURE__ */ jsxs(
|
|
563
|
+
"a",
|
|
564
|
+
{
|
|
565
|
+
href: budgetHref,
|
|
566
|
+
target: "_blank",
|
|
567
|
+
rel: "noopener noreferrer",
|
|
568
|
+
className: "inline-flex items-center gap-1 text-xs font-medium underline underline-offset-2 hover:opacity-80",
|
|
569
|
+
style: { color: "inherit" },
|
|
570
|
+
children: [
|
|
571
|
+
"Review in Costs",
|
|
572
|
+
/* @__PURE__ */ jsxs("svg", { viewBox: "0 0 16 16", className: "h-3 w-3", fill: "none", stroke: "currentColor", strokeWidth: "1.5", children: [
|
|
573
|
+
/* @__PURE__ */ jsx("path", { d: "M6.5 3.5h-3a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1v-3" }),
|
|
574
|
+
/* @__PURE__ */ jsx("path", { d: "M8.5 1.5h6v6" }),
|
|
575
|
+
/* @__PURE__ */ jsx("path", { d: "M14.5 1.5l-7 7" })
|
|
576
|
+
] })
|
|
577
|
+
]
|
|
578
|
+
}
|
|
579
|
+
) }) : null
|
|
580
|
+
] })
|
|
581
|
+
] });
|
|
582
|
+
}
|
|
583
|
+
function QuietNow() {
|
|
584
|
+
return /* @__PURE__ */ jsx("div", { className: "flex min-h-16 items-center", children: /* @__PURE__ */ jsx("div", { className: "text-sm text-muted-foreground", children: "Nothing active right now." }) });
|
|
585
|
+
}
|
|
586
|
+
function NowPane(props) {
|
|
587
|
+
const frame = props.snapshot.active;
|
|
588
|
+
const [detailsOpen, setDetailsOpen] = useState(false);
|
|
589
|
+
const activeFrameId = frame?.id ?? null;
|
|
590
|
+
useEffect(() => {
|
|
591
|
+
setDetailsOpen(false);
|
|
592
|
+
}, [activeFrameId]);
|
|
593
|
+
return /* @__PURE__ */ jsxs("section", { className: "border border-border bg-card shadow-sm", children: [
|
|
594
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3 border-b border-border bg-accent px-4 py-2.5 sm:px-6", children: [
|
|
595
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
596
|
+
/* @__PURE__ */ jsx(Accent, { className: "text-base leading-none", children: props.posture.glyph }),
|
|
597
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-0.5", children: [
|
|
598
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-end gap-2", children: [
|
|
599
|
+
/* @__PURE__ */ jsx(Accent, { className: "text-[18px] leading-none font-bold tracking-[0.04em]", children: props.brand.wordmark }),
|
|
600
|
+
/* @__PURE__ */ jsx(Accent, { className: "text-xs leading-none", children: props.posture.label })
|
|
601
|
+
] }),
|
|
602
|
+
/* @__PURE__ */ jsx("div", { className: "text-[10px] leading-none text-muted-foreground/60", children: props.brand.supportCopy })
|
|
603
|
+
] })
|
|
604
|
+
] }),
|
|
605
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 text-xs tabular-nums text-muted-foreground", children: [
|
|
606
|
+
/* @__PURE__ */ jsxs("span", { className: props.counts.active > 0 ? "font-semibold" : "", children: [
|
|
607
|
+
"now ",
|
|
608
|
+
props.counts.active > 0 ? /* @__PURE__ */ jsx(Accent, { children: props.counts.active }) : props.counts.active
|
|
609
|
+
] }),
|
|
610
|
+
/* @__PURE__ */ jsxs("span", { className: props.counts.queued > 0 ? "font-semibold" : "", children: [
|
|
611
|
+
"next ",
|
|
612
|
+
props.counts.queued > 0 ? /* @__PURE__ */ jsx(Accent, { children: props.counts.queued }) : props.counts.queued
|
|
613
|
+
] }),
|
|
614
|
+
/* @__PURE__ */ jsxs("span", { className: props.counts.ambient > 0 ? "font-semibold" : "", children: [
|
|
615
|
+
"ambient ",
|
|
616
|
+
props.counts.ambient > 0 ? /* @__PURE__ */ jsx(Accent, { children: props.counts.ambient }) : props.counts.ambient
|
|
617
|
+
] })
|
|
618
|
+
] })
|
|
619
|
+
] }),
|
|
620
|
+
/* @__PURE__ */ jsx("div", { style: { height: "1.25rem" }, "aria-hidden": "true" }),
|
|
621
|
+
/* @__PURE__ */ jsx("div", { className: "px-4 pb-5 sm:px-6", children: !frame ? /* @__PURE__ */ jsx(QuietNow, {}) : /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
622
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-xs font-medium uppercase tracking-wider text-muted-foreground", children: [
|
|
623
|
+
/* @__PURE__ */ jsx("span", { children: "Now" }),
|
|
624
|
+
/* @__PURE__ */ jsx("span", { className: "normal-case tracking-normal", style: { opacity: 0.6 }, children: sourceLabel(frame) })
|
|
625
|
+
] }),
|
|
626
|
+
/* @__PURE__ */ jsx("div", { className: "text-lg font-semibold leading-snug text-foreground", children: renderTitle(frame) }),
|
|
627
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
628
|
+
/* @__PURE__ */ jsx(Badge, { className: toneBadgeStyle(frame).className, style: toneBadgeStyle(frame).style, children: urgencyLabel(frame) }),
|
|
629
|
+
/* @__PURE__ */ jsx(Badge, { className: "border-border bg-secondary text-foreground/80", children: riskLabel(frame) }),
|
|
630
|
+
/* @__PURE__ */ jsx(Badge, { className: "border-border bg-secondary text-muted-foreground", children: modeLabel(frame) }),
|
|
631
|
+
isBudgetOverride(frame) ? /* @__PURE__ */ jsx(Badge, { className: "border-transparent text-white", style: ACCENT_BG_STYLE, children: "budget stop" }) : null,
|
|
632
|
+
/* @__PURE__ */ jsx("div", { className: "ml-auto flex items-center gap-2", children: /* @__PURE__ */ jsx(
|
|
633
|
+
FrameActions,
|
|
634
|
+
{
|
|
635
|
+
frame,
|
|
636
|
+
lane: "active",
|
|
637
|
+
pendingId: props.pendingId,
|
|
638
|
+
onApprove: props.onApprove,
|
|
639
|
+
onReject: props.onReject,
|
|
640
|
+
onRequestRevision: props.onRequestRevision,
|
|
641
|
+
onAcknowledge: props.onAcknowledge,
|
|
642
|
+
onDismiss: props.onDismiss
|
|
643
|
+
}
|
|
644
|
+
) })
|
|
645
|
+
] }),
|
|
646
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4 pb-1", children: [
|
|
647
|
+
/* @__PURE__ */ jsx(Accent, { children: /* @__PURE__ */ jsxs(
|
|
648
|
+
"button",
|
|
649
|
+
{
|
|
650
|
+
type: "button",
|
|
651
|
+
onClick: () => setDetailsOpen(!detailsOpen),
|
|
652
|
+
className: "flex items-center gap-1.5 text-xs font-medium transition-opacity hover:opacity-100",
|
|
653
|
+
style: { color: "inherit" },
|
|
654
|
+
children: [
|
|
655
|
+
/* @__PURE__ */ jsx(
|
|
656
|
+
"svg",
|
|
657
|
+
{
|
|
658
|
+
viewBox: "0 0 16 16",
|
|
659
|
+
className: cn("h-3 w-3 transition-transform", detailsOpen && "rotate-90"),
|
|
660
|
+
fill: "currentColor",
|
|
661
|
+
children: /* @__PURE__ */ jsx("path", { d: "M6.22 3.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L9.94 8 6.22 4.28a.75.75 0 0 1 0-1.06Z" })
|
|
662
|
+
}
|
|
663
|
+
),
|
|
664
|
+
detailsOpen ? "Hide details" : "Show details"
|
|
665
|
+
]
|
|
666
|
+
}
|
|
667
|
+
) }),
|
|
668
|
+
itemHref(frame, props.companyPrefix) ? /* @__PURE__ */ jsxs(
|
|
669
|
+
"a",
|
|
670
|
+
{
|
|
671
|
+
href: itemHref(frame, props.companyPrefix),
|
|
672
|
+
target: "_blank",
|
|
673
|
+
rel: "noopener noreferrer",
|
|
674
|
+
className: "flex items-center gap-1 text-xs font-medium text-muted-foreground transition-opacity hover:text-foreground",
|
|
675
|
+
children: [
|
|
676
|
+
"Open in Paperclip",
|
|
677
|
+
/* @__PURE__ */ jsxs("svg", { viewBox: "0 0 16 16", className: "h-3 w-3", fill: "none", stroke: "currentColor", strokeWidth: "1.5", children: [
|
|
678
|
+
/* @__PURE__ */ jsx("path", { d: "M6.5 3.5h-3a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1v-3" }),
|
|
679
|
+
/* @__PURE__ */ jsx("path", { d: "M8.5 1.5h6v6" }),
|
|
680
|
+
/* @__PURE__ */ jsx("path", { d: "M14.5 1.5l-7 7" })
|
|
681
|
+
] })
|
|
682
|
+
]
|
|
683
|
+
}
|
|
684
|
+
) : null
|
|
685
|
+
] }),
|
|
686
|
+
detailsOpen ? /* @__PURE__ */ jsx(NowDetails, { frame, snapshotUpdatedAt: props.snapshot.updatedAt, companyPrefix: props.companyPrefix }) : null
|
|
687
|
+
] }) })
|
|
688
|
+
] });
|
|
689
|
+
}
|
|
690
|
+
function NextRow(props) {
|
|
691
|
+
const { frame } = props;
|
|
692
|
+
const [expanded, setExpanded] = useState(false);
|
|
693
|
+
return /* @__PURE__ */ jsxs("div", { className: "border-b border-border/80 last:border-b-0", style: expanded ? { borderLeftWidth: 2, borderLeftColor: ACCENT_COLOR } : void 0, children: [
|
|
694
|
+
/* @__PURE__ */ jsxs(
|
|
695
|
+
"button",
|
|
696
|
+
{
|
|
697
|
+
type: "button",
|
|
698
|
+
onClick: () => setExpanded(!expanded),
|
|
699
|
+
className: "flex w-full items-center gap-3 px-4 py-3 text-left transition-colors hover:bg-accent/50 sm:px-6",
|
|
700
|
+
children: [
|
|
701
|
+
/* @__PURE__ */ jsx(Accent, { className: "w-5 shrink-0 text-xs font-medium tabular-nums", children: String(props.rank).padStart(2, "0") }),
|
|
702
|
+
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-sm font-medium text-foreground", children: renderTitle(frame) }),
|
|
703
|
+
isBudgetOverride(frame) ? /* @__PURE__ */ jsx(Badge, { className: "border-transparent shrink-0 text-white", style: ACCENT_BG_STYLE, children: "budget" }) : null,
|
|
704
|
+
/* @__PURE__ */ jsx(Badge, { className: cn("shrink-0", toneBadgeStyle(frame).className), style: toneBadgeStyle(frame).style, children: urgencyLabel(frame) }),
|
|
705
|
+
/* @__PURE__ */ jsx("span", { className: "shrink-0 text-xs text-muted-foreground", style: { opacity: 0.7 }, children: formatRelativeTime(frameUpdatedAt(frame, props.snapshotUpdatedAt)) }),
|
|
706
|
+
/* @__PURE__ */ jsx(
|
|
707
|
+
"svg",
|
|
708
|
+
{
|
|
709
|
+
viewBox: "0 0 16 16",
|
|
710
|
+
className: cn("h-3 w-3 shrink-0 text-muted-foreground transition-transform", expanded && "rotate-90"),
|
|
711
|
+
fill: "currentColor",
|
|
712
|
+
children: /* @__PURE__ */ jsx("path", { d: "M6.22 3.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L9.94 8 6.22 4.28a.75.75 0 0 1 0-1.06Z" })
|
|
713
|
+
}
|
|
714
|
+
)
|
|
715
|
+
]
|
|
716
|
+
}
|
|
717
|
+
),
|
|
718
|
+
expanded ? /* @__PURE__ */ jsxs("div", { className: "space-y-3 px-4 pb-3 pl-12 sm:px-6 sm:pl-14", children: [
|
|
719
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm leading-relaxed text-muted-foreground", children: frame.summary ?? judgmentLine(frame, "queued") }),
|
|
720
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-muted-foreground", children: [
|
|
721
|
+
/* @__PURE__ */ jsx("span", { children: riskLabel(frame) }),
|
|
722
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
723
|
+
"updated ",
|
|
724
|
+
formatRelativeTime(frameUpdatedAt(frame, props.snapshotUpdatedAt))
|
|
725
|
+
] }),
|
|
726
|
+
itemHref(frame, props.companyPrefix) ? /* @__PURE__ */ jsx(
|
|
727
|
+
"a",
|
|
728
|
+
{
|
|
729
|
+
href: itemHref(frame, props.companyPrefix),
|
|
730
|
+
className: "font-medium underline underline-offset-2 hover:text-foreground",
|
|
731
|
+
children: "View in Paperclip"
|
|
732
|
+
}
|
|
733
|
+
) : null
|
|
734
|
+
] }),
|
|
735
|
+
/* @__PURE__ */ jsx(
|
|
736
|
+
FrameActions,
|
|
737
|
+
{
|
|
738
|
+
frame,
|
|
739
|
+
lane: "queued",
|
|
740
|
+
pendingId: props.pendingId,
|
|
741
|
+
onApprove: props.onApprove,
|
|
742
|
+
onReject: props.onReject,
|
|
743
|
+
onRequestRevision: props.onRequestRevision,
|
|
744
|
+
onAcknowledge: props.onAcknowledge,
|
|
745
|
+
onDismiss: props.onDismiss
|
|
746
|
+
}
|
|
747
|
+
)
|
|
748
|
+
] }) : null
|
|
749
|
+
] });
|
|
750
|
+
}
|
|
751
|
+
function NextLane(props) {
|
|
752
|
+
return /* @__PURE__ */ jsxs("section", { className: "border border-border/80 bg-card", children: [
|
|
753
|
+
/* @__PURE__ */ jsx("div", { className: "border-b border-border/80 px-4 py-2.5 sm:px-6", children: /* @__PURE__ */ jsx("h2", { className: "text-xs font-medium uppercase tracking-wider text-muted-foreground", children: "Next" }) }),
|
|
754
|
+
props.rows.length === 0 ? /* @__PURE__ */ jsx("div", { className: "px-4 py-4 text-sm text-muted-foreground sm:px-6", style: { opacity: 0.7 }, children: "Nothing is staged behind the current focus." }) : props.rows.map((row, i) => /* @__PURE__ */ jsx(
|
|
755
|
+
NextRow,
|
|
756
|
+
{
|
|
757
|
+
rank: i + 1,
|
|
758
|
+
frame: row.frame,
|
|
759
|
+
snapshotUpdatedAt: props.snapshotUpdatedAt,
|
|
760
|
+
companyPrefix: props.companyPrefix,
|
|
761
|
+
pendingId: props.pendingId,
|
|
762
|
+
onApprove: props.onApprove,
|
|
763
|
+
onReject: props.onReject,
|
|
764
|
+
onRequestRevision: props.onRequestRevision,
|
|
765
|
+
onAcknowledge: props.onAcknowledge,
|
|
766
|
+
onDismiss: props.onDismiss
|
|
767
|
+
},
|
|
768
|
+
row.frame.id
|
|
769
|
+
))
|
|
770
|
+
] });
|
|
771
|
+
}
|
|
772
|
+
function AmbientRow(props) {
|
|
773
|
+
const { frame, snapshotUpdatedAt } = props;
|
|
774
|
+
const [expanded, setExpanded] = useState(false);
|
|
775
|
+
const href = itemHref(frame, props.companyPrefix);
|
|
776
|
+
return /* @__PURE__ */ jsxs("div", { style: { borderBottomWidth: 1, borderBottomColor: "var(--border)", opacity: 0.7 }, className: "last:border-b-0", children: [
|
|
777
|
+
/* @__PURE__ */ jsxs(
|
|
778
|
+
"button",
|
|
779
|
+
{
|
|
780
|
+
type: "button",
|
|
781
|
+
onClick: () => setExpanded(!expanded),
|
|
782
|
+
className: "flex w-full items-center gap-3 px-4 py-2.5 text-left transition-colors hover:bg-accent/50 sm:px-6",
|
|
783
|
+
children: [
|
|
784
|
+
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-sm text-muted-foreground", children: renderTitle(frame) }),
|
|
785
|
+
/* @__PURE__ */ jsx("span", { className: "shrink-0 text-xs text-muted-foreground", style: { opacity: 0.6 }, children: sourceLabel(frame) }),
|
|
786
|
+
/* @__PURE__ */ jsx("span", { className: "shrink-0 text-xs text-muted-foreground", style: { opacity: 0.5 }, children: formatRelativeTime(frameUpdatedAt(frame, snapshotUpdatedAt)) }),
|
|
787
|
+
/* @__PURE__ */ jsx(
|
|
788
|
+
"svg",
|
|
789
|
+
{
|
|
790
|
+
viewBox: "0 0 16 16",
|
|
791
|
+
className: cn("h-3 w-3 shrink-0 text-muted-foreground transition-transform", expanded && "rotate-90"),
|
|
792
|
+
fill: "currentColor",
|
|
793
|
+
style: { opacity: 0.4 },
|
|
794
|
+
children: /* @__PURE__ */ jsx("path", { d: "M6.22 3.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L9.94 8 6.22 4.28a.75.75 0 0 1 0-1.06Z" })
|
|
795
|
+
}
|
|
796
|
+
)
|
|
797
|
+
]
|
|
798
|
+
}
|
|
799
|
+
),
|
|
800
|
+
expanded ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 px-4 pb-3 sm:px-6", children: [
|
|
801
|
+
href ? /* @__PURE__ */ jsx(
|
|
802
|
+
"a",
|
|
803
|
+
{
|
|
804
|
+
href,
|
|
805
|
+
className: "text-xs font-medium underline underline-offset-2 text-muted-foreground hover:text-foreground",
|
|
806
|
+
children: "View in Paperclip"
|
|
807
|
+
}
|
|
808
|
+
) : null,
|
|
809
|
+
/* @__PURE__ */ jsxs("div", { className: "ml-auto flex items-center gap-2", children: [
|
|
810
|
+
/* @__PURE__ */ jsx(
|
|
811
|
+
ActionButton,
|
|
812
|
+
{
|
|
813
|
+
label: props.pendingId === frame.id ? "Saving\u2026" : "Acknowledge",
|
|
814
|
+
tone: "secondary",
|
|
815
|
+
disabled: props.pendingId === frame.id,
|
|
816
|
+
onClick: () => void props.onAcknowledge(frame)
|
|
817
|
+
}
|
|
818
|
+
),
|
|
819
|
+
/* @__PURE__ */ jsx(
|
|
820
|
+
ActionButton,
|
|
821
|
+
{
|
|
822
|
+
label: "Dismiss",
|
|
823
|
+
tone: "secondary",
|
|
824
|
+
disabled: props.pendingId === frame.id,
|
|
825
|
+
onClick: () => void props.onDismiss(frame)
|
|
826
|
+
}
|
|
827
|
+
)
|
|
828
|
+
] })
|
|
829
|
+
] }) : null
|
|
830
|
+
] });
|
|
831
|
+
}
|
|
832
|
+
function AmbientLane(props) {
|
|
833
|
+
return /* @__PURE__ */ jsxs("section", { style: { borderWidth: 1, borderColor: "var(--border)", opacity: 0.85 }, className: "bg-card", children: [
|
|
834
|
+
/* @__PURE__ */ jsx("div", { style: { borderBottomWidth: 1, borderBottomColor: "var(--border)" }, className: "px-4 py-2.5 sm:px-6", children: /* @__PURE__ */ jsx("h2", { className: "text-xs font-medium uppercase tracking-wider text-muted-foreground", style: { opacity: 0.6 }, children: "Ambient" }) }),
|
|
835
|
+
props.rows.length === 0 ? /* @__PURE__ */ jsx("div", { className: "px-4 py-4 text-sm text-muted-foreground sm:px-6", style: { opacity: 0.4 }, children: "Nothing in peripheral view." }) : props.rows.map((row) => /* @__PURE__ */ jsx(
|
|
836
|
+
AmbientRow,
|
|
837
|
+
{
|
|
838
|
+
frame: row.frame,
|
|
839
|
+
snapshotUpdatedAt: props.snapshotUpdatedAt,
|
|
840
|
+
companyPrefix: props.companyPrefix,
|
|
841
|
+
pendingId: props.pendingId,
|
|
842
|
+
onAcknowledge: props.onAcknowledge,
|
|
843
|
+
onDismiss: props.onDismiss
|
|
844
|
+
},
|
|
845
|
+
row.frame.id
|
|
846
|
+
))
|
|
847
|
+
] });
|
|
848
|
+
}
|
|
849
|
+
function DashboardWidget(props) {
|
|
850
|
+
const companyId = props.context.companyId;
|
|
851
|
+
const brand = currentSurfaceBrand();
|
|
852
|
+
const summaryQuery = usePluginData("attention-summary", { companyId });
|
|
853
|
+
const approvalsQuery = usePendingApprovals(companyId);
|
|
854
|
+
useAttentionPolling(companyId, [summaryQuery.refresh, approvalsQuery.refresh]);
|
|
855
|
+
const merged = useMemo(
|
|
856
|
+
() => companyId ? mergeSnapshotWithApprovals(summaryQuery.data, companyId, approvalsQuery.data) : null,
|
|
857
|
+
[summaryQuery.data, approvalsQuery.data, companyId]
|
|
858
|
+
);
|
|
859
|
+
const loading = summaryQuery.loading || approvalsQuery.loading;
|
|
860
|
+
const error = summaryQuery.error ?? approvalsQuery.error;
|
|
861
|
+
const data = merged;
|
|
862
|
+
if (!companyId) return /* @__PURE__ */ jsxs("div", { className: "border border-border bg-card p-4 shadow-sm text-sm text-muted-foreground", children: [
|
|
863
|
+
"Open a company to see ",
|
|
864
|
+
brand.wordmark,
|
|
865
|
+
"."
|
|
866
|
+
] });
|
|
867
|
+
if (loading) return /* @__PURE__ */ jsx("div", { className: "border border-border bg-card p-4 shadow-sm text-sm text-muted-foreground", children: brand.loadingLabel });
|
|
868
|
+
if (error) return /* @__PURE__ */ jsxs("div", { className: "border border-border bg-card p-4 shadow-sm text-sm text-muted-foreground", children: [
|
|
869
|
+
"Plugin error: ",
|
|
870
|
+
error.message
|
|
871
|
+
] });
|
|
872
|
+
if (!data) return /* @__PURE__ */ jsx("div", { className: "border border-border bg-card p-4 shadow-sm text-sm text-muted-foreground", children: brand.headingEmptyState });
|
|
873
|
+
const posture = postureForSnapshot(data);
|
|
874
|
+
return /* @__PURE__ */ jsxs("div", { className: "border border-border bg-card px-4 py-2.5 shadow-sm", children: [
|
|
875
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2 text-xs", children: [
|
|
876
|
+
/* @__PURE__ */ jsx(Accent, { className: "pt-0.5 text-sm", children: posture.glyph }),
|
|
877
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
878
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-end gap-2", children: [
|
|
879
|
+
/* @__PURE__ */ jsx(Accent, { className: "font-semibold tracking-[0.04em]", children: brand.wordmark }),
|
|
880
|
+
/* @__PURE__ */ jsx(Accent, { className: "text-[11px]", children: posture.label })
|
|
881
|
+
] }),
|
|
882
|
+
/* @__PURE__ */ jsx("div", { className: "mt-0.5 text-[10px] leading-none text-muted-foreground/60", children: brand.supportCopy })
|
|
883
|
+
] }),
|
|
884
|
+
/* @__PURE__ */ jsxs("span", { className: "ml-auto tabular-nums text-muted-foreground", children: [
|
|
885
|
+
"now ",
|
|
886
|
+
data.counts.active,
|
|
887
|
+
" \xB7 next ",
|
|
888
|
+
data.counts.queued,
|
|
889
|
+
" \xB7 ambient ",
|
|
890
|
+
data.counts.ambient
|
|
891
|
+
] })
|
|
892
|
+
] }),
|
|
893
|
+
data.active ? /* @__PURE__ */ jsx("div", { className: "mt-2 truncate text-sm font-medium text-foreground", children: data.active.title }) : /* @__PURE__ */ jsx("div", { className: "mt-2 text-sm text-muted-foreground", children: "No active interruption right now." })
|
|
894
|
+
] });
|
|
895
|
+
}
|
|
896
|
+
function AttentionSidebarLink({ context }) {
|
|
897
|
+
const companyId = context.companyId;
|
|
898
|
+
const brand = currentSurfaceBrand();
|
|
899
|
+
const href = pluginPagePath(context.companyPrefix);
|
|
900
|
+
const isActive = typeof window !== "undefined" && window.location.pathname === href;
|
|
901
|
+
const summaryQuery = usePluginData("attention-summary", { companyId });
|
|
902
|
+
const approvalsQuery = usePendingApprovals(companyId);
|
|
903
|
+
useAttentionPolling(companyId, [summaryQuery.refresh, approvalsQuery.refresh]);
|
|
904
|
+
const merged = useMemo(
|
|
905
|
+
() => companyId ? mergeSnapshotWithApprovals(summaryQuery.data, companyId, approvalsQuery.data) : null,
|
|
906
|
+
[summaryQuery.data, approvalsQuery.data, companyId]
|
|
907
|
+
);
|
|
908
|
+
const actionable = merged ? actionableCount(merged) : 0;
|
|
909
|
+
return /* @__PURE__ */ jsxs(
|
|
910
|
+
"a",
|
|
911
|
+
{
|
|
912
|
+
href,
|
|
913
|
+
"aria-current": isActive ? "page" : void 0,
|
|
914
|
+
className: cn(
|
|
915
|
+
"flex items-center gap-2.5 px-3 py-2 text-[13px] font-medium transition-colors",
|
|
916
|
+
isActive ? "bg-accent text-foreground" : "text-foreground/80 hover:bg-accent/50 hover:text-foreground"
|
|
917
|
+
),
|
|
918
|
+
children: [
|
|
919
|
+
/* @__PURE__ */ jsx("span", { className: "relative shrink-0", "aria-hidden": "true", children: /* @__PURE__ */ jsxs(
|
|
920
|
+
"svg",
|
|
921
|
+
{
|
|
922
|
+
viewBox: "0 0 24 24",
|
|
923
|
+
className: "h-4 w-4",
|
|
924
|
+
fill: "none",
|
|
925
|
+
stroke: "currentColor",
|
|
926
|
+
strokeWidth: "1.9",
|
|
927
|
+
strokeLinecap: "round",
|
|
928
|
+
strokeLinejoin: "round",
|
|
929
|
+
children: [
|
|
930
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "9" }),
|
|
931
|
+
/* @__PURE__ */ jsx("path", { d: "m14.3 8 5.2 9" }),
|
|
932
|
+
/* @__PURE__ */ jsx("path", { d: "M9.7 8h10.4" }),
|
|
933
|
+
/* @__PURE__ */ jsx("path", { d: "m7.4 12 5.2-9" }),
|
|
934
|
+
/* @__PURE__ */ jsx("path", { d: "m9.7 16-5.2-9" }),
|
|
935
|
+
/* @__PURE__ */ jsx("path", { d: "M14.3 16H3.9" }),
|
|
936
|
+
/* @__PURE__ */ jsx("path", { d: "m16.6 12-5.2 9" })
|
|
937
|
+
]
|
|
938
|
+
}
|
|
939
|
+
) }),
|
|
940
|
+
/* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: brand.wordmark }),
|
|
941
|
+
actionable > 0 ? /* @__PURE__ */ jsx("span", { className: "inline-flex items-center justify-center rounded-full px-1.5 py-0.5 text-xs leading-none font-medium text-white", style: ACCENT_BG_STYLE, children: actionable }) : null
|
|
942
|
+
]
|
|
943
|
+
}
|
|
944
|
+
);
|
|
945
|
+
}
|
|
946
|
+
function AttentionPage(props) {
|
|
947
|
+
const companyId = props.context.companyId;
|
|
948
|
+
const brand = currentSurfaceBrand();
|
|
949
|
+
const summaryQuery = usePluginData("attention-summary", { companyId });
|
|
950
|
+
const approvalsQuery = usePendingApprovals(companyId);
|
|
951
|
+
useAttentionPolling(companyId, [summaryQuery.refresh, approvalsQuery.refresh]);
|
|
952
|
+
const snapshot = useMemo(
|
|
953
|
+
() => companyId ? mergeSnapshotWithApprovals(summaryQuery.data, companyId, approvalsQuery.data) : null,
|
|
954
|
+
[summaryQuery.data, approvalsQuery.data, companyId]
|
|
955
|
+
);
|
|
956
|
+
const loading = summaryQuery.loading || approvalsQuery.loading;
|
|
957
|
+
const error = summaryQuery.error ?? approvalsQuery.error;
|
|
958
|
+
const refresh = summaryQuery.refresh;
|
|
959
|
+
const acknowledge = usePluginAction("acknowledge-frame");
|
|
960
|
+
const dismiss = usePluginAction("dismiss-frame");
|
|
961
|
+
const [pendingId, setPendingId] = useState(null);
|
|
962
|
+
const [statusOverride, setStatusOverride] = useState(null);
|
|
963
|
+
useEffect(() => {
|
|
964
|
+
if (!statusOverride) return;
|
|
965
|
+
const timer = window.setTimeout(() => {
|
|
966
|
+
setStatusOverride(null);
|
|
967
|
+
}, 4e3);
|
|
968
|
+
return () => {
|
|
969
|
+
window.clearTimeout(timer);
|
|
970
|
+
};
|
|
971
|
+
}, [statusOverride]);
|
|
972
|
+
const posture = useMemo(() => snapshot ? postureForSnapshot(snapshot) : null, [snapshot]);
|
|
973
|
+
const nextRows = useMemo(
|
|
974
|
+
() => snapshot ? snapshot.queued.map((frame) => ({ frame, lane: "queued" })) : [],
|
|
975
|
+
[snapshot]
|
|
976
|
+
);
|
|
977
|
+
const ambientRows = useMemo(
|
|
978
|
+
() => snapshot ? snapshot.ambient.map((frame) => ({ frame, lane: "ambient" })) : [],
|
|
979
|
+
[snapshot]
|
|
980
|
+
);
|
|
981
|
+
function nudgeRefresh() {
|
|
982
|
+
refresh();
|
|
983
|
+
approvalsQuery.refresh();
|
|
984
|
+
window.setTimeout(() => refresh(), 500);
|
|
985
|
+
window.setTimeout(() => approvalsQuery.refresh(), 500);
|
|
986
|
+
}
|
|
987
|
+
async function acknowledgeFrame(frame) {
|
|
988
|
+
setPendingId(frame.id);
|
|
989
|
+
try {
|
|
990
|
+
await acknowledge({ companyId, taskId: frame.taskId, interactionId: frame.interactionId });
|
|
991
|
+
setStatusOverride(`Acknowledged ${compactTitle(frame)}.`);
|
|
992
|
+
nudgeRefresh();
|
|
993
|
+
} catch (actionError) {
|
|
994
|
+
setStatusOverride(actionError instanceof Error ? actionError.message : "Failed to acknowledge frame.");
|
|
995
|
+
} finally {
|
|
996
|
+
setPendingId(null);
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
async function dismissFrame(frame) {
|
|
1000
|
+
setPendingId(frame.id);
|
|
1001
|
+
try {
|
|
1002
|
+
await dismiss({ companyId, taskId: frame.taskId, interactionId: frame.interactionId });
|
|
1003
|
+
setStatusOverride(`Dismissed ${compactTitle(frame)}.`);
|
|
1004
|
+
nudgeRefresh();
|
|
1005
|
+
} catch (actionError) {
|
|
1006
|
+
setStatusOverride(actionError instanceof Error ? actionError.message : "Failed to dismiss frame.");
|
|
1007
|
+
} finally {
|
|
1008
|
+
setPendingId(null);
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
async function submitApprovalDecision(frame, decision) {
|
|
1012
|
+
const approvalId = approvalIdForFrame(frame);
|
|
1013
|
+
if (!approvalId) {
|
|
1014
|
+
setStatusOverride("This frame is not backed by a Paperclip approval.");
|
|
1015
|
+
return;
|
|
1016
|
+
}
|
|
1017
|
+
setPendingId(frame.id);
|
|
1018
|
+
try {
|
|
1019
|
+
const actionPath = decision === "approve" ? "approve" : "reject";
|
|
1020
|
+
const response = await window.fetch(`/api/approvals/${approvalId}/${actionPath}`, {
|
|
1021
|
+
method: "POST",
|
|
1022
|
+
credentials: "same-origin",
|
|
1023
|
+
headers: {
|
|
1024
|
+
"Content-Type": "application/json"
|
|
1025
|
+
},
|
|
1026
|
+
body: JSON.stringify({})
|
|
1027
|
+
});
|
|
1028
|
+
if (!response.ok) {
|
|
1029
|
+
throw new Error(`Approval ${actionPath} failed (${response.status}).`);
|
|
1030
|
+
}
|
|
1031
|
+
setStatusOverride(`${decision === "approve" ? "Approved" : "Rejected"} ${compactTitle(frame)}.`);
|
|
1032
|
+
nudgeRefresh();
|
|
1033
|
+
} catch (actionError) {
|
|
1034
|
+
setStatusOverride(actionError instanceof Error ? actionError.message : "Failed to submit approval decision.");
|
|
1035
|
+
} finally {
|
|
1036
|
+
setPendingId(null);
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
async function requestApprovalRevision(frame) {
|
|
1040
|
+
const approvalId = approvalIdForFrame(frame);
|
|
1041
|
+
if (!approvalId) {
|
|
1042
|
+
setStatusOverride("This frame is not backed by a Paperclip approval.");
|
|
1043
|
+
return;
|
|
1044
|
+
}
|
|
1045
|
+
setPendingId(frame.id);
|
|
1046
|
+
try {
|
|
1047
|
+
const response = await window.fetch(`/api/approvals/${approvalId}/request-revision`, {
|
|
1048
|
+
method: "POST",
|
|
1049
|
+
credentials: "same-origin",
|
|
1050
|
+
headers: {
|
|
1051
|
+
"Content-Type": "application/json"
|
|
1052
|
+
},
|
|
1053
|
+
body: JSON.stringify({})
|
|
1054
|
+
});
|
|
1055
|
+
if (!response.ok) {
|
|
1056
|
+
throw new Error(`Approval request-revision failed (${response.status}).`);
|
|
1057
|
+
}
|
|
1058
|
+
setStatusOverride(`Requested revision for ${compactTitle(frame)}.`);
|
|
1059
|
+
nudgeRefresh();
|
|
1060
|
+
} catch (actionError) {
|
|
1061
|
+
setStatusOverride(actionError instanceof Error ? actionError.message : "Failed to request approval revision.");
|
|
1062
|
+
} finally {
|
|
1063
|
+
setPendingId(null);
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
if (!companyId) return /* @__PURE__ */ jsxs("div", { className: "border border-border bg-card p-4 shadow-sm text-sm text-muted-foreground", children: [
|
|
1067
|
+
"Select a company to open ",
|
|
1068
|
+
brand.wordmark,
|
|
1069
|
+
"."
|
|
1070
|
+
] });
|
|
1071
|
+
if (!snapshot && loading) return /* @__PURE__ */ jsxs("div", { className: "border border-border bg-card p-4 shadow-sm text-sm text-muted-foreground", children: [
|
|
1072
|
+
"Loading attention center",
|
|
1073
|
+
"\u2026"
|
|
1074
|
+
] });
|
|
1075
|
+
if (!snapshot && error) return /* @__PURE__ */ jsxs("div", { className: "border border-border bg-card p-4 shadow-sm text-sm text-muted-foreground", children: [
|
|
1076
|
+
"Plugin error: ",
|
|
1077
|
+
error.message
|
|
1078
|
+
] });
|
|
1079
|
+
if (!snapshot || !posture) return /* @__PURE__ */ jsxs("div", { className: "border border-border bg-card p-4 shadow-sm text-sm text-muted-foreground", children: [
|
|
1080
|
+
"No ",
|
|
1081
|
+
brand.key === "focus" ? "focus" : "attention",
|
|
1082
|
+
" state has been captured for this company yet."
|
|
1083
|
+
] });
|
|
1084
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-5", children: [
|
|
1085
|
+
/* @__PURE__ */ jsx(
|
|
1086
|
+
NowPane,
|
|
1087
|
+
{
|
|
1088
|
+
snapshot,
|
|
1089
|
+
posture,
|
|
1090
|
+
brand,
|
|
1091
|
+
companyPrefix: props.context.companyPrefix,
|
|
1092
|
+
counts: snapshot.counts,
|
|
1093
|
+
pendingId,
|
|
1094
|
+
onApprove: (frame) => submitApprovalDecision(frame, "approve"),
|
|
1095
|
+
onReject: (frame) => submitApprovalDecision(frame, "reject"),
|
|
1096
|
+
onRequestRevision: requestApprovalRevision,
|
|
1097
|
+
onAcknowledge: acknowledgeFrame,
|
|
1098
|
+
onDismiss: dismissFrame
|
|
1099
|
+
}
|
|
1100
|
+
),
|
|
1101
|
+
/* @__PURE__ */ jsx(
|
|
1102
|
+
NextLane,
|
|
1103
|
+
{
|
|
1104
|
+
rows: nextRows,
|
|
1105
|
+
snapshotUpdatedAt: snapshot.updatedAt,
|
|
1106
|
+
companyPrefix: props.context.companyPrefix,
|
|
1107
|
+
pendingId,
|
|
1108
|
+
onApprove: (frame) => submitApprovalDecision(frame, "approve"),
|
|
1109
|
+
onReject: (frame) => submitApprovalDecision(frame, "reject"),
|
|
1110
|
+
onRequestRevision: requestApprovalRevision,
|
|
1111
|
+
onAcknowledge: acknowledgeFrame,
|
|
1112
|
+
onDismiss: dismissFrame
|
|
1113
|
+
}
|
|
1114
|
+
),
|
|
1115
|
+
/* @__PURE__ */ jsx(
|
|
1116
|
+
AmbientLane,
|
|
1117
|
+
{
|
|
1118
|
+
rows: ambientRows,
|
|
1119
|
+
snapshotUpdatedAt: snapshot.updatedAt,
|
|
1120
|
+
companyPrefix: props.context.companyPrefix,
|
|
1121
|
+
pendingId,
|
|
1122
|
+
onAcknowledge: acknowledgeFrame,
|
|
1123
|
+
onDismiss: dismissFrame
|
|
1124
|
+
}
|
|
1125
|
+
)
|
|
1126
|
+
] });
|
|
1127
|
+
}
|
|
1128
|
+
export {
|
|
1129
|
+
AttentionPage,
|
|
1130
|
+
AttentionSidebarLink,
|
|
1131
|
+
DashboardWidget
|
|
1132
|
+
};
|
|
1133
|
+
//# sourceMappingURL=index.js.map
|