@burdenoff/microfe-timecampus 2026.703.1 → 2026.703.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/dist/generated/wspace-types.js +28 -0
  2. package/dist/generated/wspace-types.js.map +1 -0
  3. package/dist/index.js +4 -4
  4. package/dist/timecampus/components/Modal.js +65 -0
  5. package/dist/timecampus/components/Modal.js.map +1 -0
  6. package/dist/timecampus/components/StatusBadge.js +28 -0
  7. package/dist/timecampus/components/StatusBadge.js.map +1 -0
  8. package/dist/timecampus/components/Toast.js +87 -0
  9. package/dist/timecampus/components/Toast.js.map +1 -0
  10. package/dist/timecampus/index.js +1 -1
  11. package/dist/timecampus/pages/HomePage.js +143 -135
  12. package/dist/timecampus/pages/HomePage.js.map +1 -1
  13. package/dist/timecampus/pages/applets/AppletStorePage.js +14 -19
  14. package/dist/timecampus/pages/applets/AppletStorePage.js.map +1 -1
  15. package/dist/timecampus/pages/applets/InstalledAppletsPage.js +49 -37
  16. package/dist/timecampus/pages/applets/InstalledAppletsPage.js.map +1 -1
  17. package/dist/timecampus/pages/application-usage/ApplicationUsagePage.js +315 -87
  18. package/dist/timecampus/pages/application-usage/ApplicationUsagePage.js.map +1 -1
  19. package/dist/timecampus/pages/channels/ChannelsPage.js +340 -46
  20. package/dist/timecampus/pages/channels/ChannelsPage.js.map +1 -1
  21. package/dist/timecampus/pages/client-portal/ClientPortalPage.js +432 -118
  22. package/dist/timecampus/pages/client-portal/ClientPortalPage.js.map +1 -1
  23. package/dist/timecampus/pages/dashboard/ReportsPage.js +70 -37
  24. package/dist/timecampus/pages/dashboard/ReportsPage.js.map +1 -1
  25. package/dist/timecampus/pages/email-tracking/EmailTrackingPage.js +366 -60
  26. package/dist/timecampus/pages/email-tracking/EmailTrackingPage.js.map +1 -1
  27. package/dist/timecampus/pages/expense-tracking/ExpenseTrackingPage.js +562 -85
  28. package/dist/timecampus/pages/expense-tracking/ExpenseTrackingPage.js.map +1 -1
  29. package/dist/timecampus/pages/geofencing/GeofencingPage.js +314 -55
  30. package/dist/timecampus/pages/geofencing/GeofencingPage.js.map +1 -1
  31. package/dist/timecampus/pages/industry-solutions/IndustrySolutionsPage.js +30 -27
  32. package/dist/timecampus/pages/industry-solutions/IndustrySolutionsPage.js.map +1 -1
  33. package/dist/timecampus/pages/location-tracking/LocationTrackingPage.js +269 -74
  34. package/dist/timecampus/pages/location-tracking/LocationTrackingPage.js.map +1 -1
  35. package/dist/timecampus/pages/meetings/ActionItemFormModal.js +127 -0
  36. package/dist/timecampus/pages/meetings/ActionItemFormModal.js.map +1 -0
  37. package/dist/timecampus/pages/meetings/MeetingCreatePage.js +62 -52
  38. package/dist/timecampus/pages/meetings/MeetingCreatePage.js.map +1 -1
  39. package/dist/timecampus/pages/meetings/MeetingDetailPage.js +361 -74
  40. package/dist/timecampus/pages/meetings/MeetingDetailPage.js.map +1 -1
  41. package/dist/timecampus/pages/meetings/MeetingFormModal.js +208 -0
  42. package/dist/timecampus/pages/meetings/MeetingFormModal.js.map +1 -0
  43. package/dist/timecampus/pages/meetings/MeetingsPage.js +175 -74
  44. package/dist/timecampus/pages/meetings/MeetingsPage.js.map +1 -1
  45. package/dist/timecampus/pages/meetings/ParticipantFormModal.js +105 -0
  46. package/dist/timecampus/pages/meetings/ParticipantFormModal.js.map +1 -0
  47. package/dist/timecampus/pages/pomodoro/PomodoroPage.js +307 -67
  48. package/dist/timecampus/pages/pomodoro/PomodoroPage.js.map +1 -1
  49. package/dist/timecampus/pages/projects/ProjectCreatePage.js +51 -48
  50. package/dist/timecampus/pages/projects/ProjectCreatePage.js.map +1 -1
  51. package/dist/timecampus/pages/projects/ProjectDetailPage.js +248 -51
  52. package/dist/timecampus/pages/projects/ProjectDetailPage.js.map +1 -1
  53. package/dist/timecampus/pages/projects/ProjectsPage.js +354 -87
  54. package/dist/timecampus/pages/projects/ProjectsPage.js.map +1 -1
  55. package/dist/timecampus/pages/projects/TasksPage.js +367 -65
  56. package/dist/timecampus/pages/projects/TasksPage.js.map +1 -1
  57. package/dist/timecampus/pages/resource-planning/ResourcePlanningPage.js +674 -104
  58. package/dist/timecampus/pages/resource-planning/ResourcePlanningPage.js.map +1 -1
  59. package/dist/timecampus/pages/team/TeamAnalyticsPage.js +46 -34
  60. package/dist/timecampus/pages/team/TeamAnalyticsPage.js.map +1 -1
  61. package/dist/timecampus/pages/team/TeamMembersPage.js +93 -69
  62. package/dist/timecampus/pages/team/TeamMembersPage.js.map +1 -1
  63. package/dist/timecampus/pages/team/TeamPage.js +4 -3
  64. package/dist/timecampus/pages/team/TeamPage.js.map +1 -1
  65. package/dist/timecampus/pages/templates/TemplateDetailPage.js +72 -53
  66. package/dist/timecampus/pages/templates/TemplateDetailPage.js.map +1 -1
  67. package/dist/timecampus/pages/templates/TemplatesPage.js +40 -35
  68. package/dist/timecampus/pages/templates/TemplatesPage.js.map +1 -1
  69. package/dist/timecampus/pages/time/TimeEntriesPage.js +484 -97
  70. package/dist/timecampus/pages/time/TimeEntriesPage.js.map +1 -1
  71. package/dist/timecampus/pages/time/TimeTrackingPage.js +210 -99
  72. package/dist/timecampus/pages/time/TimeTrackingPage.js.map +1 -1
  73. package/dist/timecampus/pages/time/TimerPage.js +229 -97
  74. package/dist/timecampus/pages/time/TimerPage.js.map +1 -1
  75. package/dist/timecampus/pages/time-approval/TimeApprovalPage.js +339 -110
  76. package/dist/timecampus/pages/time-approval/TimeApprovalPage.js.map +1 -1
  77. package/dist/timecampus/pages/widgets/WidgetDetailPage.js +158 -76
  78. package/dist/timecampus/pages/widgets/WidgetDetailPage.js.map +1 -1
  79. package/dist/timecampus/pages/widgets/WidgetsPage.js +181 -52
  80. package/dist/timecampus/pages/widgets/WidgetsPage.js.map +1 -1
  81. package/dist/timecampus/pages/workflows/WorkflowsPage.js +212 -61
  82. package/dist/timecampus/pages/workflows/WorkflowsPage.js.map +1 -1
  83. package/dist/timecampus/store/timecampusStore.js +13 -1
  84. package/dist/timecampus/store/timecampusStore.js.map +1 -1
  85. package/package.json +1 -1
