@burdenoff/microfe-timecampus 2026.830.2 → 2026.903.1
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/timecampus/components/index.js +4 -4
- package/dist/timecampus/pages/HomePage.d.ts.map +1 -1
- package/dist/timecampus/pages/HomePage.js +142 -131
- package/dist/timecampus/pages/HomePage.js.map +1 -1
- package/dist/timecampus/pages/application-usage/ApplicationUsagePage.js +7 -7
- package/dist/timecampus/pages/application-usage/ApplicationUsagePage.js.map +1 -1
- package/dist/timecampus/pages/client-portal/ClientPortalPage.js +7 -7
- package/dist/timecampus/pages/client-portal/ClientPortalPage.js.map +1 -1
- package/dist/timecampus/pages/dashboard/ReportsPage.js +13 -13
- package/dist/timecampus/pages/email-tracking/EmailTrackingPage.js +7 -7
- package/dist/timecampus/pages/expense-tracking/ExpenseTrackingPage.js +7 -7
- package/dist/timecampus/pages/expense-tracking/ExpenseTrackingPage.js.map +1 -1
- package/dist/timecampus/pages/focus-score/FocusScorePage.js +13 -13
- package/dist/timecampus/pages/geofencing/GeofencingPage.js +135 -135
- package/dist/timecampus/pages/geofencing/GeofencingPage.js.map +1 -1
- package/dist/timecampus/pages/invoicing/InvoicingPage.js +7 -7
- package/dist/timecampus/pages/invoicing/InvoicingPage.js.map +1 -1
- package/dist/timecampus/pages/lifetime/DeadTimeRadarPage.js +8 -8
- package/dist/timecampus/pages/lifetime/FiniteLifePage.js +4 -4
- package/dist/timecampus/pages/lifetime/FiniteLifePage.js.map +1 -1
- package/dist/timecampus/pages/lifetime/LifeBalancePage.js +104 -104
- package/dist/timecampus/pages/lifetime/LifeBalancePage.js.map +1 -1
- package/dist/timecampus/pages/lifetime/LifeTimelinePage.js +7 -7
- package/dist/timecampus/pages/lifetime/LifeTimelinePage.js.map +1 -1
- package/dist/timecampus/pages/lifetime/PersonalCorporatePage.js +33 -33
- package/dist/timecampus/pages/lifetime/PersonalCorporatePage.js.map +1 -1
- package/dist/timecampus/pages/lifetime/RecommendationsPage.js +80 -80
- package/dist/timecampus/pages/lifetime/RecommendationsPage.js.map +1 -1
- package/dist/timecampus/pages/lifetime/TimeWellSpentPage.js +16 -16
- package/dist/timecampus/pages/lifetime/ValuesGoalsPage.js +11 -11
- package/dist/timecampus/pages/location-tracking/LocationTrackingPage.js +11 -11
- package/dist/timecampus/pages/location-tracking/LocationTrackingPage.js.map +1 -1
- package/dist/timecampus/pages/meetings/MeetingDetailPage.js +14 -14
- package/dist/timecampus/pages/meetings/MeetingDetailPage.js.map +1 -1
- package/dist/timecampus/pages/meetings/MeetingsPage.js +12 -12
- package/dist/timecampus/pages/projects/ProjectsPage.js +5 -5
- package/dist/timecampus/pages/projects/ProjectsPage.js.map +1 -1
- package/dist/timecampus/pages/projects/TasksPage.js +8 -8
- package/dist/timecampus/pages/resource-planning/ResourcePlanningPage.js +8 -8
- package/dist/timecampus/pages/resource-planning/ResourcePlanningPage.js.map +1 -1
- package/dist/timecampus/pages/team/TeamMembersPage.js +8 -8
- package/dist/timecampus/pages/team-activity/TeamActivityPage.js +13 -13
- package/dist/timecampus/pages/time/TimeEntriesPage.js +5 -5
- package/dist/timecampus/pages/time/TimeEntriesPage.js.map +1 -1
- package/dist/timecampus/pages/time/TimeTrackingPage.js +138 -138
- package/dist/timecampus/pages/time/TimeTrackingPage.js.map +1 -1
- package/dist/timecampus/pages/time/TimerPage.js +14 -14
- package/dist/timecampus/pages/time/TimerPage.js.map +1 -1
- package/dist/timecampus/pages/time-approval/TimeApprovalPage.js +26 -26
- package/dist/timecampus/pages/time-approval/TimeApprovalPage.js.map +1 -1
- package/dist/timecampus/pages/timeline/TimelinePage.js +8 -8
- package/dist/timecampus/pages/work-patterns/WorkPatternsPage.js +79 -79
- package/dist/timecampus/pages/work-patterns/WorkPatternsPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TimeApprovalPage.js","names":[],"sources":["../../../../src/timecampus/pages/time-approval/TimeApprovalPage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport {\n CheckCircle,\n XCircle,\n X,\n Clock,\n MessageSquare,\n MessagesSquare,\n Undo2,\n} from \"lucide-react\";\nimport { ContextConversationWidget } from \"@burdenoff/fe-libs/shared\";\nimport { useRegisterPageConversationContext } from \"@burdenoff/fe-libs/shared/providers/shell\";\nimport { GlassCard } from \"@burdenoff/fe-libs/ui\";\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 { ChartCard, DonutChart, ProgressMeter } from \"../../components\";\nimport { getInitials, formatHours, formatDate, cn } from \"../../utils/helpers\";\nimport {\n useListApprovalRequestsQuery,\n useGetApprovalStatsQuery,\n useApproveRequestMutation,\n useRejectRequestMutation,\n useRequestRevisionMutation,\n useWithdrawRequestMutation,\n type ApprovalRequestFieldsFragment,\n type ListApprovalRequestsQuery,\n} from \"@/generated/wspace-operations\";\nimport { ApprovalStatus } from \"@/generated/wspace-types\";\n\nconst PAGE_SIZE = 25;\n\n// Rendered as a child of the review Modal so the registration hook only runs\n// while the modal is mounted — it scopes the shared page-conversation widget to\n// the selected approval request, then releases the context on close/unmount.\nfunction ApprovalReviewThread({\n request,\n}: {\n request: ApprovalRequestFieldsFragment;\n}) {\n useRegisterPageConversationContext({\n entityType: \"TIMECAMPUS_APPROVAL_REQUEST\",\n entityId: request.id,\n contextTitle: `Approval · ${request.period ?? request.id}`,\n });\n\n return <ContextConversationWidget widgetKey=\"approval-review\" />;\n}\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\nexport function TimeApprovalPage() {\n const { toasts, addToast, dismissToast } = useToast();\n\n const {\n data: requestsData,\n loading: requestsLoading,\n error: requestsError,\n refetch,\n fetchMore,\n } = useListApprovalRequestsQuery({\n variables: { pagination: { limit: PAGE_SIZE, offset: 0 } },\n notifyOnNetworkStatusChange: true,\n });\n const {\n data: statsData,\n loading: statsLoading,\n error: statsError,\n refetch: refetchStats,\n } = useGetApprovalStatsQuery();\n\n const [approveRequest] = useApproveRequestMutation();\n const [rejectRequest, { loading: rejecting }] = useRejectRequestMutation();\n const [requestRevision, { loading: requestingRevision }] =\n useRequestRevisionMutation();\n const [withdrawRequest] = useWithdrawRequestMutation();\n\n const requests = requestsData?.approvalRequests?.items ?? [];\n const total = requestsData?.approvalRequests?.total ?? 0;\n const hasMore = requestsData?.approvalRequests?.hasMore ?? false;\n const stats = statsData?.approvalStats;\n\n const approvalMix = [\n { name: \"Pending\", value: stats?.pendingCount ?? 0 },\n { name: \"Approved\", value: stats?.approvedCount ?? 0 },\n { name: \"Rejected\", value: stats?.rejectedCount ?? 0 },\n ];\n\n const [loadingMore, setLoadingMore] = useState(false);\n const [busyId, setBusyId] = useState<string | null>(null);\n\n // Reject reason modal\n const [rejectTarget, setRejectTarget] =\n useState<ApprovalRequestFieldsFragment | null>(null);\n const [rejectReason, setRejectReason] = useState(\"\");\n\n // Request revision modal\n const [revisionTarget, setRevisionTarget] =\n useState<ApprovalRequestFieldsFragment | null>(null);\n const [revisionNotes, setRevisionNotes] = useState(\"\");\n\n // Withdraw confirm modal\n const [withdrawTarget, setWithdrawTarget] =\n useState<ApprovalRequestFieldsFragment | null>(null);\n\n // Review / discussion thread modal\n const [reviewTarget, setReviewTarget] =\n useState<ApprovalRequestFieldsFragment | null>(null);\n\n const refreshAll = async () => {\n await Promise.all([refetch(), refetchStats()]);\n };\n\n const handleApprove = async (id: string) => {\n setBusyId(id);\n try {\n await approveRequest({ variables: { id } });\n addToast({ title: \"Timesheet approved\", variant: \"success\" });\n await refreshAll();\n } catch {\n addToast({ title: \"Failed to approve timesheet\", variant: \"error\" });\n } finally {\n setBusyId(null);\n }\n };\n\n const handleReject = async () => {\n if (!rejectTarget || !rejectReason.trim()) return;\n try {\n await rejectRequest({\n variables: { id: rejectTarget.id, reason: rejectReason.trim() },\n });\n addToast({ title: \"Timesheet rejected\", variant: \"success\" });\n setRejectTarget(null);\n setRejectReason(\"\");\n await refreshAll();\n } catch {\n addToast({ title: \"Failed to reject timesheet\", variant: \"error\" });\n }\n };\n\n const handleRequestRevision = async () => {\n if (!revisionTarget || !revisionNotes.trim()) return;\n try {\n await requestRevision({\n variables: { id: revisionTarget.id, notes: revisionNotes.trim() },\n });\n addToast({ title: \"Revision requested\", variant: \"success\" });\n setRevisionTarget(null);\n setRevisionNotes(\"\");\n await refreshAll();\n } catch {\n addToast({ title: \"Failed to request revision\", variant: \"error\" });\n }\n };\n\n const handleWithdraw = async () => {\n if (!withdrawTarget) return;\n try {\n await withdrawRequest({ variables: { id: withdrawTarget.id } });\n addToast({ title: \"Request withdrawn\", variant: \"success\" });\n setWithdrawTarget(null);\n await refreshAll();\n } catch {\n addToast({ title: \"Failed to withdraw request\", variant: \"error\" });\n }\n };\n\n const handleLoadMore = async () => {\n setLoadingMore(true);\n try {\n await fetchMore({\n variables: {\n pagination: { limit: PAGE_SIZE, offset: requests.length },\n },\n updateQuery: (\n prev: ListApprovalRequestsQuery,\n { fetchMoreResult }: { fetchMoreResult?: ListApprovalRequestsQuery },\n ) => {\n if (!fetchMoreResult) return prev;\n return {\n ...prev,\n approvalRequests: {\n ...fetchMoreResult.approvalRequests,\n items: [\n ...prev.approvalRequests.items,\n ...fetchMoreResult.approvalRequests.items,\n ],\n },\n };\n },\n });\n } finally {\n setLoadingMore(false);\n }\n };\n\n if (requestsLoading && statsLoading && requests.length === 0) {\n return (\n <PageLayout\n title=\"Time Approval\"\n description=\"Review and approve team timesheets\"\n >\n <LoadingSkeleton variant=\"list\" count={4} />\n </PageLayout>\n );\n }\n\n if (requestsError && statsError) {\n return (\n <PageLayout\n title=\"Time Approval\"\n description=\"Review and approve team timesheets\"\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 approval data. Please try again later.\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title=\"Time Approval\"\n description=\"Review and approve team timesheets\"\n purpose=\"The reviewer's queue for signing off team timesheets before they're billed or paid — approve, reject or request changes in one place so payroll and invoices go out accurate and on time.\"\n >\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <StatCard\n featured\n title=\"Pending Approvals\"\n value={String(stats?.pendingCount ?? 0)}\n icon={<Clock className=\"h-5 w-5\" />}\n iconBgColor=\"bg-status-warning-bg text-status-warning-text\"\n />\n <StatCard\n title=\"Approved This Week\"\n value={String(stats?.approvedCount ?? 0)}\n icon={<CheckCircle className=\"h-5 w-5\" />}\n iconBgColor=\"bg-status-success-bg text-status-success-text\"\n />\n <StatCard\n title=\"Rejected\"\n value={String(stats?.rejectedCount ?? 0)}\n icon={<XCircle className=\"h-5 w-5\" />}\n iconBgColor=\"bg-status-error-bg text-status-error-text\"\n />\n <StatCard\n title=\"Avg. Review Time\"\n value={\n stats?.averageReviewTime\n ? `${Math.round(stats.averageReviewTime)}h`\n : \"N/A\"\n }\n description=\"Target: 24h\"\n />\n </div>\n\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-3\">\n <div className=\"space-y-4 lg:col-span-2\">\n <div className=\"flex items-center justify-between gap-3\">\n <h2 className=\"text-sm font-semibold text-text-primary\">\n Pending Requests\n </h2>\n <p className=\"text-xs text-text-secondary\">\n Showing {requests.length} of {total}\n </p>\n </div>\n\n {requests.length === 0 && !requestsLoading ? (\n <EmptyState\n illustration=\"empty-notifications\"\n title=\"No approval requests found\"\n description=\"You're all caught up — submitted timesheets waiting on your review will appear here.\"\n />\n ) : (\n <>\n <div className=\"space-y-3\">\n {requests.map((request: ApprovalRequestFieldsFragment) => {\n const isPending = request.status === ApprovalStatus.Pending;\n const rowBusy = busyId === request.id;\n const selected = reviewTarget?.id === request.id;\n const billablePct =\n request.totalHours > 0\n ? Math.round(\n (request.billableHours / request.totalHours) * 100,\n )\n : 0;\n return (\n <GlassCard\n key={request.id}\n treatment=\"plain\"\n glow={selected}\n className={cn(\n \"p-4\",\n selected && \"border-action-primary-bg/50\",\n )}\n >\n <div className=\"flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between\">\n <div className=\"flex min-w-0 items-center gap-3\">\n <div className=\"flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-action-primary-bg/10 text-sm font-medium text-primary\">\n {getInitials(request.userId)}\n </div>\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {request.userId}\n </p>\n <p className=\"truncate text-xs text-text-secondary\">\n {request.period} · Submitted{\" \"}\n {formatDate(request.submittedAt)}\n </p>\n </div>\n </div>\n\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center sm:gap-5\">\n <div className=\"flex items-center gap-6\">\n <div>\n <p className=\"text-[11px] font-medium uppercase tracking-wide text-text-secondary\">\n Total submitted\n </p>\n <p className=\"text-lg font-semibold tabular-nums text-text-primary\">\n {formatHours(request.totalHours)}\n </p>\n </div>\n <div>\n <p className=\"text-[11px] font-medium uppercase tracking-wide text-text-secondary\">\n Billable\n </p>\n <p className=\"text-lg font-semibold tabular-nums text-text-primary\">\n {formatHours(request.billableHours)}\n </p>\n </div>\n </div>\n\n <div className=\"flex flex-wrap items-center gap-2\">\n <StatusBadge\n label={request.status.toLowerCase()}\n status={request.status.toLowerCase()}\n />\n {isPending && (\n <>\n <button\n onClick={() => handleApprove(request.id)}\n disabled={rowBusy}\n className=\"inline-flex items-center gap-1.5 rounded-md bg-status-success-bg px-3 py-1.5 text-sm font-medium text-status-success-text hover:bg-status-success-bg/80 disabled:opacity-50\"\n >\n <CheckCircle className=\"h-4 w-4\" />\n Approve\n </button>\n <button\n onClick={() => {\n setRejectTarget(request);\n setRejectReason(\"\");\n }}\n disabled={rowBusy}\n className=\"inline-flex items-center gap-1.5 rounded-md bg-status-error-bg px-3 py-1.5 text-sm font-medium text-status-error-text hover:bg-status-error-bg/80 disabled:opacity-50\"\n >\n <XCircle className=\"h-4 w-4\" />\n Reject\n </button>\n <button\n onClick={() => {\n setRevisionTarget(request);\n setRevisionNotes(\"\");\n }}\n disabled={rowBusy}\n title=\"Request revision\"\n className=\"rounded-md border border-border-seam bg-bg-surface p-1.5 text-status-warning-text hover:bg-status-warning-bg disabled:opacity-50\"\n >\n <MessageSquare className=\"h-4 w-4\" />\n </button>\n <button\n onClick={() => setWithdrawTarget(request)}\n disabled={rowBusy}\n title=\"Withdraw request\"\n className=\"rounded-md border border-border-seam bg-bg-surface p-1.5 text-text-secondary hover:bg-bg-sunken disabled:opacity-50\"\n >\n <Undo2 className=\"h-4 w-4\" />\n </button>\n </>\n )}\n <button\n onClick={() =>\n setReviewTarget(selected ? null : request)\n }\n title=\"Review / Discussion\"\n className={cn(\n \"rounded-md border p-1.5\",\n selected\n ? \"border-action-primary-bg bg-action-primary-bg/10 text-primary\"\n : \"border-border-seam bg-bg-surface text-text-secondary hover:bg-bg-sunken\",\n )}\n >\n <MessagesSquare className=\"h-4 w-4\" />\n </button>\n </div>\n </div>\n </div>\n\n {request.totalHours > 0 && (\n <div className=\"mt-4\">\n <ProgressMeter\n value={billablePct}\n max={100}\n tone=\"success\"\n size=\"sm\"\n showValue={false}\n label={`Billable share · ${billablePct}%`}\n />\n </div>\n )}\n </GlassCard>\n );\n })}\n </div>\n\n {hasMore && (\n <div className=\"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 </div>\n\n <div className=\"lg:col-span-1\">\n <div className=\"space-y-4 lg:sticky lg:top-4\">\n <ChartCard\n title=\"Approval mix\"\n description=\"Requests by current status\"\n >\n <DonutChart\n data={approvalMix}\n centerLabel=\"Total\"\n centerValue={stats?.totalRequests ?? 0}\n height={220}\n emptyLabel=\"No requests to summarise yet.\"\n ariaLabel=\"Approval requests by status\"\n />\n </ChartCard>\n\n <div className=\"space-y-2\">\n <div className=\"flex items-center justify-between px-1\">\n <h3 className=\"text-sm font-medium text-text-primary\">\n Review discussion\n </h3>\n {reviewTarget && (\n <button\n onClick={() => setReviewTarget(null)}\n title=\"Close thread\"\n className=\"inline-flex items-center gap-1 rounded-md px-2 py-1 text-xs text-text-secondary hover:bg-bg-sunken\"\n >\n <X className=\"h-3.5 w-3.5\" />\n Clear\n </button>\n )}\n </div>\n {reviewTarget ? (\n <>\n <p className=\"px-1 text-xs text-text-secondary\">\n Discussing {reviewTarget.userId} ·{\" \"}\n {reviewTarget.period}\n </p>\n <ApprovalReviewThread request={reviewTarget} />\n </>\n ) : (\n <GlassCard\n treatment=\"insight\"\n className=\"border-border-seam p-6\"\n >\n <div className=\"flex flex-col items-center justify-center gap-2 text-center\">\n <MessagesSquare className=\"h-6 w-6 text-text-secondary\" />\n <p className=\"text-sm font-medium text-text-primary\">\n No timesheet selected\n </p>\n <p className=\"text-xs text-text-secondary\">\n Open a request's discussion icon to review it with the\n submitter.\n </p>\n </div>\n </GlassCard>\n )}\n </div>\n </div>\n </div>\n </div>\n\n <Modal\n open={rejectTarget !== null}\n onClose={() => setRejectTarget(null)}\n title=\"Reject timesheet\"\n description={\n rejectTarget\n ? `Rejecting ${rejectTarget.userId}'s ${rejectTarget.period} timesheet.`\n : undefined\n }\n size=\"sm\"\n footer={\n <>\n <button\n onClick={() => setRejectTarget(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={handleReject}\n disabled={rejecting || !rejectReason.trim()}\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 {rejecting ? \"Rejecting...\" : \"Reject\"}\n </button>\n </>\n }\n >\n <label className=\"block text-sm font-medium text-text-primary\">\n Rejection reason\n </label>\n <textarea\n rows={3}\n placeholder=\"Explain why this timesheet is being rejected\"\n value={rejectReason}\n onChange={(e) => setRejectReason(e.target.value)}\n className={inputClass}\n />\n </Modal>\n\n <Modal\n open={revisionTarget !== null}\n onClose={() => setRevisionTarget(null)}\n title=\"Request revision\"\n description={\n revisionTarget\n ? `Ask ${revisionTarget.userId} to revise their ${revisionTarget.period} timesheet.`\n : undefined\n }\n size=\"sm\"\n footer={\n <>\n <button\n onClick={() => setRevisionTarget(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={handleRequestRevision}\n disabled={requestingRevision || !revisionNotes.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 {requestingRevision ? \"Sending...\" : \"Request Revision\"}\n </button>\n </>\n }\n >\n <label className=\"block text-sm font-medium text-text-primary\">\n What needs changing?\n </label>\n <textarea\n rows={3}\n placeholder=\"Describe the changes needed before this can be approved\"\n value={revisionNotes}\n onChange={(e) => setRevisionNotes(e.target.value)}\n className={inputClass}\n />\n </Modal>\n\n <Modal\n open={withdrawTarget !== null}\n onClose={() => setWithdrawTarget(null)}\n title=\"Withdraw request?\"\n description={\n withdrawTarget\n ? `${withdrawTarget.userId}'s ${withdrawTarget.period} timesheet will be pulled from the review queue.`\n : undefined\n }\n size=\"sm\"\n footer={\n <>\n <button\n onClick={() => setWithdrawTarget(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={handleWithdraw}\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\"\n >\n Withdraw\n </button>\n </>\n }\n >\n <p className=\"text-sm text-text-secondary\">\n Withdrawing removes the request from the approval queue. It can be\n resubmitted later.\n </p>\n </Modal>\n\n <ToastContainer toasts={toasts} onDismiss={dismissToast} />\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"TimeApprovalPage.js","names":[],"sources":["../../../../src/timecampus/pages/time-approval/TimeApprovalPage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport {\n CheckCircle,\n XCircle,\n X,\n Clock,\n MessageSquare,\n MessagesSquare,\n Undo2,\n} from \"lucide-react\";\nimport { ContextConversationWidget } from \"@burdenoff/fe-libs/shared\";\nimport { useRegisterPageConversationContext } from \"@burdenoff/fe-libs/shared/providers/shell\";\nimport { GlassCard } from \"@burdenoff/fe-libs/ui\";\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 { ChartCard, DonutChart, ProgressMeter } from \"../../components\";\nimport { getInitials, formatHours, formatDate, cn } from \"../../utils/helpers\";\nimport {\n useListApprovalRequestsQuery,\n useGetApprovalStatsQuery,\n useApproveRequestMutation,\n useRejectRequestMutation,\n useRequestRevisionMutation,\n useWithdrawRequestMutation,\n type ApprovalRequestFieldsFragment,\n type ListApprovalRequestsQuery,\n} from \"@/generated/wspace-operations\";\nimport { ApprovalStatus } from \"@/generated/wspace-types\";\n\nconst PAGE_SIZE = 25;\n\n// Rendered as a child of the review Modal so the registration hook only runs\n// while the modal is mounted — it scopes the shared page-conversation widget to\n// the selected approval request, then releases the context on close/unmount.\nfunction ApprovalReviewThread({\n request,\n}: {\n request: ApprovalRequestFieldsFragment;\n}) {\n useRegisterPageConversationContext({\n entityType: \"TIMECAMPUS_APPROVAL_REQUEST\",\n entityId: request.id,\n contextTitle: `Approval · ${request.period ?? request.id}`,\n });\n\n return <ContextConversationWidget widgetKey=\"approval-review\" />;\n}\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\nexport function TimeApprovalPage() {\n const { toasts, addToast, dismissToast } = useToast();\n\n const {\n data: requestsData,\n loading: requestsLoading,\n error: requestsError,\n refetch,\n fetchMore,\n } = useListApprovalRequestsQuery({\n variables: { pagination: { limit: PAGE_SIZE, offset: 0 } },\n notifyOnNetworkStatusChange: true,\n });\n const {\n data: statsData,\n loading: statsLoading,\n error: statsError,\n refetch: refetchStats,\n } = useGetApprovalStatsQuery();\n\n const [approveRequest] = useApproveRequestMutation();\n const [rejectRequest, { loading: rejecting }] = useRejectRequestMutation();\n const [requestRevision, { loading: requestingRevision }] =\n useRequestRevisionMutation();\n const [withdrawRequest] = useWithdrawRequestMutation();\n\n const requests = requestsData?.approvalRequests?.items ?? [];\n const total = requestsData?.approvalRequests?.total ?? 0;\n const hasMore = requestsData?.approvalRequests?.hasMore ?? false;\n const stats = statsData?.approvalStats;\n\n const approvalMix = [\n { name: \"Pending\", value: stats?.pendingCount ?? 0 },\n { name: \"Approved\", value: stats?.approvedCount ?? 0 },\n { name: \"Rejected\", value: stats?.rejectedCount ?? 0 },\n ];\n\n const [loadingMore, setLoadingMore] = useState(false);\n const [busyId, setBusyId] = useState<string | null>(null);\n\n // Reject reason modal\n const [rejectTarget, setRejectTarget] =\n useState<ApprovalRequestFieldsFragment | null>(null);\n const [rejectReason, setRejectReason] = useState(\"\");\n\n // Request revision modal\n const [revisionTarget, setRevisionTarget] =\n useState<ApprovalRequestFieldsFragment | null>(null);\n const [revisionNotes, setRevisionNotes] = useState(\"\");\n\n // Withdraw confirm modal\n const [withdrawTarget, setWithdrawTarget] =\n useState<ApprovalRequestFieldsFragment | null>(null);\n\n // Review / discussion thread modal\n const [reviewTarget, setReviewTarget] =\n useState<ApprovalRequestFieldsFragment | null>(null);\n\n const refreshAll = async () => {\n await Promise.all([refetch(), refetchStats()]);\n };\n\n const handleApprove = async (id: string) => {\n setBusyId(id);\n try {\n await approveRequest({ variables: { id } });\n addToast({ title: \"Timesheet approved\", variant: \"success\" });\n await refreshAll();\n } catch {\n addToast({ title: \"Failed to approve timesheet\", variant: \"error\" });\n } finally {\n setBusyId(null);\n }\n };\n\n const handleReject = async () => {\n if (!rejectTarget || !rejectReason.trim()) return;\n try {\n await rejectRequest({\n variables: { id: rejectTarget.id, reason: rejectReason.trim() },\n });\n addToast({ title: \"Timesheet rejected\", variant: \"success\" });\n setRejectTarget(null);\n setRejectReason(\"\");\n await refreshAll();\n } catch {\n addToast({ title: \"Failed to reject timesheet\", variant: \"error\" });\n }\n };\n\n const handleRequestRevision = async () => {\n if (!revisionTarget || !revisionNotes.trim()) return;\n try {\n await requestRevision({\n variables: { id: revisionTarget.id, notes: revisionNotes.trim() },\n });\n addToast({ title: \"Revision requested\", variant: \"success\" });\n setRevisionTarget(null);\n setRevisionNotes(\"\");\n await refreshAll();\n } catch {\n addToast({ title: \"Failed to request revision\", variant: \"error\" });\n }\n };\n\n const handleWithdraw = async () => {\n if (!withdrawTarget) return;\n try {\n await withdrawRequest({ variables: { id: withdrawTarget.id } });\n addToast({ title: \"Request withdrawn\", variant: \"success\" });\n setWithdrawTarget(null);\n await refreshAll();\n } catch {\n addToast({ title: \"Failed to withdraw request\", variant: \"error\" });\n }\n };\n\n const handleLoadMore = async () => {\n setLoadingMore(true);\n try {\n await fetchMore({\n variables: {\n pagination: { limit: PAGE_SIZE, offset: requests.length },\n },\n updateQuery: (\n prev: ListApprovalRequestsQuery,\n { fetchMoreResult }: { fetchMoreResult?: ListApprovalRequestsQuery },\n ) => {\n if (!fetchMoreResult) return prev;\n return {\n ...prev,\n approvalRequests: {\n ...fetchMoreResult.approvalRequests,\n items: [\n ...prev.approvalRequests.items,\n ...fetchMoreResult.approvalRequests.items,\n ],\n },\n };\n },\n });\n } finally {\n setLoadingMore(false);\n }\n };\n\n if (requestsLoading && statsLoading && requests.length === 0) {\n return (\n <PageLayout\n title=\"Time Approval\"\n description=\"Review and approve team timesheets\"\n >\n <LoadingSkeleton variant=\"list\" count={4} />\n </PageLayout>\n );\n }\n\n if (requestsError && statsError) {\n return (\n <PageLayout\n title=\"Time Approval\"\n description=\"Review and approve team timesheets\"\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 approval data. Please try again later.\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title=\"Time Approval\"\n description=\"Review and approve team timesheets\"\n purpose=\"The reviewer's queue for signing off team timesheets before they're billed or paid — approve, reject or request changes in one place so payroll and invoices go out accurate and on time.\"\n >\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <StatCard\n featured\n title=\"Pending Approvals\"\n value={String(stats?.pendingCount ?? 0)}\n icon={<Clock className=\"h-5 w-5\" />}\n iconBgColor=\"bg-status-warning-bg text-status-warning-text\"\n />\n <StatCard\n title=\"Approved This Week\"\n value={String(stats?.approvedCount ?? 0)}\n icon={<CheckCircle className=\"h-5 w-5\" />}\n iconBgColor=\"bg-status-success-bg text-status-success-text\"\n />\n <StatCard\n title=\"Rejected\"\n value={String(stats?.rejectedCount ?? 0)}\n icon={<XCircle className=\"h-5 w-5\" />}\n iconBgColor=\"bg-status-error-bg text-status-error-text\"\n />\n <StatCard\n title=\"Avg. Review Time\"\n value={\n stats?.averageReviewTime\n ? `${Math.round(stats.averageReviewTime)}h`\n : \"N/A\"\n }\n description=\"Target: 24h\"\n />\n </div>\n\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-3\">\n <div className=\"space-y-4 lg:col-span-2\">\n <div className=\"flex items-center justify-between gap-3\">\n <h2 className=\"text-sm font-semibold text-text-primary\">\n Pending Requests\n </h2>\n <p className=\"text-xs text-text-secondary\">\n Showing {requests.length} of {total}\n </p>\n </div>\n\n {requests.length === 0 && !requestsLoading ? (\n <EmptyState\n illustration=\"empty-notifications\"\n title=\"No approval requests found\"\n description=\"You're all caught up — submitted timesheets waiting on your review will appear here.\"\n />\n ) : (\n <>\n <div className=\"space-y-3\">\n {requests.map((request: ApprovalRequestFieldsFragment) => {\n const isPending = request.status === ApprovalStatus.Pending;\n const rowBusy = busyId === request.id;\n const selected = reviewTarget?.id === request.id;\n const billablePct =\n request.totalHours > 0\n ? Math.round(\n (request.billableHours / request.totalHours) * 100,\n )\n : 0;\n return (\n <GlassCard\n key={request.id}\n treatment=\"plain\"\n glow={selected}\n className={cn(\n \"p-4\",\n selected && \"border-action-primary-bg/50\",\n )}\n >\n <div className=\"flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between\">\n <div className=\"flex min-w-0 items-center gap-3\">\n <div className=\"flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-action-primary-bg/10 text-sm font-medium text-primary\">\n {getInitials(request.userId)}\n </div>\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {request.userId}\n </p>\n <p className=\"truncate text-xs text-text-secondary\">\n {request.period} · Submitted{\" \"}\n {formatDate(request.submittedAt)}\n </p>\n </div>\n </div>\n\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center sm:gap-5\">\n <div className=\"flex items-center gap-6\">\n <div>\n <p className=\"text-[11px] font-medium uppercase tracking-wide text-text-secondary\">\n Total submitted\n </p>\n <p className=\"text-lg font-semibold tabular-nums text-text-primary\">\n {formatHours(request.totalHours)}\n </p>\n </div>\n <div>\n <p className=\"text-[11px] font-medium uppercase tracking-wide text-text-secondary\">\n Billable\n </p>\n <p className=\"text-lg font-semibold tabular-nums text-text-primary\">\n {formatHours(request.billableHours)}\n </p>\n </div>\n </div>\n\n <div className=\"flex flex-wrap items-center gap-2\">\n <StatusBadge\n label={request.status.toLowerCase()}\n status={request.status.toLowerCase()}\n />\n {isPending && (\n <>\n <button\n onClick={() => handleApprove(request.id)}\n disabled={rowBusy}\n className=\"inline-flex items-center gap-1.5 rounded-md bg-status-success-bg px-3 py-1.5 text-sm font-medium text-status-success-text hover:bg-status-success-bg/80 disabled:opacity-50\"\n >\n <CheckCircle className=\"h-4 w-4\" />\n Approve\n </button>\n <button\n onClick={() => {\n setRejectTarget(request);\n setRejectReason(\"\");\n }}\n disabled={rowBusy}\n className=\"inline-flex items-center gap-1.5 rounded-md bg-status-error-bg px-3 py-1.5 text-sm font-medium text-status-error-text hover:bg-status-error-bg/80 disabled:opacity-50\"\n >\n <XCircle className=\"h-4 w-4\" />\n Reject\n </button>\n <button\n onClick={() => {\n setRevisionTarget(request);\n setRevisionNotes(\"\");\n }}\n disabled={rowBusy}\n title=\"Request revision\"\n className=\"rounded-md border border-border-seam bg-bg-surface p-1.5 text-status-warning-text hover:bg-status-warning-bg disabled:opacity-50\"\n >\n <MessageSquare className=\"h-4 w-4\" />\n </button>\n <button\n onClick={() => setWithdrawTarget(request)}\n disabled={rowBusy}\n title=\"Withdraw request\"\n className=\"rounded-md border border-border-seam bg-bg-surface p-1.5 text-text-secondary hover:bg-bg-sunken disabled:opacity-50\"\n >\n <Undo2 className=\"h-4 w-4\" />\n </button>\n </>\n )}\n <button\n onClick={() =>\n setReviewTarget(selected ? null : request)\n }\n title=\"Review / Discussion\"\n className={cn(\n \"rounded-md border p-1.5\",\n selected\n ? \"border-action-primary-bg bg-action-primary-bg/10 text-primary\"\n : \"border-border-seam bg-bg-surface text-text-secondary hover:bg-bg-sunken\",\n )}\n >\n <MessagesSquare className=\"h-4 w-4\" />\n </button>\n </div>\n </div>\n </div>\n\n {request.totalHours > 0 && (\n <div className=\"mt-4\">\n <ProgressMeter\n value={billablePct}\n max={100}\n tone=\"success\"\n size=\"sm\"\n showValue={false}\n label={`Billable share · ${billablePct}%`}\n />\n </div>\n )}\n </GlassCard>\n );\n })}\n </div>\n\n {hasMore && (\n <div className=\"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 </div>\n\n <div className=\"lg:col-span-1\">\n <div className=\"space-y-4 lg:sticky lg:top-4\">\n <ChartCard\n title=\"Approval mix\"\n description=\"Requests by current status\"\n >\n <DonutChart\n data={approvalMix}\n centerLabel=\"Total\"\n centerValue={stats?.totalRequests ?? 0}\n height={220}\n emptyLabel=\"No requests to summarise yet.\"\n ariaLabel=\"Approval requests by status\"\n />\n </ChartCard>\n\n <div className=\"space-y-2\">\n <div className=\"flex items-center justify-between px-1\">\n <h3 className=\"text-sm font-medium text-text-primary\">\n Review discussion\n </h3>\n {reviewTarget && (\n <button\n onClick={() => setReviewTarget(null)}\n title=\"Close thread\"\n className=\"inline-flex items-center gap-1 rounded-md px-2 py-1 text-xs text-text-secondary hover:bg-bg-sunken\"\n >\n <X className=\"h-3.5 w-3.5\" />\n Clear\n </button>\n )}\n </div>\n {reviewTarget ? (\n <>\n <p className=\"px-1 text-xs text-text-secondary\">\n Discussing {reviewTarget.userId} ·{\" \"}\n {reviewTarget.period}\n </p>\n <ApprovalReviewThread request={reviewTarget} />\n </>\n ) : (\n <GlassCard\n treatment=\"insight\"\n className=\"border-border-seam p-6\"\n >\n <div className=\"flex flex-col items-center justify-center gap-2 text-center\">\n <MessagesSquare className=\"h-6 w-6 text-text-secondary\" />\n <p className=\"text-sm font-medium text-text-primary\">\n No timesheet selected\n </p>\n <p className=\"text-xs text-text-secondary\">\n Open a request's discussion icon to review it with the\n submitter.\n </p>\n </div>\n </GlassCard>\n )}\n </div>\n </div>\n </div>\n </div>\n\n <Modal\n open={rejectTarget !== null}\n onClose={() => setRejectTarget(null)}\n title=\"Reject timesheet\"\n description={\n rejectTarget\n ? `Rejecting ${rejectTarget.userId}'s ${rejectTarget.period} timesheet.`\n : undefined\n }\n size=\"sm\"\n footer={\n <>\n <button\n onClick={() => setRejectTarget(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={handleReject}\n disabled={rejecting || !rejectReason.trim()}\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 {rejecting ? \"Rejecting...\" : \"Reject\"}\n </button>\n </>\n }\n >\n <label className=\"block text-sm font-medium text-text-primary\">\n Rejection reason\n </label>\n <textarea\n rows={3}\n placeholder=\"Explain why this timesheet is being rejected\"\n value={rejectReason}\n onChange={(e) => setRejectReason(e.target.value)}\n className={inputClass}\n />\n </Modal>\n\n <Modal\n open={revisionTarget !== null}\n onClose={() => setRevisionTarget(null)}\n title=\"Request revision\"\n description={\n revisionTarget\n ? `Ask ${revisionTarget.userId} to revise their ${revisionTarget.period} timesheet.`\n : undefined\n }\n size=\"sm\"\n footer={\n <>\n <button\n onClick={() => setRevisionTarget(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={handleRequestRevision}\n disabled={requestingRevision || !revisionNotes.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 {requestingRevision ? \"Sending...\" : \"Request Revision\"}\n </button>\n </>\n }\n >\n <label className=\"block text-sm font-medium text-text-primary\">\n What needs changing?\n </label>\n <textarea\n rows={3}\n placeholder=\"Describe the changes needed before this can be approved\"\n value={revisionNotes}\n onChange={(e) => setRevisionNotes(e.target.value)}\n className={inputClass}\n />\n </Modal>\n\n <Modal\n open={withdrawTarget !== null}\n onClose={() => setWithdrawTarget(null)}\n title=\"Withdraw request?\"\n description={\n withdrawTarget\n ? `${withdrawTarget.userId}'s ${withdrawTarget.period} timesheet will be pulled from the review queue.`\n : undefined\n }\n size=\"sm\"\n footer={\n <>\n <button\n onClick={() => setWithdrawTarget(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={handleWithdraw}\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\"\n >\n Withdraw\n </button>\n </>\n }\n >\n <p className=\"text-sm text-text-secondary\">\n Withdrawing removes the request from the approval queue. It can be\n resubmitted later.\n </p>\n </Modal>\n\n <ToastContainer toasts={toasts} onDismiss={dismissToast} />\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -2,9 +2,9 @@ import { useTimeCampus as e } from "../../providers/TimeCampusProvider.js";
|
|
|
2
2
|
import { formatDuration as t, formatTime as n } from "../../utils/helpers.js";
|
|
3
3
|
import { StatCard as r } from "../../components/StatCard.js";
|
|
4
4
|
import { LoadingSkeleton as i } from "../../components/LoadingSkeleton.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
5
|
+
import { EmptyState as a } from "../../components/EmptyState.js";
|
|
6
|
+
import { useGetTimeCampusTimelineQuery as o, useListProjectsQuery as s, useRecategorizeTimeCampusCaptureEventMutation as c } from "../../../generated/wspace-operations.js";
|
|
7
|
+
import { PageLayout as l } from "../../components/PageLayout.js";
|
|
8
8
|
import { useMemo as u, useState as d } from "react";
|
|
9
9
|
import { Activity as f, Brain as p, Calendar as m, Coffee as h, FolderOpen as g, Github as _, GripVertical as v, MessageSquare as y, Users as b, Zap as x } from "lucide-react";
|
|
10
10
|
import { jsx as S, jsxs as C } from "react/jsx-runtime";
|
|
@@ -172,17 +172,17 @@ function V(e) {
|
|
|
172
172
|
};
|
|
173
173
|
}
|
|
174
174
|
function H() {
|
|
175
|
-
let { workspaceId: h = "" } = e(), [g, _] = d(() => B(/* @__PURE__ */ new Date())), [y, b] = d(null), { from: k, to: A } = u(() => V(g), [g]), { data: P, loading: I, error: L, refetch: H } =
|
|
175
|
+
let { workspaceId: h = "" } = e(), [g, _] = d(() => B(/* @__PURE__ */ new Date())), [y, b] = d(null), { from: k, to: A } = u(() => V(g), [g]), { data: P, loading: I, error: L, refetch: H } = o({
|
|
176
176
|
variables: {
|
|
177
177
|
from: k,
|
|
178
178
|
to: A
|
|
179
179
|
},
|
|
180
180
|
skip: !h,
|
|
181
181
|
fetchPolicy: "cache-and-network"
|
|
182
|
-
}), { data: U } =
|
|
182
|
+
}), { data: U } = s({
|
|
183
183
|
variables: { pagination: { limit: 200 } },
|
|
184
184
|
skip: !h
|
|
185
|
-
}), [W, { loading: G }] =
|
|
185
|
+
}), [W, { loading: G }] = c(), K = M(j(O, { activationConstraint: { distance: 6 } }), j(D)), q = P?.timeCampusTimeline, J = q?.segments ?? [], Y = q?.summary, X = u(() => {
|
|
186
186
|
let e = /* @__PURE__ */ new Map();
|
|
187
187
|
for (let t of U?.projects?.items ?? []) e.set(t.id, t.name);
|
|
188
188
|
return e;
|
|
@@ -209,7 +209,7 @@ function H() {
|
|
|
209
209
|
let r = n.replace(/^category:/, "");
|
|
210
210
|
F.includes(r) && Q(t, r);
|
|
211
211
|
}, ne = Y && Y.focusRatio != null ? Math.round(Y.focusRatio * 100) : 0, $ = Y?.firstActivityAt && Y?.lastActivityAt ? `${n(Y.firstActivityAt)} – ${n(Y.lastActivityAt)}` : null;
|
|
212
|
-
return /* @__PURE__ */ S(
|
|
212
|
+
return /* @__PURE__ */ S(l, {
|
|
213
213
|
title: "Timeline",
|
|
214
214
|
description: "Your day, assembled automatically",
|
|
215
215
|
purpose: "See where your time actually went — meetings, deep work, communication and breaks stitched into one honest, chronological view from everything TimeCampus captures, so you can spot the patterns worth changing.",
|
|
@@ -304,7 +304,7 @@ function H() {
|
|
|
304
304
|
})
|
|
305
305
|
]
|
|
306
306
|
}),
|
|
307
|
-
J.length === 0 ? /* @__PURE__ */ S(
|
|
307
|
+
J.length === 0 ? /* @__PURE__ */ S(a, {
|
|
308
308
|
illustration: "empty-generic",
|
|
309
309
|
title: "No activity captured for this day",
|
|
310
310
|
description: "As you track time, hold meetings, run focus sessions, and connect shared integrations, your day assembles here automatically — categorized and in order."
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
import { useTimeCampus as e } from "../../providers/TimeCampusProvider.js";
|
|
2
2
|
import { StatCard as t } from "../../components/StatCard.js";
|
|
3
3
|
import { LoadingSkeleton as n } from "../../components/LoadingSkeleton.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
4
|
+
import { EmptyState as r } from "../../components/EmptyState.js";
|
|
5
|
+
import { useGetWorkPatternSummaryQuery as i, useListWorkPatternsQuery as ee } from "../../../generated/wspace-operations.js";
|
|
6
|
+
import { PageLayout as a } from "../../components/PageLayout.js";
|
|
7
|
+
import { StatusBadge as o } from "../../components/StatusBadge.js";
|
|
8
8
|
import { ChartCard as s } from "../../components/ChartCard.js";
|
|
9
|
-
import { TrendChart as
|
|
10
|
-
import { Heatmap as
|
|
9
|
+
import { TrendChart as te } from "../../components/charts/TrendChart.js";
|
|
10
|
+
import { Heatmap as c } from "../../components/charts/Heatmap.js";
|
|
11
11
|
import "../../components/index.js";
|
|
12
|
-
import { WorkPatternInsightType as
|
|
13
|
-
import { AlertTriangle as
|
|
14
|
-
import { jsx as
|
|
15
|
-
import { GlassCard as
|
|
12
|
+
import { WorkPatternInsightType as l } from "../../../generated/wspace-types.js";
|
|
13
|
+
import { AlertTriangle as u, ArrowRight as d, Brain as f, Clock as p, Lightbulb as m, ShieldCheck as h, TrendingUp as ne, Zap as re } from "lucide-react";
|
|
14
|
+
import { jsx as g, jsxs as _ } from "react/jsx-runtime";
|
|
15
|
+
import { GlassCard as v } from "@burdenoff/fe-libs/ui";
|
|
16
16
|
//#region src/timecampus/pages/work-patterns/WorkPatternsPage.tsx
|
|
17
|
-
var
|
|
17
|
+
var y = [
|
|
18
18
|
"Mon",
|
|
19
19
|
"Tue",
|
|
20
20
|
"Wed",
|
|
@@ -23,21 +23,21 @@ var b = [
|
|
|
23
23
|
"Sat",
|
|
24
24
|
"Sun"
|
|
25
25
|
];
|
|
26
|
-
function
|
|
26
|
+
function b(e) {
|
|
27
27
|
return (e.getDay() + 6) % 7;
|
|
28
28
|
}
|
|
29
|
-
function
|
|
29
|
+
function x(e) {
|
|
30
30
|
let t = new Date(e);
|
|
31
|
-
return t.setDate(e.getDate() -
|
|
31
|
+
return t.setDate(e.getDate() - b(e)), t.setHours(0, 0, 0, 0), t;
|
|
32
32
|
}
|
|
33
|
-
function
|
|
33
|
+
function S(e) {
|
|
34
34
|
let t = new Date(e);
|
|
35
35
|
return Number.isNaN(t.getTime()) ? "" : t.toLocaleDateString(void 0, {
|
|
36
36
|
month: "short",
|
|
37
37
|
day: "numeric"
|
|
38
38
|
});
|
|
39
39
|
}
|
|
40
|
-
var
|
|
40
|
+
var C = {
|
|
41
41
|
VERY_LOW: 1,
|
|
42
42
|
LOW: 2,
|
|
43
43
|
MEDIUM: 3,
|
|
@@ -50,144 +50,144 @@ var re = {
|
|
|
50
50
|
POSITIVE: 4,
|
|
51
51
|
VERY_POSITIVE: 5
|
|
52
52
|
}, ae = /* @__PURE__ */ new Set(["HIGH", "VERY_HIGH"]), oe = /* @__PURE__ */ new Set(["LOW", "VERY_LOW"]), se = (e) => `${e.toFixed(1)}h`, ce = {
|
|
53
|
-
[
|
|
54
|
-
[
|
|
55
|
-
[
|
|
56
|
-
[
|
|
57
|
-
[
|
|
53
|
+
[l.Positive]: "success",
|
|
54
|
+
[l.Negative]: "error",
|
|
55
|
+
[l.Warning]: "warning",
|
|
56
|
+
[l.Recommendation]: "info",
|
|
57
|
+
[l.Neutral]: "muted"
|
|
58
58
|
}, le = {
|
|
59
|
-
[
|
|
60
|
-
[
|
|
61
|
-
[
|
|
62
|
-
[
|
|
63
|
-
[
|
|
59
|
+
[l.Positive]: "Positive",
|
|
60
|
+
[l.Negative]: "Negative",
|
|
61
|
+
[l.Warning]: "Warning",
|
|
62
|
+
[l.Recommendation]: "Tip",
|
|
63
|
+
[l.Neutral]: "Neutral"
|
|
64
64
|
};
|
|
65
65
|
function w() {
|
|
66
66
|
let { workspaceId: w = "" } = e(), { data: ue, loading: T, error: E } = ee({
|
|
67
67
|
variables: { pagination: { limit: 30 } },
|
|
68
68
|
skip: !w
|
|
69
|
-
}), { data: D, loading: O, error: k } =
|
|
69
|
+
}), { data: D, loading: O, error: k } = i({ skip: !w }), A = T || O, j = E || k, M = D?.workPatternSummary, N = ue?.workPatterns ?? [], P = M?.insights ?? [], F = [...N].sort((e, t) => {
|
|
70
70
|
let n = new Date(e.date).getTime(), r = new Date(t.date).getTime();
|
|
71
71
|
return (Number.isNaN(n) ? 0 : n) - (Number.isNaN(r) ? 0 : r);
|
|
72
72
|
}), I = N[0], L = M?.averageProductivity == null ? "--" : `${Math.round(M.averageProductivity)}%`, R = I?.focusHours == null ? "--" : `${I.focusHours.toFixed(1)}h`, z = I ? (I.focusHours + I.meetingHours + I.breakHours).toFixed(0) : "8", B = I?.deepWorkSessions == null ? "--" : String(I.deepWorkSessions), de = M?.mostProductiveDay ?? "--", V = [];
|
|
73
73
|
for (let e of F) {
|
|
74
74
|
let t = new Date(e.date);
|
|
75
75
|
if (Number.isNaN(t.getTime())) continue;
|
|
76
|
-
let n =
|
|
76
|
+
let n = x(t).getTime();
|
|
77
77
|
V.includes(n) || V.push(n);
|
|
78
78
|
}
|
|
79
|
-
let H = V.slice(-6), fe = H.map((e) =>
|
|
79
|
+
let H = V.slice(-6), fe = H.map((e) => S(new Date(e).toISOString())), U = y.map(() => H.map(() => null));
|
|
80
80
|
for (let e of F) {
|
|
81
81
|
let t = new Date(e.date);
|
|
82
82
|
if (Number.isNaN(t.getTime())) continue;
|
|
83
|
-
let n = H.indexOf(
|
|
84
|
-
n < 0 || (U[
|
|
83
|
+
let n = H.indexOf(x(t).getTime());
|
|
84
|
+
n < 0 || (U[b(t)][n] = e.focusHours);
|
|
85
85
|
}
|
|
86
86
|
let W = F.map((e) => ({
|
|
87
|
-
date:
|
|
88
|
-
energy: e.energyLevel == null ? null :
|
|
87
|
+
date: S(e.date),
|
|
88
|
+
energy: e.energyLevel == null ? null : C[e.energyLevel] ?? null,
|
|
89
89
|
mood: e.mood == null ? null : ie[e.mood] ?? null
|
|
90
|
-
})), pe = W.some((e) => e.energy != null || e.mood != null), G = N.slice(0, 7), K = G.length, q = G.filter((e) => e.stressLevel != null && ae.has(e.stressLevel)).length, J = G.filter((e) => e.energyLevel != null && oe.has(e.energyLevel)).length, Y = P.find((e) => e.type ===
|
|
91
|
-
K > 0 && q >= X ? Z = "danger" : (q > 0 || J >= X || Y) && (Z = "warning"), Y?.type ===
|
|
90
|
+
})), pe = W.some((e) => e.energy != null || e.mood != null), G = N.slice(0, 7), K = G.length, q = G.filter((e) => e.stressLevel != null && ae.has(e.stressLevel)).length, J = G.filter((e) => e.energyLevel != null && oe.has(e.energyLevel)).length, Y = P.find((e) => e.type === l.Warning || e.type === l.Negative), X = Math.ceil(Math.max(K, 1) / 2), Z = "success";
|
|
91
|
+
K > 0 && q >= X ? Z = "danger" : (q > 0 || J >= X || Y) && (Z = "warning"), Y?.type === l.Negative && (Z = "danger");
|
|
92
92
|
let Q = [];
|
|
93
93
|
q > 0 && Q.push(`${q} of your last ${K} logged day${K === 1 ? "" : "s"} showed high stress`), J > 0 && Q.push(`${J} showed low energy`);
|
|
94
94
|
let me = Y?.title ?? (Z === "danger" ? "Sustained overload detected" : Z === "warning" ? "Watch your workload" : "Balanced workload"), he = Y?.description ?? (Z === "success" ? "Your recent stress and energy levels look healthy — keep protecting your focus blocks." : Q.length > 0 ? `${Q.join(" and ")}. Protect your deep-work time before it erodes.` : "Recent signals suggest an uneven balance — schedule focus time to stay ahead of overload."), $ = Y?.recommendation ?? null, ge = Z === "danger" ? "bg-status-error-bg text-status-error-text" : Z === "warning" ? "bg-status-warning-bg text-status-warning-text" : "bg-status-success-bg text-status-success-text";
|
|
95
|
-
return A ? /* @__PURE__ */
|
|
95
|
+
return A ? /* @__PURE__ */ g(a, {
|
|
96
96
|
title: "Work Patterns",
|
|
97
97
|
description: "Analyze your work habits and productivity patterns",
|
|
98
|
-
children: /* @__PURE__ */
|
|
98
|
+
children: /* @__PURE__ */ g(n, {
|
|
99
99
|
variant: "card",
|
|
100
100
|
count: 4
|
|
101
101
|
})
|
|
102
|
-
}) : j ? /* @__PURE__ */
|
|
102
|
+
}) : j ? /* @__PURE__ */ g(a, {
|
|
103
103
|
title: "Work Patterns",
|
|
104
104
|
description: "Analyze your work habits and productivity patterns",
|
|
105
|
-
children: /* @__PURE__ */
|
|
105
|
+
children: /* @__PURE__ */ g("div", {
|
|
106
106
|
className: "rounded-xl border border-status-error-border/50 bg-status-error-bg/10 p-4 text-sm text-status-error-text",
|
|
107
107
|
children: "Failed to load work patterns. Please try again later."
|
|
108
108
|
})
|
|
109
|
-
}) : /* @__PURE__ */
|
|
109
|
+
}) : /* @__PURE__ */ g(a, {
|
|
110
110
|
title: "Work Patterns",
|
|
111
111
|
description: "Analyze your work habits and productivity patterns",
|
|
112
112
|
purpose: "Learn how you actually work across a typical week — when you focus best, where meetings pile up, and which day is your peak — so you can schedule deep work for the hours you're sharpest.",
|
|
113
|
-
children: /* @__PURE__ */
|
|
113
|
+
children: /* @__PURE__ */ _("div", {
|
|
114
114
|
className: "space-y-6",
|
|
115
115
|
children: [
|
|
116
|
-
/* @__PURE__ */
|
|
116
|
+
/* @__PURE__ */ _("div", {
|
|
117
117
|
className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4",
|
|
118
118
|
children: [
|
|
119
|
-
/* @__PURE__ */
|
|
119
|
+
/* @__PURE__ */ g(t, {
|
|
120
120
|
featured: !0,
|
|
121
121
|
title: "Productivity Score",
|
|
122
122
|
value: L,
|
|
123
|
-
icon: /* @__PURE__ */
|
|
123
|
+
icon: /* @__PURE__ */ g(ne, { className: "h-5 w-5" }),
|
|
124
124
|
trend: M?.averageProductivity == null ? void 0 : {
|
|
125
125
|
value: 5,
|
|
126
126
|
label: "vs last week"
|
|
127
127
|
}
|
|
128
128
|
}),
|
|
129
|
-
/* @__PURE__ */
|
|
129
|
+
/* @__PURE__ */ g(t, {
|
|
130
130
|
title: "Focus Hours",
|
|
131
131
|
value: R,
|
|
132
|
-
icon: /* @__PURE__ */
|
|
132
|
+
icon: /* @__PURE__ */ g(f, { className: "h-5 w-5" }),
|
|
133
133
|
description: `Out of ${z}h total`
|
|
134
134
|
}),
|
|
135
|
-
/* @__PURE__ */
|
|
135
|
+
/* @__PURE__ */ g(t, {
|
|
136
136
|
title: "Peak Day",
|
|
137
137
|
value: de,
|
|
138
|
-
icon: /* @__PURE__ */
|
|
138
|
+
icon: /* @__PURE__ */ g(re, { className: "h-5 w-5" }),
|
|
139
139
|
description: "Most productive day"
|
|
140
140
|
}),
|
|
141
|
-
/* @__PURE__ */
|
|
141
|
+
/* @__PURE__ */ g(t, {
|
|
142
142
|
title: "Deep Work",
|
|
143
143
|
value: `${B} sessions`,
|
|
144
|
-
icon: /* @__PURE__ */
|
|
144
|
+
icon: /* @__PURE__ */ g(p, { className: "h-5 w-5" }),
|
|
145
145
|
description: "90+ min uninterrupted"
|
|
146
146
|
})
|
|
147
147
|
]
|
|
148
148
|
}),
|
|
149
|
-
/* @__PURE__ */
|
|
149
|
+
/* @__PURE__ */ g(v, {
|
|
150
150
|
treatment: "glass",
|
|
151
151
|
glow: Z !== "success",
|
|
152
152
|
className: "p-5 sm:p-6",
|
|
153
|
-
children: /* @__PURE__ */
|
|
153
|
+
children: /* @__PURE__ */ _("div", {
|
|
154
154
|
className: "flex flex-col gap-4 sm:flex-row sm:items-start",
|
|
155
|
-
children: [/* @__PURE__ */
|
|
155
|
+
children: [/* @__PURE__ */ g("div", {
|
|
156
156
|
className: `flex h-11 w-11 shrink-0 items-center justify-center rounded-xl ${ge}`,
|
|
157
|
-
children:
|
|
158
|
-
}), /* @__PURE__ */
|
|
157
|
+
children: g(Z === "success" ? h : u, { className: "h-5 w-5" })
|
|
158
|
+
}), /* @__PURE__ */ _("div", {
|
|
159
159
|
className: "min-w-0 flex-1",
|
|
160
160
|
children: [
|
|
161
|
-
/* @__PURE__ */
|
|
161
|
+
/* @__PURE__ */ _("div", {
|
|
162
162
|
className: "flex flex-wrap items-center gap-2",
|
|
163
|
-
children: [/* @__PURE__ */
|
|
163
|
+
children: [/* @__PURE__ */ g("h3", {
|
|
164
164
|
className: "text-base font-semibold text-text-primary",
|
|
165
165
|
children: "Burnout Signal"
|
|
166
|
-
}), /* @__PURE__ */
|
|
166
|
+
}), /* @__PURE__ */ g(o, {
|
|
167
167
|
variant: Z === "danger" ? "error" : Z === "warning" ? "warning" : "success",
|
|
168
168
|
label: me
|
|
169
169
|
})]
|
|
170
170
|
}),
|
|
171
|
-
/* @__PURE__ */
|
|
171
|
+
/* @__PURE__ */ g("p", {
|
|
172
172
|
className: "mt-1.5 text-sm text-text-secondary",
|
|
173
173
|
children: he
|
|
174
174
|
}),
|
|
175
|
-
$ && /* @__PURE__ */
|
|
175
|
+
$ && /* @__PURE__ */ _("p", {
|
|
176
176
|
className: "mt-2 flex items-start gap-1.5 text-sm text-text-primary",
|
|
177
|
-
children: [/* @__PURE__ */
|
|
177
|
+
children: [/* @__PURE__ */ g(m, { className: "mt-0.5 h-4 w-4 shrink-0 text-primary" }), /* @__PURE__ */ g("span", { children: $ })]
|
|
178
178
|
})
|
|
179
179
|
]
|
|
180
180
|
})]
|
|
181
181
|
})
|
|
182
182
|
}),
|
|
183
|
-
/* @__PURE__ */
|
|
183
|
+
/* @__PURE__ */ _("div", {
|
|
184
184
|
className: "grid grid-cols-1 gap-6 lg:grid-cols-2",
|
|
185
|
-
children: [/* @__PURE__ */
|
|
185
|
+
children: [/* @__PURE__ */ g(s, {
|
|
186
186
|
title: "Weekly Productive Hours",
|
|
187
187
|
description: "Focused hours logged by weekday across recent weeks",
|
|
188
|
-
children: /* @__PURE__ */
|
|
188
|
+
children: /* @__PURE__ */ g(c, {
|
|
189
189
|
data: U,
|
|
190
|
-
rowLabels: [...
|
|
190
|
+
rowLabels: [...y],
|
|
191
191
|
columnLabels: fe,
|
|
192
192
|
rowsLabel: "Day",
|
|
193
193
|
valueFormat: se,
|
|
@@ -196,10 +196,10 @@ function w() {
|
|
|
196
196
|
emptyLabel: "No work-pattern days logged yet.",
|
|
197
197
|
ariaLabel: "Focused hours by weekday and week"
|
|
198
198
|
})
|
|
199
|
-
}), /* @__PURE__ */
|
|
199
|
+
}), /* @__PURE__ */ g(s, {
|
|
200
200
|
title: "Energy & Mood Trends",
|
|
201
201
|
description: "Self-reported energy and mood, each on a 1 (low) – 5 (high) scale",
|
|
202
|
-
children: pe ? /* @__PURE__ */
|
|
202
|
+
children: pe ? /* @__PURE__ */ g(te, {
|
|
203
203
|
data: W,
|
|
204
204
|
xKey: "date",
|
|
205
205
|
series: [{
|
|
@@ -213,40 +213,40 @@ function w() {
|
|
|
213
213
|
height: 240,
|
|
214
214
|
emptyLabel: "No energy or mood check-ins yet.",
|
|
215
215
|
ariaLabel: "Energy and mood over time"
|
|
216
|
-
}) : /* @__PURE__ */
|
|
216
|
+
}) : /* @__PURE__ */ g(r, {
|
|
217
217
|
title: "No energy or mood data yet",
|
|
218
218
|
description: "Log how you feel on your work-pattern entries to see the trend here."
|
|
219
219
|
})
|
|
220
220
|
})]
|
|
221
221
|
}),
|
|
222
|
-
P.length > 0 && /* @__PURE__ */ v
|
|
222
|
+
P.length > 0 && /* @__PURE__ */ _(v, {
|
|
223
223
|
treatment: "glass",
|
|
224
224
|
className: "p-5 sm:p-6",
|
|
225
|
-
children: [/* @__PURE__ */
|
|
225
|
+
children: [/* @__PURE__ */ _("h3", {
|
|
226
226
|
className: "mb-4 flex items-center gap-2 text-sm font-semibold text-text-primary",
|
|
227
|
-
children: [/* @__PURE__ */
|
|
228
|
-
}), /* @__PURE__ */
|
|
227
|
+
children: [/* @__PURE__ */ g(m, { className: "h-4 w-4 text-primary" }), "Insights"]
|
|
228
|
+
}), /* @__PURE__ */ g("div", {
|
|
229
229
|
className: "space-y-3",
|
|
230
|
-
children: P.map((e, t) => /* @__PURE__ */
|
|
230
|
+
children: P.map((e, t) => /* @__PURE__ */ _("div", {
|
|
231
231
|
className: "rounded-lg border border-border-subtle bg-bg-surface/40 p-4",
|
|
232
232
|
children: [
|
|
233
|
-
/* @__PURE__ */
|
|
233
|
+
/* @__PURE__ */ _("div", {
|
|
234
234
|
className: "flex flex-wrap items-center gap-2",
|
|
235
|
-
children: [/* @__PURE__ */
|
|
235
|
+
children: [/* @__PURE__ */ g(o, {
|
|
236
236
|
variant: ce[e.type],
|
|
237
237
|
label: le[e.type]
|
|
238
|
-
}), /* @__PURE__ */
|
|
238
|
+
}), /* @__PURE__ */ g("p", {
|
|
239
239
|
className: "text-sm font-medium text-text-primary",
|
|
240
240
|
children: e.title
|
|
241
241
|
})]
|
|
242
242
|
}),
|
|
243
|
-
/* @__PURE__ */
|
|
243
|
+
/* @__PURE__ */ g("p", {
|
|
244
244
|
className: "mt-1.5 text-sm text-text-secondary",
|
|
245
245
|
children: e.description
|
|
246
246
|
}),
|
|
247
|
-
e.recommendation && /* @__PURE__ */
|
|
247
|
+
e.recommendation && /* @__PURE__ */ _("p", {
|
|
248
248
|
className: "mt-2 flex items-start gap-1.5 text-xs text-text-primary",
|
|
249
|
-
children: [/* @__PURE__ */
|
|
249
|
+
children: [/* @__PURE__ */ g(d, { className: "mt-0.5 h-3.5 w-3.5 shrink-0 text-primary" }), /* @__PURE__ */ g("span", { children: e.recommendation })]
|
|
250
250
|
})
|
|
251
251
|
]
|
|
252
252
|
}, `${e.type}-${t}`))
|