@burdenoff/microfe-timecampus 2026.716.2 → 2026.719.2
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/services/createSandboxAssistantTransport.d.ts +1 -1
- package/dist/services/createSandboxAssistantTransport.js +6 -4
- package/dist/services/createSandboxAssistantTransport.js.map +1 -1
- package/dist/timecampus/pages/meetings/MeetingDetailPage.d.ts.map +1 -1
- package/dist/timecampus/pages/meetings/MeetingDetailPage.js +231 -226
- package/dist/timecampus/pages/meetings/MeetingDetailPage.js.map +1 -1
- package/dist/utils/pageContext.d.ts +13 -5
- package/dist/utils/pageContext.d.ts.map +1 -1
- package/dist/utils/pageContext.js +61 -0
- package/dist/utils/pageContext.js.map +1 -0
- package/package.json +8 -3
|
@@ -16,7 +16,7 @@ export interface AssistantTransport {
|
|
|
16
16
|
sendPrompt: (args: AssistantSendArgs) => Promise<{
|
|
17
17
|
text: string;
|
|
18
18
|
}>;
|
|
19
|
-
prewarm?: (level?:
|
|
19
|
+
prewarm?: (level?: "probe" | "full") => void;
|
|
20
20
|
suspend?: () => void;
|
|
21
21
|
}
|
|
22
22
|
export declare function useSandboxAssistantTransport(): AssistantTransport;
|
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { gatherPageContext as e } from "../utils/pageContext.js";
|
|
2
|
+
import { useSandboxAssistantTransport as t } from "@burdenoff/fe-libs/shared/assistant";
|
|
2
3
|
//#region src/services/createSandboxAssistantTransport.ts
|
|
3
|
-
function
|
|
4
|
-
return
|
|
4
|
+
function n() {
|
|
5
|
+
return t({
|
|
5
6
|
product: "timecampus",
|
|
7
|
+
gatherPageContext: e,
|
|
6
8
|
enableCredits: !0
|
|
7
9
|
});
|
|
8
10
|
}
|
|
9
11
|
//#endregion
|
|
10
|
-
export {
|
|
12
|
+
export { n as useSandboxAssistantTransport };
|
|
11
13
|
|
|
12
14
|
//# sourceMappingURL=createSandboxAssistantTransport.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createSandboxAssistantTransport.js","names":[],"sources":["../../src/services/createSandboxAssistantTransport.ts"],"sourcesContent":["/**\n * timecampus adapter for the shared fe-libs AssistantWidget transport.\n *\n * The sandbox lifecycle + poll/stream loop now lives in\n * `@burdenoff/fe-libs/shared/assistant` (one implementation for all products);\n * this is a thin wrapper. The exported name/shape are unchanged so app-shell\n * imports don't move. The public return type is a LOCALLY-declared structural\n * mirror — never re-export fe-libs types (vite-plugin-dts path rewriting).\n */\n\nimport { useSandboxAssistantTransport as useSharedTransport } from
|
|
1
|
+
{"version":3,"file":"createSandboxAssistantTransport.js","names":[],"sources":["../../src/services/createSandboxAssistantTransport.ts"],"sourcesContent":["/**\n * timecampus adapter for the shared fe-libs AssistantWidget transport.\n *\n * The sandbox lifecycle + poll/stream loop now lives in\n * `@burdenoff/fe-libs/shared/assistant` (one implementation for all products);\n * this is a thin wrapper. The exported name/shape are unchanged so app-shell\n * imports don't move. The public return type is a LOCALLY-declared structural\n * mirror — never re-export fe-libs types (vite-plugin-dts path rewriting).\n */\n\nimport { useSandboxAssistantTransport as useSharedTransport } from \"@burdenoff/fe-libs/shared/assistant\";\nimport { gatherPageContext } from \"../utils/pageContext\";\n\ninterface AssistantSendArgs {\n prompt: string;\n onProgress: (partialText: string) => void;\n signal: AbortSignal;\n}\n\nexport interface AssistantTransport {\n sendPrompt: (args: AssistantSendArgs) => Promise<{ text: string }>;\n prewarm?: (level?: \"probe\" | \"full\") => void;\n suspend?: () => void;\n}\n\nexport function useSandboxAssistantTransport(): AssistantTransport {\n return useSharedTransport({\n product: \"timecampus\",\n gatherPageContext,\n enableCredits: true,\n });\n}\n"],"mappings":";;;AAyBA,SAAgB,IAAmD;AACjE,QAAO,EAAmB;EACxB,SAAS;EACT;EACA,eAAe;EAChB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MeetingDetailPage.d.ts","sourceRoot":"","sources":["../../../../src/timecampus/pages/meetings/MeetingDetailPage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"MeetingDetailPage.d.ts","sourceRoot":"","sources":["../../../../src/timecampus/pages/meetings/MeetingDetailPage.tsx"],"names":[],"mappings":"AAkFA,wBAAgB,iBAAiB,gCAsjBhC"}
|
|
@@ -1,182 +1,187 @@
|
|
|
1
1
|
import { useTimeCampus as e, useTimeCampusNavigate as t } from "../../providers/TimeCampusProvider.js";
|
|
2
|
-
import { cn as
|
|
3
|
-
import { StatCard as
|
|
4
|
-
import { LoadingSkeleton as
|
|
5
|
-
import { useAddParticipantMutation as
|
|
6
|
-
import { PageLayout as
|
|
7
|
-
import { StatusBadge as
|
|
8
|
-
import { EmptyState as
|
|
9
|
-
import { Modal as
|
|
10
|
-
import { MeetingActionItemStatus as
|
|
11
|
-
import { MeetingFormModal as
|
|
12
|
-
import { ParticipantFormModal as
|
|
13
|
-
import { ActionItemFormModal as
|
|
14
|
-
import { useState as
|
|
15
|
-
import { Check as
|
|
16
|
-
import { useParams as
|
|
17
|
-
import { Fragment as
|
|
18
|
-
import { NextSteps as
|
|
2
|
+
import { cn as ee, formatCurrency as te } from "../../utils/helpers.js";
|
|
3
|
+
import { StatCard as n } from "../../components/StatCard.js";
|
|
4
|
+
import { LoadingSkeleton as ne } from "../../components/LoadingSkeleton.js";
|
|
5
|
+
import { useAddParticipantMutation as re, useCompleteActionItemMutation as r, useCreateActionItemMutation as i, useDeleteActionItemMutation as a, useDeleteMeetingMutation as o, useGetMeetingQuery as s, useRateMeetingMutation as c, useRemoveParticipantMutation as ie, useUpdateActionItemMutation as ae, useUpdateMeetingMutation as oe, useUpdateParticipantStatusMutation as se } from "../../../generated/wspace-operations.js";
|
|
6
|
+
import { PageLayout as l } from "../../components/PageLayout.js";
|
|
7
|
+
import { StatusBadge as ce } from "../../components/StatusBadge.js";
|
|
8
|
+
import { EmptyState as u } from "../../components/EmptyState.js";
|
|
9
|
+
import { Modal as d } from "../../components/Modal.js";
|
|
10
|
+
import { MeetingActionItemStatus as f, MeetingParticipantStatus as p } from "../../../generated/wspace-types.js";
|
|
11
|
+
import { MeetingFormModal as le } from "./MeetingFormModal.js";
|
|
12
|
+
import { ParticipantFormModal as ue } from "./ParticipantFormModal.js";
|
|
13
|
+
import { ActionItemFormModal as de } from "./ActionItemFormModal.js";
|
|
14
|
+
import { useState as m } from "react";
|
|
15
|
+
import { CalendarDays as h, Check as g, Clock as fe, Pencil as _, Plus as v, Star as pe, Trash2 as y, UserMinus as b, Users as x } from "lucide-react";
|
|
16
|
+
import { Link as S, useParams as C } from "react-router-dom";
|
|
17
|
+
import { Fragment as w, jsx as T, jsxs as E } from "react/jsx-runtime";
|
|
18
|
+
import { NextSteps as D } from "@burdenoff/fe-libs/ui";
|
|
19
19
|
//#region src/timecampus/pages/meetings/MeetingDetailPage.tsx
|
|
20
|
-
var
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
],
|
|
26
|
-
[
|
|
27
|
-
[
|
|
28
|
-
[
|
|
29
|
-
[
|
|
20
|
+
var O = [
|
|
21
|
+
p.Pending,
|
|
22
|
+
p.Accepted,
|
|
23
|
+
p.Tentative,
|
|
24
|
+
p.Declined
|
|
25
|
+
], k = {
|
|
26
|
+
[f.Completed]: "success",
|
|
27
|
+
[f.InProgress]: "info",
|
|
28
|
+
[f.Pending]: "warning",
|
|
29
|
+
[f.Cancelled]: "muted"
|
|
30
30
|
};
|
|
31
|
-
function
|
|
31
|
+
function A(e) {
|
|
32
32
|
return e.replace(/[^a-zA-Z0-9]/g, "").slice(0, 2).toUpperCase() || "?";
|
|
33
33
|
}
|
|
34
|
-
function
|
|
34
|
+
function j(e) {
|
|
35
35
|
let t = e.replace(/_/g, " ").toLowerCase();
|
|
36
36
|
return t.charAt(0).toUpperCase() + t.slice(1);
|
|
37
37
|
}
|
|
38
|
-
function
|
|
39
|
-
let { id:
|
|
38
|
+
function M() {
|
|
39
|
+
let { id: p } = C(), { workspaceId: M = "", currentUser: N } = e(), P = t(), F = p ?? "", { data: I, loading: L, error: R, refetch: z } = s({
|
|
40
40
|
variables: {
|
|
41
|
-
id:
|
|
42
|
-
workspaceId:
|
|
41
|
+
id: F,
|
|
42
|
+
workspaceId: M
|
|
43
43
|
},
|
|
44
|
-
skip: !
|
|
45
|
-
}), [
|
|
46
|
-
if (
|
|
44
|
+
skip: !M || !p
|
|
45
|
+
}), [B, { loading: me, error: he }] = oe(), [ge, { loading: V }] = o(), [_e, { loading: ve }] = c(), [ye, { loading: be, error: H }] = re(), [xe] = ie(), [Se] = se(), [Ce, { loading: we, error: Te }] = i(), [Ee, { loading: De, error: Oe }] = ae(), [ke] = r(), [Ae] = a(), [je, U] = m(!1), [Me, W] = m(!1), [Ne, G] = m(!1), [K, q] = m(null), [J, Y] = m(null), [X, Z] = m(null), Q = I?.meeting;
|
|
46
|
+
if (L) return /* @__PURE__ */ T(l, {
|
|
47
47
|
title: "Meeting Details",
|
|
48
|
-
description: `Meeting ID: ${
|
|
49
|
-
children: /* @__PURE__ */
|
|
48
|
+
description: `Meeting ID: ${p}`,
|
|
49
|
+
children: /* @__PURE__ */ T(ne, { variant: "detail" })
|
|
50
50
|
});
|
|
51
|
-
if (
|
|
51
|
+
if (R || !Q) return /* @__PURE__ */ T(l, {
|
|
52
52
|
title: "Meeting Details",
|
|
53
|
-
description: `Meeting ID: ${
|
|
54
|
-
children: /* @__PURE__ */
|
|
53
|
+
description: `Meeting ID: ${p}`,
|
|
54
|
+
children: /* @__PURE__ */ T("div", {
|
|
55
55
|
className: "rounded-lg border border-status-error-bg/40 bg-status-error-bg p-4 text-sm text-status-error-text",
|
|
56
|
-
children:
|
|
56
|
+
children: R ? "Failed to load meeting details. Please try again later." : "Meeting not found."
|
|
57
57
|
})
|
|
58
58
|
});
|
|
59
|
-
let
|
|
60
|
-
await
|
|
61
|
-
id:
|
|
62
|
-
workspaceId:
|
|
59
|
+
let Pe = Q.actionItems.filter((e) => e.status !== "COMPLETED").length, $ = Q.rating ?? 0, Fe = async (e) => {
|
|
60
|
+
await B({ variables: {
|
|
61
|
+
id: F,
|
|
62
|
+
workspaceId: M,
|
|
63
63
|
input: e
|
|
64
|
-
} }), await
|
|
65
|
-
},
|
|
66
|
-
await
|
|
67
|
-
id:
|
|
68
|
-
workspaceId:
|
|
69
|
-
} }),
|
|
70
|
-
}, Ie = async (e) => {
|
|
71
|
-
await he({ variables: {
|
|
72
|
-
id: N,
|
|
73
|
-
workspaceId: A,
|
|
74
|
-
rating: e
|
|
75
|
-
} }), await L();
|
|
64
|
+
} }), await z(), U(!1);
|
|
65
|
+
}, Ie = async () => {
|
|
66
|
+
await ge({ variables: {
|
|
67
|
+
id: F,
|
|
68
|
+
workspaceId: M
|
|
69
|
+
} }), W(!1), P("/meetings");
|
|
76
70
|
}, Le = async (e) => {
|
|
77
71
|
await _e({ variables: {
|
|
78
|
-
|
|
79
|
-
workspaceId:
|
|
72
|
+
id: F,
|
|
73
|
+
workspaceId: M,
|
|
74
|
+
rating: e
|
|
75
|
+
} }), await z();
|
|
76
|
+
}, Re = async (e) => {
|
|
77
|
+
await ye({ variables: {
|
|
78
|
+
meetingId: F,
|
|
79
|
+
workspaceId: M,
|
|
80
80
|
input: e
|
|
81
|
-
} }), await
|
|
82
|
-
},
|
|
83
|
-
|
|
84
|
-
meetingId:
|
|
85
|
-
participantId:
|
|
86
|
-
workspaceId:
|
|
87
|
-
} }),
|
|
88
|
-
},
|
|
89
|
-
await
|
|
90
|
-
meetingId:
|
|
81
|
+
} }), await z(), G(!1);
|
|
82
|
+
}, ze = async () => {
|
|
83
|
+
K && (await xe({ variables: {
|
|
84
|
+
meetingId: F,
|
|
85
|
+
participantId: K.id,
|
|
86
|
+
workspaceId: M
|
|
87
|
+
} }), q(null), await z());
|
|
88
|
+
}, Be = async (e, t) => {
|
|
89
|
+
await Se({ variables: {
|
|
90
|
+
meetingId: F,
|
|
91
91
|
participantId: e,
|
|
92
|
-
workspaceId:
|
|
92
|
+
workspaceId: M,
|
|
93
93
|
status: t
|
|
94
|
-
} }), await
|
|
95
|
-
},
|
|
96
|
-
await
|
|
97
|
-
meetingId:
|
|
98
|
-
workspaceId:
|
|
94
|
+
} }), await z();
|
|
95
|
+
}, Ve = async (e) => {
|
|
96
|
+
await Ce({ variables: {
|
|
97
|
+
meetingId: F,
|
|
98
|
+
workspaceId: M,
|
|
99
99
|
input: e
|
|
100
|
-
} }), await
|
|
101
|
-
},
|
|
102
|
-
await
|
|
100
|
+
} }), await z(), Y(null);
|
|
101
|
+
}, He = async (e, t) => {
|
|
102
|
+
await Ee({ variables: {
|
|
103
103
|
id: e,
|
|
104
|
-
workspaceId:
|
|
104
|
+
workspaceId: M,
|
|
105
105
|
input: t
|
|
106
|
-
} }), await
|
|
107
|
-
},
|
|
108
|
-
await
|
|
106
|
+
} }), await z(), Y(null);
|
|
107
|
+
}, Ue = async (e) => {
|
|
108
|
+
await ke({ variables: {
|
|
109
109
|
id: e,
|
|
110
|
-
workspaceId:
|
|
111
|
-
} }), await
|
|
110
|
+
workspaceId: M
|
|
111
|
+
} }), await z();
|
|
112
112
|
};
|
|
113
|
-
return /* @__PURE__ */
|
|
113
|
+
return /* @__PURE__ */ E(l, {
|
|
114
114
|
title: "Meeting Details",
|
|
115
|
-
description:
|
|
115
|
+
description: Q.title,
|
|
116
116
|
purpose: "The full record of one meeting — who attended, what it cost in people's time, and the action items it produced — so you can follow up on commitments and judge whether the meeting was worth it.",
|
|
117
|
-
actions: /* @__PURE__ */ w
|
|
118
|
-
onClick: () => H(!0),
|
|
119
|
-
className: "inline-flex items-center gap-1.5 rounded-md border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50",
|
|
120
|
-
children: [/* @__PURE__ */ C(_, { className: "h-4 w-4" }), "Edit"]
|
|
121
|
-
}), /* @__PURE__ */ w("button", {
|
|
117
|
+
actions: /* @__PURE__ */ E(w, { children: [/* @__PURE__ */ E("button", {
|
|
122
118
|
onClick: () => U(!0),
|
|
119
|
+
className: "inline-flex items-center gap-1.5 rounded-md border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50",
|
|
120
|
+
children: [/* @__PURE__ */ T(_, { className: "h-4 w-4" }), "Edit"]
|
|
121
|
+
}), /* @__PURE__ */ E("button", {
|
|
122
|
+
onClick: () => W(!0),
|
|
123
123
|
className: "inline-flex items-center gap-1.5 rounded-md border border-border-subtle px-3 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg/40",
|
|
124
|
-
children: [/* @__PURE__ */
|
|
124
|
+
children: [/* @__PURE__ */ T(y, { className: "h-4 w-4" }), "Delete"]
|
|
125
125
|
})] }),
|
|
126
126
|
children: [
|
|
127
|
-
/* @__PURE__ */
|
|
127
|
+
/* @__PURE__ */ T(D, {
|
|
128
128
|
storageKey: "timecampus-meeting-detail",
|
|
129
129
|
className: "mb-6",
|
|
130
130
|
steps: [{
|
|
131
131
|
id: "all-meetings",
|
|
132
132
|
label: "Back to all meetings",
|
|
133
133
|
description: "Browse and schedule other meetings.",
|
|
134
|
-
onClick: () =>
|
|
134
|
+
onClick: () => P("/meetings")
|
|
135
135
|
}, {
|
|
136
136
|
id: "analytics",
|
|
137
137
|
label: "See meeting analytics",
|
|
138
138
|
description: "Understand your overall meeting load and cost.",
|
|
139
|
-
onClick: () =>
|
|
139
|
+
onClick: () => P("/meetings/analytics")
|
|
140
140
|
}]
|
|
141
141
|
}),
|
|
142
|
-
/* @__PURE__ */
|
|
142
|
+
/* @__PURE__ */ E("div", {
|
|
143
143
|
className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4",
|
|
144
144
|
children: [
|
|
145
|
-
/* @__PURE__ */
|
|
145
|
+
/* @__PURE__ */ T(n, {
|
|
146
146
|
featured: !0,
|
|
147
147
|
title: "Duration",
|
|
148
|
-
value: `${
|
|
149
|
-
icon: /* @__PURE__ */
|
|
148
|
+
value: `${Q.duration ?? 0} min`,
|
|
149
|
+
icon: /* @__PURE__ */ T(fe, { className: "h-5 w-5" })
|
|
150
150
|
}),
|
|
151
|
-
/* @__PURE__ */
|
|
151
|
+
/* @__PURE__ */ T(n, {
|
|
152
152
|
title: "Participants",
|
|
153
|
-
value: String(
|
|
154
|
-
icon: /* @__PURE__ */
|
|
153
|
+
value: String(Q.participants.length),
|
|
154
|
+
icon: /* @__PURE__ */ T(x, { className: "h-5 w-5" })
|
|
155
155
|
}),
|
|
156
|
-
/* @__PURE__ */
|
|
156
|
+
/* @__PURE__ */ T(n, {
|
|
157
157
|
title: "Cost",
|
|
158
|
-
value:
|
|
158
|
+
value: Q.cost == null ? "N/A" : te(Q.cost),
|
|
159
159
|
description: "Based on participant rates"
|
|
160
160
|
}),
|
|
161
|
-
/* @__PURE__ */
|
|
161
|
+
/* @__PURE__ */ T(n, {
|
|
162
162
|
title: "Action Items",
|
|
163
|
-
value: String(
|
|
164
|
-
description: `${
|
|
163
|
+
value: String(Q.actionItems.length),
|
|
164
|
+
description: `${Pe} pending`
|
|
165
165
|
})
|
|
166
166
|
]
|
|
167
167
|
}),
|
|
168
|
-
/* @__PURE__ */
|
|
168
|
+
Q.calendarEventId && /* @__PURE__ */ E(S, {
|
|
169
|
+
to: `/calendar/event/${Q.calendarEventId}`,
|
|
170
|
+
className: "inline-flex w-fit items-center gap-1.5 rounded-full border border-border-subtle bg-bg-surface px-3 py-1.5 text-xs font-medium text-text-secondary hover:bg-bg-sunken/50 hover:text-text-primary",
|
|
171
|
+
children: [/* @__PURE__ */ T(h, { className: "h-3.5 w-3.5" }), "View in Calendar"]
|
|
172
|
+
}),
|
|
173
|
+
/* @__PURE__ */ E("div", {
|
|
169
174
|
className: "rounded-xl border border-border-seam bg-gloss-card shadow-pop p-6",
|
|
170
175
|
children: [
|
|
171
|
-
/* @__PURE__ */
|
|
176
|
+
/* @__PURE__ */ T("h3", {
|
|
172
177
|
className: "mb-1 text-sm font-semibold text-text-primary",
|
|
173
178
|
children: "Rate this meeting"
|
|
174
179
|
}),
|
|
175
|
-
/* @__PURE__ */
|
|
180
|
+
/* @__PURE__ */ T("p", {
|
|
176
181
|
className: "mb-3 text-xs text-text-secondary",
|
|
177
182
|
children: "Was it worth the time? Your rating feeds meeting analytics."
|
|
178
183
|
}),
|
|
179
|
-
/* @__PURE__ */
|
|
184
|
+
/* @__PURE__ */ E("div", {
|
|
180
185
|
className: "flex items-center gap-1",
|
|
181
186
|
children: [[
|
|
182
187
|
1,
|
|
@@ -184,258 +189,258 @@ function A() {
|
|
|
184
189
|
3,
|
|
185
190
|
4,
|
|
186
191
|
5
|
|
187
|
-
].map((e) => /* @__PURE__ */
|
|
188
|
-
onClick: () =>
|
|
189
|
-
disabled:
|
|
192
|
+
].map((e) => /* @__PURE__ */ T("button", {
|
|
193
|
+
onClick: () => Le(e),
|
|
194
|
+
disabled: ve,
|
|
190
195
|
"aria-label": `Rate ${e} star${e > 1 ? "s" : ""}`,
|
|
191
196
|
className: "rounded-md p-1 text-text-secondary hover:bg-bg-sunken disabled:opacity-50",
|
|
192
|
-
children: /* @__PURE__ */
|
|
193
|
-
}, e)),
|
|
197
|
+
children: /* @__PURE__ */ T(pe, { className: ee("h-6 w-6", e <= $ ? "fill-status-warning-text text-status-warning-text" : "text-text-secondary") })
|
|
198
|
+
}, e)), $ > 0 && /* @__PURE__ */ E("span", {
|
|
194
199
|
className: "ml-2 text-sm text-text-secondary",
|
|
195
|
-
children: [
|
|
200
|
+
children: [$, " / 5"]
|
|
196
201
|
})]
|
|
197
202
|
})
|
|
198
203
|
]
|
|
199
204
|
}),
|
|
200
|
-
/* @__PURE__ */
|
|
205
|
+
/* @__PURE__ */ E("div", {
|
|
201
206
|
className: "grid grid-cols-1 gap-6 lg:grid-cols-2",
|
|
202
|
-
children: [/* @__PURE__ */
|
|
207
|
+
children: [/* @__PURE__ */ E("div", {
|
|
203
208
|
className: "rounded-xl border border-border-seam bg-gloss-card shadow-pop p-6",
|
|
204
|
-
children: [/* @__PURE__ */
|
|
209
|
+
children: [/* @__PURE__ */ E("div", {
|
|
205
210
|
className: "mb-4 flex items-center justify-between",
|
|
206
|
-
children: [/* @__PURE__ */
|
|
211
|
+
children: [/* @__PURE__ */ T("h3", {
|
|
207
212
|
className: "text-sm font-semibold text-text-primary",
|
|
208
213
|
children: "Participants"
|
|
209
|
-
}), /* @__PURE__ */
|
|
210
|
-
onClick: () =>
|
|
214
|
+
}), /* @__PURE__ */ E("button", {
|
|
215
|
+
onClick: () => G(!0),
|
|
211
216
|
className: "inline-flex items-center gap-1 rounded-md bg-action-primary-bg px-2.5 py-1.5 text-xs font-medium text-action-primary-text hover:bg-action-primary-bg/90",
|
|
212
|
-
children: [/* @__PURE__ */
|
|
217
|
+
children: [/* @__PURE__ */ T(v, { className: "h-3.5 w-3.5" }), "Add"]
|
|
213
218
|
})]
|
|
214
|
-
}),
|
|
219
|
+
}), Q.participants.length > 0 ? /* @__PURE__ */ T("div", {
|
|
215
220
|
className: "space-y-2",
|
|
216
|
-
children:
|
|
221
|
+
children: Q.participants.map((e) => /* @__PURE__ */ E("div", {
|
|
217
222
|
className: "flex items-center justify-between gap-2 rounded-lg border border-border-subtle px-3 py-2",
|
|
218
|
-
children: [/* @__PURE__ */
|
|
223
|
+
children: [/* @__PURE__ */ E("div", {
|
|
219
224
|
className: "flex min-w-0 items-center gap-2.5",
|
|
220
|
-
children: [/* @__PURE__ */
|
|
225
|
+
children: [/* @__PURE__ */ T("span", {
|
|
221
226
|
className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-action-primary-bg/10 text-[11px] font-semibold text-primary",
|
|
222
|
-
children:
|
|
223
|
-
}), /* @__PURE__ */
|
|
227
|
+
children: A(e.userId)
|
|
228
|
+
}), /* @__PURE__ */ E("div", {
|
|
224
229
|
className: "min-w-0",
|
|
225
|
-
children: [/* @__PURE__ */
|
|
230
|
+
children: [/* @__PURE__ */ T("p", {
|
|
226
231
|
className: "truncate text-sm text-text-primary",
|
|
227
232
|
children: e.userId
|
|
228
|
-
}), /* @__PURE__ */
|
|
233
|
+
}), /* @__PURE__ */ T("p", {
|
|
229
234
|
className: "text-xs text-text-secondary",
|
|
230
235
|
children: e.role
|
|
231
236
|
})]
|
|
232
237
|
})]
|
|
233
|
-
}), /* @__PURE__ */
|
|
238
|
+
}), /* @__PURE__ */ E("div", {
|
|
234
239
|
className: "flex items-center gap-2",
|
|
235
|
-
children: [/* @__PURE__ */
|
|
240
|
+
children: [/* @__PURE__ */ T("select", {
|
|
236
241
|
value: e.status,
|
|
237
|
-
onChange: (t) =>
|
|
242
|
+
onChange: (t) => Be(e.id, t.target.value),
|
|
238
243
|
className: "rounded-md border border-border-subtle bg-bg-surface px-2 py-1 text-xs text-text-primary",
|
|
239
244
|
"aria-label": "Participant status",
|
|
240
|
-
children:
|
|
245
|
+
children: O.map((e) => /* @__PURE__ */ T("option", {
|
|
241
246
|
value: e,
|
|
242
247
|
children: e
|
|
243
248
|
}, e))
|
|
244
|
-
}), /* @__PURE__ */
|
|
245
|
-
onClick: () =>
|
|
249
|
+
}), /* @__PURE__ */ T("button", {
|
|
250
|
+
onClick: () => q(e),
|
|
246
251
|
className: "rounded-md p-1 text-text-secondary hover:bg-status-error-bg hover:text-status-error-text",
|
|
247
252
|
"aria-label": "Remove participant",
|
|
248
|
-
children: /* @__PURE__ */
|
|
253
|
+
children: /* @__PURE__ */ T(b, { className: "h-4 w-4" })
|
|
249
254
|
})]
|
|
250
255
|
})]
|
|
251
256
|
}, e.id))
|
|
252
|
-
}) : /* @__PURE__ */
|
|
257
|
+
}) : /* @__PURE__ */ T(u, {
|
|
253
258
|
illustration: "empty-people",
|
|
254
259
|
title: "No participants added",
|
|
255
260
|
description: "Add attendees to capture who was in the room and what it cost.",
|
|
256
|
-
action: /* @__PURE__ */
|
|
257
|
-
onClick: () =>
|
|
261
|
+
action: /* @__PURE__ */ E("button", {
|
|
262
|
+
onClick: () => G(!0),
|
|
258
263
|
className: "inline-flex items-center gap-1 rounded-md bg-action-primary-bg px-3 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90",
|
|
259
|
-
children: [/* @__PURE__ */
|
|
264
|
+
children: [/* @__PURE__ */ T(v, { className: "h-4 w-4" }), "Add participant"]
|
|
260
265
|
})
|
|
261
266
|
})]
|
|
262
|
-
}), /* @__PURE__ */
|
|
267
|
+
}), /* @__PURE__ */ E("div", {
|
|
263
268
|
className: "rounded-xl border border-border-seam bg-gloss-card shadow-pop p-6",
|
|
264
|
-
children: [/* @__PURE__ */
|
|
269
|
+
children: [/* @__PURE__ */ E("div", {
|
|
265
270
|
className: "mb-4 flex items-center justify-between",
|
|
266
|
-
children: [/* @__PURE__ */
|
|
271
|
+
children: [/* @__PURE__ */ T("h3", {
|
|
267
272
|
className: "text-sm font-semibold text-text-primary",
|
|
268
273
|
children: "Action Items"
|
|
269
|
-
}), /* @__PURE__ */
|
|
270
|
-
onClick: () =>
|
|
274
|
+
}), /* @__PURE__ */ E("button", {
|
|
275
|
+
onClick: () => Y({ mode: "create" }),
|
|
271
276
|
className: "inline-flex items-center gap-1 rounded-md bg-action-primary-bg px-2.5 py-1.5 text-xs font-medium text-action-primary-text hover:bg-action-primary-bg/90",
|
|
272
|
-
children: [/* @__PURE__ */
|
|
277
|
+
children: [/* @__PURE__ */ T(v, { className: "h-3.5 w-3.5" }), "Add"]
|
|
273
278
|
})]
|
|
274
|
-
}),
|
|
279
|
+
}), Q.actionItems.length > 0 ? /* @__PURE__ */ T("div", {
|
|
275
280
|
className: "space-y-2",
|
|
276
|
-
children:
|
|
281
|
+
children: Q.actionItems.map((e) => /* @__PURE__ */ E("div", {
|
|
277
282
|
className: "flex items-center justify-between gap-2 rounded-lg border border-border-subtle px-3 py-2",
|
|
278
|
-
children: [/* @__PURE__ */
|
|
283
|
+
children: [/* @__PURE__ */ E("div", {
|
|
279
284
|
className: "min-w-0",
|
|
280
|
-
children: [/* @__PURE__ */
|
|
285
|
+
children: [/* @__PURE__ */ T("p", {
|
|
281
286
|
className: "truncate text-sm text-text-primary",
|
|
282
287
|
children: e.title
|
|
283
|
-
}), /* @__PURE__ */
|
|
288
|
+
}), /* @__PURE__ */ T("div", {
|
|
284
289
|
className: "mt-1",
|
|
285
|
-
children: /* @__PURE__ */
|
|
286
|
-
label:
|
|
287
|
-
variant:
|
|
290
|
+
children: /* @__PURE__ */ T(ce, {
|
|
291
|
+
label: j(e.status),
|
|
292
|
+
variant: k[e.status]
|
|
288
293
|
})
|
|
289
294
|
})]
|
|
290
|
-
}), /* @__PURE__ */
|
|
295
|
+
}), /* @__PURE__ */ E("div", {
|
|
291
296
|
className: "flex items-center gap-1",
|
|
292
297
|
children: [
|
|
293
|
-
e.status !==
|
|
294
|
-
onClick: () =>
|
|
298
|
+
e.status !== f.Completed && /* @__PURE__ */ T("button", {
|
|
299
|
+
onClick: () => Ue(e.id),
|
|
295
300
|
className: "rounded-md p-1 text-text-secondary hover:bg-status-success-bg hover:text-status-success-text",
|
|
296
301
|
"aria-label": "Complete action item",
|
|
297
|
-
children: /* @__PURE__ */
|
|
302
|
+
children: /* @__PURE__ */ T(g, { className: "h-4 w-4" })
|
|
298
303
|
}),
|
|
299
|
-
/* @__PURE__ */
|
|
300
|
-
onClick: () =>
|
|
304
|
+
/* @__PURE__ */ T("button", {
|
|
305
|
+
onClick: () => Y({
|
|
301
306
|
mode: "edit",
|
|
302
307
|
item: e
|
|
303
308
|
}),
|
|
304
309
|
className: "rounded-md p-1 text-text-secondary hover:bg-bg-sunken hover:text-text-primary",
|
|
305
310
|
"aria-label": "Edit action item",
|
|
306
|
-
children: /* @__PURE__ */
|
|
311
|
+
children: /* @__PURE__ */ T(_, { className: "h-4 w-4" })
|
|
307
312
|
}),
|
|
308
|
-
/* @__PURE__ */
|
|
309
|
-
onClick: () =>
|
|
313
|
+
/* @__PURE__ */ T("button", {
|
|
314
|
+
onClick: () => Z(e),
|
|
310
315
|
className: "rounded-md p-1 text-text-secondary hover:bg-status-error-bg hover:text-status-error-text",
|
|
311
316
|
"aria-label": "Delete action item",
|
|
312
|
-
children: /* @__PURE__ */
|
|
317
|
+
children: /* @__PURE__ */ T(y, { className: "h-4 w-4" })
|
|
313
318
|
})
|
|
314
319
|
]
|
|
315
320
|
})]
|
|
316
321
|
}, e.id))
|
|
317
|
-
}) : /* @__PURE__ */
|
|
322
|
+
}) : /* @__PURE__ */ T(u, {
|
|
318
323
|
illustration: "empty-generic",
|
|
319
324
|
title: "No action items",
|
|
320
325
|
description: "Capture follow-ups here so commitments from the meeting don't get lost.",
|
|
321
|
-
action: /* @__PURE__ */
|
|
322
|
-
onClick: () =>
|
|
326
|
+
action: /* @__PURE__ */ E("button", {
|
|
327
|
+
onClick: () => Y({ mode: "create" }),
|
|
323
328
|
className: "inline-flex items-center gap-1 rounded-md bg-action-primary-bg px-3 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90",
|
|
324
|
-
children: [/* @__PURE__ */
|
|
329
|
+
children: [/* @__PURE__ */ T(v, { className: "h-4 w-4" }), "Add action item"]
|
|
325
330
|
})
|
|
326
331
|
})]
|
|
327
332
|
})]
|
|
328
333
|
}),
|
|
329
|
-
|
|
334
|
+
je && /* @__PURE__ */ T(le, {
|
|
330
335
|
open: !0,
|
|
331
|
-
onClose: () => H(!1),
|
|
332
|
-
workspaceId: A,
|
|
333
|
-
meeting: Z,
|
|
334
|
-
submitting: z,
|
|
335
|
-
error: !!B,
|
|
336
|
-
onUpdate: Pe
|
|
337
|
-
}),
|
|
338
|
-
/* @__PURE__ */ C(p, {
|
|
339
|
-
open: je,
|
|
340
336
|
onClose: () => U(!1),
|
|
337
|
+
workspaceId: M,
|
|
338
|
+
meeting: Q,
|
|
339
|
+
submitting: me,
|
|
340
|
+
error: !!he,
|
|
341
|
+
onUpdate: Fe
|
|
342
|
+
}),
|
|
343
|
+
/* @__PURE__ */ T(d, {
|
|
344
|
+
open: Me,
|
|
345
|
+
onClose: () => W(!1),
|
|
341
346
|
title: "Delete meeting",
|
|
342
347
|
description: "This permanently removes the meeting and its analytics.",
|
|
343
348
|
size: "sm",
|
|
344
|
-
footer: /* @__PURE__ */ w
|
|
345
|
-
onClick: () =>
|
|
349
|
+
footer: /* @__PURE__ */ E(w, { children: [/* @__PURE__ */ T("button", {
|
|
350
|
+
onClick: () => W(!1),
|
|
346
351
|
className: "rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50",
|
|
347
352
|
children: "Cancel"
|
|
348
|
-
}), /* @__PURE__ */
|
|
349
|
-
onClick:
|
|
353
|
+
}), /* @__PURE__ */ T("button", {
|
|
354
|
+
onClick: Ie,
|
|
350
355
|
disabled: V,
|
|
351
356
|
className: "rounded-md bg-status-error-bg px-4 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg/90 disabled:opacity-50",
|
|
352
357
|
children: V ? "Deleting..." : "Delete"
|
|
353
358
|
})] }),
|
|
354
|
-
children: /* @__PURE__ */
|
|
359
|
+
children: /* @__PURE__ */ E("p", {
|
|
355
360
|
className: "text-sm text-text-secondary",
|
|
356
361
|
children: [
|
|
357
362
|
"Are you sure you want to delete",
|
|
358
363
|
" ",
|
|
359
|
-
/* @__PURE__ */
|
|
364
|
+
/* @__PURE__ */ T("span", {
|
|
360
365
|
className: "font-medium text-text-primary",
|
|
361
|
-
children:
|
|
366
|
+
children: Q.title
|
|
362
367
|
}),
|
|
363
368
|
"? This action cannot be undone."
|
|
364
369
|
]
|
|
365
370
|
})
|
|
366
371
|
}),
|
|
367
|
-
|
|
372
|
+
Ne && /* @__PURE__ */ T(ue, {
|
|
368
373
|
open: !0,
|
|
369
|
-
onClose: () =>
|
|
370
|
-
defaultUserId:
|
|
371
|
-
submitting:
|
|
372
|
-
error: !!
|
|
373
|
-
onSubmit:
|
|
374
|
+
onClose: () => G(!1),
|
|
375
|
+
defaultUserId: N?.id,
|
|
376
|
+
submitting: be,
|
|
377
|
+
error: !!H,
|
|
378
|
+
onSubmit: Re
|
|
374
379
|
}),
|
|
375
|
-
/* @__PURE__ */
|
|
376
|
-
open: !!
|
|
377
|
-
onClose: () =>
|
|
380
|
+
/* @__PURE__ */ T(d, {
|
|
381
|
+
open: !!K,
|
|
382
|
+
onClose: () => q(null),
|
|
378
383
|
title: "Remove participant",
|
|
379
384
|
size: "sm",
|
|
380
|
-
footer: /* @__PURE__ */ w
|
|
381
|
-
onClick: () =>
|
|
385
|
+
footer: /* @__PURE__ */ E(w, { children: [/* @__PURE__ */ T("button", {
|
|
386
|
+
onClick: () => q(null),
|
|
382
387
|
className: "rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50",
|
|
383
388
|
children: "Cancel"
|
|
384
|
-
}), /* @__PURE__ */
|
|
385
|
-
onClick:
|
|
389
|
+
}), /* @__PURE__ */ T("button", {
|
|
390
|
+
onClick: ze,
|
|
386
391
|
className: "rounded-md bg-status-error-bg px-4 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg/90",
|
|
387
392
|
children: "Remove"
|
|
388
393
|
})] }),
|
|
389
|
-
children: /* @__PURE__ */
|
|
394
|
+
children: /* @__PURE__ */ E("p", {
|
|
390
395
|
className: "text-sm text-text-secondary",
|
|
391
396
|
children: [
|
|
392
397
|
"Remove",
|
|
393
398
|
" ",
|
|
394
|
-
/* @__PURE__ */
|
|
399
|
+
/* @__PURE__ */ T("span", {
|
|
395
400
|
className: "font-medium text-text-primary",
|
|
396
|
-
children:
|
|
401
|
+
children: K?.userId
|
|
397
402
|
}),
|
|
398
403
|
" ",
|
|
399
404
|
"from this meeting?"
|
|
400
405
|
]
|
|
401
406
|
})
|
|
402
407
|
}),
|
|
403
|
-
|
|
408
|
+
J && /* @__PURE__ */ T(de, {
|
|
404
409
|
open: !0,
|
|
405
|
-
onClose: () =>
|
|
406
|
-
item:
|
|
407
|
-
submitting:
|
|
408
|
-
error: !!(
|
|
409
|
-
onCreate:
|
|
410
|
-
onUpdate:
|
|
410
|
+
onClose: () => Y(null),
|
|
411
|
+
item: J.mode === "edit" ? J.item : void 0,
|
|
412
|
+
submitting: J.mode === "edit" ? De : we,
|
|
413
|
+
error: !!(J.mode === "edit" ? Oe : Te),
|
|
414
|
+
onCreate: Ve,
|
|
415
|
+
onUpdate: J.mode === "edit" ? (e) => He(J.item.id, e) : void 0
|
|
411
416
|
}),
|
|
412
|
-
/* @__PURE__ */
|
|
413
|
-
open: !!
|
|
414
|
-
onClose: () =>
|
|
417
|
+
/* @__PURE__ */ T(d, {
|
|
418
|
+
open: !!X,
|
|
419
|
+
onClose: () => Z(null),
|
|
415
420
|
title: "Delete action item",
|
|
416
421
|
size: "sm",
|
|
417
|
-
footer: /* @__PURE__ */ w
|
|
418
|
-
onClick: () =>
|
|
422
|
+
footer: /* @__PURE__ */ E(w, { children: [/* @__PURE__ */ T("button", {
|
|
423
|
+
onClick: () => Z(null),
|
|
419
424
|
className: "rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50",
|
|
420
425
|
children: "Cancel"
|
|
421
|
-
}), /* @__PURE__ */
|
|
426
|
+
}), /* @__PURE__ */ T("button", {
|
|
422
427
|
onClick: async () => {
|
|
423
|
-
|
|
424
|
-
id:
|
|
425
|
-
workspaceId:
|
|
426
|
-
} }),
|
|
428
|
+
X && (await Ae({ variables: {
|
|
429
|
+
id: X.id,
|
|
430
|
+
workspaceId: M
|
|
431
|
+
} }), Z(null), await z());
|
|
427
432
|
},
|
|
428
433
|
className: "rounded-md bg-status-error-bg px-4 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg/90",
|
|
429
434
|
children: "Delete"
|
|
430
435
|
})] }),
|
|
431
|
-
children: /* @__PURE__ */
|
|
436
|
+
children: /* @__PURE__ */ E("p", {
|
|
432
437
|
className: "text-sm text-text-secondary",
|
|
433
438
|
children: [
|
|
434
439
|
"Delete action item",
|
|
435
440
|
" ",
|
|
436
|
-
/* @__PURE__ */
|
|
441
|
+
/* @__PURE__ */ T("span", {
|
|
437
442
|
className: "font-medium text-text-primary",
|
|
438
|
-
children:
|
|
443
|
+
children: X?.title
|
|
439
444
|
}),
|
|
440
445
|
"?"
|
|
441
446
|
]
|
|
@@ -445,6 +450,6 @@ function A() {
|
|
|
445
450
|
});
|
|
446
451
|
}
|
|
447
452
|
//#endregion
|
|
448
|
-
export {
|
|
453
|
+
export { M as MeetingDetailPage };
|
|
449
454
|
|
|
450
455
|
//# sourceMappingURL=MeetingDetailPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MeetingDetailPage.js","names":[],"sources":["../../../../src/timecampus/pages/meetings/MeetingDetailPage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { useParams } from \"react-router-dom\";\nimport {\n Users,\n Clock,\n Pencil,\n Trash2,\n Plus,\n Star,\n Check,\n UserMinus,\n} from \"lucide-react\";\nimport { NextSteps } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { StatCard } from \"../../components/StatCard\";\nimport { StatusBadge } from \"../../components/StatusBadge\";\nimport { EmptyState } from \"../../components/EmptyState\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport { Modal } from \"../../components/Modal\";\nimport { cn, type BadgeVariant } from \"../../utils/helpers\";\nimport {\n useTimeCampus,\n useTimeCampusNavigate,\n} from \"../../providers/TimeCampusProvider\";\nimport {\n useGetMeetingQuery,\n useUpdateMeetingMutation,\n useDeleteMeetingMutation,\n useRateMeetingMutation,\n useAddParticipantMutation,\n useRemoveParticipantMutation,\n useUpdateParticipantStatusMutation,\n useCreateActionItemMutation,\n useUpdateActionItemMutation,\n useCompleteActionItemMutation,\n useDeleteActionItemMutation,\n type MeetingParticipantFieldsFragment,\n type MeetingActionItemFieldsFragment,\n} from \"@/generated/wspace-operations\";\nimport {\n MeetingParticipantStatus,\n MeetingActionItemStatus,\n type UpdateMeetingInput,\n type AddMeetingParticipantInput,\n type CreateActionItemInput,\n type UpdateActionItemInput,\n} from \"@/generated/wspace-types\";\nimport { formatCurrency } from \"../../utils/helpers\";\nimport { MeetingFormModal } from \"./MeetingFormModal\";\nimport { ParticipantFormModal } from \"./ParticipantFormModal\";\nimport { ActionItemFormModal } from \"./ActionItemFormModal\";\n\nconst PARTICIPANT_STATUS_OPTIONS = [\n MeetingParticipantStatus.Pending,\n MeetingParticipantStatus.Accepted,\n MeetingParticipantStatus.Tentative,\n MeetingParticipantStatus.Declined,\n];\n\nconst ACTION_ITEM_VARIANT: Record<MeetingActionItemStatus, BadgeVariant> = {\n [MeetingActionItemStatus.Completed]: \"success\",\n [MeetingActionItemStatus.InProgress]: \"info\",\n [MeetingActionItemStatus.Pending]: \"warning\",\n [MeetingActionItemStatus.Cancelled]: \"muted\",\n};\n\n/** Deterministic 2-char monogram from an opaque participant id. */\nfunction idMonogram(id: string): string {\n return (\n id\n .replace(/[^a-zA-Z0-9]/g, \"\")\n .slice(0, 2)\n .toUpperCase() || \"?\"\n );\n}\n\nfunction prettyStatus(status: string): string {\n const lower = status.replace(/_/g, \" \").toLowerCase();\n return lower.charAt(0).toUpperCase() + lower.slice(1);\n}\n\nexport function MeetingDetailPage() {\n const { id } = useParams<{ id: string }>();\n const { workspaceId = \"\", currentUser } = useTimeCampus();\n const navigate = useTimeCampusNavigate();\n const meetingId = id ?? \"\";\n\n const { data, loading, error, refetch } = useGetMeetingQuery({\n variables: { id: meetingId, workspaceId },\n skip: !workspaceId || !id,\n });\n\n const [updateMeeting, { loading: updating, error: updateError }] =\n useUpdateMeetingMutation();\n const [deleteMeeting, { loading: deletingMeeting }] =\n useDeleteMeetingMutation();\n const [rateMeeting, { loading: rating }] = useRateMeetingMutation();\n const [addParticipant, { loading: addingParticipant, error: addPError }] =\n useAddParticipantMutation();\n const [removeParticipant] = useRemoveParticipantMutation();\n const [updateParticipantStatus] = useUpdateParticipantStatusMutation();\n const [createActionItem, { loading: creatingItem, error: createItemError }] =\n useCreateActionItemMutation();\n const [updateActionItem, { loading: updatingItem, error: updateItemError }] =\n useUpdateActionItemMutation();\n const [completeActionItem] = useCompleteActionItemMutation();\n const [deleteActionItem] = useDeleteActionItemMutation();\n\n const [editOpen, setEditOpen] = useState(false);\n const [deleteOpen, setDeleteOpen] = useState(false);\n const [participantOpen, setParticipantOpen] = useState(false);\n const [removingParticipant, setRemovingParticipant] =\n useState<MeetingParticipantFieldsFragment | null>(null);\n const [actionItemModal, setActionItemModal] = useState<\n | { mode: \"create\" }\n | { mode: \"edit\"; item: MeetingActionItemFieldsFragment }\n | null\n >(null);\n const [deletingItem, setDeletingItem] =\n useState<MeetingActionItemFieldsFragment | null>(null);\n\n const meeting = data?.meeting;\n\n if (loading) {\n return (\n <PageLayout title=\"Meeting Details\" description={`Meeting ID: ${id}`}>\n <LoadingSkeleton variant=\"detail\" />\n </PageLayout>\n );\n }\n\n if (error || !meeting) {\n return (\n <PageLayout title=\"Meeting Details\" description={`Meeting ID: ${id}`}>\n <div className=\"rounded-lg border border-status-error-bg/40 bg-status-error-bg p-4 text-sm text-status-error-text\">\n {error\n ? \"Failed to load meeting details. Please try again later.\"\n : \"Meeting not found.\"}\n </div>\n </PageLayout>\n );\n }\n\n const pendingActions = meeting.actionItems.filter(\n (a: MeetingActionItemFieldsFragment) => a.status !== \"COMPLETED\",\n ).length;\n const currentRating = meeting.rating ?? 0;\n\n const handleUpdateMeeting = async (input: UpdateMeetingInput) => {\n await updateMeeting({ variables: { id: meetingId, workspaceId, input } });\n await refetch();\n setEditOpen(false);\n };\n\n const handleDeleteMeeting = async () => {\n await deleteMeeting({ variables: { id: meetingId, workspaceId } });\n setDeleteOpen(false);\n navigate(\"/meetings\");\n };\n\n const handleRate = async (value: number) => {\n await rateMeeting({\n variables: { id: meetingId, workspaceId, rating: value },\n });\n await refetch();\n };\n\n const handleAddParticipant = async (input: AddMeetingParticipantInput) => {\n await addParticipant({\n variables: { meetingId, workspaceId, input },\n });\n await refetch();\n setParticipantOpen(false);\n };\n\n const handleRemoveParticipant = async () => {\n if (!removingParticipant) return;\n await removeParticipant({\n variables: {\n meetingId,\n participantId: removingParticipant.id,\n workspaceId,\n },\n });\n setRemovingParticipant(null);\n await refetch();\n };\n\n const handleParticipantStatus = async (\n participantId: string,\n status: MeetingParticipantStatus,\n ) => {\n await updateParticipantStatus({\n variables: { meetingId, participantId, workspaceId, status },\n });\n await refetch();\n };\n\n const handleCreateItem = async (input: CreateActionItemInput) => {\n await createActionItem({ variables: { meetingId, workspaceId, input } });\n await refetch();\n setActionItemModal(null);\n };\n\n const handleUpdateItem = async (\n itemId: string,\n input: UpdateActionItemInput,\n ) => {\n await updateActionItem({ variables: { id: itemId, workspaceId, input } });\n await refetch();\n setActionItemModal(null);\n };\n\n const handleCompleteItem = async (itemId: string) => {\n await completeActionItem({ variables: { id: itemId, workspaceId } });\n await refetch();\n };\n\n const handleDeleteItem = async () => {\n if (!deletingItem) return;\n await deleteActionItem({ variables: { id: deletingItem.id, workspaceId } });\n setDeletingItem(null);\n await refetch();\n };\n\n return (\n <PageLayout\n title=\"Meeting Details\"\n description={meeting.title}\n purpose=\"The full record of one meeting — who attended, what it cost in people's time, and the action items it produced — so you can follow up on commitments and judge whether the meeting was worth it.\"\n actions={\n <>\n <button\n onClick={() => setEditOpen(true)}\n className=\"inline-flex items-center gap-1.5 rounded-md border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n <Pencil className=\"h-4 w-4\" />\n Edit\n </button>\n <button\n onClick={() => setDeleteOpen(true)}\n className=\"inline-flex items-center gap-1.5 rounded-md border border-border-subtle px-3 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg/40\"\n >\n <Trash2 className=\"h-4 w-4\" />\n Delete\n </button>\n </>\n }\n >\n <NextSteps\n storageKey=\"timecampus-meeting-detail\"\n className=\"mb-6\"\n steps={[\n {\n id: \"all-meetings\",\n label: \"Back to all meetings\",\n description: \"Browse and schedule other meetings.\",\n onClick: () => navigate(\"/meetings\"),\n },\n {\n id: \"analytics\",\n label: \"See meeting analytics\",\n description: \"Understand your overall meeting load and cost.\",\n onClick: () => navigate(\"/meetings/analytics\"),\n },\n ]}\n />\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <StatCard\n featured\n title=\"Duration\"\n value={`${meeting.duration ?? 0} min`}\n icon={<Clock className=\"h-5 w-5\" />}\n />\n <StatCard\n title=\"Participants\"\n value={String(meeting.participants.length)}\n icon={<Users className=\"h-5 w-5\" />}\n />\n <StatCard\n title=\"Cost\"\n value={meeting.cost != null ? formatCurrency(meeting.cost) : \"N/A\"}\n description=\"Based on participant rates\"\n />\n <StatCard\n title=\"Action Items\"\n value={String(meeting.actionItems.length)}\n description={`${pendingActions} pending`}\n />\n </div>\n\n {/* Rating */}\n <div className=\"rounded-xl border border-border-seam bg-gloss-card shadow-pop p-6\">\n <h3 className=\"mb-1 text-sm font-semibold text-text-primary\">\n Rate this meeting\n </h3>\n <p className=\"mb-3 text-xs text-text-secondary\">\n Was it worth the time? Your rating feeds meeting analytics.\n </p>\n <div className=\"flex items-center gap-1\">\n {[1, 2, 3, 4, 5].map((value) => (\n <button\n key={value}\n onClick={() => handleRate(value)}\n disabled={rating}\n aria-label={`Rate ${value} star${value > 1 ? \"s\" : \"\"}`}\n className=\"rounded-md p-1 text-text-secondary hover:bg-bg-sunken disabled:opacity-50\"\n >\n <Star\n className={cn(\n \"h-6 w-6\",\n value <= currentRating\n ? \"fill-status-warning-text text-status-warning-text\"\n : \"text-text-secondary\",\n )}\n />\n </button>\n ))}\n {currentRating > 0 && (\n <span className=\"ml-2 text-sm text-text-secondary\">\n {currentRating} / 5\n </span>\n )}\n </div>\n </div>\n\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\n <div className=\"rounded-xl border border-border-seam bg-gloss-card shadow-pop p-6\">\n <div className=\"mb-4 flex items-center justify-between\">\n <h3 className=\"text-sm font-semibold text-text-primary\">\n Participants\n </h3>\n <button\n onClick={() => setParticipantOpen(true)}\n className=\"inline-flex items-center gap-1 rounded-md bg-action-primary-bg px-2.5 py-1.5 text-xs font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n <Plus className=\"h-3.5 w-3.5\" />\n Add\n </button>\n </div>\n {meeting.participants.length > 0 ? (\n <div className=\"space-y-2\">\n {meeting.participants.map(\n (p: MeetingParticipantFieldsFragment) => (\n <div\n key={p.id}\n className=\"flex items-center justify-between gap-2 rounded-lg border border-border-subtle px-3 py-2\"\n >\n <div className=\"flex min-w-0 items-center gap-2.5\">\n <span className=\"flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-action-primary-bg/10 text-[11px] font-semibold text-primary\">\n {idMonogram(p.userId)}\n </span>\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm text-text-primary\">\n {p.userId}\n </p>\n <p className=\"text-xs text-text-secondary\">{p.role}</p>\n </div>\n </div>\n <div className=\"flex items-center gap-2\">\n <select\n value={p.status}\n onChange={(e) =>\n handleParticipantStatus(\n p.id,\n e.target.value as MeetingParticipantStatus,\n )\n }\n className=\"rounded-md border border-border-subtle bg-bg-surface px-2 py-1 text-xs text-text-primary\"\n aria-label=\"Participant status\"\n >\n {PARTICIPANT_STATUS_OPTIONS.map((s) => (\n <option key={s} value={s}>\n {s}\n </option>\n ))}\n </select>\n <button\n onClick={() => setRemovingParticipant(p)}\n className=\"rounded-md p-1 text-text-secondary hover:bg-status-error-bg hover:text-status-error-text\"\n aria-label=\"Remove participant\"\n >\n <UserMinus className=\"h-4 w-4\" />\n </button>\n </div>\n </div>\n ),\n )}\n </div>\n ) : (\n <EmptyState\n illustration=\"empty-people\"\n title=\"No participants added\"\n description=\"Add attendees to capture who was in the room and what it cost.\"\n action={\n <button\n onClick={() => setParticipantOpen(true)}\n className=\"inline-flex items-center gap-1 rounded-md bg-action-primary-bg px-3 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n <Plus className=\"h-4 w-4\" />\n Add participant\n </button>\n }\n />\n )}\n </div>\n <div className=\"rounded-xl border border-border-seam bg-gloss-card shadow-pop p-6\">\n <div className=\"mb-4 flex items-center justify-between\">\n <h3 className=\"text-sm font-semibold text-text-primary\">\n Action Items\n </h3>\n <button\n onClick={() => setActionItemModal({ mode: \"create\" })}\n className=\"inline-flex items-center gap-1 rounded-md bg-action-primary-bg px-2.5 py-1.5 text-xs font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n <Plus className=\"h-3.5 w-3.5\" />\n Add\n </button>\n </div>\n {meeting.actionItems.length > 0 ? (\n <div className=\"space-y-2\">\n {meeting.actionItems.map(\n (item: MeetingActionItemFieldsFragment) => (\n <div\n key={item.id}\n className=\"flex items-center justify-between gap-2 rounded-lg border border-border-subtle px-3 py-2\"\n >\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm text-text-primary\">\n {item.title}\n </p>\n <div className=\"mt-1\">\n <StatusBadge\n label={prettyStatus(item.status)}\n variant={ACTION_ITEM_VARIANT[item.status]}\n />\n </div>\n </div>\n <div className=\"flex items-center gap-1\">\n {item.status !== MeetingActionItemStatus.Completed && (\n <button\n onClick={() => handleCompleteItem(item.id)}\n className=\"rounded-md p-1 text-text-secondary hover:bg-status-success-bg hover:text-status-success-text\"\n aria-label=\"Complete action item\"\n >\n <Check className=\"h-4 w-4\" />\n </button>\n )}\n <button\n onClick={() =>\n setActionItemModal({ mode: \"edit\", item })\n }\n className=\"rounded-md p-1 text-text-secondary hover:bg-bg-sunken hover:text-text-primary\"\n aria-label=\"Edit action item\"\n >\n <Pencil className=\"h-4 w-4\" />\n </button>\n <button\n onClick={() => setDeletingItem(item)}\n className=\"rounded-md p-1 text-text-secondary hover:bg-status-error-bg hover:text-status-error-text\"\n aria-label=\"Delete action item\"\n >\n <Trash2 className=\"h-4 w-4\" />\n </button>\n </div>\n </div>\n ),\n )}\n </div>\n ) : (\n <EmptyState\n illustration=\"empty-generic\"\n title=\"No action items\"\n description=\"Capture follow-ups here so commitments from the meeting don't get lost.\"\n action={\n <button\n onClick={() => setActionItemModal({ mode: \"create\" })}\n className=\"inline-flex items-center gap-1 rounded-md bg-action-primary-bg px-3 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n <Plus className=\"h-4 w-4\" />\n Add action item\n </button>\n }\n />\n )}\n </div>\n </div>\n\n {/* Edit meeting */}\n {editOpen && (\n <MeetingFormModal\n open\n onClose={() => setEditOpen(false)}\n workspaceId={workspaceId}\n meeting={meeting}\n submitting={updating}\n error={Boolean(updateError)}\n onUpdate={handleUpdateMeeting}\n />\n )}\n\n {/* Delete meeting */}\n <Modal\n open={deleteOpen}\n onClose={() => setDeleteOpen(false)}\n title=\"Delete meeting\"\n description=\"This permanently removes the meeting and its analytics.\"\n size=\"sm\"\n footer={\n <>\n <button\n onClick={() => setDeleteOpen(false)}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n Cancel\n </button>\n <button\n onClick={handleDeleteMeeting}\n disabled={deletingMeeting}\n className=\"rounded-md bg-status-error-bg px-4 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg/90 disabled:opacity-50\"\n >\n {deletingMeeting ? \"Deleting...\" : \"Delete\"}\n </button>\n </>\n }\n >\n <p className=\"text-sm text-text-secondary\">\n Are you sure you want to delete{\" \"}\n <span className=\"font-medium text-text-primary\">{meeting.title}</span>\n ? This action cannot be undone.\n </p>\n </Modal>\n\n {/* Add participant */}\n {participantOpen && (\n <ParticipantFormModal\n open\n onClose={() => setParticipantOpen(false)}\n defaultUserId={currentUser?.id}\n submitting={addingParticipant}\n error={Boolean(addPError)}\n onSubmit={handleAddParticipant}\n />\n )}\n\n {/* Remove participant confirm */}\n <Modal\n open={Boolean(removingParticipant)}\n onClose={() => setRemovingParticipant(null)}\n title=\"Remove participant\"\n size=\"sm\"\n footer={\n <>\n <button\n onClick={() => setRemovingParticipant(null)}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n Cancel\n </button>\n <button\n onClick={handleRemoveParticipant}\n className=\"rounded-md bg-status-error-bg px-4 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg/90\"\n >\n Remove\n </button>\n </>\n }\n >\n <p className=\"text-sm text-text-secondary\">\n Remove{\" \"}\n <span className=\"font-medium text-text-primary\">\n {removingParticipant?.userId}\n </span>{\" \"}\n from this meeting?\n </p>\n </Modal>\n\n {/* Create / edit action item */}\n {actionItemModal && (\n <ActionItemFormModal\n open\n onClose={() => setActionItemModal(null)}\n item={\n actionItemModal.mode === \"edit\" ? actionItemModal.item : undefined\n }\n submitting={\n actionItemModal.mode === \"edit\" ? updatingItem : creatingItem\n }\n error={Boolean(\n actionItemModal.mode === \"edit\" ? updateItemError : createItemError,\n )}\n onCreate={handleCreateItem}\n onUpdate={\n actionItemModal.mode === \"edit\"\n ? (input) => handleUpdateItem(actionItemModal.item.id, input)\n : undefined\n }\n />\n )}\n\n {/* Delete action item confirm */}\n <Modal\n open={Boolean(deletingItem)}\n onClose={() => setDeletingItem(null)}\n title=\"Delete action item\"\n size=\"sm\"\n footer={\n <>\n <button\n onClick={() => setDeletingItem(null)}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n Cancel\n </button>\n <button\n onClick={handleDeleteItem}\n className=\"rounded-md bg-status-error-bg px-4 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg/90\"\n >\n Delete\n </button>\n </>\n }\n >\n <p className=\"text-sm text-text-secondary\">\n Delete action item{\" \"}\n <span className=\"font-medium text-text-primary\">\n {deletingItem?.title}\n </span>\n ?\n </p>\n </Modal>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"MeetingDetailPage.js","names":[],"sources":["../../../../src/timecampus/pages/meetings/MeetingDetailPage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { useParams, Link } from \"react-router-dom\";\nimport {\n Users,\n Clock,\n Pencil,\n Trash2,\n Plus,\n Star,\n Check,\n UserMinus,\n CalendarDays,\n} from \"lucide-react\";\nimport { NextSteps } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { StatCard } from \"../../components/StatCard\";\nimport { StatusBadge } from \"../../components/StatusBadge\";\nimport { EmptyState } from \"../../components/EmptyState\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport { Modal } from \"../../components/Modal\";\nimport { cn, type BadgeVariant } from \"../../utils/helpers\";\nimport {\n useTimeCampus,\n useTimeCampusNavigate,\n} from \"../../providers/TimeCampusProvider\";\nimport {\n useGetMeetingQuery,\n useUpdateMeetingMutation,\n useDeleteMeetingMutation,\n useRateMeetingMutation,\n useAddParticipantMutation,\n useRemoveParticipantMutation,\n useUpdateParticipantStatusMutation,\n useCreateActionItemMutation,\n useUpdateActionItemMutation,\n useCompleteActionItemMutation,\n useDeleteActionItemMutation,\n type MeetingParticipantFieldsFragment,\n type MeetingActionItemFieldsFragment,\n} from \"@/generated/wspace-operations\";\nimport {\n MeetingParticipantStatus,\n MeetingActionItemStatus,\n type UpdateMeetingInput,\n type AddMeetingParticipantInput,\n type CreateActionItemInput,\n type UpdateActionItemInput,\n} from \"@/generated/wspace-types\";\nimport { formatCurrency } from \"../../utils/helpers\";\nimport { MeetingFormModal } from \"./MeetingFormModal\";\nimport { ParticipantFormModal } from \"./ParticipantFormModal\";\nimport { ActionItemFormModal } from \"./ActionItemFormModal\";\n\nconst PARTICIPANT_STATUS_OPTIONS = [\n MeetingParticipantStatus.Pending,\n MeetingParticipantStatus.Accepted,\n MeetingParticipantStatus.Tentative,\n MeetingParticipantStatus.Declined,\n];\n\nconst ACTION_ITEM_VARIANT: Record<MeetingActionItemStatus, BadgeVariant> = {\n [MeetingActionItemStatus.Completed]: \"success\",\n [MeetingActionItemStatus.InProgress]: \"info\",\n [MeetingActionItemStatus.Pending]: \"warning\",\n [MeetingActionItemStatus.Cancelled]: \"muted\",\n};\n\n/** Deterministic 2-char monogram from an opaque participant id. */\nfunction idMonogram(id: string): string {\n return (\n id\n .replace(/[^a-zA-Z0-9]/g, \"\")\n .slice(0, 2)\n .toUpperCase() || \"?\"\n );\n}\n\nfunction prettyStatus(status: string): string {\n const lower = status.replace(/_/g, \" \").toLowerCase();\n return lower.charAt(0).toUpperCase() + lower.slice(1);\n}\n\nexport function MeetingDetailPage() {\n const { id } = useParams<{ id: string }>();\n const { workspaceId = \"\", currentUser } = useTimeCampus();\n const navigate = useTimeCampusNavigate();\n const meetingId = id ?? \"\";\n\n const { data, loading, error, refetch } = useGetMeetingQuery({\n variables: { id: meetingId, workspaceId },\n skip: !workspaceId || !id,\n });\n\n const [updateMeeting, { loading: updating, error: updateError }] =\n useUpdateMeetingMutation();\n const [deleteMeeting, { loading: deletingMeeting }] =\n useDeleteMeetingMutation();\n const [rateMeeting, { loading: rating }] = useRateMeetingMutation();\n const [addParticipant, { loading: addingParticipant, error: addPError }] =\n useAddParticipantMutation();\n const [removeParticipant] = useRemoveParticipantMutation();\n const [updateParticipantStatus] = useUpdateParticipantStatusMutation();\n const [createActionItem, { loading: creatingItem, error: createItemError }] =\n useCreateActionItemMutation();\n const [updateActionItem, { loading: updatingItem, error: updateItemError }] =\n useUpdateActionItemMutation();\n const [completeActionItem] = useCompleteActionItemMutation();\n const [deleteActionItem] = useDeleteActionItemMutation();\n\n const [editOpen, setEditOpen] = useState(false);\n const [deleteOpen, setDeleteOpen] = useState(false);\n const [participantOpen, setParticipantOpen] = useState(false);\n const [removingParticipant, setRemovingParticipant] =\n useState<MeetingParticipantFieldsFragment | null>(null);\n const [actionItemModal, setActionItemModal] = useState<\n | { mode: \"create\" }\n | { mode: \"edit\"; item: MeetingActionItemFieldsFragment }\n | null\n >(null);\n const [deletingItem, setDeletingItem] =\n useState<MeetingActionItemFieldsFragment | null>(null);\n\n const meeting = data?.meeting;\n\n if (loading) {\n return (\n <PageLayout title=\"Meeting Details\" description={`Meeting ID: ${id}`}>\n <LoadingSkeleton variant=\"detail\" />\n </PageLayout>\n );\n }\n\n if (error || !meeting) {\n return (\n <PageLayout title=\"Meeting Details\" description={`Meeting ID: ${id}`}>\n <div className=\"rounded-lg border border-status-error-bg/40 bg-status-error-bg p-4 text-sm text-status-error-text\">\n {error\n ? \"Failed to load meeting details. Please try again later.\"\n : \"Meeting not found.\"}\n </div>\n </PageLayout>\n );\n }\n\n const pendingActions = meeting.actionItems.filter(\n (a: MeetingActionItemFieldsFragment) => a.status !== \"COMPLETED\",\n ).length;\n const currentRating = meeting.rating ?? 0;\n\n const handleUpdateMeeting = async (input: UpdateMeetingInput) => {\n await updateMeeting({ variables: { id: meetingId, workspaceId, input } });\n await refetch();\n setEditOpen(false);\n };\n\n const handleDeleteMeeting = async () => {\n await deleteMeeting({ variables: { id: meetingId, workspaceId } });\n setDeleteOpen(false);\n navigate(\"/meetings\");\n };\n\n const handleRate = async (value: number) => {\n await rateMeeting({\n variables: { id: meetingId, workspaceId, rating: value },\n });\n await refetch();\n };\n\n const handleAddParticipant = async (input: AddMeetingParticipantInput) => {\n await addParticipant({\n variables: { meetingId, workspaceId, input },\n });\n await refetch();\n setParticipantOpen(false);\n };\n\n const handleRemoveParticipant = async () => {\n if (!removingParticipant) return;\n await removeParticipant({\n variables: {\n meetingId,\n participantId: removingParticipant.id,\n workspaceId,\n },\n });\n setRemovingParticipant(null);\n await refetch();\n };\n\n const handleParticipantStatus = async (\n participantId: string,\n status: MeetingParticipantStatus,\n ) => {\n await updateParticipantStatus({\n variables: { meetingId, participantId, workspaceId, status },\n });\n await refetch();\n };\n\n const handleCreateItem = async (input: CreateActionItemInput) => {\n await createActionItem({ variables: { meetingId, workspaceId, input } });\n await refetch();\n setActionItemModal(null);\n };\n\n const handleUpdateItem = async (\n itemId: string,\n input: UpdateActionItemInput,\n ) => {\n await updateActionItem({ variables: { id: itemId, workspaceId, input } });\n await refetch();\n setActionItemModal(null);\n };\n\n const handleCompleteItem = async (itemId: string) => {\n await completeActionItem({ variables: { id: itemId, workspaceId } });\n await refetch();\n };\n\n const handleDeleteItem = async () => {\n if (!deletingItem) return;\n await deleteActionItem({ variables: { id: deletingItem.id, workspaceId } });\n setDeletingItem(null);\n await refetch();\n };\n\n return (\n <PageLayout\n title=\"Meeting Details\"\n description={meeting.title}\n purpose=\"The full record of one meeting — who attended, what it cost in people's time, and the action items it produced — so you can follow up on commitments and judge whether the meeting was worth it.\"\n actions={\n <>\n <button\n onClick={() => setEditOpen(true)}\n className=\"inline-flex items-center gap-1.5 rounded-md border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n <Pencil className=\"h-4 w-4\" />\n Edit\n </button>\n <button\n onClick={() => setDeleteOpen(true)}\n className=\"inline-flex items-center gap-1.5 rounded-md border border-border-subtle px-3 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg/40\"\n >\n <Trash2 className=\"h-4 w-4\" />\n Delete\n </button>\n </>\n }\n >\n <NextSteps\n storageKey=\"timecampus-meeting-detail\"\n className=\"mb-6\"\n steps={[\n {\n id: \"all-meetings\",\n label: \"Back to all meetings\",\n description: \"Browse and schedule other meetings.\",\n onClick: () => navigate(\"/meetings\"),\n },\n {\n id: \"analytics\",\n label: \"See meeting analytics\",\n description: \"Understand your overall meeting load and cost.\",\n onClick: () => navigate(\"/meetings/analytics\"),\n },\n ]}\n />\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <StatCard\n featured\n title=\"Duration\"\n value={`${meeting.duration ?? 0} min`}\n icon={<Clock className=\"h-5 w-5\" />}\n />\n <StatCard\n title=\"Participants\"\n value={String(meeting.participants.length)}\n icon={<Users className=\"h-5 w-5\" />}\n />\n <StatCard\n title=\"Cost\"\n value={meeting.cost != null ? formatCurrency(meeting.cost) : \"N/A\"}\n description=\"Based on participant rates\"\n />\n <StatCard\n title=\"Action Items\"\n value={String(meeting.actionItems.length)}\n description={`${pendingActions} pending`}\n />\n </div>\n\n {/* Calendar link — surfaces the wspace-calendar event auto-linked on\n create (MeetingMeta.calendarEventId). Hidden entirely when the\n meeting has no linked event (e.g. calendar-svc was unreachable at\n creation time), rather than showing a dead/broken state. */}\n {meeting.calendarEventId && (\n <Link\n to={`/calendar/event/${meeting.calendarEventId}`}\n className=\"inline-flex w-fit items-center gap-1.5 rounded-full border border-border-subtle bg-bg-surface px-3 py-1.5 text-xs font-medium text-text-secondary hover:bg-bg-sunken/50 hover:text-text-primary\"\n >\n <CalendarDays className=\"h-3.5 w-3.5\" />\n View in Calendar\n </Link>\n )}\n\n {/* Rating */}\n <div className=\"rounded-xl border border-border-seam bg-gloss-card shadow-pop p-6\">\n <h3 className=\"mb-1 text-sm font-semibold text-text-primary\">\n Rate this meeting\n </h3>\n <p className=\"mb-3 text-xs text-text-secondary\">\n Was it worth the time? Your rating feeds meeting analytics.\n </p>\n <div className=\"flex items-center gap-1\">\n {[1, 2, 3, 4, 5].map((value) => (\n <button\n key={value}\n onClick={() => handleRate(value)}\n disabled={rating}\n aria-label={`Rate ${value} star${value > 1 ? \"s\" : \"\"}`}\n className=\"rounded-md p-1 text-text-secondary hover:bg-bg-sunken disabled:opacity-50\"\n >\n <Star\n className={cn(\n \"h-6 w-6\",\n value <= currentRating\n ? \"fill-status-warning-text text-status-warning-text\"\n : \"text-text-secondary\",\n )}\n />\n </button>\n ))}\n {currentRating > 0 && (\n <span className=\"ml-2 text-sm text-text-secondary\">\n {currentRating} / 5\n </span>\n )}\n </div>\n </div>\n\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\n <div className=\"rounded-xl border border-border-seam bg-gloss-card shadow-pop p-6\">\n <div className=\"mb-4 flex items-center justify-between\">\n <h3 className=\"text-sm font-semibold text-text-primary\">\n Participants\n </h3>\n <button\n onClick={() => setParticipantOpen(true)}\n className=\"inline-flex items-center gap-1 rounded-md bg-action-primary-bg px-2.5 py-1.5 text-xs font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n <Plus className=\"h-3.5 w-3.5\" />\n Add\n </button>\n </div>\n {meeting.participants.length > 0 ? (\n <div className=\"space-y-2\">\n {meeting.participants.map(\n (p: MeetingParticipantFieldsFragment) => (\n <div\n key={p.id}\n className=\"flex items-center justify-between gap-2 rounded-lg border border-border-subtle px-3 py-2\"\n >\n <div className=\"flex min-w-0 items-center gap-2.5\">\n <span className=\"flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-action-primary-bg/10 text-[11px] font-semibold text-primary\">\n {idMonogram(p.userId)}\n </span>\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm text-text-primary\">\n {p.userId}\n </p>\n <p className=\"text-xs text-text-secondary\">{p.role}</p>\n </div>\n </div>\n <div className=\"flex items-center gap-2\">\n <select\n value={p.status}\n onChange={(e) =>\n handleParticipantStatus(\n p.id,\n e.target.value as MeetingParticipantStatus,\n )\n }\n className=\"rounded-md border border-border-subtle bg-bg-surface px-2 py-1 text-xs text-text-primary\"\n aria-label=\"Participant status\"\n >\n {PARTICIPANT_STATUS_OPTIONS.map((s) => (\n <option key={s} value={s}>\n {s}\n </option>\n ))}\n </select>\n <button\n onClick={() => setRemovingParticipant(p)}\n className=\"rounded-md p-1 text-text-secondary hover:bg-status-error-bg hover:text-status-error-text\"\n aria-label=\"Remove participant\"\n >\n <UserMinus className=\"h-4 w-4\" />\n </button>\n </div>\n </div>\n ),\n )}\n </div>\n ) : (\n <EmptyState\n illustration=\"empty-people\"\n title=\"No participants added\"\n description=\"Add attendees to capture who was in the room and what it cost.\"\n action={\n <button\n onClick={() => setParticipantOpen(true)}\n className=\"inline-flex items-center gap-1 rounded-md bg-action-primary-bg px-3 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n <Plus className=\"h-4 w-4\" />\n Add participant\n </button>\n }\n />\n )}\n </div>\n <div className=\"rounded-xl border border-border-seam bg-gloss-card shadow-pop p-6\">\n <div className=\"mb-4 flex items-center justify-between\">\n <h3 className=\"text-sm font-semibold text-text-primary\">\n Action Items\n </h3>\n <button\n onClick={() => setActionItemModal({ mode: \"create\" })}\n className=\"inline-flex items-center gap-1 rounded-md bg-action-primary-bg px-2.5 py-1.5 text-xs font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n <Plus className=\"h-3.5 w-3.5\" />\n Add\n </button>\n </div>\n {meeting.actionItems.length > 0 ? (\n <div className=\"space-y-2\">\n {meeting.actionItems.map(\n (item: MeetingActionItemFieldsFragment) => (\n <div\n key={item.id}\n className=\"flex items-center justify-between gap-2 rounded-lg border border-border-subtle px-3 py-2\"\n >\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm text-text-primary\">\n {item.title}\n </p>\n <div className=\"mt-1\">\n <StatusBadge\n label={prettyStatus(item.status)}\n variant={ACTION_ITEM_VARIANT[item.status]}\n />\n </div>\n </div>\n <div className=\"flex items-center gap-1\">\n {item.status !== MeetingActionItemStatus.Completed && (\n <button\n onClick={() => handleCompleteItem(item.id)}\n className=\"rounded-md p-1 text-text-secondary hover:bg-status-success-bg hover:text-status-success-text\"\n aria-label=\"Complete action item\"\n >\n <Check className=\"h-4 w-4\" />\n </button>\n )}\n <button\n onClick={() =>\n setActionItemModal({ mode: \"edit\", item })\n }\n className=\"rounded-md p-1 text-text-secondary hover:bg-bg-sunken hover:text-text-primary\"\n aria-label=\"Edit action item\"\n >\n <Pencil className=\"h-4 w-4\" />\n </button>\n <button\n onClick={() => setDeletingItem(item)}\n className=\"rounded-md p-1 text-text-secondary hover:bg-status-error-bg hover:text-status-error-text\"\n aria-label=\"Delete action item\"\n >\n <Trash2 className=\"h-4 w-4\" />\n </button>\n </div>\n </div>\n ),\n )}\n </div>\n ) : (\n <EmptyState\n illustration=\"empty-generic\"\n title=\"No action items\"\n description=\"Capture follow-ups here so commitments from the meeting don't get lost.\"\n action={\n <button\n onClick={() => setActionItemModal({ mode: \"create\" })}\n className=\"inline-flex items-center gap-1 rounded-md bg-action-primary-bg px-3 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n <Plus className=\"h-4 w-4\" />\n Add action item\n </button>\n }\n />\n )}\n </div>\n </div>\n\n {/* Edit meeting */}\n {editOpen && (\n <MeetingFormModal\n open\n onClose={() => setEditOpen(false)}\n workspaceId={workspaceId}\n meeting={meeting}\n submitting={updating}\n error={Boolean(updateError)}\n onUpdate={handleUpdateMeeting}\n />\n )}\n\n {/* Delete meeting */}\n <Modal\n open={deleteOpen}\n onClose={() => setDeleteOpen(false)}\n title=\"Delete meeting\"\n description=\"This permanently removes the meeting and its analytics.\"\n size=\"sm\"\n footer={\n <>\n <button\n onClick={() => setDeleteOpen(false)}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n Cancel\n </button>\n <button\n onClick={handleDeleteMeeting}\n disabled={deletingMeeting}\n className=\"rounded-md bg-status-error-bg px-4 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg/90 disabled:opacity-50\"\n >\n {deletingMeeting ? \"Deleting...\" : \"Delete\"}\n </button>\n </>\n }\n >\n <p className=\"text-sm text-text-secondary\">\n Are you sure you want to delete{\" \"}\n <span className=\"font-medium text-text-primary\">{meeting.title}</span>\n ? This action cannot be undone.\n </p>\n </Modal>\n\n {/* Add participant */}\n {participantOpen && (\n <ParticipantFormModal\n open\n onClose={() => setParticipantOpen(false)}\n defaultUserId={currentUser?.id}\n submitting={addingParticipant}\n error={Boolean(addPError)}\n onSubmit={handleAddParticipant}\n />\n )}\n\n {/* Remove participant confirm */}\n <Modal\n open={Boolean(removingParticipant)}\n onClose={() => setRemovingParticipant(null)}\n title=\"Remove participant\"\n size=\"sm\"\n footer={\n <>\n <button\n onClick={() => setRemovingParticipant(null)}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n Cancel\n </button>\n <button\n onClick={handleRemoveParticipant}\n className=\"rounded-md bg-status-error-bg px-4 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg/90\"\n >\n Remove\n </button>\n </>\n }\n >\n <p className=\"text-sm text-text-secondary\">\n Remove{\" \"}\n <span className=\"font-medium text-text-primary\">\n {removingParticipant?.userId}\n </span>{\" \"}\n from this meeting?\n </p>\n </Modal>\n\n {/* Create / edit action item */}\n {actionItemModal && (\n <ActionItemFormModal\n open\n onClose={() => setActionItemModal(null)}\n item={\n actionItemModal.mode === \"edit\" ? actionItemModal.item : undefined\n }\n submitting={\n actionItemModal.mode === \"edit\" ? updatingItem : creatingItem\n }\n error={Boolean(\n actionItemModal.mode === \"edit\" ? updateItemError : createItemError,\n )}\n onCreate={handleCreateItem}\n onUpdate={\n actionItemModal.mode === \"edit\"\n ? (input) => handleUpdateItem(actionItemModal.item.id, input)\n : undefined\n }\n />\n )}\n\n {/* Delete action item confirm */}\n <Modal\n open={Boolean(deletingItem)}\n onClose={() => setDeletingItem(null)}\n title=\"Delete action item\"\n size=\"sm\"\n footer={\n <>\n <button\n onClick={() => setDeletingItem(null)}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n Cancel\n </button>\n <button\n onClick={handleDeleteItem}\n className=\"rounded-md bg-status-error-bg px-4 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg/90\"\n >\n Delete\n </button>\n </>\n }\n >\n <p className=\"text-sm text-text-secondary\">\n Delete action item{\" \"}\n <span className=\"font-medium text-text-primary\">\n {deletingItem?.title}\n </span>\n ?\n </p>\n </Modal>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,11 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Page context gathering — captures the user's current browser context
|
|
3
3
|
* to be sent alongside every assistant prompt for better AI awareness.
|
|
4
|
+
*
|
|
5
|
+
* Only emit keys the agent's ALLOWED_CONTEXT_KEYS allow-list actually
|
|
6
|
+
* forwards (`~/products/wspace/sandbox/ai-assistant/src/context-loader.ts`):
|
|
7
|
+
* currentUrl, currentPage, entityType, entityId, entityName, pageTitle,
|
|
8
|
+
* pageType, activeTab, activeTool, selectedText, workspaceId, workspaceName,
|
|
9
|
+
* projectId, projectName (+ legacy per-product back-compat keys). `platform`
|
|
10
|
+
* and `version` are silently dropped server-side (the agent already knows
|
|
11
|
+
* the product from `AI_ASSISTANT_PRODUCT` at boot), and `userAgent` is sent
|
|
12
|
+
* over the wire but dropped agent-side — a needless fingerprinting leak for
|
|
13
|
+
* a privacy-first product. None of the five are emitted here.
|
|
4
14
|
*/
|
|
5
15
|
interface ConversationContextShape {
|
|
6
16
|
entityType?: string;
|
|
7
17
|
entityId?: string;
|
|
8
18
|
activeTab?: string;
|
|
19
|
+
projectId?: string;
|
|
9
20
|
metadata?: Record<string, unknown>;
|
|
10
21
|
}
|
|
11
22
|
export interface PageContext {
|
|
@@ -13,11 +24,8 @@ export interface PageContext {
|
|
|
13
24
|
currentPage: string;
|
|
14
25
|
entityType?: string;
|
|
15
26
|
entityId?: string;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
viewportSize: string;
|
|
19
|
-
userAgent: string;
|
|
20
|
-
timestamp: string;
|
|
27
|
+
activeTab?: string;
|
|
28
|
+
projectId?: string;
|
|
21
29
|
}
|
|
22
30
|
export declare function gatherPageContext(): PageContext;
|
|
23
31
|
export declare function resolveTimeCampusConversationContext(pathname: string, search: string): ConversationContextShape;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pageContext.d.ts","sourceRoot":"","sources":["../../src/utils/pageContext.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"pageContext.d.ts","sourceRoot":"","sources":["../../src/utils/pageContext.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,UAAU,wBAAwB;IAChC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,WAAW;IAG1B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IAGpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAGlB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,iBAAiB,IAAI,WAAW,CAiB/C;AAMD,wBAAgB,oCAAoC,CAClD,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,GACb,wBAAwB,CAoE1B"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
//#region src/utils/pageContext.ts
|
|
2
|
+
function e() {
|
|
3
|
+
let e = window.location.href, t = window.location.pathname.split("/").filter(Boolean).slice(0, 3).join("/") || "home", r = n(window.location.pathname, window.location.search);
|
|
4
|
+
return {
|
|
5
|
+
currentUrl: e,
|
|
6
|
+
currentPage: t,
|
|
7
|
+
entityType: r.entityType,
|
|
8
|
+
entityId: r.entityId,
|
|
9
|
+
activeTab: r.activeTab,
|
|
10
|
+
projectId: r.projectId
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
function t(e, t) {
|
|
14
|
+
return t.match(e);
|
|
15
|
+
}
|
|
16
|
+
function n(e, n) {
|
|
17
|
+
let r = new URLSearchParams(n);
|
|
18
|
+
for (let [n, r] of [
|
|
19
|
+
[/^\/?timecampus\/time\/projects\/([^/]+)$/, "TIMECAMPUS_PROJECT"],
|
|
20
|
+
[/^\/?timecampus\/time\/tasks\/([^/]+)$/, "TIMECAMPUS_TASK"],
|
|
21
|
+
[/^\/?timecampus\/meetings\/([^/]+)$/, "TIMECAMPUS_MEETING"],
|
|
22
|
+
[/^\/?timecampus\/pomodoro\/sessions\/([^/]+)$/, "TIMECAMPUS_POMODORO_SESSION"],
|
|
23
|
+
[/^\/?timecampus\/lifetime\/goals\/([^/]+)$/, "TIMECAMPUS_LIFE_GOAL"],
|
|
24
|
+
[/^\/?timecampus\/resource-planning\/plans\/([^/]+)$/, "TIMECAMPUS_RESOURCE_PLAN"]
|
|
25
|
+
]) {
|
|
26
|
+
let i = t(n, e);
|
|
27
|
+
if (i) return {
|
|
28
|
+
entityType: r,
|
|
29
|
+
entityId: i[1],
|
|
30
|
+
metadata: { routePattern: n.source }
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
for (let [n, r] of [
|
|
34
|
+
[/^\/?timecampus\/timeline\/?$/, "TIMECAMPUS_TIMELINE"],
|
|
35
|
+
[/^\/?timecampus\/time\/entries\/?$/, "TIMECAMPUS_TIME_ENTRY"],
|
|
36
|
+
[/^\/?timecampus\/expense-tracking\/?$/, "TIMECAMPUS_EXPENSE"],
|
|
37
|
+
[/^\/?timecampus\/time-approval\/?$/, "TIMECAMPUS_APPROVAL"],
|
|
38
|
+
[/^\/?timecampus\/client-portal\/?$/, "TIMECAMPUS_CLIENT_PORTAL"],
|
|
39
|
+
[/^\/?timecampus\/application-usage\/?$/, "TIMECAMPUS_APP_USAGE"]
|
|
40
|
+
]) if (t(n, e)) return {
|
|
41
|
+
entityType: r,
|
|
42
|
+
metadata: { routePattern: n.source }
|
|
43
|
+
};
|
|
44
|
+
let i = r.get("tab") ?? (() => {
|
|
45
|
+
let t = e.split("/").filter(Boolean);
|
|
46
|
+
return t.length > 2 ? t[t.length - 1] : void 0;
|
|
47
|
+
})();
|
|
48
|
+
return {
|
|
49
|
+
activeTab: i ?? void 0,
|
|
50
|
+
projectId: r.get("projectId") ?? void 0,
|
|
51
|
+
metadata: {
|
|
52
|
+
...i ? { activeTab: i } : {},
|
|
53
|
+
...r.get("projectId") ? { projectId: r.get("projectId") } : {},
|
|
54
|
+
...r.get("date") ? { date: r.get("date") } : {}
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
//#endregion
|
|
59
|
+
export { e as gatherPageContext };
|
|
60
|
+
|
|
61
|
+
//# sourceMappingURL=pageContext.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pageContext.js","names":[],"sources":["../../src/utils/pageContext.ts"],"sourcesContent":["/**\n * Page context gathering — captures the user's current browser context\n * to be sent alongside every assistant prompt for better AI awareness.\n *\n * Only emit keys the agent's ALLOWED_CONTEXT_KEYS allow-list actually\n * forwards (`~/products/wspace/sandbox/ai-assistant/src/context-loader.ts`):\n * currentUrl, currentPage, entityType, entityId, entityName, pageTitle,\n * pageType, activeTab, activeTool, selectedText, workspaceId, workspaceName,\n * projectId, projectName (+ legacy per-product back-compat keys). `platform`\n * and `version` are silently dropped server-side (the agent already knows\n * the product from `AI_ASSISTANT_PRODUCT` at boot), and `userAgent` is sent\n * over the wire but dropped agent-side — a needless fingerprinting leak for\n * a privacy-first product. None of the five are emitted here.\n */\n\ninterface ConversationContextShape {\n entityType?: string;\n entityId?: string;\n activeTab?: string;\n projectId?: string;\n metadata?: Record<string, unknown>;\n}\n\nexport interface PageContext {\n // `currentUrl` (NOT `activeUrl`) — must match the agent's allowed-context-key\n // contract, otherwise the agent filters it out.\n currentUrl: string;\n currentPage: string;\n // The entity the user is currently looking at, resolved from the route. Lets\n // the assistant act on \"this / current / here\" without asking for an id.\n entityType?: string;\n entityId?: string;\n // Surfaced separately from `metadata` so the allow-listed `activeTab` /\n // `projectId` keys actually reach the agent instead of being discarded.\n activeTab?: string;\n projectId?: string;\n}\n\nexport function gatherPageContext(): PageContext {\n const url = window.location.href;\n const pathSegments = window.location.pathname.split(\"/\").filter(Boolean);\n const currentPage = pathSegments.slice(0, 3).join(\"/\") || \"home\";\n const entity = resolveTimeCampusConversationContext(\n window.location.pathname,\n window.location.search,\n );\n\n return {\n currentUrl: url,\n currentPage,\n entityType: entity.entityType,\n entityId: entity.entityId,\n activeTab: entity.activeTab,\n projectId: entity.projectId,\n };\n}\n\nfunction matchPath(pattern: RegExp, pathname: string): RegExpMatchArray | null {\n return pathname.match(pattern);\n}\n\nexport function resolveTimeCampusConversationContext(\n pathname: string,\n search: string,\n): ConversationContextShape {\n const params = new URLSearchParams(search);\n\n const directMatches: Array<[RegExp, string]> = [\n [/^\\/?timecampus\\/time\\/projects\\/([^/]+)$/, \"TIMECAMPUS_PROJECT\"],\n [/^\\/?timecampus\\/time\\/tasks\\/([^/]+)$/, \"TIMECAMPUS_TASK\"],\n [/^\\/?timecampus\\/meetings\\/([^/]+)$/, \"TIMECAMPUS_MEETING\"],\n [\n /^\\/?timecampus\\/pomodoro\\/sessions\\/([^/]+)$/,\n \"TIMECAMPUS_POMODORO_SESSION\",\n ],\n [/^\\/?timecampus\\/lifetime\\/goals\\/([^/]+)$/, \"TIMECAMPUS_LIFE_GOAL\"],\n [\n /^\\/?timecampus\\/resource-planning\\/plans\\/([^/]+)$/,\n \"TIMECAMPUS_RESOURCE_PLAN\",\n ],\n ];\n\n for (const [pattern, entityType] of directMatches) {\n const match = matchPath(pattern, pathname);\n if (match) {\n return {\n entityType,\n entityId: match[1],\n metadata: { routePattern: pattern.source },\n };\n }\n }\n\n // Page-level matches: these TimeCampus modules are flat list/dashboard\n // pages with no per-entity detail route (confirmed against\n // TimeCampusRoutes.tsx), so there is no id to capture — but the assistant\n // still benefits from knowing which module the user is looking at, so it\n // can scope \"show me my expenses\" / \"approve this\" style prompts without\n // asking which module the user means.\n const pageLevelMatches: Array<[RegExp, string]> = [\n [/^\\/?timecampus\\/timeline\\/?$/, \"TIMECAMPUS_TIMELINE\"],\n [/^\\/?timecampus\\/time\\/entries\\/?$/, \"TIMECAMPUS_TIME_ENTRY\"],\n [/^\\/?timecampus\\/expense-tracking\\/?$/, \"TIMECAMPUS_EXPENSE\"],\n [/^\\/?timecampus\\/time-approval\\/?$/, \"TIMECAMPUS_APPROVAL\"],\n [/^\\/?timecampus\\/client-portal\\/?$/, \"TIMECAMPUS_CLIENT_PORTAL\"],\n [/^\\/?timecampus\\/application-usage\\/?$/, \"TIMECAMPUS_APP_USAGE\"],\n ];\n\n for (const [pattern, entityType] of pageLevelMatches) {\n if (matchPath(pattern, pathname)) {\n return { entityType, metadata: { routePattern: pattern.source } };\n }\n }\n\n const activeTab =\n params.get(\"tab\") ??\n (() => {\n const segments = pathname.split(\"/\").filter(Boolean);\n return segments.length > 2 ? segments[segments.length - 1] : undefined;\n })();\n\n return {\n activeTab: activeTab ?? undefined,\n projectId: params.get(\"projectId\") ?? undefined,\n metadata: {\n ...(activeTab ? { activeTab } : {}),\n ...(params.get(\"projectId\")\n ? { projectId: params.get(\"projectId\") }\n : {}),\n ...(params.get(\"date\") ? { date: params.get(\"date\") } : {}),\n },\n };\n}\n"],"mappings":";AAsCA,SAAgB,IAAiC;CAC/C,IAAM,IAAM,OAAO,SAAS,MAEtB,IADe,OAAO,SAAS,SAAS,MAAM,IAAI,CAAC,OAAO,QAAQ,CACvC,MAAM,GAAG,EAAE,CAAC,KAAK,IAAI,IAAI,QACpD,IAAS,EACb,OAAO,SAAS,UAChB,OAAO,SAAS,OACjB;AAED,QAAO;EACL,YAAY;EACZ;EACA,YAAY,EAAO;EACnB,UAAU,EAAO;EACjB,WAAW,EAAO;EAClB,WAAW,EAAO;EACnB;;AAGH,SAAS,EAAU,GAAiB,GAA2C;AAC7E,QAAO,EAAS,MAAM,EAAQ;;AAGhC,SAAgB,EACd,GACA,GAC0B;CAC1B,IAAM,IAAS,IAAI,gBAAgB,EAAO;AAiB1C,MAAK,IAAM,CAAC,GAAS,MAf0B;EAC7C,CAAC,4CAA4C,qBAAqB;EAClE,CAAC,yCAAyC,kBAAkB;EAC5D,CAAC,sCAAsC,qBAAqB;EAC5D,CACE,gDACA,8BACD;EACD,CAAC,6CAA6C,uBAAuB;EACrE,CACE,sDACA,2BACD;EACF,EAEkD;EACjD,IAAM,IAAQ,EAAU,GAAS,EAAS;AAC1C,MAAI,EACF,QAAO;GACL;GACA,UAAU,EAAM;GAChB,UAAU,EAAE,cAAc,EAAQ,QAAQ;GAC3C;;AAmBL,MAAK,IAAM,CAAC,GAAS,MAT6B;EAChD,CAAC,gCAAgC,sBAAsB;EACvD,CAAC,qCAAqC,wBAAwB;EAC9D,CAAC,wCAAwC,qBAAqB;EAC9D,CAAC,qCAAqC,sBAAsB;EAC5D,CAAC,qCAAqC,2BAA2B;EACjE,CAAC,yCAAyC,uBAAuB;EAClE,CAGC,KAAI,EAAU,GAAS,EAAS,CAC9B,QAAO;EAAE;EAAY,UAAU,EAAE,cAAc,EAAQ,QAAQ;EAAE;CAIrE,IAAM,IACJ,EAAO,IAAI,MAAM,WACV;EACL,IAAM,IAAW,EAAS,MAAM,IAAI,CAAC,OAAO,QAAQ;AACpD,SAAO,EAAS,SAAS,IAAI,EAAS,EAAS,SAAS,KAAK,KAAA;KAC3D;AAEN,QAAO;EACL,WAAW,KAAa,KAAA;EACxB,WAAW,EAAO,IAAI,YAAY,IAAI,KAAA;EACtC,UAAU;GACR,GAAI,IAAY,EAAE,cAAW,GAAG,EAAE;GAClC,GAAI,EAAO,IAAI,YAAY,GACvB,EAAE,WAAW,EAAO,IAAI,YAAY,EAAE,GACtC,EAAE;GACN,GAAI,EAAO,IAAI,OAAO,GAAG,EAAE,MAAM,EAAO,IAAI,OAAO,EAAE,GAAG,EAAE;GAC3D;EACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@burdenoff/microfe-timecampus",
|
|
3
|
-
"version": "2026.
|
|
3
|
+
"version": "2026.719.2",
|
|
4
4
|
"description": "Burdenoff TimeCampus Microfrontend - AI-powered time management and productivity platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -31,7 +31,10 @@
|
|
|
31
31
|
"lint:sanity": "if [ \"${CI:-}\" = \"true\" ]; then bun run lint; else bun run lint -- --cache --cache-location .eslintcache; fi",
|
|
32
32
|
"format": "prettier --write \"src/**/*.{ts,tsx}\"",
|
|
33
33
|
"type:check": "tsc --noEmit",
|
|
34
|
-
"
|
|
34
|
+
"test": "vitest run",
|
|
35
|
+
"test:watch": "vitest",
|
|
36
|
+
"test:coverage": "vitest run --coverage",
|
|
37
|
+
"sanity": "bun run codegen && bun run lint:sanity && bun run format && bun run type:check && bun run test && bun run build && bun run build-storybook",
|
|
35
38
|
"storybook": "storybook dev -p 6006",
|
|
36
39
|
"build-storybook": "storybook build",
|
|
37
40
|
"test:storybook": "test-storybook",
|
|
@@ -80,6 +83,7 @@
|
|
|
80
83
|
"@types/react": "^19.0.2",
|
|
81
84
|
"@types/react-dom": "^19.0.2",
|
|
82
85
|
"@vitejs/plugin-react": "^4.3.4",
|
|
86
|
+
"@vitest/coverage-v8": "3.2.7",
|
|
83
87
|
"autoprefixer": "^10.4.20",
|
|
84
88
|
"eslint": "^9.18.0",
|
|
85
89
|
"eslint-plugin-react": "^7.37.5",
|
|
@@ -94,7 +98,8 @@
|
|
|
94
98
|
"typescript": "^5.9.0",
|
|
95
99
|
"typescript-eslint": "^8.56.0",
|
|
96
100
|
"vite": "8.0.8",
|
|
97
|
-
"vite-plugin-dts": "3.9.1"
|
|
101
|
+
"vite-plugin-dts": "3.9.1",
|
|
102
|
+
"vitest": "3.2.7"
|
|
98
103
|
},
|
|
99
104
|
"peerDependencies": {
|
|
100
105
|
"@apollo/client": "^4.0.11",
|