@@ -1,150 +1,464 @@
1
- import { useTimeCampus as e } from "../../providers/TimeCampusProvider.js";
2
- import { getInitials as t, getStatusColor as n } from "../../utils/helpers.js";
3
- import { StatCard as r } from "../../components/StatCard.js";
4
- import { LoadingSkeleton as i } from "../../components/LoadingSkeleton.js";
5
- import { useGetClientPortalStatsQuery as a, useListClientPortalConfigsQuery as o, useRegenerateClientPortalShareTokenMutation as s } from "../../../generated/wspace-operations.js";
6
- import { PageLayout as c } from "../../components/PageLayout.js";
7
- import { EmptyState as l } from "../../components/EmptyState.js";
8
- import { useState as u } from "react";
9
- import { Building2 as d, Check as f, DollarSign as p, FolderOpen as m, Link2 as h, Plus as g, RefreshCw as _ } from "lucide-react";
10
- import { Fragment as v, jsx as y, jsxs as b } from "react/jsx-runtime";
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 { gql as m } from "@apollo/client";
13
+ import { useApolloClient as pe } from "@apollo/client/react";
14
+ import { Fragment as h, jsx as g, jsxs as _ } from "react/jsx-runtime";
15
+ import { uploadBlobToFiles as me } from "@burdenoff/fe-libs/storage";
11
16
  //#region src/timecampus/pages/client-portal/ClientPortalPage.tsx
