@burdenoff/microfe-timecampus 2026.703.1 → 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.
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 +366 -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 +461 -87
  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 +156 -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 +315 -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 +1 @@
1
- {"version":3,"file":"ApplicationUsagePage.js","names":[],"sources":["../../../../src/timecampus/pages/application-usage/ApplicationUsagePage.tsx"],"sourcesContent":["import { Monitor, Clock } from \"lucide-react\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { StatCard } from \"../../components/StatCard\";\nimport { EmptyState } from \"../../components/EmptyState\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport { formatHours, getStatusColor } from \"../../utils/helpers\";\nimport { useTimeCampus } from \"../../providers/TimeCampusProvider\";\nimport {\n useListApplicationUsagesQuery,\n useGetAppUsageSummaryQuery,\n type ApplicationUsageFieldsFragment,\n} from \"@/generated/wspace-operations\";\n\nconst categoryColors: Record<string, string> = {\n productive: \"bg-status-success-bg text-status-success-text\",\n neutral: \"bg-bg-sunken text-text-secondary\",\n communication: \"bg-status-info-bg text-status-info-text\",\n distracting: \"bg-status-error-bg text-status-error-text\",\n};\n\nexport function ApplicationUsagePage() {\n const { workspaceId = \"\" } = useTimeCampus();\n\n const {\n data: usagesData,\n loading: usagesLoading,\n error: usagesError,\n } = useListApplicationUsagesQuery({\n variables: { limit: 50 },\n skip: !workspaceId,\n });\n\n const {\n data: summaryData,\n loading: summaryLoading,\n error: summaryError,\n } = useGetAppUsageSummaryQuery({\n skip: !workspaceId,\n });\n\n const loading = usagesLoading || summaryLoading;\n const error = usagesError || summaryError;\n const apps = usagesData?.applicationUsages ?? [];\n const summary = summaryData?.appUsageSummary;\n\n const totalScreenTimeSeconds = apps.reduce(\n (sum: number, app: ApplicationUsageFieldsFragment) => sum + app.timeSpent,\n 0,\n );\n const totalSwitches = apps.reduce(\n (sum: number, app: ApplicationUsageFieldsFragment) => sum + app.switches,\n 0,\n );\n const productiveRatio =\n summary?.productivityRatio != null\n ? `${Math.round(summary.productivityRatio * 100)}%`\n : \"--\";\n\n // Count focus sessions (apps with > 90 min continuous usage)\n const focusSessions = apps.filter(\n (app: ApplicationUsageFieldsFragment) => app.timeSpent >= 5400,\n ).length;\n\n if (loading) {\n return (\n <PageLayout\n title=\"App Usage\"\n description=\"Track application usage and productivity\"\n >\n <LoadingSkeleton variant=\"card\" count={4} />\n </PageLayout>\n );\n }\n\n if (error) {\n return (\n <PageLayout\n title=\"App Usage\"\n description=\"Track application usage and productivity\"\n >\n <div className=\"rounded-xl border border-status-error-border/50 bg-status-error-bg/10 p-4 text-sm text-status-error-text\">\n Failed to load application usage. Please try again later.\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title=\"App Usage\"\n description=\"Track application usage and productivity\"\n purpose=\"See where your screen time actually goes — which apps are productive, how often you context-switch, and how many real focus sessions you get — so you can cut the digital distractions eating your day.\"\n >\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <StatCard\n featured\n title=\"Total Screen Time\"\n value={formatHours(totalScreenTimeSeconds / 3600)}\n icon={<Monitor className=\"h-5 w-5\" />}\n />\n <StatCard\n title=\"Productive Time\"\n value={productiveRatio}\n trend={\n summary?.productivityRatio != null\n ? { value: 5, label: \"vs last week\" }\n : undefined\n }\n />\n <StatCard\n title=\"App Switches\"\n value={String(totalSwitches)}\n description={`Avg. ${apps.length > 0 ? Math.round(totalSwitches / apps.length) : 0}/app`}\n />\n <StatCard\n title=\"Focus Sessions\"\n value={String(focusSessions)}\n icon={<Clock className=\"h-5 w-5\" />}\n description=\"90+ min each\"\n />\n </div>\n\n {apps.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n title=\"No application usage data found\"\n description=\"Once the desktop tracker is active, the apps you use will appear here ranked by time spent.\"\n />\n ) : (\n <div className=\"rounded-xl border border-border-seam bg-gloss-card shadow-pop\">\n <div className=\"divide-y divide-border\">\n {apps.map((app: ApplicationUsageFieldsFragment) => (\n <div\n key={app.id}\n className=\"flex items-center justify-between p-4 hover:bg-bg-sunken/50 transition-colors\"\n >\n <div className=\"flex items-center gap-3\">\n <Monitor className=\"h-5 w-5 text-text-secondary\" />\n <div>\n <p className=\"text-sm font-medium text-text-primary\">\n {app.applicationName}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {app.sessions} sessions\n </p>\n </div>\n </div>\n <div className=\"flex items-center gap-4\">\n <div className=\"text-right\">\n <p className=\"text-sm font-mono font-medium text-text-primary\">\n {formatHours(app.timeSpent / 3600)}\n </p>\n </div>\n <span\n className={`inline-flex rounded-full px-2 py-0.5 text-xs font-medium ${categoryColors[app.category] || \"\"}`}\n >\n {app.category}\n </span>\n <div className=\"w-16\">\n <div className=\"h-2 rounded-full bg-bg-sunken\">\n <div\n className=\"h-2 rounded-full bg-action-primary-bg\"\n style={{\n width: `${Math.min(100, Math.round((app.timeSpent / (totalScreenTimeSeconds || 1)) * 100))}%`,\n }}\n />\n </div>\n </div>\n </div>\n </div>\n ))}\n </div>\n </div>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ApplicationUsagePage.js","names":[],"sources":["../../../../src/timecampus/pages/application-usage/ApplicationUsagePage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { Monitor, Clock, Plus, Tag, Trash2 } from \"lucide-react\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { StatCard } from \"../../components/StatCard\";\nimport { EmptyState } from \"../../components/EmptyState\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport { Modal } from \"../../components/Modal\";\nimport { formatHours } from \"../../utils/helpers\";\nimport { useTimeCampus } from \"../../providers/TimeCampusProvider\";\nimport {\n useListApplicationUsagesQuery,\n useGetAppUsageSummaryQuery,\n useRecordApplicationUsageMutation,\n useCategorizeApplicationMutation,\n useDeleteApplicationUsageMutation,\n type ApplicationUsageFieldsFragment,\n} from \"@/generated/wspace-operations\";\n\nconst PAGE_SIZE = 50;\n\nconst CATEGORY_OPTIONS = [\n \"productive\",\n \"neutral\",\n \"communication\",\n \"distracting\",\n] as const;\n\nconst categoryColors: Record<string, string> = {\n productive: \"bg-status-success-bg text-status-success-text\",\n neutral: \"bg-bg-sunken text-text-secondary\",\n communication: \"bg-status-info-bg text-status-info-text\",\n distracting: \"bg-status-error-bg text-status-error-text\",\n};\n\nconst inputClasses =\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\";\nconst labelClasses = \"block text-sm font-medium text-text-primary\";\n\nfunction todayIso(): string {\n return new Date().toISOString();\n}\n\nexport function ApplicationUsagePage() {\n const { workspaceId = \"\" } = useTimeCampus();\n\n const {\n data: usagesData,\n loading: usagesLoading,\n error: usagesError,\n fetchMore,\n refetch: refetchUsages,\n } = useListApplicationUsagesQuery({\n variables: { limit: PAGE_SIZE, offset: 0 },\n skip: !workspaceId,\n notifyOnNetworkStatusChange: true,\n });\n\n const {\n data: summaryData,\n loading: summaryLoading,\n error: summaryError,\n refetch: refetchSummary,\n } = useGetAppUsageSummaryQuery({\n skip: !workspaceId,\n });\n\n const loading = usagesLoading || summaryLoading;\n const error = usagesError || summaryError;\n const apps = usagesData?.applicationUsages ?? [];\n const summary = summaryData?.appUsageSummary;\n const hasMore = apps.length > 0 && apps.length % PAGE_SIZE === 0;\n\n const [loadingMore, setLoadingMore] = useState(false);\n\n // Add-usage modal\n const [addOpen, setAddOpen] = useState(false);\n const [appName, setAppName] = useState(\"\");\n const [appCategory, setAppCategory] = useState<string>(\"productive\");\n const [appMinutes, setAppMinutes] = useState(\"\");\n const [appSessions, setAppSessions] = useState(\"\");\n\n // Recategorize + delete\n const [recatApp, setRecatApp] =\n useState<ApplicationUsageFieldsFragment | null>(null);\n const [recatCategory, setRecatCategory] = useState<string>(\"productive\");\n const [deleteApp, setDeleteApp] =\n useState<ApplicationUsageFieldsFragment | null>(null);\n\n const [recordUsage, { loading: recording, error: recordError }] =\n useRecordApplicationUsageMutation();\n const [categorize, { loading: categorizing, error: categorizeError }] =\n useCategorizeApplicationMutation();\n const [removeUsage, { loading: deleting, error: deleteError }] =\n useDeleteApplicationUsageMutation();\n\n const refreshAll = async () => {\n await Promise.all([refetchUsages(), refetchSummary()]);\n };\n\n const openAdd = () => {\n setAppName(\"\");\n setAppCategory(\"productive\");\n setAppMinutes(\"\");\n setAppSessions(\"\");\n setAddOpen(true);\n };\n\n const handleRecord = async () => {\n if (!appName.trim() || !appMinutes) return;\n await recordUsage({\n variables: {\n input: {\n applicationName: appName.trim(),\n category: appCategory,\n timeSpent: Math.round(Number(appMinutes) * 60),\n sessions: appSessions ? Number(appSessions) : undefined,\n date: todayIso(),\n },\n },\n });\n setAddOpen(false);\n await refreshAll();\n };\n\n const openRecat = (app: ApplicationUsageFieldsFragment) => {\n setRecatApp(app);\n setRecatCategory(app.category);\n };\n\n const handleRecategorize = async () => {\n if (!recatApp) return;\n await categorize({\n variables: {\n applicationName: recatApp.applicationName,\n category: recatCategory,\n },\n });\n setRecatApp(null);\n await refreshAll();\n };\n\n const handleDelete = async () => {\n if (!deleteApp) return;\n await removeUsage({ variables: { id: deleteApp.id } });\n setDeleteApp(null);\n await refreshAll();\n };\n\n const handleLoadMore = async () => {\n setLoadingMore(true);\n try {\n await fetchMore({\n variables: { offset: apps.length },\n updateQuery: (prev, { fetchMoreResult }) => {\n if (!fetchMoreResult) return prev;\n return {\n ...prev,\n applicationUsages: [\n ...prev.applicationUsages,\n ...fetchMoreResult.applicationUsages,\n ],\n };\n },\n });\n } finally {\n setLoadingMore(false);\n }\n };\n\n const totalScreenTimeSeconds = apps.reduce(\n (sum: number, app: ApplicationUsageFieldsFragment) => sum + app.timeSpent,\n 0,\n );\n const totalSwitches = apps.reduce(\n (sum: number, app: ApplicationUsageFieldsFragment) => sum + app.switches,\n 0,\n );\n const productiveRatio =\n summary?.productivityRatio != null\n ? `${Math.round(summary.productivityRatio * 100)}%`\n : \"--\";\n\n // Count focus sessions (apps with > 90 min continuous usage)\n const focusSessions = apps.filter(\n (app: ApplicationUsageFieldsFragment) => app.timeSpent >= 5400,\n ).length;\n\n if (loading) {\n return (\n <PageLayout\n title=\"App Usage\"\n description=\"Track application usage and productivity\"\n >\n <LoadingSkeleton variant=\"card\" count={4} />\n </PageLayout>\n );\n }\n\n if (error) {\n return (\n <PageLayout\n title=\"App Usage\"\n description=\"Track application usage and productivity\"\n >\n <div className=\"rounded-xl border border-status-error-border/50 bg-status-error-bg/10 p-4 text-sm text-status-error-text\">\n Failed to load application usage. Please try again later.\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title=\"App Usage\"\n description=\"Track application usage and productivity\"\n purpose=\"See where your screen time actually goes — which apps are productive, how often you context-switch, and how many real focus sessions you get — so you can cut the digital distractions eating your day.\"\n actions={\n <button\n onClick={openAdd}\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 Usage\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=\"Total Screen Time\"\n value={formatHours(totalScreenTimeSeconds / 3600)}\n icon={<Monitor className=\"h-5 w-5\" />}\n />\n <StatCard\n title=\"Productive Time\"\n value={productiveRatio}\n trend={\n summary?.productivityRatio != null\n ? { value: 5, label: \"vs last week\" }\n : undefined\n }\n />\n <StatCard\n title=\"App Switches\"\n value={String(totalSwitches)}\n description={`Avg. ${apps.length > 0 ? Math.round(totalSwitches / apps.length) : 0}/app`}\n />\n <StatCard\n title=\"Focus Sessions\"\n value={String(focusSessions)}\n icon={<Clock className=\"h-5 w-5\" />}\n description=\"90+ min each\"\n />\n </div>\n\n {apps.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n title=\"No application usage data found\"\n description=\"Record app usage manually or connect the desktop tracker, and your apps will appear here ranked by time spent.\"\n action={\n <button\n onClick={openAdd}\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 Usage\n </button>\n }\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 {apps.map((app: ApplicationUsageFieldsFragment) => (\n <div\n key={app.id}\n className=\"flex items-center justify-between gap-3 p-4 transition-colors hover:bg-bg-sunken/50\"\n >\n <div className=\"flex min-w-0 items-center gap-3\">\n <Monitor className=\"h-5 w-5 shrink-0 text-text-secondary\" />\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {app.applicationName}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {app.sessions} sessions\n </p>\n </div>\n </div>\n <div className=\"flex shrink-0 items-center gap-3 sm:gap-4\">\n <p className=\"text-sm font-mono font-medium text-text-primary\">\n {formatHours(app.timeSpent / 3600)}\n </p>\n <span\n className={`hidden rounded-full px-2 py-0.5 text-xs font-medium sm:inline-flex ${categoryColors[app.category] || \"bg-bg-sunken text-text-secondary\"}`}\n >\n {app.category}\n </span>\n <div className=\"hidden w-16 lg:block\">\n <div className=\"h-2 rounded-full bg-bg-sunken\">\n <div\n className=\"h-2 rounded-full bg-action-primary-bg\"\n style={{\n width: `${Math.min(100, Math.round((app.timeSpent / (totalScreenTimeSeconds || 1)) * 100))}%`,\n }}\n />\n </div>\n </div>\n <button\n onClick={() => openRecat(app)}\n className=\"rounded-md p-1.5 text-text-secondary hover:bg-bg-sunken hover:text-text-primary\"\n aria-label=\"Recategorize application\"\n >\n <Tag className=\"h-4 w-4\" />\n </button>\n <button\n onClick={() => setDeleteApp(app)}\n className=\"rounded-md p-1.5 text-text-secondary hover:bg-status-error-bg hover:text-status-error-text\"\n aria-label=\"Delete usage entry\"\n >\n <Trash2 className=\"h-4 w-4\" />\n </button>\n </div>\n </div>\n ))}\n </div>\n </div>\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 {/* Add usage modal */}\n <Modal\n open={addOpen}\n onClose={() => setAddOpen(false)}\n title=\"Record App Usage\"\n description=\"Manually log time spent in an application\"\n footer={\n <>\n <button\n onClick={() => setAddOpen(false)}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n Cancel\n </button>\n <button\n onClick={handleRecord}\n disabled={recording || !appName.trim() || !appMinutes}\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 {recording ? \"Saving...\" : \"Record\"}\n </button>\n </>\n }\n >\n {recordError && (\n <div className=\"mb-4 rounded-lg border border-status-error-bg/40 bg-status-error-bg p-3 text-sm text-status-error-text\">\n Failed to record usage. Please try again.\n </div>\n )}\n <div className=\"space-y-4\">\n <div>\n <label className={labelClasses}>Application Name</label>\n <input\n type=\"text\"\n placeholder=\"e.g. Visual Studio Code\"\n value={appName}\n onChange={(e) => setAppName(e.target.value)}\n className={inputClasses}\n />\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label className={labelClasses}>Category</label>\n <select\n value={appCategory}\n onChange={(e) => setAppCategory(e.target.value)}\n className={inputClasses}\n >\n {CATEGORY_OPTIONS.map((c) => (\n <option key={c} value={c} className=\"capitalize\">\n {c}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label className={labelClasses}>Time Spent (minutes)</label>\n <input\n type=\"number\"\n min=\"0\"\n placeholder=\"0\"\n value={appMinutes}\n onChange={(e) => setAppMinutes(e.target.value)}\n className={inputClasses}\n />\n </div>\n </div>\n <div>\n <label className={labelClasses}>Sessions</label>\n <input\n type=\"number\"\n min=\"0\"\n placeholder=\"0\"\n value={appSessions}\n onChange={(e) => setAppSessions(e.target.value)}\n className={inputClasses}\n />\n </div>\n </div>\n </Modal>\n\n {/* Recategorize modal */}\n <Modal\n open={recatApp !== null}\n onClose={() => setRecatApp(null)}\n title=\"Recategorize Application\"\n description={recatApp?.applicationName}\n size=\"sm\"\n footer={\n <>\n <button\n onClick={() => setRecatApp(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={handleRecategorize}\n disabled={categorizing}\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 {categorizing ? \"Saving...\" : \"Save\"}\n </button>\n </>\n }\n >\n {categorizeError && (\n <div className=\"mb-4 rounded-lg border border-status-error-bg/40 bg-status-error-bg p-3 text-sm text-status-error-text\">\n Failed to recategorize. Please try again.\n </div>\n )}\n <label className={labelClasses}>Category</label>\n <select\n value={recatCategory}\n onChange={(e) => setRecatCategory(e.target.value)}\n className={inputClasses}\n >\n {CATEGORY_OPTIONS.map((c) => (\n <option key={c} value={c} className=\"capitalize\">\n {c}\n </option>\n ))}\n </select>\n <p className=\"mt-3 text-xs text-text-secondary\">\n This updates the category for all usage of &ldquo;\n {recatApp?.applicationName}&rdquo;.\n </p>\n </Modal>\n\n {/* Delete confirm modal */}\n <Modal\n open={deleteApp !== null}\n onClose={() => setDeleteApp(null)}\n title=\"Delete Usage Entry\"\n size=\"sm\"\n footer={\n <>\n <button\n onClick={() => setDeleteApp(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/90 disabled:opacity-50\"\n >\n {deleting ? \"Deleting...\" : \"Delete\"}\n </button>\n </>\n }\n >\n {deleteError && (\n <div className=\"mb-4 rounded-lg border border-status-error-bg/40 bg-status-error-bg p-3 text-sm text-status-error-text\">\n Failed to delete entry. Please try again.\n </div>\n )}\n <p className=\"text-sm text-text-secondary\">\n Are you sure you want to delete the usage entry for{\" \"}\n <span className=\"font-medium text-text-primary\">\n {deleteApp?.applicationName}\n </span>\n ? This action cannot be undone.\n </p>\n </Modal>\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;;AAkBA,IAAM,IAAY,IAEZ,IAAmB;CACvB;CACA;CACA;CACA;CACD,EAEK,KAAyC;CAC7C,YAAY;CACZ,SAAS;CACT,eAAe;CACf,aAAa;CACd,EAEK,IACJ,wIACI,IAAe;AAErB,SAAS,KAAmB;AAC1B,yBAAO,IAAI,MAAM,EAAC,aAAa;;AAGjC,SAAgB,IAAuB;CACrC,IAAM,EAAE,iBAAc,OAAO,GAAe,EAEtC,EACJ,MAAM,GACN,SAAS,GACT,OAAO,GACP,cACA,SAAS,MACP,EAA8B;EAChC,WAAW;GAAE,OAAO;GAAW,QAAQ;GAAG;EAC1C,MAAM,CAAC;EACP,6BAA6B;EAC9B,CAAC,EAEI,EACJ,MAAM,GACN,SAAS,IACT,OAAO,IACP,SAAS,OACP,EAA2B,EAC7B,MAAM,CAAC,GACR,CAAC,EAEI,KAAU,KAAiB,IAC3B,KAAQ,KAAe,IACvB,IAAO,GAAY,qBAAqB,EAAE,EAC1C,IAAU,GAAa,iBACvB,KAAU,EAAK,SAAS,KAAK,EAAK,SAAS,MAAc,GAEzD,CAAC,GAAa,KAAkB,EAAS,GAAM,EAG/C,CAAC,GAAS,KAAc,EAAS,GAAM,EACvC,CAAC,GAAS,KAAc,EAAS,GAAG,EACpC,CAAC,GAAa,KAAkB,EAAiB,aAAa,EAC9D,CAAC,GAAY,KAAiB,EAAS,GAAG,EAC1C,CAAC,GAAa,KAAkB,EAAS,GAAG,EAG5C,CAAC,GAAU,KACf,EAAgD,KAAK,EACjD,CAAC,GAAe,KAAoB,EAAiB,aAAa,EAClE,CAAC,GAAW,KAChB,EAAgD,KAAK,EAEjD,CAAC,IAAa,EAAE,SAAS,GAAW,OAAO,QAC/C,IAAmC,EAC/B,CAAC,IAAY,EAAE,SAAS,GAAc,OAAO,QACjD,IAAkC,EAC9B,CAAC,IAAa,EAAE,SAAS,GAAU,OAAO,QAC9C,IAAmC,EAE/B,IAAa,YAAY;AAC7B,QAAM,QAAQ,IAAI,CAAC,GAAe,EAAE,IAAgB,CAAC,CAAC;IAGlD,UAAgB;AAKpB,EAJA,EAAW,GAAG,EACd,EAAe,aAAa,EAC5B,EAAc,GAAG,EACjB,EAAe,GAAG,EAClB,EAAW,GAAK;IAGZ,KAAe,YAAY;AAC3B,GAAC,EAAQ,MAAM,IAAI,CAAC,MACxB,MAAM,GAAY,EAChB,WAAW,EACT,OAAO;GACL,iBAAiB,EAAQ,MAAM;GAC/B,UAAU;GACV,WAAW,KAAK,MAAM,OAAO,EAAW,GAAG,GAAG;GAC9C,UAAU,IAAc,OAAO,EAAY,GAAG,KAAA;GAC9C,MAAM,IAAU;GACjB,EACF,EACF,CAAC,EACF,EAAW,GAAM,EACjB,MAAM,GAAY;IAGd,MAAa,MAAwC;AAEzD,EADA,EAAY,EAAI,EAChB,EAAiB,EAAI,SAAS;IAG1B,KAAqB,YAAY;AAChC,QACL,MAAM,GAAW,EACf,WAAW;GACT,iBAAiB,EAAS;GAC1B,UAAU;GACX,EACF,CAAC,EACF,EAAY,KAAK,EACjB,MAAM,GAAY;IAGd,KAAe,YAAY;AAC1B,QACL,MAAM,GAAY,EAAE,WAAW,EAAE,IAAI,EAAU,IAAI,EAAE,CAAC,EACtD,EAAa,KAAK,EAClB,MAAM,GAAY;IAGd,KAAiB,YAAY;AACjC,IAAe,GAAK;AACpB,MAAI;AACF,SAAM,EAAU;IACd,WAAW,EAAE,QAAQ,EAAK,QAAQ;IAClC,cAAc,GAAM,EAAE,yBACf,IACE;KACL,GAAG;KACH,mBAAmB,CACjB,GAAG,EAAK,mBACR,GAAG,EAAgB,kBACpB;KACF,GAP4B;IAShC,CAAC;YACM;AACR,KAAe,GAAM;;IAInB,IAAyB,EAAK,QACjC,GAAa,MAAwC,IAAM,EAAI,WAChE,EACD,EACK,IAAgB,EAAK,QACxB,GAAa,MAAwC,IAAM,EAAI,UAChE,EACD,EACK,KACJ,GAAS,qBAAqB,OAE1B,OADA,GAAG,KAAK,MAAM,EAAQ,oBAAoB,IAAI,CAAC,IAI/C,KAAgB,EAAK,QACxB,MAAwC,EAAI,aAAa,KAC3D,CAAC;AA0BF,QAxBI,KAEA,kBAAC,GAAD;EACE,OAAM;EACN,aAAY;YAEZ,kBAAC,GAAD;GAAiB,SAAQ;GAAO,OAAO;GAAK,CAAA;EACjC,CAAA,GAIb,KAEA,kBAAC,GAAD;EACE,OAAM;EACN,aAAY;YAEZ,kBAAC,OAAD;GAAK,WAAU;aAA2G;GAEpH,CAAA;EACK,CAAA,GAKf,kBAAC,GAAD;EACE,OAAM;EACN,aAAY;EACZ,SAAQ;EACR,SACE,kBAAC,UAAD;GACE,SAAS;GACT,WAAU;aAFZ,CAIE,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA,EAAA,YAErB;;YAXb;GAcE,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,GAAD;MACE,UAAA;MACA,OAAM;MACN,OAAO,EAAY,IAAyB,KAAK;MACjD,MAAM,kBAAC,GAAD,EAAS,WAAU,WAAY,CAAA;MACrC,CAAA;KACF,kBAAC,GAAD;MACE,OAAM;MACN,OAAO;MACP,OACE,GAAS,qBAAqB,OAE1B,KAAA,IADA;OAAE,OAAO;OAAG,OAAO;OAAgB;MAGzC,CAAA;KACF,kBAAC,GAAD;MACE,OAAM;MACN,OAAO,OAAO,EAAc;MAC5B,aAAa,QAAQ,EAAK,SAAS,IAAI,KAAK,MAAM,IAAgB,EAAK,OAAO,GAAG,EAAE;MACnF,CAAA;KACF,kBAAC,GAAD;MACE,OAAM;MACN,OAAO,OAAO,GAAc;MAC5B,MAAM,kBAAC,IAAD,EAAO,WAAU,WAAY,CAAA;MACnC,aAAY;MACZ,CAAA;KACE;;GAEL,EAAK,WAAW,IACf,kBAAC,IAAD;IACE,cAAa;IACb,OAAM;IACN,aAAY;IACZ,QACE,kBAAC,UAAD;KACE,SAAS;KACT,WAAU;eAFZ,CAIE,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA,EAAA,YAErB;;IAEX,CAAA,GAEF,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,OAAD;KAAK,WAAU;eACZ,EAAK,KAAK,MACT,kBAAC,OAAD;MAEE,WAAU;gBAFZ,CAIE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD,EAAS,WAAU,wCAAyC,CAAA,EAC5D,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,KAAD;SAAG,WAAU;mBACV,EAAI;SACH,CAAA,EACJ,kBAAC,KAAD;SAAG,WAAU;mBAAb,CACG,EAAI,UAAS,YACZ;WACA;UACF;UACN,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,KAAD;SAAG,WAAU;mBACV,EAAY,EAAI,YAAY,KAAK;SAChC,CAAA;QACJ,kBAAC,QAAD;SACE,WAAW,sEAAsE,GAAe,EAAI,aAAa;mBAEhH,EAAI;SACA,CAAA;QACP,kBAAC,OAAD;SAAK,WAAU;mBACb,kBAAC,OAAD;UAAK,WAAU;oBACb,kBAAC,OAAD;WACE,WAAU;WACV,OAAO,EACL,OAAO,GAAG,KAAK,IAAI,KAAK,KAAK,MAAO,EAAI,aAAa,KAA0B,KAAM,IAAI,CAAC,CAAC,IAC5F;WACD,CAAA;UACE,CAAA;SACF,CAAA;QACN,kBAAC,UAAD;SACE,eAAe,GAAU,EAAI;SAC7B,WAAU;SACV,cAAW;mBAEX,kBAAC,GAAD,EAAK,WAAU,WAAY,CAAA;SACpB,CAAA;QACT,kBAAC,UAAD;SACE,eAAe,EAAa,EAAI;SAChC,WAAU;SACV,cAAW;mBAEX,kBAAC,GAAD,EAAQ,WAAU,WAAY,CAAA;SACvB,CAAA;QACL;SACF;QAhDC,EAAI,GAgDL,CACN;KACE,CAAA;IACF,CAAA,EACL,MACC,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,UAAD;KACE,SAAS;KACT,UAAU;KACV,WAAU;eAET,IAAc,eAAe;KACvB,CAAA;IACL,CAAA,CAEP,EAAA,CAAA;GAIL,kBAAC,GAAD;IACE,MAAM;IACN,eAAe,EAAW,GAAM;IAChC,OAAM;IACN,aAAY;IACZ,QACE,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,UAAD;KACE,eAAe,EAAW,GAAM;KAChC,WAAU;eACX;KAEQ,CAAA,EACT,kBAAC,UAAD;KACE,SAAS;KACT,UAAU,KAAa,CAAC,EAAQ,MAAM,IAAI,CAAC;KAC3C,WAAU;eAET,IAAY,cAAc;KACpB,CAAA,CACR,EAAA,CAAA;cApBP,CAuBG,MACC,kBAAC,OAAD;KAAK,WAAU;eAAyG;KAElH,CAAA,EAER,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;OAAO,WAAW;iBAAc;OAAwB,CAAA,EACxD,kBAAC,SAAD;OACE,MAAK;OACL,aAAY;OACZ,OAAO;OACP,WAAW,MAAM,EAAW,EAAE,OAAO,MAAM;OAC3C,WAAW;OACX,CAAA,CACE,EAAA,CAAA;MACN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;QAAO,WAAW;kBAAc;QAAgB,CAAA,EAChD,kBAAC,UAAD;QACE,OAAO;QACP,WAAW,MAAM,EAAe,EAAE,OAAO,MAAM;QAC/C,WAAW;kBAEV,EAAiB,KAAK,MACrB,kBAAC,UAAD;SAAgB,OAAO;SAAG,WAAU;mBACjC;SACM,EAFI,EAEJ,CACT;QACK,CAAA,CACL,EAAA,CAAA,EACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;QAAO,WAAW;kBAAc;QAA4B,CAAA,EAC5D,kBAAC,SAAD;QACE,MAAK;QACL,KAAI;QACJ,aAAY;QACZ,OAAO;QACP,WAAW,MAAM,EAAc,EAAE,OAAO,MAAM;QAC9C,WAAW;QACX,CAAA,CACE,EAAA,CAAA,CACF;;MACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;OAAO,WAAW;iBAAc;OAAgB,CAAA,EAChD,kBAAC,SAAD;OACE,MAAK;OACL,KAAI;OACJ,aAAY;OACZ,OAAO;OACP,WAAW,MAAM,EAAe,EAAE,OAAO,MAAM;OAC/C,WAAW;OACX,CAAA,CACE,EAAA,CAAA;MACF;OACA;;GAGR,kBAAC,GAAD;IACE,MAAM,MAAa;IACnB,eAAe,EAAY,KAAK;IAChC,OAAM;IACN,aAAa,GAAU;IACvB,MAAK;IACL,QACE,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,UAAD;KACE,eAAe,EAAY,KAAK;KAChC,WAAU;eACX;KAEQ,CAAA,EACT,kBAAC,UAAD;KACE,SAAS;KACT,UAAU;KACV,WAAU;eAET,IAAe,cAAc;KACvB,CAAA,CACR,EAAA,CAAA;cArBP;KAwBG,MACC,kBAAC,OAAD;MAAK,WAAU;gBAAyG;MAElH,CAAA;KAER,kBAAC,SAAD;MAAO,WAAW;gBAAc;MAAgB,CAAA;KAChD,kBAAC,UAAD;MACE,OAAO;MACP,WAAW,MAAM,EAAiB,EAAE,OAAO,MAAM;MACjD,WAAW;gBAEV,EAAiB,KAAK,MACrB,kBAAC,UAAD;OAAgB,OAAO;OAAG,WAAU;iBACjC;OACM,EAFI,EAEJ,CACT;MACK,CAAA;KACT,kBAAC,KAAD;MAAG,WAAU;gBAAb;OAAgD;OAE7C,GAAU;OAAgB;OACzB;;KACE;;GAGR,kBAAC,GAAD;IACE,MAAM,MAAc;IACpB,eAAe,EAAa,KAAK;IACjC,OAAM;IACN,MAAK;IACL,QACE,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,UAAD;KACE,eAAe,EAAa,KAAK;KACjC,WAAU;eACX;KAEQ,CAAA,EACT,kBAAC,UAAD;KACE,SAAS;KACT,UAAU;KACV,WAAU;eAET,IAAW,gBAAgB;KACrB,CAAA,CACR,EAAA,CAAA;cApBP,CAuBG,MACC,kBAAC,OAAD;KAAK,WAAU;eAAyG;KAElH,CAAA,EAER,kBAAC,KAAD;KAAG,WAAU;eAAb;MAA2C;MACW;MACpD,kBAAC,QAAD;OAAM,WAAU;iBACb,GAAW;OACP,CAAA;;MAEL;OACE;;GACG"}
@@ -1,67 +1,361 @@
1
- import { useTimeCampus as e } from "../../providers/TimeCampusProvider.js";
2
- import { getStatusColor as t } from "../../utils/helpers.js";
3
- import { LoadingSkeleton as n } from "../../components/LoadingSkeleton.js";
4
- import { useListChannelConfigsQuery as r, useListChannelStatusesQuery as i } from "../../../generated/wspace-operations.js";
5
- import { PageLayout as a } from "../../components/PageLayout.js";
6
- import { EmptyState as o } from "../../components/EmptyState.js";
7
- import { Wifi as s, WifiOff as c } from "lucide-react";
8
- import { jsx as l, jsxs as u } from "react/jsx-runtime";
1
+ import { LoadingSkeleton as e } from "../../components/LoadingSkeleton.js";
2
+ import { useCreateChannelConfigMutation as t, useDeleteChannelConfigMutation as n, useListChannelConfigsQuery as r, useListChannelStatusesQuery as i, useSyncChannelMutation as ee, useToggleChannelMutation as a, useUpdateChannelConfigMutation as te } from "../../../generated/wspace-operations.js";
3
+ import { PageLayout as o } from "../../components/PageLayout.js";
4
+ import { EmptyState as ne } from "../../components/EmptyState.js";
5
+ import { ChannelStatus as s, TimeCampusChannelType as c } from "../../../generated/wspace-types.js";
6
+ import { Modal as l } from "../../components/Modal.js";
7
+ import { StatusBadge as re } from "../../components/StatusBadge.js";
8
+ import { ToastContainer as ie, useToast as ae } from "../../components/Toast.js";
9
+ import { useState as u } from "react";
10
+ import { Pencil as oe, Plus as d, Power as f, RefreshCw as p, Trash2 as m, Wifi as h, WifiOff as g } from "lucide-react";
11
+ import { Fragment as _, jsx as v, jsxs as y } from "react/jsx-runtime";
9
12
  //#region src/timecampus/pages/channels/ChannelsPage.tsx
10
- function d() {
11
- let { workspaceId: d = "" } = e(), { data: f, loading: p, error: m } = r(), { data: h } = i(), g = f?.channelConfigs ?? [], _ = h?.channelStatuses ?? [], v = new Map(_.map((e) => [e.channelId, e]));
12
- return p ? /* @__PURE__ */ l(a, {
13
+ var b = 25, se = [
14
+ {
15
+ value: c.Calendar,
16
+ label: "Calendar"
17
+ },
18
+ {
19
+ value: c.Desktop,
20
+ label: "Desktop"
21
+ },
22
+ {
23
+ value: c.Browser,
24
+ label: "Browser"
25
+ },
26
+ {
27
+ value: c.Mobile,
28
+ label: "Mobile"
29
+ },
30
+ {
31
+ value: c.Ide,
32
+ label: "IDE"
33
+ },
34
+ {
35
+ value: c.Cli,
36
+ label: "CLI"
37
+ },
38
+ {
39
+ value: c.Api,
40
+ label: "API"
41
+ },
42
+ {
43
+ value: c.Custom,
44
+ label: "Custom"
45
+ }
46
+ ], x = "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", S = {
47
+ name: "",
48
+ type: c.Calendar,
49
+ syncInterval: ""
50
+ };
51
+ function C() {
52
+ let { toasts: C, addToast: w, dismissToast: ce } = ae(), { data: le, loading: ue, error: de, refetch: T, fetchMore: E } = r({
53
+ variables: {
54
+ limit: b,
55
+ offset: 0
56
+ },
57
+ notifyOnNetworkStatusChange: !0
58
+ }), { data: D } = i(), [O, { loading: k }] = t(), [A, { loading: j }] = te(), [M, { loading: N }] = n(), [P, { loading: F }] = a(), [I, { loading: L }] = ee(), R = le?.channelConfigs ?? [], z = D?.channelStatuses ?? [], [B, V] = u(!1), [H, fe] = u(!0), [pe, U] = u(!1), [W, G] = u(null), [K, q] = u(S), [J, Y] = u(null), [me, X] = u(null), he = new Map(z.map((e) => [e.channelId, e])), ge = () => {
59
+ G(null), q(S), U(!0);
60
+ }, _e = (e) => {
61
+ G(e), q({
62
+ name: e.name,
63
+ type: Object.values(c).find((t) => t === e.type) ?? c.Custom,
64
+ syncInterval: e.syncInterval == null ? "" : String(e.syncInterval)
65
+ }), U(!0);
66
+ }, Z = () => {
67
+ U(!1), G(null);
68
+ }, Q = async () => {
69
+ if (!K.name.trim()) return;
70
+ let e = K.syncInterval ? Number(K.syncInterval) : void 0;
71
+ try {
72
+ W ? (await A({ variables: {
73
+ id: W.id,
74
+ input: {
75
+ name: K.name.trim(),
76
+ type: K.type,
77
+ syncInterval: e
78
+ }
79
+ } }), w({
80
+ title: "Channel updated",
81
+ variant: "success"
82
+ })) : (await O({ variables: { input: {
83
+ name: K.name.trim(),
84
+ type: K.type,
85
+ syncInterval: e,
86
+ status: s.Active
87
+ } } }), w({
88
+ title: "Channel added",
89
+ variant: "success"
90
+ })), Z(), await T();
91
+ } catch {
92
+ w({
93
+ title: W ? "Failed to update channel" : "Failed to add channel",
94
+ variant: "error"
95
+ });
96
+ }
97
+ }, ve = async () => {
98
+ if (J) try {
99
+ await M({ variables: { id: J.id } }), w({
100
+ title: "Channel deleted",
101
+ variant: "success"
102
+ }), Y(null), await T();
103
+ } catch {
104
+ w({
105
+ title: "Failed to delete channel",
106
+ variant: "error"
107
+ });
108
+ }
109
+ }, ye = async (e) => {
110
+ let t = e.status.toLowerCase() === "active";
111
+ X(e.id);
112
+ try {
113
+ await P({ variables: {
114
+ id: e.id,
115
+ status: t ? s.Inactive : s.Active
116
+ } }), w({
117
+ title: t ? "Channel disabled" : "Channel enabled",
118
+ variant: "success"
119
+ }), await T();
120
+ } catch {
121
+ w({
122
+ title: "Failed to toggle channel",
123
+ variant: "error"
124
+ });
125
+ } finally {
126
+ X(null);
127
+ }
128
+ }, be = async (e) => {
129
+ X(e.id);
130
+ try {
131
+ await I({ variables: { id: e.id } }), w({
132
+ title: "Sync started",
133
+ variant: "success"
134
+ }), await T();
135
+ } catch {
136
+ w({
137
+ title: "Failed to sync channel",
138
+ variant: "error"
139
+ });
140
+ } finally {
141
+ X(null);
142
+ }
143
+ }, xe = async () => {
144
+ V(!0);
145
+ try {
146
+ ((await E({
147
+ variables: {
148
+ limit: b,
149
+ offset: R.length
150
+ },
151
+ updateQuery: (e, { fetchMoreResult: t }) => t ? {
152
+ ...e,
153
+ channelConfigs: [...e.channelConfigs, ...t.channelConfigs]
154
+ } : e
155
+ })).data?.channelConfigs?.length ?? 0) < b && fe(!1);
156
+ } finally {
157
+ V(!1);
158
+ }
159
+ };
160
+ if (ue && R.length === 0) return /* @__PURE__ */ v(o, {
13
161
  title: "Channels",
14
162
  description: "Manage time tracking channels and integrations",
15
- children: /* @__PURE__ */ l(n, {
163
+ children: /* @__PURE__ */ v(e, {
16
164
  variant: "card",
17
165
  count: 6
18
166
  })
19
- }) : m ? /* @__PURE__ */ l(a, {
167
+ });
168
+ if (de) return /* @__PURE__ */ v(o, {
20
169
  title: "Channels",
21
170
  description: "Manage time tracking channels and integrations",
22
- children: /* @__PURE__ */ l("div", {
171
+ children: /* @__PURE__ */ v("div", {
23
172
  className: "rounded-lg border border-status-error-bg/40 bg-status-error-bg p-4 text-sm text-status-error-text",
24
173
  children: "Failed to load channels data. Please try again later."
25
174
  })
26
- }) : /* @__PURE__ */ l(a, {
175
+ });
176
+ let $ = /* @__PURE__ */ y("button", {
177
+ onClick: ge,
178
+ 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",
179
+ children: [/* @__PURE__ */ v(d, { className: "h-4 w-4" }), "Add Channel"]
180
+ });
181
+ return /* @__PURE__ */ y(o, {
27
182
  title: "Channels",
28
183
  description: "Manage time tracking channels and integrations",
29
184
  purpose: "Connect the tools and devices that feed time into TimeCampus — calendars, desktop trackers, integrations — and keep an eye on each connection's health so no work hours slip through the cracks.",
30
- children: g.length === 0 ? /* @__PURE__ */ l(o, {
31
- illustration: "empty-generic",
32
- title: "No channels configured yet",
33
- description: "Set up your first tracking channel to start pulling time and activity from your tools and devices."
34
- }) : /* @__PURE__ */ l("div", {
35
- className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3",
36
- children: g.map((e) => {
37
- let n = v.get(e.id), r = e.status === "active", i = e.lastSync ? new Date(e.lastSync).toLocaleString() : "Never";
38
- return /* @__PURE__ */ u("div", {
39
- className: "rounded-xl border border-border-seam bg-gloss-card shadow-pop p-5 transition-colors hover:bg-bg-sunken/50 cursor-pointer",
40
- children: [/* @__PURE__ */ u("div", {
41
- className: "flex items-start justify-between",
42
- children: [/* @__PURE__ */ u("div", {
43
- className: "flex items-center gap-3",
44
- children: [r ? /* @__PURE__ */ l(s, { className: "h-5 w-5 text-status-success-text" }) : /* @__PURE__ */ l(c, { className: "h-5 w-5 text-text-secondary" }), /* @__PURE__ */ u("div", { children: [/* @__PURE__ */ l("p", {
45
- className: "text-sm font-semibold text-text-primary",
46
- children: e.name
47
- }), /* @__PURE__ */ l("p", {
48
- className: "text-xs text-text-secondary",
49
- children: e.type
50
- })] })]
51
- }), /* @__PURE__ */ l("span", {
52
- className: `inline-flex rounded-full px-2 py-0.5 text-xs font-medium ${t(e.status)}`,
53
- children: e.status
54
- })]
55
- }), /* @__PURE__ */ u("div", {
56
- className: "mt-3 flex items-center justify-between text-xs text-text-secondary",
57
- children: [/* @__PURE__ */ l("span", { children: n?.isHealthy ? "Healthy" : "Unhealthy" }), /* @__PURE__ */ u("span", { children: ["Sync: ", i] })]
185
+ actions: $,
186
+ children: [
187
+ R.length === 0 ? /* @__PURE__ */ v(ne, {
188
+ illustration: "empty-generic",
189
+ title: "No channels configured yet",
190
+ description: "Set up your first tracking channel to start pulling time and activity from your tools and devices.",
191
+ action: $
192
+ }) : /* @__PURE__ */ y(_, { children: [/* @__PURE__ */ v("div", {
193
+ className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3",
194
+ children: R.map((e) => {
195
+ let t = he.get(e.id), n = e.status.toLowerCase() === "active", r = e.lastSync ? new Date(e.lastSync).toLocaleString() : "Never", i = me === e.id;
196
+ return /* @__PURE__ */ y("div", {
197
+ className: "flex flex-col rounded-xl border border-border-seam bg-gloss-card shadow-pop p-5 transition-colors",
198
+ children: [
199
+ /* @__PURE__ */ y("div", {
200
+ className: "flex items-start justify-between",
201
+ children: [/* @__PURE__ */ y("div", {
202
+ className: "flex items-center gap-3",
203
+ children: [n ? /* @__PURE__ */ v(h, { className: "h-5 w-5 text-status-success-text" }) : /* @__PURE__ */ v(g, { className: "h-5 w-5 text-text-secondary" }), /* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("p", {
204
+ className: "text-sm font-semibold text-text-primary",
205
+ children: e.name
206
+ }), /* @__PURE__ */ v("p", {
207
+ className: "text-xs text-text-secondary",
208
+ children: e.type
209
+ })] })]
210
+ }), /* @__PURE__ */ v(re, {
211
+ label: e.status,
212
+ status: e.status.toLowerCase()
213
+ })]
214
+ }),
215
+ /* @__PURE__ */ y("div", {
216
+ className: "mt-3 flex items-center justify-between text-xs text-text-secondary",
217
+ children: [/* @__PURE__ */ v("span", { children: t?.isHealthy ? "Healthy" : "Unhealthy" }), /* @__PURE__ */ y("span", { children: ["Sync: ", r] })]
218
+ }),
219
+ /* @__PURE__ */ y("div", {
220
+ className: "mt-4 flex items-center gap-1.5 border-t border-border-seam pt-3",
221
+ children: [
222
+ /* @__PURE__ */ y("button", {
223
+ type: "button",
224
+ disabled: i && L,
225
+ onClick: () => be(e),
226
+ title: "Sync now",
227
+ 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",
228
+ children: [/* @__PURE__ */ v(p, { className: `h-3.5 w-3.5 ${i && L ? "animate-spin" : ""}` }), "Sync"]
229
+ }),
230
+ /* @__PURE__ */ y("button", {
231
+ type: "button",
232
+ disabled: i && F,
233
+ onClick: () => ye(e),
234
+ title: n ? "Disable channel" : "Enable channel",
235
+ 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",
236
+ children: [/* @__PURE__ */ v(f, { className: "h-3.5 w-3.5" }), n ? "Disable" : "Enable"]
237
+ }),
238
+ /* @__PURE__ */ y("div", {
239
+ className: "ml-auto flex items-center gap-1",
240
+ children: [/* @__PURE__ */ v("button", {
241
+ type: "button",
242
+ onClick: () => _e(e),
243
+ title: "Edit channel",
244
+ 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",
245
+ children: /* @__PURE__ */ v(oe, { className: "h-3.5 w-3.5" })
246
+ }), /* @__PURE__ */ v("button", {
247
+ type: "button",
248
+ onClick: () => Y(e),
249
+ title: "Delete channel",
250
+ 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",
251
+ children: /* @__PURE__ */ v(m, { className: "h-3.5 w-3.5" })
252
+ })]
253
+ })
254
+ ]
255
+ })
256
+ ]
257
+ }, e.id);
258
+ })
259
+ }), H && R.length >= b && /* @__PURE__ */ v("div", {
260
+ className: "mt-4 flex justify-center",
261
+ children: /* @__PURE__ */ v("button", {
262
+ onClick: xe,
263
+ disabled: B,
264
+ 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",
265
+ children: B ? "Loading..." : "Load more"
266
+ })
267
+ })] }),
268
+ /* @__PURE__ */ v(l, {
269
+ open: pe,
270
+ onClose: Z,
271
+ title: W ? "Edit Channel" : "Add Channel",
272
+ description: W ? "Update this channel's configuration." : "Connect a new tracking channel to feed time into TimeCampus.",
273
+ footer: /* @__PURE__ */ y(_, { children: [/* @__PURE__ */ v("button", {
274
+ onClick: Z,
275
+ className: "rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50",
276
+ children: "Cancel"
277
+ }), /* @__PURE__ */ v("button", {
278
+ onClick: Q,
279
+ disabled: k || j || !K.name.trim(),
280
+ 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",
281
+ children: k || j ? "Saving..." : W ? "Save Changes" : "Add Channel"
282
+ })] }),
283
+ children: /* @__PURE__ */ y("div", {
284
+ className: "space-y-4",
285
+ children: [/* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("label", {
286
+ className: "block text-sm font-medium text-text-primary",
287
+ children: "Name"
288
+ }), /* @__PURE__ */ v("input", {
289
+ type: "text",
290
+ placeholder: "e.g. Google Calendar",
291
+ value: K.name,
292
+ onChange: (e) => q({
293
+ ...K,
294
+ name: e.target.value
295
+ }),
296
+ className: x
297
+ })] }), /* @__PURE__ */ y("div", {
298
+ className: "grid grid-cols-1 gap-4 sm:grid-cols-2",
299
+ children: [/* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("label", {
300
+ className: "block text-sm font-medium text-text-primary",
301
+ children: "Type"
302
+ }), /* @__PURE__ */ v("select", {
303
+ value: K.type,
304
+ onChange: (e) => q({
305
+ ...K,
306
+ type: e.target.value
307
+ }),
308
+ className: x,
309
+ children: se.map((e) => /* @__PURE__ */ v("option", {
310
+ value: e.value,
311
+ children: e.label
312
+ }, e.value))
313
+ })] }), /* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("label", {
314
+ className: "block text-sm font-medium text-text-primary",
315
+ children: "Sync Interval (seconds)"
316
+ }), /* @__PURE__ */ v("input", {
317
+ type: "number",
318
+ min: 0,
319
+ placeholder: "e.g. 300",
320
+ value: K.syncInterval,
321
+ onChange: (e) => q({
322
+ ...K,
323
+ syncInterval: e.target.value
324
+ }),
325
+ className: x
326
+ })] })]
58
327
  })]
59
- }, e.id);
328
+ })
329
+ }),
330
+ /* @__PURE__ */ v(l, {
331
+ open: J !== null,
332
+ onClose: () => Y(null),
333
+ title: "Delete channel?",
334
+ description: J ? `"${J.name}" will be removed. This cannot be undone.` : void 0,
335
+ size: "sm",
336
+ footer: /* @__PURE__ */ y(_, { children: [/* @__PURE__ */ v("button", {
337
+ onClick: () => Y(null),
338
+ className: "rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50",
339
+ children: "Cancel"
340
+ }), /* @__PURE__ */ v("button", {
341
+ onClick: ve,
342
+ disabled: N,
343
+ 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",
344
+ children: N ? "Deleting..." : "Delete"
345
+ })] }),
346
+ children: /* @__PURE__ */ v("p", {
347
+ className: "text-sm text-text-secondary",
348
+ children: "Deleting a channel stops it from feeding new time into TimeCampus. Previously captured entries are kept."
349
+ })
350
+ }),
351
+ /* @__PURE__ */ v(ie, {
352
+ toasts: C,
353
+ onDismiss: ce
60
354
  })
61
- })
355
+ ]
62
356
  });
63
357
  }
64
358
  //#endregion
65
- export { d as ChannelsPage };
359
+ export { C as ChannelsPage };
66
360
 
67
361
  //# sourceMappingURL=ChannelsPage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ChannelsPage.js","names":[],"sources":["../../../../src/timecampus/pages/channels/ChannelsPage.tsx"],"sourcesContent":["import { Wifi, WifiOff } from \"lucide-react\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { EmptyState } from \"../../components/EmptyState\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport { getStatusColor } from \"../../utils/helpers\";\nimport { useTimeCampus } from \"../../providers/TimeCampusProvider\";\nimport {\n useListChannelConfigsQuery,\n useListChannelStatusesQuery,\n type ChannelConfigFieldsFragment,\n} from \"@/generated/wspace-operations\";\n\nexport function ChannelsPage() {\n const { workspaceId = \"\" } = useTimeCampus();\n\n const {\n data: configsData,\n loading: configsLoading,\n error: configsError,\n } = useListChannelConfigsQuery();\n const { data: statusesData } = useListChannelStatusesQuery();\n\n const channels = configsData?.channelConfigs ?? [];\n const statuses = statusesData?.channelStatuses ?? [];\n\n type ChannelStatusItem = {\n channelId: string;\n name: string;\n type: string;\n status: string;\n isHealthy: boolean;\n lastSync?: string | null;\n };\n\n // Build a lookup map for status info\n const statusMap = new Map<string, ChannelStatusItem>(\n statuses.map((s: ChannelStatusItem) => [s.channelId, s]),\n );\n\n if (configsLoading) {\n return (\n <PageLayout\n title=\"Channels\"\n description=\"Manage time tracking channels and integrations\"\n >\n <LoadingSkeleton variant=\"card\" count={6} />\n </PageLayout>\n );\n }\n\n if (configsError) {\n return (\n <PageLayout\n title=\"Channels\"\n description=\"Manage time tracking channels and integrations\"\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 channels data. Please try again later.\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title=\"Channels\"\n description=\"Manage time tracking channels and integrations\"\n purpose=\"Connect the tools and devices that feed time into TimeCampus — calendars, desktop trackers, integrations — and keep an eye on each connection's health so no work hours slip through the cracks.\"\n >\n {channels.length === 0 ? (\n <EmptyState\n illustration=\"empty-generic\"\n title=\"No channels configured yet\"\n description=\"Set up your first tracking channel to start pulling time and activity from your tools and devices.\"\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {channels.map((channel: ChannelConfigFieldsFragment) => {\n const statusInfo = statusMap.get(channel.id);\n const isActive = channel.status === \"active\";\n const lastSync = channel.lastSync\n ? new Date(channel.lastSync).toLocaleString()\n : \"Never\";\n\n return (\n <div\n key={channel.id}\n className=\"rounded-xl border border-border-seam bg-gloss-card shadow-pop p-5 transition-colors hover:bg-bg-sunken/50 cursor-pointer\"\n >\n <div className=\"flex items-start justify-between\">\n <div className=\"flex items-center gap-3\">\n {isActive ? (\n <Wifi className=\"h-5 w-5 text-status-success-text\" />\n ) : (\n <WifiOff className=\"h-5 w-5 text-text-secondary\" />\n )}\n <div>\n <p className=\"text-sm font-semibold text-text-primary\">\n {channel.name}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {channel.type}\n </p>\n </div>\n </div>\n <span\n className={`inline-flex rounded-full px-2 py-0.5 text-xs font-medium ${getStatusColor(channel.status)}`}\n >\n {channel.status}\n </span>\n </div>\n <div className=\"mt-3 flex items-center justify-between text-xs text-text-secondary\">\n <span>{statusInfo?.isHealthy ? \"Healthy\" : \"Unhealthy\"}</span>\n <span>Sync: {lastSync}</span>\n </div>\n </div>\n );\n })}\n </div>\n )}\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;AAYA,SAAgB,IAAe;CAC7B,IAAM,EAAE,iBAAc,OAAO,GAAe,EAEtC,EACJ,MAAM,GACN,SAAS,GACT,OAAO,MACL,GAA4B,EAC1B,EAAE,MAAM,MAAiB,GAA6B,EAEtD,IAAW,GAAa,kBAAkB,EAAE,EAC5C,IAAW,GAAc,mBAAmB,EAAE,EAY9C,IAAY,IAAI,IACpB,EAAS,KAAK,MAAyB,CAAC,EAAE,WAAW,EAAE,CAAC,CACzD;AA0BD,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;YAEP,EAAS,WAAW,IACnB,kBAAC,GAAD;GACE,cAAa;GACb,OAAM;GACN,aAAY;GACZ,CAAA,GAEF,kBAAC,OAAD;GAAK,WAAU;aACZ,EAAS,KAAK,MAAyC;IACtD,IAAM,IAAa,EAAU,IAAI,EAAQ,GAAG,EACtC,IAAW,EAAQ,WAAW,UAC9B,IAAW,EAAQ,WACrB,IAAI,KAAK,EAAQ,SAAS,CAAC,gBAAgB,GAC3C;AAEJ,WACE,kBAAC,OAAD;KAEE,WAAU;eAFZ,CAIE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACG,IACC,kBAAC,GAAD,EAAM,WAAU,oCAAqC,CAAA,GAErD,kBAAC,GAAD,EAAS,WAAU,+BAAgC,CAAA,EAErD,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;QAAG,WAAU;kBACV,EAAQ;QACP,CAAA,EACJ,kBAAC,KAAD;QAAG,WAAU;kBACV,EAAQ;QACP,CAAA,CACA,EAAA,CAAA,CACF;UACN,kBAAC,QAAD;OACE,WAAW,4DAA4D,EAAe,EAAQ,OAAO;iBAEpG,EAAQ;OACJ,CAAA,CACH;SACN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,QAAD,EAAA,UAAO,GAAY,YAAY,YAAY,aAAmB,CAAA,EAC9D,kBAAC,QAAD,EAAA,UAAA,CAAM,UAAO,EAAgB,EAAA,CAAA,CACzB;QACF;OA7BC,EAAQ,GA6BT;KAER;GACE,CAAA;EAEG,CAAA"}
1
+ {"version":3,"file":"ChannelsPage.js","names":[],"sources":["../../../../src/timecampus/pages/channels/ChannelsPage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport {\n Wifi,\n WifiOff,\n Plus,\n RefreshCw,\n Pencil,\n Trash2,\n Power,\n} from \"lucide-react\";\nimport { PageLayout } from \"../../components/PageLayout\";\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 {\n useListChannelConfigsQuery,\n useListChannelStatusesQuery,\n useCreateChannelConfigMutation,\n useUpdateChannelConfigMutation,\n useDeleteChannelConfigMutation,\n useToggleChannelMutation,\n useSyncChannelMutation,\n type ChannelConfigFieldsFragment,\n} from \"@/generated/wspace-operations\";\nimport { ChannelStatus, TimeCampusChannelType } from \"@/generated/wspace-types\";\n\nconst PAGE_SIZE = 25;\n\nconst CHANNEL_TYPE_OPTIONS: { value: TimeCampusChannelType; label: string }[] =\n [\n { value: TimeCampusChannelType.Calendar, label: \"Calendar\" },\n { value: TimeCampusChannelType.Desktop, label: \"Desktop\" },\n { value: TimeCampusChannelType.Browser, label: \"Browser\" },\n { value: TimeCampusChannelType.Mobile, label: \"Mobile\" },\n { value: TimeCampusChannelType.Ide, label: \"IDE\" },\n { value: TimeCampusChannelType.Cli, label: \"CLI\" },\n { value: TimeCampusChannelType.Api, label: \"API\" },\n { value: TimeCampusChannelType.Custom, label: \"Custom\" },\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\ninterface ChannelFormState {\n name: string;\n type: TimeCampusChannelType;\n syncInterval: string;\n}\n\nconst emptyForm: ChannelFormState = {\n name: \"\",\n type: TimeCampusChannelType.Calendar,\n syncInterval: \"\",\n};\n\nexport function ChannelsPage() {\n const { toasts, addToast, dismissToast } = useToast();\n\n const {\n data: configsData,\n loading: configsLoading,\n error: configsError,\n refetch,\n fetchMore,\n } = useListChannelConfigsQuery({\n variables: { limit: PAGE_SIZE, offset: 0 },\n notifyOnNetworkStatusChange: true,\n });\n const { data: statusesData } = useListChannelStatusesQuery();\n\n const [createChannel, { loading: creating }] =\n useCreateChannelConfigMutation();\n const [updateChannel, { loading: updating }] =\n useUpdateChannelConfigMutation();\n const [deleteChannel, { loading: deleting }] =\n useDeleteChannelConfigMutation();\n const [toggleChannel, { loading: toggling }] = useToggleChannelMutation();\n const [syncChannel, { loading: syncing }] = useSyncChannelMutation();\n\n const channels = configsData?.channelConfigs ?? [];\n const statuses = statusesData?.channelStatuses ?? [];\n\n const [loadingMore, setLoadingMore] = useState(false);\n const [canLoadMore, setCanLoadMore] = useState(true);\n\n // Create / edit modal state\n const [formOpen, setFormOpen] = useState(false);\n const [editing, setEditing] = useState<ChannelConfigFieldsFragment | null>(\n null,\n );\n const [form, setForm] = useState<ChannelFormState>(emptyForm);\n\n // Delete confirm state\n const [deleteTarget, setDeleteTarget] =\n useState<ChannelConfigFieldsFragment | null>(null);\n\n // Per-row action tracking (which channel id is mid-mutation)\n const [busyId, setBusyId] = useState<string | null>(null);\n\n type ChannelStatusItem = {\n channelId: string;\n name: string;\n type: string;\n status: string;\n isHealthy: boolean;\n lastSync?: string | null;\n };\n\n const statusMap = new Map<string, ChannelStatusItem>(\n statuses.map((s: ChannelStatusItem) => [s.channelId, s]),\n );\n\n const openCreate = () => {\n setEditing(null);\n setForm(emptyForm);\n setFormOpen(true);\n };\n\n const openEdit = (channel: ChannelConfigFieldsFragment) => {\n setEditing(channel);\n setForm({\n name: channel.name,\n type:\n (Object.values(TimeCampusChannelType).find(\n (t) => t === channel.type,\n ) as TimeCampusChannelType | undefined) ?? TimeCampusChannelType.Custom,\n syncInterval:\n channel.syncInterval != null ? String(channel.syncInterval) : \"\",\n });\n setFormOpen(true);\n };\n\n const closeForm = () => {\n setFormOpen(false);\n setEditing(null);\n };\n\n const handleSubmit = async () => {\n if (!form.name.trim()) return;\n const syncInterval = form.syncInterval\n ? Number(form.syncInterval)\n : undefined;\n try {\n if (editing) {\n await updateChannel({\n variables: {\n id: editing.id,\n input: {\n name: form.name.trim(),\n type: form.type,\n syncInterval,\n },\n },\n });\n addToast({ title: \"Channel updated\", variant: \"success\" });\n } else {\n await createChannel({\n variables: {\n input: {\n name: form.name.trim(),\n type: form.type,\n syncInterval,\n status: ChannelStatus.Active,\n },\n },\n });\n addToast({ title: \"Channel added\", variant: \"success\" });\n }\n closeForm();\n await refetch();\n } catch {\n addToast({\n title: editing ? \"Failed to update channel\" : \"Failed to add channel\",\n variant: \"error\",\n });\n }\n };\n\n const handleDelete = async () => {\n if (!deleteTarget) return;\n try {\n await deleteChannel({ variables: { id: deleteTarget.id } });\n addToast({ title: \"Channel deleted\", variant: \"success\" });\n setDeleteTarget(null);\n await refetch();\n } catch {\n addToast({ title: \"Failed to delete channel\", variant: \"error\" });\n }\n };\n\n const handleToggle = async (channel: ChannelConfigFieldsFragment) => {\n const isActive = channel.status.toLowerCase() === \"active\";\n setBusyId(channel.id);\n try {\n await toggleChannel({\n variables: {\n id: channel.id,\n status: isActive ? ChannelStatus.Inactive : ChannelStatus.Active,\n },\n });\n addToast({\n title: isActive ? \"Channel disabled\" : \"Channel enabled\",\n variant: \"success\",\n });\n await refetch();\n } catch {\n addToast({ title: \"Failed to toggle channel\", variant: \"error\" });\n } finally {\n setBusyId(null);\n }\n };\n\n const handleSync = async (channel: ChannelConfigFieldsFragment) => {\n setBusyId(channel.id);\n try {\n await syncChannel({ variables: { id: channel.id } });\n addToast({ title: \"Sync started\", variant: \"success\" });\n await refetch();\n } catch {\n addToast({ title: \"Failed to sync channel\", variant: \"error\" });\n } finally {\n setBusyId(null);\n }\n };\n\n const handleLoadMore = async () => {\n setLoadingMore(true);\n try {\n const res = await fetchMore({\n variables: { limit: PAGE_SIZE, offset: channels.length },\n updateQuery: (prev, { fetchMoreResult }) => {\n if (!fetchMoreResult) return prev;\n return {\n ...prev,\n channelConfigs: [\n ...prev.channelConfigs,\n ...fetchMoreResult.channelConfigs,\n ],\n };\n },\n });\n if ((res.data?.channelConfigs?.length ?? 0) < PAGE_SIZE) {\n setCanLoadMore(false);\n }\n } finally {\n setLoadingMore(false);\n }\n };\n\n if (configsLoading && channels.length === 0) {\n return (\n <PageLayout\n title=\"Channels\"\n description=\"Manage time tracking channels and integrations\"\n >\n <LoadingSkeleton variant=\"card\" count={6} />\n </PageLayout>\n );\n }\n\n if (configsError) {\n return (\n <PageLayout\n title=\"Channels\"\n description=\"Manage time tracking channels and integrations\"\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 channels 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 Channel\n </button>\n );\n\n return (\n <PageLayout\n title=\"Channels\"\n description=\"Manage time tracking channels and integrations\"\n purpose=\"Connect the tools and devices that feed time into TimeCampus — calendars, desktop trackers, integrations — and keep an eye on each connection's health so no work hours slip through the cracks.\"\n actions={addButton}\n >\n {channels.length === 0 ? (\n <EmptyState\n illustration=\"empty-generic\"\n title=\"No channels configured yet\"\n description=\"Set up your first tracking channel to start pulling time and activity from your tools and devices.\"\n action={addButton}\n />\n ) : (\n <>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {channels.map((channel: ChannelConfigFieldsFragment) => {\n const statusInfo = statusMap.get(channel.id);\n const isActive = channel.status.toLowerCase() === \"active\";\n const lastSync = channel.lastSync\n ? new Date(channel.lastSync).toLocaleString()\n : \"Never\";\n const rowBusy = busyId === channel.id;\n\n return (\n <div\n key={channel.id}\n className=\"flex flex-col rounded-xl border border-border-seam bg-gloss-card shadow-pop p-5 transition-colors\"\n >\n <div className=\"flex items-start justify-between\">\n <div className=\"flex items-center gap-3\">\n {isActive ? (\n <Wifi className=\"h-5 w-5 text-status-success-text\" />\n ) : (\n <WifiOff className=\"h-5 w-5 text-text-secondary\" />\n )}\n <div>\n <p className=\"text-sm font-semibold text-text-primary\">\n {channel.name}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {channel.type}\n </p>\n </div>\n </div>\n <StatusBadge\n label={channel.status}\n status={channel.status.toLowerCase()}\n />\n </div>\n <div className=\"mt-3 flex items-center justify-between text-xs text-text-secondary\">\n <span>\n {statusInfo?.isHealthy ? \"Healthy\" : \"Unhealthy\"}\n </span>\n <span>Sync: {lastSync}</span>\n </div>\n <div className=\"mt-4 flex items-center gap-1.5 border-t border-border-seam pt-3\">\n <button\n type=\"button\"\n disabled={rowBusy && syncing}\n onClick={() => handleSync(channel)}\n title=\"Sync now\"\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 <RefreshCw\n className={`h-3.5 w-3.5 ${rowBusy && syncing ? \"animate-spin\" : \"\"}`}\n />\n Sync\n </button>\n <button\n type=\"button\"\n disabled={rowBusy && toggling}\n onClick={() => handleToggle(channel)}\n title={isActive ? \"Disable channel\" : \"Enable channel\"}\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 {isActive ? \"Disable\" : \"Enable\"}\n </button>\n <div className=\"ml-auto flex items-center gap-1\">\n <button\n type=\"button\"\n onClick={() => openEdit(channel)}\n title=\"Edit channel\"\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(channel)}\n title=\"Delete channel\"\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 </div>\n );\n })}\n </div>\n\n {canLoadMore && channels.length >= PAGE_SIZE && (\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 Channel\" : \"Add Channel\"}\n description={\n editing\n ? \"Update this channel's configuration.\"\n : \"Connect a new tracking channel to feed time into TimeCampus.\"\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.name.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 Channel\"}\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 Name\n </label>\n <input\n type=\"text\"\n placeholder=\"e.g. Google Calendar\"\n value={form.name}\n onChange={(e) => setForm({ ...form, name: 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 Type\n </label>\n <select\n value={form.type}\n onChange={(e) =>\n setForm({\n ...form,\n type: e.target.value as TimeCampusChannelType,\n })\n }\n className={inputClass}\n >\n {CHANNEL_TYPE_OPTIONS.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {opt.label}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Sync Interval (seconds)\n </label>\n <input\n type=\"number\"\n min={0}\n placeholder=\"e.g. 300\"\n value={form.syncInterval}\n onChange={(e) =>\n setForm({ ...form, syncInterval: e.target.value })\n }\n className={inputClass}\n />\n </div>\n </div>\n </div>\n </Modal>\n\n <Modal\n open={deleteTarget !== null}\n onClose={() => setDeleteTarget(null)}\n title=\"Delete channel?\"\n description={\n deleteTarget\n ? `\"${deleteTarget.name}\" will be removed. This cannot be undone.`\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 Deleting a channel stops it from feeding new time into TimeCampus.\n Previously captured entries are kept.\n </p>\n </Modal>\n\n <ToastContainer toasts={toasts} onDismiss={dismissToast} />\n </PageLayout>\n );\n}\n"],"mappings":"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"}