@burdenoff/microfe-timecampus 2026.626.2 → 2026.703.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/generated/wspace-operations.js +996 -816
- package/dist/generated/wspace-operations.js.map +1 -1
- package/dist/generated/wspace-types.js +28 -0
- package/dist/generated/wspace-types.js.map +1 -0
- package/dist/index.js +4 -4
- package/dist/timecampus/TimeCampusRoutes.js +16 -12
- package/dist/timecampus/TimeCampusRoutes.js.map +1 -1
- package/dist/timecampus/components/Modal.js +65 -0
- package/dist/timecampus/components/Modal.js.map +1 -0
- package/dist/timecampus/components/StatusBadge.js +28 -0
- package/dist/timecampus/components/StatusBadge.js.map +1 -0
- package/dist/timecampus/components/Toast.js +87 -0
- package/dist/timecampus/components/Toast.js.map +1 -0
- package/dist/timecampus/index.js +1 -1
- package/dist/timecampus/pages/HomePage.js +143 -135
- package/dist/timecampus/pages/HomePage.js.map +1 -1
- package/dist/timecampus/pages/applets/AppletStorePage.js +14 -19
- package/dist/timecampus/pages/applets/AppletStorePage.js.map +1 -1
- package/dist/timecampus/pages/applets/InstalledAppletsPage.js +49 -37
- package/dist/timecampus/pages/applets/InstalledAppletsPage.js.map +1 -1
- package/dist/timecampus/pages/application-usage/ApplicationUsagePage.js +315 -87
- package/dist/timecampus/pages/application-usage/ApplicationUsagePage.js.map +1 -1
- package/dist/timecampus/pages/channels/ChannelsPage.js +340 -46
- package/dist/timecampus/pages/channels/ChannelsPage.js.map +1 -1
- package/dist/timecampus/pages/client-portal/ClientPortalPage.js +374 -92
- package/dist/timecampus/pages/client-portal/ClientPortalPage.js.map +1 -1
- package/dist/timecampus/pages/dashboard/ReportsPage.js +70 -37
- package/dist/timecampus/pages/dashboard/ReportsPage.js.map +1 -1
- package/dist/timecampus/pages/email-tracking/EmailTrackingPage.js +366 -60
- package/dist/timecampus/pages/email-tracking/EmailTrackingPage.js.map +1 -1
- package/dist/timecampus/pages/expense-tracking/ExpenseTrackingPage.js +461 -87
- package/dist/timecampus/pages/expense-tracking/ExpenseTrackingPage.js.map +1 -1
- package/dist/timecampus/pages/geofencing/GeofencingPage.js +314 -55
- package/dist/timecampus/pages/geofencing/GeofencingPage.js.map +1 -1
- package/dist/timecampus/pages/industry-solutions/IndustrySolutionsPage.js +30 -27
- package/dist/timecampus/pages/industry-solutions/IndustrySolutionsPage.js.map +1 -1
- package/dist/timecampus/pages/location-tracking/LocationTrackingPage.js +269 -74
- package/dist/timecampus/pages/location-tracking/LocationTrackingPage.js.map +1 -1
- package/dist/timecampus/pages/meetings/ActionItemFormModal.js +127 -0
- package/dist/timecampus/pages/meetings/ActionItemFormModal.js.map +1 -0
- package/dist/timecampus/pages/meetings/MeetingCreatePage.js +62 -52
- package/dist/timecampus/pages/meetings/MeetingCreatePage.js.map +1 -1
- package/dist/timecampus/pages/meetings/MeetingDetailPage.js +361 -74
- package/dist/timecampus/pages/meetings/MeetingDetailPage.js.map +1 -1
- package/dist/timecampus/pages/meetings/MeetingFormModal.js +156 -0
- package/dist/timecampus/pages/meetings/MeetingFormModal.js.map +1 -0
- package/dist/timecampus/pages/meetings/MeetingsPage.js +175 -74
- package/dist/timecampus/pages/meetings/MeetingsPage.js.map +1 -1
- package/dist/timecampus/pages/meetings/ParticipantFormModal.js +105 -0
- package/dist/timecampus/pages/meetings/ParticipantFormModal.js.map +1 -0
- package/dist/timecampus/pages/pomodoro/PomodoroPage.js +307 -67
- package/dist/timecampus/pages/pomodoro/PomodoroPage.js.map +1 -1
- package/dist/timecampus/pages/projects/ProjectCreatePage.js +51 -48
- package/dist/timecampus/pages/projects/ProjectCreatePage.js.map +1 -1
- package/dist/timecampus/pages/projects/ProjectDetailPage.js +248 -51
- package/dist/timecampus/pages/projects/ProjectDetailPage.js.map +1 -1
- package/dist/timecampus/pages/projects/ProjectsPage.js +354 -87
- package/dist/timecampus/pages/projects/ProjectsPage.js.map +1 -1
- package/dist/timecampus/pages/projects/TasksPage.js +367 -65
- package/dist/timecampus/pages/projects/TasksPage.js.map +1 -1
- package/dist/timecampus/pages/resource-planning/ResourcePlanningPage.js +674 -104
- package/dist/timecampus/pages/resource-planning/ResourcePlanningPage.js.map +1 -1
- package/dist/timecampus/pages/team/TeamAnalyticsPage.js +46 -34
- package/dist/timecampus/pages/team/TeamAnalyticsPage.js.map +1 -1
- package/dist/timecampus/pages/team/TeamMembersPage.js +93 -69
- package/dist/timecampus/pages/team/TeamMembersPage.js.map +1 -1
- package/dist/timecampus/pages/team/TeamPage.js +4 -3
- package/dist/timecampus/pages/team/TeamPage.js.map +1 -1
- package/dist/timecampus/pages/templates/TemplateDetailPage.js +72 -53
- package/dist/timecampus/pages/templates/TemplateDetailPage.js.map +1 -1
- package/dist/timecampus/pages/templates/TemplatesPage.js +40 -35
- package/dist/timecampus/pages/templates/TemplatesPage.js.map +1 -1
- package/dist/timecampus/pages/time/TimeEntriesPage.js +484 -97
- package/dist/timecampus/pages/time/TimeEntriesPage.js.map +1 -1
- package/dist/timecampus/pages/time/TimeTrackingPage.js +210 -99
- package/dist/timecampus/pages/time/TimeTrackingPage.js.map +1 -1
- package/dist/timecampus/pages/time/TimerPage.js +229 -97
- package/dist/timecampus/pages/time/TimerPage.js.map +1 -1
- package/dist/timecampus/pages/time-approval/TimeApprovalPage.js +315 -110
- package/dist/timecampus/pages/time-approval/TimeApprovalPage.js.map +1 -1
- package/dist/timecampus/pages/timeline/TimelinePage.js +250 -0
- package/dist/timecampus/pages/timeline/TimelinePage.js.map +1 -0
- package/dist/timecampus/pages/widgets/WidgetDetailPage.js +158 -76
- package/dist/timecampus/pages/widgets/WidgetDetailPage.js.map +1 -1
- package/dist/timecampus/pages/widgets/WidgetsPage.js +181 -52
- package/dist/timecampus/pages/widgets/WidgetsPage.js.map +1 -1
- package/dist/timecampus/pages/workflows/WorkflowsPage.js +212 -61
- package/dist/timecampus/pages/workflows/WorkflowsPage.js.map +1 -1
- package/dist/timecampus/store/timecampusStore.js +13 -1
- package/dist/timecampus/store/timecampusStore.js.map +1 -1
- package/dist/timecampus/utils/helpers.js +20 -10
- package/dist/timecampus/utils/helpers.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,116 +1,398 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
1
|
+
import { getInitials as e } from "../../utils/helpers.js";
|
|
2
|
+
import { StatCard as t } from "../../components/StatCard.js";
|
|
3
|
+
import { LoadingSkeleton as n } from "../../components/LoadingSkeleton.js";
|
|
4
|
+
import { useCreateClientPortalConfigMutation as r, useDeleteClientPortalConfigMutation as ee, useGetClientPortalStatsQuery as te, useListClientPortalConfigsQuery as i, useRegenerateClientPortalShareTokenMutation as ne, useToggleClientPortalMutation as re, useUpdateClientPortalConfigMutation as ie } from "../../../generated/wspace-operations.js";
|
|
5
|
+
import { PageLayout as a } from "../../components/PageLayout.js";
|
|
6
|
+
import { EmptyState as ae } from "../../components/EmptyState.js";
|
|
7
|
+
import { Modal as o } from "../../components/Modal.js";
|
|
8
|
+
import { StatusBadge as oe } from "../../components/StatusBadge.js";
|
|
9
|
+
import { ToastContainer as se, useToast as ce } from "../../components/Toast.js";
|
|
10
|
+
import { useState as s } from "react";
|
|
11
|
+
import { Building2 as c, Check as l, DollarSign as u, FolderOpen as d, Link2 as f, Pencil as p, Plus as le, Power as ue, RefreshCw as de, Trash2 as fe } from "lucide-react";
|
|
12
|
+
import { Fragment as m, jsx as h, jsxs as g } from "react/jsx-runtime";
|
|
10
13
|
//#region src/timecampus/pages/client-portal/ClientPortalPage.tsx
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
return
|
|
14
|
+
var _ = 25, v = "mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary";
|
|
15
|
+
function pe(e) {
|
|
16
|
+
return `${typeof window < "u" ? window.location.origin : ""}/portal/${e}`;
|
|
17
|
+
}
|
|
18
|
+
var y = {
|
|
19
|
+
clientName: "",
|
|
20
|
+
contactEmail: "",
|
|
21
|
+
contactPhone: "",
|
|
22
|
+
description: ""
|
|
23
|
+
};
|
|
24
|
+
function b() {
|
|
25
|
+
let { toasts: b, addToast: x, dismissToast: me } = ce(), [he, S] = s(null), { data: C, loading: w, error: T, refetch: E, fetchMore: D } = i({
|
|
26
|
+
variables: {
|
|
27
|
+
limit: _,
|
|
28
|
+
offset: 0
|
|
29
|
+
},
|
|
30
|
+
notifyOnNetworkStatusChange: !0
|
|
31
|
+
}), { data: O, refetch: k } = te(), [A, { loading: j }] = ne(), [M, { loading: N }] = r(), [P, { loading: F }] = ie(), [I, { loading: L }] = ee(), [R] = re(), z = C?.clientPortalConfigs?.items ?? [], ge = C?.clientPortalConfigs?.hasMore ?? !1, B = O?.clientPortalStats, [V, H] = s(!1), [_e, U] = s(!1), [W, G] = s(null), [K, q] = s(y), [J, Y] = s(null), [ve, X] = s(null), Z = async () => {
|
|
32
|
+
await Promise.all([E(), k()]);
|
|
33
|
+
}, ye = async (e) => {
|
|
34
|
+
if (e.shareToken) try {
|
|
35
|
+
await navigator.clipboard.writeText(pe(e.shareToken)), S(e.id), window.setTimeout(() => S(null), 2e3);
|
|
36
|
+
} catch {
|
|
37
|
+
x({
|
|
38
|
+
title: "Clipboard unavailable",
|
|
39
|
+
variant: "error"
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}, be = async (e) => {
|
|
43
|
+
X(e.id);
|
|
44
|
+
try {
|
|
45
|
+
await A({ variables: { id: e.id } }), x({
|
|
46
|
+
title: "Share link regenerated",
|
|
47
|
+
variant: "success"
|
|
48
|
+
}), await E();
|
|
49
|
+
} catch {
|
|
50
|
+
x({
|
|
51
|
+
title: "Failed to regenerate link",
|
|
52
|
+
variant: "error"
|
|
53
|
+
});
|
|
54
|
+
} finally {
|
|
55
|
+
X(null);
|
|
56
|
+
}
|
|
57
|
+
}, xe = () => {
|
|
58
|
+
G(null), q(y), U(!0);
|
|
59
|
+
}, Se = (e) => {
|
|
60
|
+
G(e), q({
|
|
61
|
+
clientName: e.clientName,
|
|
62
|
+
contactEmail: e.contactEmail ?? "",
|
|
63
|
+
contactPhone: e.contactPhone ?? "",
|
|
64
|
+
description: e.description ?? ""
|
|
65
|
+
}), U(!0);
|
|
66
|
+
}, Q = () => {
|
|
67
|
+
U(!1), G(null);
|
|
68
|
+
}, Ce = async () => {
|
|
69
|
+
if (K.clientName.trim()) try {
|
|
70
|
+
W ? (await P({ variables: {
|
|
71
|
+
id: W.id,
|
|
72
|
+
input: {
|
|
73
|
+
clientName: K.clientName.trim(),
|
|
74
|
+
contactEmail: K.contactEmail.trim() || void 0,
|
|
75
|
+
contactPhone: K.contactPhone.trim() || void 0,
|
|
76
|
+
description: K.description.trim() || void 0
|
|
77
|
+
}
|
|
78
|
+
} }), x({
|
|
79
|
+
title: "Client updated",
|
|
80
|
+
variant: "success"
|
|
81
|
+
})) : (await M({ variables: { input: {
|
|
82
|
+
clientName: K.clientName.trim(),
|
|
83
|
+
contactEmail: K.contactEmail.trim() || void 0,
|
|
84
|
+
contactPhone: K.contactPhone.trim() || void 0,
|
|
85
|
+
description: K.description.trim() || void 0,
|
|
86
|
+
isActive: !0
|
|
87
|
+
} } }), x({
|
|
88
|
+
title: "Client added",
|
|
89
|
+
variant: "success"
|
|
90
|
+
})), Q(), await Z();
|
|
91
|
+
} catch {
|
|
92
|
+
x({
|
|
93
|
+
title: W ? "Failed to update client" : "Failed to add client",
|
|
94
|
+
variant: "error"
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}, we = async () => {
|
|
98
|
+
if (J) try {
|
|
99
|
+
await I({ variables: { id: J.id } }), x({
|
|
100
|
+
title: "Client deleted",
|
|
101
|
+
variant: "success"
|
|
102
|
+
}), Y(null), await Z();
|
|
103
|
+
} catch {
|
|
104
|
+
x({
|
|
105
|
+
title: "Failed to delete client",
|
|
106
|
+
variant: "error"
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}, Te = async (e) => {
|
|
110
|
+
X(e.id);
|
|
111
|
+
try {
|
|
112
|
+
await R({ variables: {
|
|
113
|
+
id: e.id,
|
|
114
|
+
isActive: !e.isActive
|
|
115
|
+
} }), x({
|
|
116
|
+
title: e.isActive ? "Client deactivated" : "Client activated",
|
|
117
|
+
variant: "success"
|
|
118
|
+
}), await Z();
|
|
119
|
+
} catch {
|
|
120
|
+
x({
|
|
121
|
+
title: "Failed to toggle client",
|
|
122
|
+
variant: "error"
|
|
123
|
+
});
|
|
124
|
+
} finally {
|
|
125
|
+
X(null);
|
|
126
|
+
}
|
|
127
|
+
}, Ee = async () => {
|
|
128
|
+
H(!0);
|
|
129
|
+
try {
|
|
130
|
+
await D({
|
|
131
|
+
variables: {
|
|
132
|
+
limit: _,
|
|
133
|
+
offset: z.length
|
|
134
|
+
},
|
|
135
|
+
updateQuery: (e, { fetchMoreResult: t }) => t ? {
|
|
136
|
+
...e,
|
|
137
|
+
clientPortalConfigs: {
|
|
138
|
+
...t.clientPortalConfigs,
|
|
139
|
+
items: [...e.clientPortalConfigs.items, ...t.clientPortalConfigs.items]
|
|
140
|
+
}
|
|
141
|
+
} : e
|
|
142
|
+
});
|
|
143
|
+
} finally {
|
|
144
|
+
H(!1);
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
if (w && z.length === 0) return /* @__PURE__ */ h(a, {
|
|
14
148
|
title: "Client Portal",
|
|
15
149
|
description: "Manage clients and their projects",
|
|
16
|
-
children: /* @__PURE__ */
|
|
150
|
+
children: /* @__PURE__ */ h(n, {
|
|
17
151
|
variant: "list",
|
|
18
152
|
count: 4
|
|
19
153
|
})
|
|
20
|
-
})
|
|
154
|
+
});
|
|
155
|
+
if (T) return /* @__PURE__ */ h(a, {
|
|
21
156
|
title: "Client Portal",
|
|
22
157
|
description: "Manage clients and their projects",
|
|
23
|
-
children: /* @__PURE__ */
|
|
158
|
+
children: /* @__PURE__ */ h("div", {
|
|
24
159
|
className: "rounded-lg border border-status-error-bg/40 bg-status-error-bg p-4 text-sm text-status-error-text",
|
|
25
160
|
children: "Failed to load client portal data. Please try again later."
|
|
26
161
|
})
|
|
27
|
-
})
|
|
162
|
+
});
|
|
163
|
+
let $ = /* @__PURE__ */ g("button", {
|
|
164
|
+
onClick: xe,
|
|
165
|
+
className: "inline-flex items-center gap-1.5 rounded-md bg-action-primary-bg px-3 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90",
|
|
166
|
+
children: [/* @__PURE__ */ h(le, { className: "h-4 w-4" }), "Add Client"]
|
|
167
|
+
});
|
|
168
|
+
return /* @__PURE__ */ g(a, {
|
|
28
169
|
title: "Client Portal",
|
|
29
170
|
description: "Manage clients and their projects",
|
|
30
171
|
purpose: "Give clients a transparent window into the work you do for them — the projects, hours and progress tied to each one — so billing conversations are backed by data and trust, not spreadsheets.",
|
|
31
|
-
actions:
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
className: "
|
|
69
|
-
children:
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
172
|
+
actions: $,
|
|
173
|
+
children: [
|
|
174
|
+
/* @__PURE__ */ g("div", {
|
|
175
|
+
className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4",
|
|
176
|
+
children: [
|
|
177
|
+
/* @__PURE__ */ h(t, {
|
|
178
|
+
featured: !0,
|
|
179
|
+
title: "Active Clients",
|
|
180
|
+
value: String(B?.activeClients ?? 0),
|
|
181
|
+
icon: /* @__PURE__ */ h(c, { className: "h-5 w-5" })
|
|
182
|
+
}),
|
|
183
|
+
/* @__PURE__ */ h(t, {
|
|
184
|
+
title: "Total Projects",
|
|
185
|
+
value: String(B?.totalProjectsShared ?? 0),
|
|
186
|
+
icon: /* @__PURE__ */ h(d, { className: "h-5 w-5" })
|
|
187
|
+
}),
|
|
188
|
+
/* @__PURE__ */ h(t, {
|
|
189
|
+
title: "Total Clients",
|
|
190
|
+
value: String(B?.totalClients ?? 0),
|
|
191
|
+
icon: /* @__PURE__ */ h(u, { className: "h-5 w-5" })
|
|
192
|
+
}),
|
|
193
|
+
/* @__PURE__ */ h(t, {
|
|
194
|
+
title: "Active",
|
|
195
|
+
value: String(B?.activeClients ?? 0),
|
|
196
|
+
trend: {
|
|
197
|
+
value: 0,
|
|
198
|
+
label: "clients"
|
|
199
|
+
}
|
|
200
|
+
})
|
|
201
|
+
]
|
|
202
|
+
}),
|
|
203
|
+
z.length === 0 ? /* @__PURE__ */ h(ae, {
|
|
204
|
+
illustration: "empty-people",
|
|
205
|
+
title: "No clients configured yet",
|
|
206
|
+
description: "Add your first client to share projects and tracked hours with them.",
|
|
207
|
+
action: $
|
|
208
|
+
}) : /* @__PURE__ */ g(m, { children: [/* @__PURE__ */ h("div", {
|
|
209
|
+
className: "rounded-xl border border-border-seam bg-gloss-card shadow-pop",
|
|
210
|
+
children: /* @__PURE__ */ h("div", {
|
|
211
|
+
className: "divide-y divide-border",
|
|
212
|
+
children: z.map((t) => {
|
|
213
|
+
let n = t.projects.reduce((e, t) => e + t.trackedHours, 0), r = ve === t.id;
|
|
214
|
+
return /* @__PURE__ */ g("div", {
|
|
215
|
+
className: "flex flex-col gap-3 p-4 hover:bg-bg-sunken/50 transition-colors sm:flex-row sm:items-center sm:justify-between",
|
|
216
|
+
children: [/* @__PURE__ */ g("div", {
|
|
217
|
+
className: "flex items-center gap-3",
|
|
218
|
+
children: [/* @__PURE__ */ h("div", {
|
|
219
|
+
className: "flex h-10 w-10 items-center justify-center rounded-lg bg-action-primary-bg/10 text-sm font-medium text-primary",
|
|
220
|
+
children: e(t.clientName)
|
|
221
|
+
}), /* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("p", {
|
|
222
|
+
className: "text-sm font-medium text-text-primary",
|
|
223
|
+
children: t.clientName
|
|
224
|
+
}), /* @__PURE__ */ g("p", {
|
|
225
|
+
className: "text-xs text-text-secondary",
|
|
226
|
+
children: [
|
|
227
|
+
t.projects.length,
|
|
228
|
+
" projects ·",
|
|
229
|
+
" ",
|
|
230
|
+
n,
|
|
231
|
+
"h tracked",
|
|
232
|
+
t.contactEmail && /* @__PURE__ */ g(m, { children: [" · ", t.contactEmail] })
|
|
233
|
+
]
|
|
234
|
+
})] })]
|
|
235
|
+
}), /* @__PURE__ */ g("div", {
|
|
236
|
+
className: "flex flex-wrap items-center gap-2",
|
|
89
237
|
children: [
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
238
|
+
t.isActive && t.shareToken && /* @__PURE__ */ g(m, { children: [/* @__PURE__ */ h("button", {
|
|
239
|
+
type: "button",
|
|
240
|
+
onClick: () => void ye(t),
|
|
241
|
+
title: "Copy the client's private share link",
|
|
242
|
+
className: "inline-flex items-center gap-1 rounded-md border border-border-seam bg-bg-surface px-2 py-1 text-xs text-text-secondary hover:bg-bg-sunken/50",
|
|
243
|
+
children: he === t.id ? /* @__PURE__ */ g(m, { children: [/* @__PURE__ */ h(l, { className: "h-3.5 w-3.5" }), " Copied"] }) : /* @__PURE__ */ g(m, { children: [/* @__PURE__ */ h(f, { className: "h-3.5 w-3.5" }), " Share link"] })
|
|
244
|
+
}), /* @__PURE__ */ h("button", {
|
|
245
|
+
type: "button",
|
|
246
|
+
disabled: r && j,
|
|
247
|
+
onClick: () => void be(t),
|
|
248
|
+
title: "Regenerate the link (invalidates the old one)",
|
|
249
|
+
className: "inline-flex items-center rounded-md border border-border-seam bg-bg-surface p-1.5 text-text-secondary hover:bg-bg-sunken/50 disabled:opacity-50",
|
|
250
|
+
children: /* @__PURE__ */ h(de, { className: `h-3.5 w-3.5 ${r && j ? "animate-spin" : ""}` })
|
|
251
|
+
})] }),
|
|
252
|
+
/* @__PURE__ */ g("button", {
|
|
253
|
+
type: "button",
|
|
254
|
+
disabled: r,
|
|
255
|
+
onClick: () => void Te(t),
|
|
256
|
+
title: t.isActive ? "Deactivate client portal" : "Activate client portal",
|
|
257
|
+
className: "inline-flex items-center gap-1 rounded-md border border-border-seam bg-bg-surface px-2 py-1 text-xs text-text-secondary hover:bg-bg-sunken/50 disabled:opacity-50",
|
|
258
|
+
children: [/* @__PURE__ */ h(ue, { className: "h-3.5 w-3.5" }), t.isActive ? "Deactivate" : "Activate"]
|
|
259
|
+
}),
|
|
260
|
+
/* @__PURE__ */ h(oe, {
|
|
261
|
+
label: t.isActive ? "active" : "inactive",
|
|
262
|
+
status: t.isActive ? "active" : "inactive"
|
|
263
|
+
}),
|
|
264
|
+
/* @__PURE__ */ h("button", {
|
|
265
|
+
type: "button",
|
|
266
|
+
onClick: () => Se(t),
|
|
267
|
+
title: "Edit client",
|
|
268
|
+
className: "inline-flex items-center rounded-md border border-border-seam bg-bg-surface p-1.5 text-text-secondary hover:bg-bg-sunken/50",
|
|
269
|
+
children: /* @__PURE__ */ h(p, { className: "h-3.5 w-3.5" })
|
|
270
|
+
}),
|
|
271
|
+
/* @__PURE__ */ h("button", {
|
|
272
|
+
type: "button",
|
|
273
|
+
onClick: () => Y(t),
|
|
274
|
+
title: "Delete client",
|
|
275
|
+
className: "inline-flex items-center rounded-md border border-border-seam bg-bg-surface p-1.5 text-status-error-text hover:bg-status-error-bg",
|
|
276
|
+
children: /* @__PURE__ */ h(fe, { className: "h-3.5 w-3.5" })
|
|
277
|
+
})
|
|
95
278
|
]
|
|
96
|
-
})] })]
|
|
97
|
-
}), /* @__PURE__ */ m("div", {
|
|
98
|
-
className: "flex items-center gap-4",
|
|
99
|
-
children: [e.contactEmail && /* @__PURE__ */ p("span", {
|
|
100
|
-
className: "text-xs text-text-secondary",
|
|
101
|
-
children: e.contactEmail
|
|
102
|
-
}), /* @__PURE__ */ p("span", {
|
|
103
|
-
className: `inline-flex rounded-full px-2 py-0.5 text-xs font-medium ${n(e.isActive ? "active" : "inactive")}`,
|
|
104
|
-
children: e.isActive ? "active" : "inactive"
|
|
105
279
|
})]
|
|
106
|
-
})
|
|
107
|
-
}
|
|
280
|
+
}, t.id);
|
|
281
|
+
})
|
|
282
|
+
})
|
|
283
|
+
}), ge && /* @__PURE__ */ h("div", {
|
|
284
|
+
className: "mt-4 flex justify-center",
|
|
285
|
+
children: /* @__PURE__ */ h("button", {
|
|
286
|
+
onClick: Ee,
|
|
287
|
+
disabled: V,
|
|
288
|
+
className: "rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50 disabled:opacity-50",
|
|
289
|
+
children: V ? "Loading..." : "Load more"
|
|
290
|
+
})
|
|
291
|
+
})] }),
|
|
292
|
+
/* @__PURE__ */ h(o, {
|
|
293
|
+
open: _e,
|
|
294
|
+
onClose: Q,
|
|
295
|
+
title: W ? "Edit Client" : "Add Client",
|
|
296
|
+
description: W ? "Update this client's portal details." : "Set up a client so you can share projects and tracked hours with them.",
|
|
297
|
+
footer: /* @__PURE__ */ g(m, { children: [/* @__PURE__ */ h("button", {
|
|
298
|
+
onClick: Q,
|
|
299
|
+
className: "rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50",
|
|
300
|
+
children: "Cancel"
|
|
301
|
+
}), /* @__PURE__ */ h("button", {
|
|
302
|
+
onClick: Ce,
|
|
303
|
+
disabled: N || F || !K.clientName.trim(),
|
|
304
|
+
className: "rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 disabled:opacity-50",
|
|
305
|
+
children: N || F ? "Saving..." : W ? "Save Changes" : "Add Client"
|
|
306
|
+
})] }),
|
|
307
|
+
children: /* @__PURE__ */ g("div", {
|
|
308
|
+
className: "space-y-4",
|
|
309
|
+
children: [
|
|
310
|
+
/* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("label", {
|
|
311
|
+
className: "block text-sm font-medium text-text-primary",
|
|
312
|
+
children: "Client Name"
|
|
313
|
+
}), /* @__PURE__ */ h("input", {
|
|
314
|
+
type: "text",
|
|
315
|
+
placeholder: "e.g. Acme Corp",
|
|
316
|
+
value: K.clientName,
|
|
317
|
+
onChange: (e) => q({
|
|
318
|
+
...K,
|
|
319
|
+
clientName: e.target.value
|
|
320
|
+
}),
|
|
321
|
+
className: v
|
|
322
|
+
})] }),
|
|
323
|
+
/* @__PURE__ */ g("div", {
|
|
324
|
+
className: "grid grid-cols-1 gap-4 sm:grid-cols-2",
|
|
325
|
+
children: [/* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("label", {
|
|
326
|
+
className: "block text-sm font-medium text-text-primary",
|
|
327
|
+
children: "Contact Email"
|
|
328
|
+
}), /* @__PURE__ */ h("input", {
|
|
329
|
+
type: "email",
|
|
330
|
+
placeholder: "contact@client.com",
|
|
331
|
+
value: K.contactEmail,
|
|
332
|
+
onChange: (e) => q({
|
|
333
|
+
...K,
|
|
334
|
+
contactEmail: e.target.value
|
|
335
|
+
}),
|
|
336
|
+
className: v
|
|
337
|
+
})] }), /* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("label", {
|
|
338
|
+
className: "block text-sm font-medium text-text-primary",
|
|
339
|
+
children: "Contact Phone"
|
|
340
|
+
}), /* @__PURE__ */ h("input", {
|
|
341
|
+
type: "tel",
|
|
342
|
+
placeholder: "Optional",
|
|
343
|
+
value: K.contactPhone,
|
|
344
|
+
onChange: (e) => q({
|
|
345
|
+
...K,
|
|
346
|
+
contactPhone: e.target.value
|
|
347
|
+
}),
|
|
348
|
+
className: v
|
|
349
|
+
})] })]
|
|
350
|
+
}),
|
|
351
|
+
/* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("label", {
|
|
352
|
+
className: "block text-sm font-medium text-text-primary",
|
|
353
|
+
children: "Description"
|
|
354
|
+
}), /* @__PURE__ */ h("textarea", {
|
|
355
|
+
rows: 3,
|
|
356
|
+
placeholder: "Notes about this client (optional)",
|
|
357
|
+
value: K.description,
|
|
358
|
+
onChange: (e) => q({
|
|
359
|
+
...K,
|
|
360
|
+
description: e.target.value
|
|
361
|
+
}),
|
|
362
|
+
className: v
|
|
363
|
+
})] })
|
|
364
|
+
]
|
|
365
|
+
})
|
|
366
|
+
}),
|
|
367
|
+
/* @__PURE__ */ h(o, {
|
|
368
|
+
open: J !== null,
|
|
369
|
+
onClose: () => Y(null),
|
|
370
|
+
title: "Delete client?",
|
|
371
|
+
description: J ? `"${J.clientName}" and its share link will be removed.` : void 0,
|
|
372
|
+
size: "sm",
|
|
373
|
+
footer: /* @__PURE__ */ g(m, { children: [/* @__PURE__ */ h("button", {
|
|
374
|
+
onClick: () => Y(null),
|
|
375
|
+
className: "rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50",
|
|
376
|
+
children: "Cancel"
|
|
377
|
+
}), /* @__PURE__ */ h("button", {
|
|
378
|
+
onClick: we,
|
|
379
|
+
disabled: L,
|
|
380
|
+
className: "rounded-md bg-status-error-bg px-4 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg/80 disabled:opacity-50",
|
|
381
|
+
children: L ? "Deleting..." : "Delete"
|
|
382
|
+
})] }),
|
|
383
|
+
children: /* @__PURE__ */ h("p", {
|
|
384
|
+
className: "text-sm text-text-secondary",
|
|
385
|
+
children: "The client will lose access to their shared portal immediately. This cannot be undone."
|
|
108
386
|
})
|
|
387
|
+
}),
|
|
388
|
+
/* @__PURE__ */ h(se, {
|
|
389
|
+
toasts: b,
|
|
390
|
+
onDismiss: me
|
|
109
391
|
})
|
|
110
|
-
|
|
392
|
+
]
|
|
111
393
|
});
|
|
112
394
|
}
|
|
113
395
|
//#endregion
|
|
114
|
-
export {
|
|
396
|
+
export { b as ClientPortalPage };
|
|
115
397
|
|
|
116
398
|
//# sourceMappingURL=ClientPortalPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ClientPortalPage.js","names":[],"sources":["../../../../src/timecampus/pages/client-portal/ClientPortalPage.tsx"],"sourcesContent":["import { Building2, Plus, DollarSign, FolderOpen } from \"lucide-react\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { StatCard } from \"../../components/StatCard\";\nimport { EmptyState } from \"../../components/EmptyState\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport {\n getStatusColor,\n formatCurrency,\n getInitials,\n} from \"../../utils/helpers\";\nimport { useTimeCampus } from \"../../providers/TimeCampusProvider\";\nimport {\n useListClientPortalConfigsQuery,\n useGetClientPortalStatsQuery,\n type ClientPortalFieldsFragment,\n} from \"@/generated/wspace-operations\";\n\nexport function ClientPortalPage() {\n const { workspaceId = \"\" } = useTimeCampus();\n\n const {\n data: configsData,\n loading: configsLoading,\n error: configsError,\n } = useListClientPortalConfigsQuery();\n const { data: statsData, loading: statsLoading } =\n useGetClientPortalStatsQuery();\n\n const clients = configsData?.clientPortalConfigs?.items ?? [];\n const stats = statsData?.clientPortalStats;\n\n if (configsLoading) {\n return (\n <PageLayout\n title=\"Client Portal\"\n description=\"Manage clients and their projects\"\n >\n <LoadingSkeleton variant=\"list\" count={4} />\n </PageLayout>\n );\n }\n\n if (configsError) {\n return (\n <PageLayout\n title=\"Client Portal\"\n description=\"Manage clients and their projects\"\n >\n <div className=\"rounded-lg border border-status-error-bg/40 bg-status-error-bg p-4 text-sm text-status-error-text\">\n Failed to load client portal data. Please try again later.\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title=\"Client Portal\"\n description=\"Manage clients and their projects\"\n purpose=\"Give clients a transparent window into the work you do for them — the projects, hours and progress tied to each one — so billing conversations are backed by data and trust, not spreadsheets.\"\n actions={\n <button className=\"inline-flex items-center gap-1.5 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 <Plus className=\"h-4 w-4\" />\n Add Client\n </button>\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=\"Active Clients\"\n value={String(stats?.activeClients ?? 0)}\n icon={<Building2 className=\"h-5 w-5\" />}\n />\n <StatCard\n title=\"Total Projects\"\n value={String(stats?.totalProjectsShared ?? 0)}\n icon={<FolderOpen className=\"h-5 w-5\" />}\n />\n <StatCard\n title=\"Total Clients\"\n value={String(stats?.totalClients ?? 0)}\n icon={<DollarSign className=\"h-5 w-5\" />}\n />\n <StatCard\n title=\"Active\"\n value={String(stats?.activeClients ?? 0)}\n trend={{ value: 0, label: \"clients\" }}\n />\n </div>\n\n {clients.length === 0 ? (\n <EmptyState\n illustration=\"empty-people\"\n title=\"No clients configured yet\"\n description=\"Add your first client to share projects and tracked hours with them.\"\n action={\n <button className=\"inline-flex items-center gap-1.5 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 <Plus className=\"h-4 w-4\" />\n Add Client\n </button>\n }\n />\n ) : (\n <div className=\"rounded-xl border border-border-seam bg-gloss-card shadow-pop\">\n <div className=\"divide-y divide-border\">\n {clients.map((client: ClientPortalFieldsFragment) => {\n const totalTrackedHours = client.projects.reduce(\n (sum: number, p: { trackedHours: number }) =>\n sum + p.trackedHours,\n 0,\n );\n return (\n <div\n key={client.id}\n className=\"flex items-center justify-between p-4 hover:bg-bg-sunken/50 transition-colors cursor-pointer\"\n >\n <div className=\"flex items-center gap-3\">\n <div className=\"flex h-10 w-10 items-center justify-center rounded-lg bg-action-primary-bg/10 text-sm font-medium text-primary\">\n {getInitials(client.clientName)}\n </div>\n <div>\n <p className=\"text-sm font-medium text-text-primary\">\n {client.clientName}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {client.projects.length} projects ·{\" \"}\n {totalTrackedHours}h tracked\n </p>\n </div>\n </div>\n <div className=\"flex items-center gap-4\">\n {client.contactEmail && (\n <span className=\"text-xs text-text-secondary\">\n {client.contactEmail}\n </span>\n )}\n <span\n className={`inline-flex rounded-full px-2 py-0.5 text-xs font-medium ${getStatusColor(client.isActive ? \"active\" : \"inactive\")}`}\n >\n {client.isActive ? \"active\" : \"inactive\"}\n </span>\n </div>\n </div>\n );\n })}\n </div>\n </div>\n )}\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;AAiBA,SAAgB,IAAmB;CACjC,IAAM,EAAE,iBAAc,OAAO,GAAe,EAEtC,EACJ,MAAM,GACN,SAAS,GACT,OAAO,MACL,GAAiC,EAC/B,EAAE,MAAM,GAAW,SAAS,MAChC,GAA8B,EAE1B,IAAU,GAAa,qBAAqB,SAAS,EAAE,EACvD,IAAQ,GAAW;AA0BzB,QAxBI,IAEA,kBAAC,GAAD;EACE,OAAM;EACN,aAAY;YAEZ,kBAAC,GAAD;GAAiB,SAAQ;GAAO,OAAO;GAAK,CAAA;EACjC,CAAA,GAIb,IAEA,kBAAC,GAAD;EACE,OAAM;EACN,aAAY;YAEZ,kBAAC,OAAD;GAAK,WAAU;aAAoG;GAE7G,CAAA;EACK,CAAA,GAKf,kBAAC,GAAD;EACE,OAAM;EACN,aAAY;EACZ,SAAQ;EACR,SACE,kBAAC,UAAD;GAAQ,WAAU;aAAlB,CACE,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA,EAAA,aAErB;;YARb,CAWE,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,GAAD;KACE,UAAA;KACA,OAAM;KACN,OAAO,OAAO,GAAO,iBAAiB,EAAE;KACxC,MAAM,kBAAC,GAAD,EAAW,WAAU,WAAY,CAAA;KACvC,CAAA;IACF,kBAAC,GAAD;KACE,OAAM;KACN,OAAO,OAAO,GAAO,uBAAuB,EAAE;KAC9C,MAAM,kBAAC,GAAD,EAAY,WAAU,WAAY,CAAA;KACxC,CAAA;IACF,kBAAC,GAAD;KACE,OAAM;KACN,OAAO,OAAO,GAAO,gBAAgB,EAAE;KACvC,MAAM,kBAAC,GAAD,EAAY,WAAU,WAAY,CAAA;KACxC,CAAA;IACF,kBAAC,GAAD;KACE,OAAM;KACN,OAAO,OAAO,GAAO,iBAAiB,EAAE;KACxC,OAAO;MAAE,OAAO;MAAG,OAAO;MAAW;KACrC,CAAA;IACE;MAEL,EAAQ,WAAW,IAClB,kBAAC,GAAD;GACE,cAAa;GACb,OAAM;GACN,aAAY;GACZ,QACE,kBAAC,UAAD;IAAQ,WAAU;cAAlB,CACE,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA,EAAA,aAErB;;GAEX,CAAA,GAEF,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,OAAD;IAAK,WAAU;cACZ,EAAQ,KAAK,MAAuC;KACnD,IAAM,IAAoB,EAAO,SAAS,QACvC,GAAa,MACZ,IAAM,EAAE,cACV,EACD;AACD,YACE,kBAAC,OAAD;MAEE,WAAU;gBAFZ,CAIE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,OAAD;QAAK,WAAU;kBACZ,EAAY,EAAO,WAAW;QAC3B,CAAA,EACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;QAAG,WAAU;kBACV,EAAO;QACN,CAAA,EACJ,kBAAC,KAAD;QAAG,WAAU;kBAAb;SACG,EAAO,SAAS;SAAO;SAAmB;SAC1C;SAAkB;SACjB;UACA,EAAA,CAAA,CACF;UACN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACG,EAAO,gBACN,kBAAC,QAAD;QAAM,WAAU;kBACb,EAAO;QACH,CAAA,EAET,kBAAC,QAAD;QACE,WAAW,4DAA4D,EAAe,EAAO,WAAW,WAAW,WAAW;kBAE7H,EAAO,WAAW,WAAW;QACzB,CAAA,CACH;SACF;QA7BC,EAAO,GA6BR;MAER;IACE,CAAA;GACF,CAAA,CAEG"}
|
|
1
|
+
{"version":3,"file":"ClientPortalPage.js","names":[],"sources":["../../../../src/timecampus/pages/client-portal/ClientPortalPage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport {\n Building2,\n Plus,\n DollarSign,\n FolderOpen,\n Link2,\n Check,\n RefreshCw,\n Pencil,\n Trash2,\n Power,\n} from \"lucide-react\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { StatCard } from \"../../components/StatCard\";\nimport { EmptyState } from \"../../components/EmptyState\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport { StatusBadge } from \"../../components/StatusBadge\";\nimport { Modal } from \"../../components/Modal\";\nimport { ToastContainer, useToast } from \"../../components/Toast\";\nimport { getInitials } from \"../../utils/helpers\";\nimport {\n useListClientPortalConfigsQuery,\n useGetClientPortalStatsQuery,\n useRegenerateClientPortalShareTokenMutation,\n useCreateClientPortalConfigMutation,\n useUpdateClientPortalConfigMutation,\n useDeleteClientPortalConfigMutation,\n useToggleClientPortalMutation,\n type ClientPortalFieldsFragment,\n} from \"@/generated/wspace-operations\";\n\nconst PAGE_SIZE = 25;\n\nconst inputClass =\n \"mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary\";\n\n/**\n * Build the public share URL for a portal token. The public client view is\n * served by the website at /portal/:token; only active portals resolve.\n */\nfunction shareUrlFor(token: string): string {\n const origin = typeof window !== \"undefined\" ? window.location.origin : \"\";\n return `${origin}/portal/${token}`;\n}\n\ninterface ClientFormState {\n clientName: string;\n contactEmail: string;\n contactPhone: string;\n description: string;\n}\n\nconst emptyForm: ClientFormState = {\n clientName: \"\",\n contactEmail: \"\",\n contactPhone: \"\",\n description: \"\",\n};\n\nexport function ClientPortalPage() {\n const { toasts, addToast, dismissToast } = useToast();\n const [copiedId, setCopiedId] = useState<string | null>(null);\n\n const {\n data: configsData,\n loading: configsLoading,\n error: configsError,\n refetch,\n fetchMore,\n } = useListClientPortalConfigsQuery({\n variables: { limit: PAGE_SIZE, offset: 0 },\n notifyOnNetworkStatusChange: true,\n });\n const { data: statsData, refetch: refetchStats } =\n useGetClientPortalStatsQuery();\n\n const [regenerate, { loading: regenerating }] =\n useRegenerateClientPortalShareTokenMutation();\n const [createClient, { loading: creating }] =\n useCreateClientPortalConfigMutation();\n const [updateClient, { loading: updating }] =\n useUpdateClientPortalConfigMutation();\n const [deleteClient, { loading: deleting }] =\n useDeleteClientPortalConfigMutation();\n const [toggleClient] = useToggleClientPortalMutation();\n\n const clients = configsData?.clientPortalConfigs?.items ?? [];\n const hasMore = configsData?.clientPortalConfigs?.hasMore ?? false;\n const stats = statsData?.clientPortalStats;\n\n const [loadingMore, setLoadingMore] = useState(false);\n\n // Create / edit modal\n const [formOpen, setFormOpen] = useState(false);\n const [editing, setEditing] = useState<ClientPortalFieldsFragment | null>(\n null,\n );\n const [form, setForm] = useState<ClientFormState>(emptyForm);\n\n // Delete confirm\n const [deleteTarget, setDeleteTarget] =\n useState<ClientPortalFieldsFragment | null>(null);\n\n // Per-row busy (toggle/regenerate)\n const [busyId, setBusyId] = useState<string | null>(null);\n\n const refreshAll = async () => {\n await Promise.all([refetch(), refetchStats()]);\n };\n\n const handleCopy = async (client: ClientPortalFieldsFragment) => {\n if (!client.shareToken) return;\n try {\n await navigator.clipboard.writeText(shareUrlFor(client.shareToken));\n setCopiedId(client.id);\n window.setTimeout(() => setCopiedId(null), 2000);\n } catch {\n addToast({ title: \"Clipboard unavailable\", variant: \"error\" });\n }\n };\n\n const handleRegenerate = async (client: ClientPortalFieldsFragment) => {\n setBusyId(client.id);\n try {\n await regenerate({ variables: { id: client.id } });\n addToast({ title: \"Share link regenerated\", variant: \"success\" });\n await refetch();\n } catch {\n addToast({ title: \"Failed to regenerate link\", variant: \"error\" });\n } finally {\n setBusyId(null);\n }\n };\n\n const openCreate = () => {\n setEditing(null);\n setForm(emptyForm);\n setFormOpen(true);\n };\n\n const openEdit = (client: ClientPortalFieldsFragment) => {\n setEditing(client);\n setForm({\n clientName: client.clientName,\n contactEmail: client.contactEmail ?? \"\",\n contactPhone: client.contactPhone ?? \"\",\n description: client.description ?? \"\",\n });\n setFormOpen(true);\n };\n\n const closeForm = () => {\n setFormOpen(false);\n setEditing(null);\n };\n\n const handleSubmit = async () => {\n if (!form.clientName.trim()) return;\n try {\n if (editing) {\n await updateClient({\n variables: {\n id: editing.id,\n input: {\n clientName: form.clientName.trim(),\n contactEmail: form.contactEmail.trim() || undefined,\n contactPhone: form.contactPhone.trim() || undefined,\n description: form.description.trim() || undefined,\n },\n },\n });\n addToast({ title: \"Client updated\", variant: \"success\" });\n } else {\n await createClient({\n variables: {\n input: {\n clientName: form.clientName.trim(),\n contactEmail: form.contactEmail.trim() || undefined,\n contactPhone: form.contactPhone.trim() || undefined,\n description: form.description.trim() || undefined,\n isActive: true,\n },\n },\n });\n addToast({ title: \"Client added\", variant: \"success\" });\n }\n closeForm();\n await refreshAll();\n } catch {\n addToast({\n title: editing ? \"Failed to update client\" : \"Failed to add client\",\n variant: \"error\",\n });\n }\n };\n\n const handleDelete = async () => {\n if (!deleteTarget) return;\n try {\n await deleteClient({ variables: { id: deleteTarget.id } });\n addToast({ title: \"Client deleted\", variant: \"success\" });\n setDeleteTarget(null);\n await refreshAll();\n } catch {\n addToast({ title: \"Failed to delete client\", variant: \"error\" });\n }\n };\n\n const handleToggle = async (client: ClientPortalFieldsFragment) => {\n setBusyId(client.id);\n try {\n await toggleClient({\n variables: { id: client.id, isActive: !client.isActive },\n });\n addToast({\n title: client.isActive ? \"Client deactivated\" : \"Client activated\",\n variant: \"success\",\n });\n await refreshAll();\n } catch {\n addToast({ title: \"Failed to toggle client\", variant: \"error\" });\n } finally {\n setBusyId(null);\n }\n };\n\n const handleLoadMore = async () => {\n setLoadingMore(true);\n try {\n await fetchMore({\n variables: { limit: PAGE_SIZE, offset: clients.length },\n updateQuery: (prev, { fetchMoreResult }) => {\n if (!fetchMoreResult) return prev;\n return {\n ...prev,\n clientPortalConfigs: {\n ...fetchMoreResult.clientPortalConfigs,\n items: [\n ...prev.clientPortalConfigs.items,\n ...fetchMoreResult.clientPortalConfigs.items,\n ],\n },\n };\n },\n });\n } finally {\n setLoadingMore(false);\n }\n };\n\n if (configsLoading && clients.length === 0) {\n return (\n <PageLayout\n title=\"Client Portal\"\n description=\"Manage clients and their projects\"\n >\n <LoadingSkeleton variant=\"list\" count={4} />\n </PageLayout>\n );\n }\n\n if (configsError) {\n return (\n <PageLayout\n title=\"Client Portal\"\n description=\"Manage clients and their projects\"\n >\n <div className=\"rounded-lg border border-status-error-bg/40 bg-status-error-bg p-4 text-sm text-status-error-text\">\n Failed to load client portal data. Please try again later.\n </div>\n </PageLayout>\n );\n }\n\n const addButton = (\n <button\n onClick={openCreate}\n className=\"inline-flex items-center gap-1.5 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 Client\n </button>\n );\n\n return (\n <PageLayout\n title=\"Client Portal\"\n description=\"Manage clients and their projects\"\n purpose=\"Give clients a transparent window into the work you do for them — the projects, hours and progress tied to each one — so billing conversations are backed by data and trust, not spreadsheets.\"\n actions={addButton}\n >\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <StatCard\n featured\n title=\"Active Clients\"\n value={String(stats?.activeClients ?? 0)}\n icon={<Building2 className=\"h-5 w-5\" />}\n />\n <StatCard\n title=\"Total Projects\"\n value={String(stats?.totalProjectsShared ?? 0)}\n icon={<FolderOpen className=\"h-5 w-5\" />}\n />\n <StatCard\n title=\"Total Clients\"\n value={String(stats?.totalClients ?? 0)}\n icon={<DollarSign className=\"h-5 w-5\" />}\n />\n <StatCard\n title=\"Active\"\n value={String(stats?.activeClients ?? 0)}\n trend={{ value: 0, label: \"clients\" }}\n />\n </div>\n\n {clients.length === 0 ? (\n <EmptyState\n illustration=\"empty-people\"\n title=\"No clients configured yet\"\n description=\"Add your first client to share projects and tracked hours with them.\"\n action={addButton}\n />\n ) : (\n <>\n <div className=\"rounded-xl border border-border-seam bg-gloss-card shadow-pop\">\n <div className=\"divide-y divide-border\">\n {clients.map((client: ClientPortalFieldsFragment) => {\n const totalTrackedHours = client.projects.reduce(\n (sum: number, p: { trackedHours: number }) =>\n sum + p.trackedHours,\n 0,\n );\n const rowBusy = busyId === client.id;\n return (\n <div\n key={client.id}\n className=\"flex flex-col gap-3 p-4 hover:bg-bg-sunken/50 transition-colors sm:flex-row sm:items-center sm:justify-between\"\n >\n <div className=\"flex items-center gap-3\">\n <div className=\"flex h-10 w-10 items-center justify-center rounded-lg bg-action-primary-bg/10 text-sm font-medium text-primary\">\n {getInitials(client.clientName)}\n </div>\n <div>\n <p className=\"text-sm font-medium text-text-primary\">\n {client.clientName}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {client.projects.length} projects ·{\" \"}\n {totalTrackedHours}h tracked\n {client.contactEmail && (\n <> · {client.contactEmail}</>\n )}\n </p>\n </div>\n </div>\n <div className=\"flex flex-wrap items-center gap-2\">\n {client.isActive && client.shareToken && (\n <>\n <button\n type=\"button\"\n onClick={() => void handleCopy(client)}\n title=\"Copy the client's private share link\"\n className=\"inline-flex items-center gap-1 rounded-md border border-border-seam bg-bg-surface px-2 py-1 text-xs text-text-secondary hover:bg-bg-sunken/50\"\n >\n {copiedId === client.id ? (\n <>\n <Check className=\"h-3.5 w-3.5\" /> Copied\n </>\n ) : (\n <>\n <Link2 className=\"h-3.5 w-3.5\" /> Share link\n </>\n )}\n </button>\n <button\n type=\"button\"\n disabled={rowBusy && regenerating}\n onClick={() => void handleRegenerate(client)}\n title=\"Regenerate the link (invalidates the old one)\"\n className=\"inline-flex items-center rounded-md border border-border-seam bg-bg-surface p-1.5 text-text-secondary hover:bg-bg-sunken/50 disabled:opacity-50\"\n >\n <RefreshCw\n className={`h-3.5 w-3.5 ${rowBusy && regenerating ? \"animate-spin\" : \"\"}`}\n />\n </button>\n </>\n )}\n <button\n type=\"button\"\n disabled={rowBusy}\n onClick={() => void handleToggle(client)}\n title={\n client.isActive\n ? \"Deactivate client portal\"\n : \"Activate client portal\"\n }\n className=\"inline-flex items-center gap-1 rounded-md border border-border-seam bg-bg-surface px-2 py-1 text-xs text-text-secondary hover:bg-bg-sunken/50 disabled:opacity-50\"\n >\n <Power className=\"h-3.5 w-3.5\" />\n {client.isActive ? \"Deactivate\" : \"Activate\"}\n </button>\n <StatusBadge\n label={client.isActive ? \"active\" : \"inactive\"}\n status={client.isActive ? \"active\" : \"inactive\"}\n />\n <button\n type=\"button\"\n onClick={() => openEdit(client)}\n title=\"Edit client\"\n className=\"inline-flex items-center rounded-md border border-border-seam bg-bg-surface p-1.5 text-text-secondary hover:bg-bg-sunken/50\"\n >\n <Pencil className=\"h-3.5 w-3.5\" />\n </button>\n <button\n type=\"button\"\n onClick={() => setDeleteTarget(client)}\n title=\"Delete client\"\n className=\"inline-flex items-center rounded-md border border-border-seam bg-bg-surface p-1.5 text-status-error-text hover:bg-status-error-bg\"\n >\n <Trash2 className=\"h-3.5 w-3.5\" />\n </button>\n </div>\n </div>\n );\n })}\n </div>\n </div>\n\n {hasMore && (\n <div className=\"mt-4 flex justify-center\">\n <button\n onClick={handleLoadMore}\n disabled={loadingMore}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50 disabled:opacity-50\"\n >\n {loadingMore ? \"Loading...\" : \"Load more\"}\n </button>\n </div>\n )}\n </>\n )}\n\n <Modal\n open={formOpen}\n onClose={closeForm}\n title={editing ? \"Edit Client\" : \"Add Client\"}\n description={\n editing\n ? \"Update this client's portal details.\"\n : \"Set up a client so you can share projects and tracked hours with them.\"\n }\n footer={\n <>\n <button\n onClick={closeForm}\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={handleSubmit}\n disabled={creating || updating || !form.clientName.trim()}\n className=\"rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 disabled:opacity-50\"\n >\n {creating || updating\n ? \"Saving...\"\n : editing\n ? \"Save Changes\"\n : \"Add Client\"}\n </button>\n </>\n }\n >\n <div className=\"space-y-4\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Client Name\n </label>\n <input\n type=\"text\"\n placeholder=\"e.g. Acme Corp\"\n value={form.clientName}\n onChange={(e) => setForm({ ...form, clientName: e.target.value })}\n className={inputClass}\n />\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Contact Email\n </label>\n <input\n type=\"email\"\n placeholder=\"contact@client.com\"\n value={form.contactEmail}\n onChange={(e) =>\n setForm({ ...form, contactEmail: e.target.value })\n }\n className={inputClass}\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Contact Phone\n </label>\n <input\n type=\"tel\"\n placeholder=\"Optional\"\n value={form.contactPhone}\n onChange={(e) =>\n setForm({ ...form, contactPhone: e.target.value })\n }\n className={inputClass}\n />\n </div>\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Description\n </label>\n <textarea\n rows={3}\n placeholder=\"Notes about this client (optional)\"\n value={form.description}\n onChange={(e) =>\n setForm({ ...form, description: e.target.value })\n }\n className={inputClass}\n />\n </div>\n </div>\n </Modal>\n\n <Modal\n open={deleteTarget !== null}\n onClose={() => setDeleteTarget(null)}\n title=\"Delete client?\"\n description={\n deleteTarget\n ? `\"${deleteTarget.clientName}\" and its share link will be removed.`\n : undefined\n }\n size=\"sm\"\n footer={\n <>\n <button\n onClick={() => setDeleteTarget(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={handleDelete}\n disabled={deleting}\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/80 disabled:opacity-50\"\n >\n {deleting ? \"Deleting...\" : \"Delete\"}\n </button>\n </>\n }\n >\n <p className=\"text-sm text-text-secondary\">\n The client will lose access to their shared portal immediately. This\n cannot be undone.\n </p>\n </Modal>\n\n <ToastContainer toasts={toasts} onDismiss={dismissToast} />\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|