12
- function x(e) {
17
+ var v = 25, y = "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", he = "mt-1 block w-full text-sm text-text-secondary file:mr-3 file:rounded-md file:border-0 file:bg-action-primary-bg file:px-3 file:py-1.5 file:text-sm file:font-medium file:text-action-primary-text hover:file:bg-action-primary-bg/90", ge = m`
18
+ query TimeCampus_GetFileDownloadUrl($id: ID!, $expiresIn: Int) {
19
+ getFileDownloadUrl(id: $id, expiresIn: $expiresIn)
20
+ }
21
+ `;
22
+ function _e(e) {
13
23
  return `${typeof window < "u" ? window.location.origin : ""}/portal/${e}`;
14
24
  }
15
- function S() {
16
- let { workspaceId: S = "" } = e(), [C, w] = u(null), { data: T, loading: E, error: D, refetch: O } = o(), { data: k } = a(), [A, { loading: j }] = s(), M = T?.clientPortalConfigs?.items ?? [], N = k?.clientPortalStats, P = async (e) => {
25
+ var b = {
26
+ clientName: "",
27
+ contactEmail: "",
28
+ contactPhone: "",
29
+ description: "",
30
+ logoUrl: ""
31
+ };
32
+ function x() {
33
+ let { toasts: m, addToast: x, dismissToast: ve } = ce(), S = pe(), [C, w] = s(null), [T, E] = s(!1), { data: D, loading: O, error: k, refetch: A, fetchMore: j } = i({
34
+ variables: {
35
+ limit: v,
36
+ offset: 0
37
+ },
38
+ notifyOnNetworkStatusChange: !0
39
+ }), { data: M, refetch: N } = te(), [P, { loading: F }] = ne(), [ye, { loading: I }] = r(), [be, { loading: L }] = ie(), [xe, { loading: R }] = ee(), [Se] = re(), z = D?.clientPortalConfigs?.items ?? [], Ce = D?.clientPortalConfigs?.hasMore ?? !1, B = M?.clientPortalStats, [V, H] = s(!1), [we, U] = s(!1), [W, G] = s(null), [K, q] = s(b), [J, Y] = s(null), [Te, X] = s(null), Z = async () => {
40
+ await Promise.all([A(), N()]);
41
+ }, Ee = async (e) => {
17
42
  if (e.shareToken) try {
18
- await navigator.clipboard.writeText(x(e.shareToken)), w(e.id), window.setTimeout(() => w(null), 2e3);
19
- } catch {}
20
- }, F = async (e) => {
21
- await A({ variables: { id: e.id } }), await O();
43
+ await navigator.clipboard.writeText(_e(e.shareToken)), w(e.id), window.setTimeout(() => w(null), 2e3);
44
+ } catch {
45
+ x({
46
+ title: "Clipboard unavailable",
47
+ variant: "error"
48
+ });
49
+ }
50
+ }, De = async (e) => {
51
+ X(e.id);
52
+ try {
53
+ await P({ variables: { id: e.id } }), x({
54
+ title: "Share link regenerated",
55
+ variant: "success"
56
+ }), await A();
57
+ } catch {
58
+ x({
59
+ title: "Failed to regenerate link",
60
+ variant: "error"
61
+ });
62
+ } finally {
63
+ X(null);
64
+ }
65
+ }, Oe = () => {
66
+ G(null), q(b), U(!0);
67
+ }, ke = (e) => {
68
+ G(e), q({
69
+ clientName: e.clientName,
70
+ contactEmail: e.contactEmail ?? "",
71
+ contactPhone: e.contactPhone ?? "",
72
+ description: e.description ?? "",
73
+ logoUrl: e.logoUrl ?? ""
74
+ }), U(!0);
75
+ }, Ae = async (e) => {
76
+ E(!0);
77
+ try {
78
+ let t = await me({
79
+ client: S,
80
+ blob: e,
81
+ filename: e.name,
82
+ mimeType: e.type || "application/octet-stream",
83
+ folderId: ""
84
+ }), { data: n } = await S.query({
85
+ query: ge,
86
+ variables: {
87
+ id: t.id,
88
+ expiresIn: 604800
89
+ },
90
+ fetchPolicy: "no-cache"
91
+ }), r = n?.getFileDownloadUrl;
92
+ if (!r) throw Error("No download URL returned");
93
+ q((e) => ({
94
+ ...e,
95
+ logoUrl: r
96
+ }));
97
+ } catch {
98
+ x({
99
+ title: "Failed to upload logo",
100
+ variant: "error"
101
+ });
102
+ } finally {
103
+ E(!1);
104
+ }
105
+ }, Q = () => {
106
+ U(!1), G(null);
107
+ }, je = async () => {
108
+ if (K.clientName.trim()) try {
109
+ W ? (await be({ variables: {
110
+ id: W.id,
111
+ input: {
112
+ clientName: K.clientName.trim(),
113
+ contactEmail: K.contactEmail.trim() || void 0,
114
+ contactPhone: K.contactPhone.trim() || void 0,
115
+ description: K.description.trim() || void 0,
116
+ logoUrl: K.logoUrl || void 0
117
+ }
118
+ } }), x({
119
+ title: "Client updated",
120
+ variant: "success"
121
+ })) : (await ye({ variables: { input: {
122
+ clientName: K.clientName.trim(),
123
+ contactEmail: K.contactEmail.trim() || void 0,
124
+ contactPhone: K.contactPhone.trim() || void 0,
125
+ description: K.description.trim() || void 0,
126
+ logoUrl: K.logoUrl || void 0,
127
+ isActive: !0
128
+ } } }), x({
129
+ title: "Client added",
130
+ variant: "success"
131
+ })), Q(), await Z();
132
+ } catch {
133
+ x({
134
+ title: W ? "Failed to update client" : "Failed to add client",
135
+ variant: "error"
136
+ });
137
+ }
138
+ }, Me = async () => {
139
+ if (J) try {
140
+ await xe({ variables: { id: J.id } }), x({
141
+ title: "Client deleted",
142
+ variant: "success"
143
+ }), Y(null), await Z();
144
+ } catch {
145
+ x({
146
+ title: "Failed to delete client",
147
+ variant: "error"
148
+ });
149
+ }
150
+ }, Ne = async (e) => {
151
+ X(e.id);
152
+ try {
153
+ await Se({ variables: {
154
+ id: e.id,
155
+ isActive: !e.isActive
156
+ } }), x({
157
+ title: e.isActive ? "Client deactivated" : "Client activated",
158
+ variant: "success"
159
+ }), await Z();
160
+ } catch {
161
+ x({
162
+ title: "Failed to toggle client",
163
+ variant: "error"
164
+ });
165
+ } finally {
166
+ X(null);
167
+ }
168
+ }, Pe = async () => {
169
+ H(!0);
170
+ try {
171
+ await j({
172
+ variables: {
173
+ limit: v,
174
+ offset: z.length
175
+ },
176
+ updateQuery: (e, { fetchMoreResult: t }) => t ? {
177
+ ...e,
178
+ clientPortalConfigs: {
179
+ ...t.clientPortalConfigs,
180
+ items: [...e.clientPortalConfigs.items, ...t.clientPortalConfigs.items]
181
+ }
182
+ } : e
183
+ });
184
+ } finally {
185
+ H(!1);
186
+ }
22
187
  };
23
- return E ? /* @__PURE__ */ y(c, {
188
+ if (O && z.length === 0) return /* @__PURE__ */ g(a, {
24
189
  title: "Client Portal",
25
190
  description: "Manage clients and their projects",
26
- children: /* @__PURE__ */ y(i, {
191
+ children: /* @__PURE__ */ g(n, {
27
192
  variant: "list",
28
193
  count: 4
29
194
  })
30
- }) : D ? /* @__PURE__ */ y(c, {
195
+ });
196
+ if (k) return /* @__PURE__ */ g(a, {
31
197
  title: "Client Portal",
32
198
  description: "Manage clients and their projects",
33
- children: /* @__PURE__ */ y("div", {
199
+ children: /* @__PURE__ */ g("div", {
34
200
  className: "rounded-lg border border-status-error-bg/40 bg-status-error-bg p-4 text-sm text-status-error-text",
35
201
  children: "Failed to load client portal data. Please try again later."
36
202
  })
37
- }) : /* @__PURE__ */ b(c, {
203
+ });
204
+ let $ = /* @__PURE__ */ _("button", {
205
+ onClick: Oe,
206
+ 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",
207
+ children: [/* @__PURE__ */ g(le, { className: "h-4 w-4" }), "Add Client"]
208
+ });
209
+ return /* @__PURE__ */ _(a, {
38
210
  title: "Client Portal",
39
211
  description: "Manage clients and their projects",
40
212
  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.",
41
- actions: /* @__PURE__ */ b("button", {
42
- 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",
43
- children: [/* @__PURE__ */ y(g, { className: "h-4 w-4" }), "Add Client"]
44
- }),
45
- children: [/* @__PURE__ */ b("div", {
46
- className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4",
47
- children: [
48
- /* @__PURE__ */ y(r, {
49
- featured: !0,
50
- title: "Active Clients",
51
- value: String(N?.activeClients ?? 0),
52
- icon: /* @__PURE__ */ y(d, { className: "h-5 w-5" })
53
- }),
54
- /* @__PURE__ */ y(r, {
55
- title: "Total Projects",
56
- value: String(N?.totalProjectsShared ?? 0),
57
- icon: /* @__PURE__ */ y(m, { className: "h-5 w-5" })
58
- }),
59
- /* @__PURE__ */ y(r, {
60
- title: "Total Clients",
61
- value: String(N?.totalClients ?? 0),
62
- icon: /* @__PURE__ */ y(p, { className: "h-5 w-5" })
63
- }),
64
- /* @__PURE__ */ y(r, {
65
- title: "Active",
66
- value: String(N?.activeClients ?? 0),
67
- trend: {
68
- value: 0,
69
- label: "clients"
70
- }
71
- })
72
- ]
73
- }), M.length === 0 ? /* @__PURE__ */ y(l, {
74
- illustration: "empty-people",
75
- title: "No clients configured yet",
76
- description: "Add your first client to share projects and tracked hours with them.",
77
- action: /* @__PURE__ */ b("button", {
78
- 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",
79
- children: [/* @__PURE__ */ y(g, { className: "h-4 w-4" }), "Add Client"]
80
- })
81
- }) : /* @__PURE__ */ y("div", {
82
- className: "rounded-xl border border-border-seam bg-gloss-card shadow-pop",
83
- children: /* @__PURE__ */ y("div", {
84
- className: "divide-y divide-border",
85
- children: M.map((e) => {
86
- let r = e.projects.reduce((e, t) => e + t.trackedHours, 0);
87
- return /* @__PURE__ */ b("div", {
88
- className: "flex items-center justify-between p-4 hover:bg-bg-sunken/50 transition-colors cursor-pointer",
89
- children: [/* @__PURE__ */ b("div", {
90
- className: "flex items-center gap-3",
91
- children: [/* @__PURE__ */ y("div", {
92
- className: "flex h-10 w-10 items-center justify-center rounded-lg bg-action-primary-bg/10 text-sm font-medium text-primary",
93
- children: t(e.clientName)
94
- }), /* @__PURE__ */ b("div", { children: [/* @__PURE__ */ y("p", {
95
- className: "text-sm font-medium text-text-primary",
96
- children: e.clientName
97
- }), /* @__PURE__ */ b("p", {
98
- className: "text-xs text-text-secondary",
213
+ actions: $,
214
+ children: [
215
+ /* @__PURE__ */ _("div", {
216
+ className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4",
217
+ children: [
218
+ /* @__PURE__ */ g(t, {
219
+ featured: !0,
220
+ title: "Active Clients",
221
+ value: String(B?.activeClients ?? 0),
222
+ icon: /* @__PURE__ */ g(c, { className: "h-5 w-5" })
223
+ }),
224
+ /* @__PURE__ */ g(t, {
225
+ title: "Total Projects",
226
+ value: String(B?.totalProjectsShared ?? 0),
227
+ icon: /* @__PURE__ */ g(d, { className: "h-5 w-5" })
228
+ }),
229
+ /* @__PURE__ */ g(t, {
230
+ title: "Total Clients",
231
+ value: String(B?.totalClients ?? 0),
232
+ icon: /* @__PURE__ */ g(u, { className: "h-5 w-5" })
233
+ }),
234
+ /* @__PURE__ */ g(t, {
235
+ title: "Active",
236
+ value: String(B?.activeClients ?? 0),
237
+ trend: {
238
+ value: 0,
239
+ label: "clients"
240
+ }
241
+ })
242
+ ]
243
+ }),
244
+ z.length === 0 ? /* @__PURE__ */ g(ae, {
245
+ illustration: "empty-people",
246
+ title: "No clients configured yet",
247
+ description: "Add your first client to share projects and tracked hours with them.",
248
+ action: $
249
+ }) : /* @__PURE__ */ _(h, { children: [/* @__PURE__ */ g("div", {
250
+ className: "rounded-xl border border-border-seam bg-gloss-card shadow-pop",
251
+ children: /* @__PURE__ */ g("div", {
252
+ className: "divide-y divide-border",
253
+ children: z.map((t) => {
254
+ let n = t.projects.reduce((e, t) => e + t.trackedHours, 0), r = Te === t.id;
255
+ return /* @__PURE__ */ _("div", {
256
+ className: "flex flex-col gap-3 p-4 hover:bg-bg-sunken/50 transition-colors sm:flex-row sm:items-center sm:justify-between",
257
+ children: [/* @__PURE__ */ _("div", {
258
+ className: "flex items-center gap-3",
259
+ children: [/* @__PURE__ */ g("div", {
260
+ className: "flex h-10 w-10 items-center justify-center rounded-lg bg-action-primary-bg/10 text-sm font-medium text-primary",
261
+ children: e(t.clientName)
262
+ }), /* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("p", {
263
+ className: "text-sm font-medium text-text-primary",
264
+ children: t.clientName
265
+ }), /* @__PURE__ */ _("p", {
266
+ className: "text-xs text-text-secondary",
267
+ children: [
268
+ t.projects.length,
269
+ " projects ·",
270
+ " ",
271
+ n,
272
+ "h tracked",
273
+ t.contactEmail && /* @__PURE__ */ _(h, { children: [" · ", t.contactEmail] })
274
+ ]
275
+ })] })]
276
+ }), /* @__PURE__ */ _("div", {
277
+ className: "flex flex-wrap items-center gap-2",
99
278
  children: [
100
- e.projects.length,
101
- " projects ·",
102
- " ",
103
- r,
104
- "h tracked"
105
- ]
106
- })] })]
107
- }), /* @__PURE__ */ b("div", {
108
- className: "flex items-center gap-3",
109
- children: [
110
- e.contactEmail && /* @__PURE__ */ y("span", {
111
- className: "hidden text-xs text-text-secondary sm:inline",
112
- children: e.contactEmail
113
- }),
114
- e.isActive && e.shareToken && /* @__PURE__ */ b("div", {
115
- className: "flex items-center gap-1",
116
- children: [/* @__PURE__ */ y("button", {
279
+ t.isActive && t.shareToken && /* @__PURE__ */ _(h, { children: [/* @__PURE__ */ g("button", {
117
280
  type: "button",
118
- onClick: (t) => {
119
- t.stopPropagation(), P(e);
120
- },
281
+ onClick: () => void Ee(t),
121
282
  title: "Copy the client's private share link",
122
283
  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",
123
- children: C === e.id ? /* @__PURE__ */ b(v, { children: [/* @__PURE__ */ y(f, { className: "h-3.5 w-3.5" }), " Copied"] }) : /* @__PURE__ */ b(v, { children: [/* @__PURE__ */ y(h, { className: "h-3.5 w-3.5" }), " Share link"] })
124
- }), /* @__PURE__ */ y("button", {
284
+ children: C === t.id ? /* @__PURE__ */ _(h, { children: [/* @__PURE__ */ g(l, { className: "h-3.5 w-3.5" }), " Copied"] }) : /* @__PURE__ */ _(h, { children: [/* @__PURE__ */ g(f, { className: "h-3.5 w-3.5" }), " Share link"] })
285
+ }), /* @__PURE__ */ g("button", {
125
286
  type: "button",
126
- disabled: j,
127
- onClick: (t) => {
128
- t.stopPropagation(), F(e);
129
- },
287
+ disabled: r && F,
288
+ onClick: () => void De(t),
130
289
  title: "Regenerate the link (invalidates the old one)",
131
- className: "inline-flex items-center rounded-md border border-border-seam bg-bg-surface p-1 text-text-secondary hover:bg-bg-sunken/50 disabled:opacity-50",
132
- children: /* @__PURE__ */ y(_, { className: "h-3.5 w-3.5" })
133
- })]
290
+ 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",
291
+ children: /* @__PURE__ */ g(de, { className: `h-3.5 w-3.5 ${r && F ? "animate-spin" : ""}` })
292
+ })] }),
293
+ /* @__PURE__ */ _("button", {
294
+ type: "button",
295
+ disabled: r,
296
+ onClick: () => void Ne(t),
297
+ title: t.isActive ? "Deactivate client portal" : "Activate client portal",
298
+ 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",
299
+ children: [/* @__PURE__ */ g(ue, { className: "h-3.5 w-3.5" }), t.isActive ? "Deactivate" : "Activate"]
300
+ }),
301
+ /* @__PURE__ */ g(oe, {
302
+ label: t.isActive ? "active" : "inactive",
303
+ status: t.isActive ? "active" : "inactive"
304
+ }),
305
+ /* @__PURE__ */ g("button", {
306
+ type: "button",
307
+ onClick: () => ke(t),
308
+ title: "Edit client",
309
+ 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",
310
+ children: /* @__PURE__ */ g(p, { className: "h-3.5 w-3.5" })
311
+ }),
312
+ /* @__PURE__ */ g("button", {
313
+ type: "button",
314
+ onClick: () => Y(t),
315
+ title: "Delete client",
316
+ 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",
317
+ children: /* @__PURE__ */ g(fe, { className: "h-3.5 w-3.5" })
318
+ })
319
+ ]
320
+ })]
321
+ }, t.id);
322
+ })
323
+ })
324
+ }), Ce && /* @__PURE__ */ g("div", {
325
+ className: "mt-4 flex justify-center",
326
+ children: /* @__PURE__ */ g("button", {
327
+ onClick: Pe,
328
+ disabled: V,
329
+ 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",
330
+ children: V ? "Loading..." : "Load more"
331
+ })
332
+ })] }),
333
+ /* @__PURE__ */ g(o, {
334
+ open: we,
335
+ onClose: Q,
336
+ title: W ? "Edit Client" : "Add Client",
337
+ description: W ? "Update this client's portal details." : "Set up a client so you can share projects and tracked hours with them.",
338
+ footer: /* @__PURE__ */ _(h, { children: [/* @__PURE__ */ g("button", {
339
+ onClick: Q,
340
+ className: "rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50",
341
+ children: "Cancel"
342
+ }), /* @__PURE__ */ g("button", {
343
+ onClick: je,
344
+ disabled: I || L || !K.clientName.trim(),
345
+ 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",
346
+ children: I || L ? "Saving..." : W ? "Save Changes" : "Add Client"
347
+ })] }),
348
+ children: /* @__PURE__ */ _("div", {
349
+ className: "space-y-4",
350
+ children: [
351
+ /* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
352
+ className: "block text-sm font-medium text-text-primary",
353
+ children: "Client Name"
354
+ }), /* @__PURE__ */ g("input", {
355
+ type: "text",
356
+ placeholder: "e.g. Acme Corp",
357
+ value: K.clientName,
358
+ onChange: (e) => q({
359
+ ...K,
360
+ clientName: e.target.value
361
+ }),
362
+ className: y
363
+ })] }),
364
+ /* @__PURE__ */ _("div", {
365
+ className: "grid grid-cols-1 gap-4 sm:grid-cols-2",
366
+ children: [/* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
367
+ className: "block text-sm font-medium text-text-primary",
368
+ children: "Contact Email"
369
+ }), /* @__PURE__ */ g("input", {
370
+ type: "email",
371
+ placeholder: "contact@client.com",
372
+ value: K.contactEmail,
373
+ onChange: (e) => q({
374
+ ...K,
375
+ contactEmail: e.target.value
376
+ }),
377
+ className: y
378
+ })] }), /* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
379
+ className: "block text-sm font-medium text-text-primary",
380
+ children: "Contact Phone"
381
+ }), /* @__PURE__ */ g("input", {
382
+ type: "tel",
383
+ placeholder: "Optional",
384
+ value: K.contactPhone,
385
+ onChange: (e) => q({
386
+ ...K,
387
+ contactPhone: e.target.value
134
388
  }),
135
- /* @__PURE__ */ y("span", {
136
- className: `inline-flex rounded-full px-2 py-0.5 text-xs font-medium ${n(e.isActive ? "active" : "inactive")}`,
137
- children: e.isActive ? "active" : "inactive"
138
- })
139
- ]
140
- })]
141
- }, e.id);
389
+ className: y
390
+ })] })]
391
+ }),
392
+ /* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
393
+ className: "block text-sm font-medium text-text-primary",
394
+ children: "Description"
395
+ }), /* @__PURE__ */ g("textarea", {
396
+ rows: 3,
397
+ placeholder: "Notes about this client (optional)",
398
+ value: K.description,
399
+ onChange: (e) => q({
400
+ ...K,
401
+ description: e.target.value
402
+ }),
403
+ className: y
404
+ })] }),
405
+ /* @__PURE__ */ _("div", { children: [
406
+ /* @__PURE__ */ g("label", {
407
+ className: "block text-sm font-medium text-text-primary",
408
+ children: "Logo"
409
+ }),
410
+ /* @__PURE__ */ g("input", {
411
+ type: "file",
412
+ accept: "image/*",
413
+ disabled: T,
414
+ onChange: (e) => {
415
+ let t = e.target.files?.[0];
416
+ t && Ae(t), e.target.value = "";
417
+ },
418
+ className: he
419
+ }),
420
+ T && /* @__PURE__ */ g("p", {
421
+ className: "mt-1 text-xs text-text-secondary",
422
+ children: "Uploading…"
423
+ }),
424
+ K.logoUrl && /* @__PURE__ */ g("img", {
425
+ src: K.logoUrl,
426
+ alt: "Client logo preview",
427
+ className: "mt-2 h-16 w-16 rounded-md border border-border-seam object-cover"
428
+ })
429
+ ] })
430
+ ]
431
+ })
432
+ }),
433
+ /* @__PURE__ */ g(o, {
434
+ open: J !== null,
435
+ onClose: () => Y(null),
436
+ title: "Delete client?",
437
+ description: J ? `"${J.clientName}" and its share link will be removed.` : void 0,
438
+ size: "sm",
439
+ footer: /* @__PURE__ */ _(h, { children: [/* @__PURE__ */ g("button", {
440
+ onClick: () => Y(null),
441
+ className: "rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50",
442
+ children: "Cancel"
443
+ }), /* @__PURE__ */ g("button", {
444
+ onClick: Me,
445
+ disabled: R,
446
+ 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",
447
+ children: R ? "Deleting..." : "Delete"
448
+ })] }),
449
+ children: /* @__PURE__ */ g("p", {
450
+ className: "text-sm text-text-secondary",
451
+ children: "The client will lose access to their shared portal immediately. This cannot be undone."
142
452
  })
453
+ }),
454
+ /* @__PURE__ */ g(se, {
455
+ toasts: m,
456
+ onDismiss: ve
143
457
  })
144
- })]
458
+ ]
145
459
  });
146
460
  }
147
461
  //#endregion
148
- export { S as ClientPortalPage };
462
+ export { x as ClientPortalPage };
149
463
 
150
464
  //# sourceMappingURL=ClientPortalPage.js.map