@burdenoff/microfe-timecampus 2026.626.2 → 2026.703.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/dist/generated/wspace-operations.js +996 -816
  2. package/dist/generated/wspace-operations.js.map +1 -1
  3. package/dist/generated/wspace-types.js +28 -0
  4. package/dist/generated/wspace-types.js.map +1 -0
  5. package/dist/index.js +4 -4
  6. package/dist/timecampus/TimeCampusRoutes.js +16 -12
  7. package/dist/timecampus/TimeCampusRoutes.js.map +1 -1
  8. package/dist/timecampus/components/Modal.js +65 -0
  9. package/dist/timecampus/components/Modal.js.map +1 -0
  10. package/dist/timecampus/components/StatusBadge.js +28 -0
  11. package/dist/timecampus/components/StatusBadge.js.map +1 -0
  12. package/dist/timecampus/components/Toast.js +87 -0
  13. package/dist/timecampus/components/Toast.js.map +1 -0
  14. package/dist/timecampus/index.js +1 -1
  15. package/dist/timecampus/pages/HomePage.js +143 -135
  16. package/dist/timecampus/pages/HomePage.js.map +1 -1
  17. package/dist/timecampus/pages/applets/AppletStorePage.js +14 -19
  18. package/dist/timecampus/pages/applets/AppletStorePage.js.map +1 -1
  19. package/dist/timecampus/pages/applets/InstalledAppletsPage.js +49 -37
  20. package/dist/timecampus/pages/applets/InstalledAppletsPage.js.map +1 -1
  21. package/dist/timecampus/pages/application-usage/ApplicationUsagePage.js +315 -87
  22. package/dist/timecampus/pages/application-usage/ApplicationUsagePage.js.map +1 -1
  23. package/dist/timecampus/pages/channels/ChannelsPage.js +340 -46
  24. package/dist/timecampus/pages/channels/ChannelsPage.js.map +1 -1
  25. package/dist/timecampus/pages/client-portal/ClientPortalPage.js +374 -92
  26. package/dist/timecampus/pages/client-portal/ClientPortalPage.js.map +1 -1
  27. package/dist/timecampus/pages/dashboard/ReportsPage.js +70 -37
  28. package/dist/timecampus/pages/dashboard/ReportsPage.js.map +1 -1
  29. package/dist/timecampus/pages/email-tracking/EmailTrackingPage.js +366 -60
  30. package/dist/timecampus/pages/email-tracking/EmailTrackingPage.js.map +1 -1
  31. package/dist/timecampus/pages/expense-tracking/ExpenseTrackingPage.js +461 -87
  32. package/dist/timecampus/pages/expense-tracking/ExpenseTrackingPage.js.map +1 -1
  33. package/dist/timecampus/pages/geofencing/GeofencingPage.js +314 -55
  34. package/dist/timecampus/pages/geofencing/GeofencingPage.js.map +1 -1
  35. package/dist/timecampus/pages/industry-solutions/IndustrySolutionsPage.js +30 -27
  36. package/dist/timecampus/pages/industry-solutions/IndustrySolutionsPage.js.map +1 -1
  37. package/dist/timecampus/pages/location-tracking/LocationTrackingPage.js +269 -74
  38. package/dist/timecampus/pages/location-tracking/LocationTrackingPage.js.map +1 -1
  39. package/dist/timecampus/pages/meetings/ActionItemFormModal.js +127 -0
  40. package/dist/timecampus/pages/meetings/ActionItemFormModal.js.map +1 -0
  41. package/dist/timecampus/pages/meetings/MeetingCreatePage.js +62 -52
  42. package/dist/timecampus/pages/meetings/MeetingCreatePage.js.map +1 -1
  43. package/dist/timecampus/pages/meetings/MeetingDetailPage.js +361 -74
  44. package/dist/timecampus/pages/meetings/MeetingDetailPage.js.map +1 -1
  45. package/dist/timecampus/pages/meetings/MeetingFormModal.js +156 -0
  46. package/dist/timecampus/pages/meetings/MeetingFormModal.js.map +1 -0
  47. package/dist/timecampus/pages/meetings/MeetingsPage.js +175 -74
  48. package/dist/timecampus/pages/meetings/MeetingsPage.js.map +1 -1
  49. package/dist/timecampus/pages/meetings/ParticipantFormModal.js +105 -0
  50. package/dist/timecampus/pages/meetings/ParticipantFormModal.js.map +1 -0
  51. package/dist/timecampus/pages/pomodoro/PomodoroPage.js +307 -67
  52. package/dist/timecampus/pages/pomodoro/PomodoroPage.js.map +1 -1
  53. package/dist/timecampus/pages/projects/ProjectCreatePage.js +51 -48
  54. package/dist/timecampus/pages/projects/ProjectCreatePage.js.map +1 -1
  55. package/dist/timecampus/pages/projects/ProjectDetailPage.js +248 -51
  56. package/dist/timecampus/pages/projects/ProjectDetailPage.js.map +1 -1
  57. package/dist/timecampus/pages/projects/ProjectsPage.js +354 -87
  58. package/dist/timecampus/pages/projects/ProjectsPage.js.map +1 -1
  59. package/dist/timecampus/pages/projects/TasksPage.js +367 -65
  60. package/dist/timecampus/pages/projects/TasksPage.js.map +1 -1
  61. package/dist/timecampus/pages/resource-planning/ResourcePlanningPage.js +674 -104
  62. package/dist/timecampus/pages/resource-planning/ResourcePlanningPage.js.map +1 -1
  63. package/dist/timecampus/pages/team/TeamAnalyticsPage.js +46 -34
  64. package/dist/timecampus/pages/team/TeamAnalyticsPage.js.map +1 -1
  65. package/dist/timecampus/pages/team/TeamMembersPage.js +93 -69
  66. package/dist/timecampus/pages/team/TeamMembersPage.js.map +1 -1
  67. package/dist/timecampus/pages/team/TeamPage.js +4 -3
  68. package/dist/timecampus/pages/team/TeamPage.js.map +1 -1
  69. package/dist/timecampus/pages/templates/TemplateDetailPage.js +72 -53
  70. package/dist/timecampus/pages/templates/TemplateDetailPage.js.map +1 -1
  71. package/dist/timecampus/pages/templates/TemplatesPage.js +40 -35
  72. package/dist/timecampus/pages/templates/TemplatesPage.js.map +1 -1
  73. package/dist/timecampus/pages/time/TimeEntriesPage.js +484 -97
  74. package/dist/timecampus/pages/time/TimeEntriesPage.js.map +1 -1
  75. package/dist/timecampus/pages/time/TimeTrackingPage.js +210 -99
  76. package/dist/timecampus/pages/time/TimeTrackingPage.js.map +1 -1
  77. package/dist/timecampus/pages/time/TimerPage.js +229 -97
  78. package/dist/timecampus/pages/time/TimerPage.js.map +1 -1
  79. package/dist/timecampus/pages/time-approval/TimeApprovalPage.js +315 -110
  80. package/dist/timecampus/pages/time-approval/TimeApprovalPage.js.map +1 -1
  81. package/dist/timecampus/pages/timeline/TimelinePage.js +250 -0
  82. package/dist/timecampus/pages/timeline/TimelinePage.js.map +1 -0
  83. package/dist/timecampus/pages/widgets/WidgetDetailPage.js +158 -76
  84. package/dist/timecampus/pages/widgets/WidgetDetailPage.js.map +1 -1
  85. package/dist/timecampus/pages/widgets/WidgetsPage.js +181 -52
  86. package/dist/timecampus/pages/widgets/WidgetsPage.js.map +1 -1
  87. package/dist/timecampus/pages/workflows/WorkflowsPage.js +212 -61
  88. package/dist/timecampus/pages/workflows/WorkflowsPage.js.map +1 -1
  89. package/dist/timecampus/store/timecampusStore.js +13 -1
  90. package/dist/timecampus/store/timecampusStore.js.map +1 -1
  91. package/dist/timecampus/utils/helpers.js +20 -10
  92. package/dist/timecampus/utils/helpers.js.map +1 -1
  93. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"WorkflowsPage.js","names":[],"sources":["../../../../src/timecampus/pages/workflows/WorkflowsPage.tsx"],"sourcesContent":["import { GitBranch, Plus, Zap } from \"lucide-react\";\nimport { GlassCard } from \"@burdenoff/fe-libs/ui\";\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 useListConfigurationsQuery,\n type ConfigurationFieldsFragment,\n} from \"@/generated/wspace-operations\";\n\nexport function WorkflowsPage() {\n const { workspaceId = \"\" } = useTimeCampus();\n\n const { data, loading, error } = useListConfigurationsQuery({\n variables: { scope: \"WORKSPACE\" as any },\n });\n\n const configurations = data?.configurations ?? [];\n\n // Filter configurations that represent workflows (key starts with 'workflow.')\n const workflows = configurations.filter((c: ConfigurationFieldsFragment) =>\n c.key.startsWith(\"workflow.\"),\n );\n\n if (loading) {\n return (\n <PageLayout title=\"Workflows\" description=\"Automate repetitive tasks\">\n <LoadingSkeleton variant=\"list\" count={5} />\n </PageLayout>\n );\n }\n\n if (error) {\n return (\n <PageLayout title=\"Workflows\" description=\"Automate repetitive tasks\">\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 workflow configurations. Please try again later.\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title=\"Workflows\"\n description=\"Automate repetitive tasks\"\n purpose=\"Put the busywork on autopilot — auto-start timers, submit timesheets, notify on overruns — by wiring up rules that react to what happens in TimeCampus, so the routine handles itself.\"\n actions={\n <button className=\"inline-flex items-center gap-1.5 rounded-md bg-action-primary-bg px-3 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\">\n <Plus className=\"h-4 w-4\" />\n Create Workflow\n </button>\n }\n >\n {workflows.length === 0 ? (\n <EmptyState\n illustration=\"empty-generic\"\n title=\"No workflows configured yet\"\n description=\"Create your first automation to handle repetitive time-tracking tasks — workflows are stored as workspace configurations and appear here.\"\n action={\n <button className=\"inline-flex items-center gap-1.5 rounded-md bg-action-primary-bg px-3 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\">\n <Plus className=\"h-4 w-4\" />\n Create Workflow\n </button>\n }\n />\n ) : (\n <GlassCard treatment=\"glass\" glow className=\"overflow-hidden\">\n <div className=\"divide-y divide-border\">\n {workflows.map((wf: ConfigurationFieldsFragment) => {\n const val = wf.value as Record<string, unknown>;\n const name =\n (val.name as string) ?? wf.key.replace(\"workflow.\", \"\");\n const status = (val.status as string) ?? \"inactive\";\n const trigger = (val.trigger as string) ?? \"Manual\";\n const lastRun = (val.lastRun as string) ?? \"Never\";\n const runCount = (val.runCount as number) ?? 0;\n const successRate = (val.successRate as number) ?? 0;\n\n return (\n <div\n key={wf.id}\n className=\"flex items-center justify-between p-4 hover:bg-bg-sunken/50 transition-colors cursor-pointer\"\n >\n <div className=\"flex items-center gap-3\">\n <div className=\"flex h-9 w-9 items-center justify-center rounded-lg bg-action-primary-bg/10\">\n <GitBranch className=\"h-4 w-4 text-primary\" />\n </div>\n <div>\n <p className=\"text-sm font-medium text-text-primary\">\n {name}\n </p>\n <p className=\"text-xs text-text-secondary\">\n <Zap className=\"inline h-3 w-3\" /> {trigger} &middot;\n Last run: {lastRun}\n </p>\n </div>\n </div>\n <div className=\"flex items-center gap-4\">\n <div className=\"text-right text-xs text-text-secondary\">\n <p>{runCount} runs</p>\n <p>{successRate}% success</p>\n </div>\n <span\n className={`inline-flex rounded-full px-2 py-0.5 text-xs font-medium ${getStatusColor(status)}`}\n >\n {status}\n </span>\n </div>\n </div>\n );\n })}\n </div>\n </GlassCard>\n )}\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;AAYA,SAAgB,IAAgB;CAC9B,IAAM,EAAE,iBAAc,OAAO,GAAe,EAEtC,EAAE,SAAM,YAAS,aAAU,EAA2B,EAC1D,WAAW,EAAE,OAAO,aAAoB,EACzC,CAAC,EAKI,KAHiB,GAAM,kBAAkB,EAAE,EAGhB,QAAQ,MACvC,EAAE,IAAI,WAAW,YAAY,CAC9B;AAoBD,QAlBI,IAEA,kBAAC,GAAD;EAAY,OAAM;EAAY,aAAY;YACxC,kBAAC,GAAD;GAAiB,SAAQ;GAAO,OAAO;GAAK,CAAA;EACjC,CAAA,GAIb,IAEA,kBAAC,GAAD;EAAY,OAAM;EAAY,aAAY;YACxC,kBAAC,OAAD;GAAK,WAAU;aAAoG;GAE7G,CAAA;EACK,CAAA,GAKf,kBAAC,GAAD;EACE,OAAM;EACN,aAAY;EACZ,SAAQ;EACR,SACE,kBAAC,UAAD;GAAQ,WAAU;aAAlB,CACE,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA,EAAA,kBAErB;;YAGV,EAAU,WAAW,IACpB,kBAAC,GAAD;GACE,cAAa;GACb,OAAM;GACN,aAAY;GACZ,QACE,kBAAC,UAAD;IAAQ,WAAU;cAAlB,CACE,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA,EAAA,kBAErB;;GAEX,CAAA,GAEF,kBAAC,GAAD;GAAW,WAAU;GAAQ,MAAA;GAAK,WAAU;aAC1C,kBAAC,OAAD;IAAK,WAAU;cACZ,EAAU,KAAK,MAAoC;KAClD,IAAM,IAAM,EAAG,OACT,IACH,EAAI,QAAmB,EAAG,IAAI,QAAQ,aAAa,GAAG,EACnD,IAAU,EAAI,UAAqB,YACnC,IAAW,EAAI,WAAsB,UACrC,IAAW,EAAI,WAAsB,SACrC,IAAY,EAAI,YAAuB,GACvC,IAAe,EAAI,eAA0B;AAEnD,YACE,kBAAC,OAAD;MAEE,WAAU;gBAFZ,CAIE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,OAAD;QAAK,WAAU;kBACb,kBAAC,GAAD,EAAW,WAAU,wBAAyB,CAAA;QAC1C,CAAA,EACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;QAAG,WAAU;kBACV;QACC,CAAA,EACJ,kBAAC,KAAD;QAAG,WAAU;kBAAb;SACE,kBAAC,GAAD,EAAK,WAAU,kBAAmB,CAAA;;SAAE;SAAQ;SACjC;SACT;UACA,EAAA,CAAA,CACF;UACN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,KAAD,EAAA,UAAA,CAAI,GAAS,QAAS,EAAA,CAAA,EACtB,kBAAC,KAAD,EAAA,UAAA,CAAI,GAAY,YAAa,EAAA,CAAA,CACzB;WACN,kBAAC,QAAD;QACE,WAAW,4DAA4D,EAAe,EAAO;kBAE5F;QACI,CAAA,CACH;SACF;QA5BC,EAAG,GA4BJ;MAER;IACE,CAAA;GACI,CAAA;EAEH,CAAA"}
1
+ {"version":3,"file":"WorkflowsPage.js","names":[],"sources":["../../../../src/timecampus/pages/workflows/WorkflowsPage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { GitBranch, Plus, Zap, Trash2, Power } from \"lucide-react\";\nimport { GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { EmptyState } from \"../../components/EmptyState\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport { Modal } from \"../../components/Modal\";\nimport { getStatusColor } from \"../../utils/helpers\";\nimport {\n useListConfigurationsQuery,\n useSetConfigurationMutation,\n useDeleteConfigurationMutation,\n type ConfigurationFieldsFragment,\n} from \"@/generated/wspace-operations\";\nimport { TimeCampusConfigScope } from \"@/generated/wspace-types\";\n\nconst TRIGGERS = [\n { value: \"timer.started\", label: \"When a timer starts\" },\n { value: \"timer.stopped\", label: \"When a timer stops\" },\n { value: \"entry.created\", label: \"When a time entry is created\" },\n { value: \"timesheet.due\", label: \"When a timesheet is due\" },\n { value: \"budget.overrun\", label: \"When a project runs over budget\" },\n { value: \"manual\", label: \"Manually\" },\n];\n\nfunction triggerLabel(value: string): string {\n return TRIGGERS.find((t) => t.value === value)?.label ?? value;\n}\n\nexport function WorkflowsPage() {\n const { data, loading, error, refetch } = useListConfigurationsQuery({\n variables: { scope: TimeCampusConfigScope.Workspace },\n });\n\n const [setConfiguration, { loading: creating, error: createError }] =\n useSetConfigurationMutation();\n const [deleteConfiguration] = useDeleteConfigurationMutation();\n\n const [modalOpen, setModalOpen] = useState(false);\n const [name, setName] = useState(\"\");\n const [trigger, setTrigger] = useState(TRIGGERS[0].value);\n const [description, setDescription] = useState(\"\");\n const [busyId, setBusyId] = useState<string | null>(null);\n\n const configurations = data?.configurations ?? [];\n const workflows = configurations.filter((c: ConfigurationFieldsFragment) =>\n c.key.startsWith(\"workflow.\"),\n );\n\n const resetForm = () => {\n setName(\"\");\n setTrigger(TRIGGERS[0].value);\n setDescription(\"\");\n };\n\n const openModal = () => {\n resetForm();\n setModalOpen(true);\n };\n\n const handleCreate = async () => {\n if (!name.trim()) return;\n const id =\n typeof crypto !== \"undefined\" && \"randomUUID\" in crypto\n ? crypto.randomUUID()\n : `${Date.now()}`;\n const trimmedDescription = description.trim();\n await setConfiguration({\n variables: {\n input: {\n key: `workflow.${id}`,\n scope: TimeCampusConfigScope.Workspace,\n value: {\n name: name.trim(),\n trigger,\n status: \"active\",\n description: trimmedDescription || undefined,\n createdAt: new Date().toISOString(),\n },\n description: trimmedDescription || undefined,\n },\n },\n });\n await refetch();\n setModalOpen(false);\n resetForm();\n };\n\n const handleToggle = async (wf: ConfigurationFieldsFragment) => {\n setBusyId(wf.id);\n try {\n const val = (wf.value as Record<string, unknown>) ?? {};\n const nextStatus =\n (val.status as string) === \"active\" ? \"inactive\" : \"active\";\n await setConfiguration({\n variables: {\n input: {\n key: wf.key,\n scope: TimeCampusConfigScope.Workspace,\n value: { ...val, status: nextStatus },\n description: wf.description ?? undefined,\n },\n },\n });\n await refetch();\n } finally {\n setBusyId(null);\n }\n };\n\n const handleDelete = async (id: string) => {\n setBusyId(id);\n try {\n await deleteConfiguration({ variables: { id } });\n await refetch();\n } finally {\n setBusyId(null);\n }\n };\n\n if (loading) {\n return (\n <PageLayout title=\"Workflows\" description=\"Automate repetitive tasks\">\n <LoadingSkeleton variant=\"list\" count={5} />\n </PageLayout>\n );\n }\n\n if (error) {\n return (\n <PageLayout title=\"Workflows\" description=\"Automate repetitive tasks\">\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 workflow configurations. Please try again later.\n </div>\n </PageLayout>\n );\n }\n\n const createButton = (\n <button\n type=\"button\"\n onClick={openModal}\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 Create Workflow\n </button>\n );\n\n return (\n <PageLayout\n title=\"Workflows\"\n description=\"Automate repetitive tasks\"\n purpose=\"Put the busywork on autopilot — auto-start timers, submit timesheets, flag overruns — by wiring up rules that react to what happens in TimeCampus. Each workflow you define is saved to your workspace; the advanced trigger/condition builder is on the roadmap.\"\n actions={createButton}\n >\n {workflows.length === 0 ? (\n <EmptyState\n illustration=\"empty-generic\"\n title=\"No workflows configured yet\"\n description=\"Create your first automation to handle repetitive time-tracking tasks — workflows are stored as workspace configurations and appear here.\"\n action={createButton}\n />\n ) : (\n <GlassCard treatment=\"glass\" glow className=\"overflow-hidden\">\n <div className=\"divide-y divide-border-seam\">\n {workflows.map((wf: ConfigurationFieldsFragment) => {\n const val = (wf.value as Record<string, unknown>) ?? {};\n const wfName =\n (val.name as string) ?? wf.key.replace(\"workflow.\", \"\");\n const status = (val.status as string) ?? \"inactive\";\n const wfTrigger = (val.trigger as string) ?? \"manual\";\n\n return (\n <div\n key={wf.id}\n className=\"flex items-center justify-between p-4 transition-colors hover:bg-bg-sunken/50\"\n >\n <div className=\"flex min-w-0 items-center gap-3\">\n <div className=\"flex h-9 w-9 items-center justify-center rounded-lg bg-action-primary-bg/10\">\n <GitBranch className=\"h-4 w-4 text-primary\" />\n </div>\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {wfName}\n </p>\n <p className=\"text-xs text-text-secondary\">\n <Zap className=\"inline h-3 w-3\" />{\" \"}\n {triggerLabel(wfTrigger)}\n </p>\n </div>\n </div>\n <div className=\"flex items-center gap-2\">\n <span\n className={`inline-flex rounded-full px-2 py-0.5 text-xs font-medium ${getStatusColor(status)}`}\n >\n {status}\n </span>\n <button\n type=\"button\"\n aria-label={\n status === \"active\"\n ? `Deactivate ${wfName}`\n : `Activate ${wfName}`\n }\n disabled={busyId === wf.id}\n onClick={() => void handleToggle(wf)}\n className=\"rounded-md p-1.5 text-text-secondary hover:bg-bg-sunken hover:text-text-primary disabled:opacity-50\"\n >\n <Power className=\"h-4 w-4\" />\n </button>\n <button\n type=\"button\"\n aria-label={`Delete ${wfName}`}\n disabled={busyId === wf.id}\n onClick={() => void handleDelete(wf.id)}\n className=\"rounded-md p-1.5 text-text-secondary hover:bg-bg-sunken hover:text-status-error-text disabled:opacity-50\"\n >\n <Trash2 className=\"h-4 w-4\" />\n </button>\n </div>\n </div>\n );\n })}\n </div>\n </GlassCard>\n )}\n\n <Modal\n open={modalOpen}\n onClose={() => setModalOpen(false)}\n title=\"Create Workflow\"\n description=\"Define an automation rule for your workspace.\"\n footer={\n <>\n <button\n type=\"button\"\n onClick={() => setModalOpen(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 type=\"button\"\n onClick={handleCreate}\n disabled={creating || !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 ? \"Creating...\" : \"Create Workflow\"}\n </button>\n </>\n }\n >\n {createError && (\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 create workflow. Please try again.\n </div>\n )}\n <div className=\"space-y-4\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Workflow Name\n </label>\n <input\n type=\"text\"\n placeholder=\"e.g. Auto-submit my weekly timesheet\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n className=\"mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary\"\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Trigger\n </label>\n <select\n value={trigger}\n onChange={(e) => setTrigger(e.target.value)}\n className=\"mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n >\n {TRIGGERS.map((t) => (\n <option key={t.value} value={t.value}>\n {t.label}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Description\n <span className=\"ml-1 font-normal text-text-secondary\">\n (optional)\n </span>\n </label>\n <textarea\n placeholder=\"What this workflow should do\"\n rows={3}\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n className=\"mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary\"\n />\n </div>\n </div>\n </Modal>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -22,10 +22,22 @@ var i = e()(t(n((e) => ({
22
22
  tickTimer: () => e((e) => ({ activeTimer: e.activeTimer ? {
23
23
  ...e.activeTimer,
24
24
  seconds: e.activeTimer.seconds + 1
25
+ } : null })),
26
+ syncTimerFromServer: (t) => e(() => ({ activeTimer: t ? {
27
+ isRunning: !0,
28
+ seconds: t.elapsedSeconds,
29
+ timeEntryId: t.timeEntryId,
30
+ projectId: t.projectId,
31
+ taskId: t.taskId,
32
+ description: t.description
25
33
  } : null }))
26
34
  }), {
27
35
  name: "timecampus-store",
28
- storage: r()
36
+ storage: r(),
37
+ partialize: (e) => ({
38
+ viewMode: e.viewMode,
39
+ activeModule: e.activeModule
40
+ })
29
41
  }), { name: "timecampus-store" }));
30
42
  //#endregion
31
43
  export { i as useTimeCampusStore };
@@ -1 +1 @@
1
- {"version":3,"file":"timecampusStore.js","names":[],"sources":["../../../src/timecampus/store/timecampusStore.ts"],"sourcesContent":["/**\n * TimeCampus Zustand Store\n * UI state management for the TimeCampus microfrontend.\n * @module timecampus\n */\n\nimport { create } from \"zustand\";\nimport { devtools, persist } from \"zustand/middleware\";\nimport { createProfileScopedStorage } from \"@burdenoff/fe-libs/shared/utils\";\n\ninterface TimeCampusStore {\n // View\n viewMode: \"grid\" | \"list\" | \"table\";\n setViewMode: (mode: \"grid\" | \"list\" | \"table\") => void;\n\n // Active section\n activeModule: string;\n setActiveModule: (module: string) => void;\n\n // Search\n searchQuery: string;\n setSearchQuery: (query: string) => void;\n\n // Selection\n selectedItems: string[];\n toggleSelection: (id: string) => void;\n clearSelection: () => void;\n\n // Timer\n activeTimer: {\n isRunning: boolean;\n seconds: number;\n projectId?: string;\n taskId?: string;\n description?: string;\n } | null;\n startTimer: (config?: {\n projectId?: string;\n taskId?: string;\n description?: string;\n }) => void;\n stopTimer: () => void;\n tickTimer: () => void;\n}\n\nexport const useTimeCampusStore = create<TimeCampusStore>()(\n devtools(\n persist(\n (set) => ({\n viewMode: \"grid\",\n setViewMode: (mode) => set({ viewMode: mode }),\n\n activeModule: \"dashboard\",\n setActiveModule: (module) => set({ activeModule: module }),\n\n searchQuery: \"\",\n setSearchQuery: (query) => set({ searchQuery: query }),\n\n selectedItems: [],\n toggleSelection: (id) =>\n set((state) => ({\n selectedItems: state.selectedItems.includes(id)\n ? state.selectedItems.filter((i) => i !== id)\n : [...state.selectedItems, id],\n })),\n clearSelection: () => set({ selectedItems: [] }),\n\n activeTimer: null,\n startTimer: (config) =>\n set({\n activeTimer: { isRunning: true, seconds: 0, ...config },\n }),\n stopTimer: () => set({ activeTimer: null }),\n tickTimer: () =>\n set((state) => ({\n activeTimer: state.activeTimer\n ? { ...state.activeTimer, seconds: state.activeTimer.seconds + 1 }\n : null,\n })),\n }),\n { name: \"timecampus-store\", storage: createProfileScopedStorage() },\n ),\n { name: \"timecampus-store\" },\n ),\n);\n"],"mappings":";;;;AA6CA,IAAa,IAAqB,GAAyB,CACzD,EACE,GACG,OAAS;CACR,UAAU;CACV,cAAc,MAAS,EAAI,EAAE,UAAU,GAAM,CAAC;CAE9C,cAAc;CACd,kBAAkB,MAAW,EAAI,EAAE,cAAc,GAAQ,CAAC;CAE1D,aAAa;CACb,iBAAiB,MAAU,EAAI,EAAE,aAAa,GAAO,CAAC;CAEtD,eAAe,EAAE;CACjB,kBAAkB,MAChB,GAAK,OAAW,EACd,eAAe,EAAM,cAAc,SAAS,EAAG,GAC3C,EAAM,cAAc,QAAQ,MAAM,MAAM,EAAG,GAC3C,CAAC,GAAG,EAAM,eAAe,EAAG,EACjC,EAAE;CACL,sBAAsB,EAAI,EAAE,eAAe,EAAE,EAAE,CAAC;CAEhD,aAAa;CACb,aAAa,MACX,EAAI,EACF,aAAa;EAAE,WAAW;EAAM,SAAS;EAAG,GAAG;EAAQ,EACxD,CAAC;CACJ,iBAAiB,EAAI,EAAE,aAAa,MAAM,CAAC;CAC3C,iBACE,GAAK,OAAW,EACd,aAAa,EAAM,cACf;EAAE,GAAG,EAAM;EAAa,SAAS,EAAM,YAAY,UAAU;EAAG,GAChE,MACL,EAAE;CACN,GACD;CAAE,MAAM;CAAoB,SAAS,GAA4B;CAAE,CACpE,EACD,EAAE,MAAM,oBAAoB,CAC7B,CACF"}
1
+ {"version":3,"file":"timecampusStore.js","names":[],"sources":["../../../src/timecampus/store/timecampusStore.ts"],"sourcesContent":["/**\n * TimeCampus Zustand Store\n * UI state management for the TimeCampus microfrontend.\n *\n * NOTE ON THE TIMER: the backend is the single source of truth for whether a\n * timer is running (a running TimeEntry surfaced by `runningTimers`). This\n * store only holds a lightweight *display* mirror of that running timer so the\n * on-screen stopwatch can tick every second without re-querying the server.\n * Pages sync it from the backend via `syncTimerFromServer` and advance the\n * live seconds via `tickTimer`. `startTimer`/`stopTimer` here only update the\n * local display mirror — persistence is done by the Start/Stop timer\n * mutations in the pages, not by this store.\n * @module timecampus\n */\n\nimport { create } from \"zustand\";\nimport { devtools, persist } from \"zustand/middleware\";\nimport { createProfileScopedStorage } from \"@burdenoff/fe-libs/shared/utils\";\n\nexport interface TimeCampusActiveTimer {\n isRunning: boolean;\n seconds: number;\n /** Backend TimeEntry id of the running timer, when synced from the server. */\n timeEntryId?: string;\n projectId?: string;\n taskId?: string;\n description?: string;\n}\n\ninterface TimeCampusStore {\n // View\n viewMode: \"grid\" | \"list\" | \"table\";\n setViewMode: (mode: \"grid\" | \"list\" | \"table\") => void;\n\n // Active section\n activeModule: string;\n setActiveModule: (module: string) => void;\n\n // Search\n searchQuery: string;\n setSearchQuery: (query: string) => void;\n\n // Selection\n selectedItems: string[];\n toggleSelection: (id: string) => void;\n clearSelection: () => void;\n\n // Timer (display mirror of the backend running timer)\n activeTimer: TimeCampusActiveTimer | null;\n /** Optimistically show a running timer in the UI (display only). */\n startTimer: (config?: {\n projectId?: string;\n taskId?: string;\n description?: string;\n }) => void;\n /** Clear the local display timer (display only). */\n stopTimer: () => void;\n /** Advance the live seconds counter by one (called by a page-owned ticker). */\n tickTimer: () => void;\n /**\n * Reconcile the display timer with the backend running timer. Pass the\n * running timer (with its elapsed seconds) to make it authoritative, or\n * `null` to clear the display when nothing is running server-side.\n */\n syncTimerFromServer: (\n timer: {\n timeEntryId: string;\n elapsedSeconds: number;\n projectId?: string;\n taskId?: string;\n description?: string;\n } | null,\n ) => void;\n}\n\nexport const useTimeCampusStore = create<TimeCampusStore>()(\n devtools(\n persist(\n (set) => ({\n viewMode: \"grid\",\n setViewMode: (mode) => set({ viewMode: mode }),\n\n activeModule: \"dashboard\",\n setActiveModule: (module) => set({ activeModule: module }),\n\n searchQuery: \"\",\n setSearchQuery: (query) => set({ searchQuery: query }),\n\n selectedItems: [],\n toggleSelection: (id) =>\n set((state) => ({\n selectedItems: state.selectedItems.includes(id)\n ? state.selectedItems.filter((i) => i !== id)\n : [...state.selectedItems, id],\n })),\n clearSelection: () => set({ selectedItems: [] }),\n\n activeTimer: null,\n startTimer: (config) =>\n set({\n activeTimer: { isRunning: true, seconds: 0, ...config },\n }),\n stopTimer: () => set({ activeTimer: null }),\n tickTimer: () =>\n set((state) => ({\n activeTimer: state.activeTimer\n ? { ...state.activeTimer, seconds: state.activeTimer.seconds + 1 }\n : null,\n })),\n syncTimerFromServer: (timer) =>\n set(() => ({\n activeTimer: timer\n ? {\n isRunning: true,\n seconds: timer.elapsedSeconds,\n timeEntryId: timer.timeEntryId,\n projectId: timer.projectId,\n taskId: timer.taskId,\n description: timer.description,\n }\n : null,\n })),\n }),\n {\n name: \"timecampus-store\",\n storage: createProfileScopedStorage(),\n // The timer is a live mirror of server state; never rehydrate a stale\n // \"running\" timer from disk — it is re-synced from the backend on load.\n partialize: (state) => ({\n viewMode: state.viewMode,\n activeModule: state.activeModule,\n }),\n },\n ),\n { name: \"timecampus-store\" },\n ),\n);\n"],"mappings":";;;;AA2EA,IAAa,IAAqB,GAAyB,CACzD,EACE,GACG,OAAS;CACR,UAAU;CACV,cAAc,MAAS,EAAI,EAAE,UAAU,GAAM,CAAC;CAE9C,cAAc;CACd,kBAAkB,MAAW,EAAI,EAAE,cAAc,GAAQ,CAAC;CAE1D,aAAa;CACb,iBAAiB,MAAU,EAAI,EAAE,aAAa,GAAO,CAAC;CAEtD,eAAe,EAAE;CACjB,kBAAkB,MAChB,GAAK,OAAW,EACd,eAAe,EAAM,cAAc,SAAS,EAAG,GAC3C,EAAM,cAAc,QAAQ,MAAM,MAAM,EAAG,GAC3C,CAAC,GAAG,EAAM,eAAe,EAAG,EACjC,EAAE;CACL,sBAAsB,EAAI,EAAE,eAAe,EAAE,EAAE,CAAC;CAEhD,aAAa;CACb,aAAa,MACX,EAAI,EACF,aAAa;EAAE,WAAW;EAAM,SAAS;EAAG,GAAG;EAAQ,EACxD,CAAC;CACJ,iBAAiB,EAAI,EAAE,aAAa,MAAM,CAAC;CAC3C,iBACE,GAAK,OAAW,EACd,aAAa,EAAM,cACf;EAAE,GAAG,EAAM;EAAa,SAAS,EAAM,YAAY,UAAU;EAAG,GAChE,MACL,EAAE;CACL,sBAAsB,MACpB,SAAW,EACT,aAAa,IACT;EACE,WAAW;EACX,SAAS,EAAM;EACf,aAAa,EAAM;EACnB,WAAW,EAAM;EACjB,QAAQ,EAAM;EACd,aAAa,EAAM;EACpB,GACD,MACL,EAAE;CACN,GACD;CACE,MAAM;CACN,SAAS,GAA4B;CAGrC,aAAa,OAAW;EACtB,UAAU,EAAM;EAChB,cAAc,EAAM;EACrB;CACF,CACF,EACD,EAAE,MAAM,oBAAoB,CAC7B,CACF"}
@@ -5,34 +5,44 @@ function n(...n) {
5
5
  return t(e(n));
6
6
  }
7
7
  function r(e) {
8
+ let t = Math.floor(e / 3600), n = Math.floor(e % 3600 / 60), r = e % 60;
9
+ return t > 0 ? `${t}h ${n}m ${r}s` : n > 0 ? `${n}m ${r}s` : `${r}s`;
10
+ }
11
+ function i(e) {
8
12
  let t = Math.floor(e);
9
13
  return `${t}h ${Math.round((e - t) * 60)}m`;
10
14
  }
11
- function i(e) {
15
+ function a(e) {
12
16
  let t = Math.floor(e / 3600), n = Math.floor(e % 3600 / 60), r = e % 60;
13
17
  return `${String(t).padStart(2, "0")}:${String(n).padStart(2, "0")}:${String(r).padStart(2, "0")}`;
14
18
  }
15
- function a(e, t = "USD") {
19
+ function o(e, t = "USD") {
16
20
  return new Intl.NumberFormat("en-US", {
17
21
  style: "currency",
18
22
  currency: t
19
23
  }).format(e);
20
24
  }
21
- function o(e) {
25
+ function s(e) {
22
26
  return new Date(e).toLocaleDateString("en-US", {
23
27
  year: "numeric",
24
28
  month: "short",
25
29
  day: "numeric"
26
30
  });
27
31
  }
28
- function s(e) {
29
- let t = /* @__PURE__ */ new Date(), n = new Date(e), r = t.getTime() - n.getTime(), i = Math.floor(r / 6e4), a = Math.floor(r / 36e5), s = Math.floor(r / 864e5);
30
- return i < 1 ? "Just now" : i < 60 ? `${i}m ago` : a < 24 ? `${a}h ago` : s < 7 ? `${s}d ago` : o(e);
31
- }
32
32
  function c(e) {
33
- return e.split(" ").map((e) => e[0]).join("").toUpperCase().slice(0, 2);
33
+ return new Date(e).toLocaleTimeString("en-US", {
34
+ hour: "2-digit",
35
+ minute: "2-digit"
36
+ });
34
37
  }
35
38
  function l(e) {
39
+ let t = /* @__PURE__ */ new Date(), n = new Date(e), r = t.getTime() - n.getTime(), i = Math.floor(r / 6e4), a = Math.floor(r / 36e5), o = Math.floor(r / 864e5);
40
+ return i < 1 ? "Just now" : i < 60 ? `${i}m ago` : a < 24 ? `${a}h ago` : o < 7 ? `${o}d ago` : s(e);
41
+ }
42
+ function u(e) {
43
+ return e.split(" ").map((e) => e[0]).join("").toUpperCase().slice(0, 2);
44
+ }
45
+ function d(e) {
36
46
  return {
37
47
  active: "success",
38
48
  completed: "info",
@@ -58,7 +68,7 @@ function l(e) {
58
68
  flagged: "error"
59
69
  }[e] || "default";
60
70
  }
61
- function u(e) {
71
+ function f(e) {
62
72
  return {
63
73
  low: "info",
64
74
  medium: "warning",
@@ -68,6 +78,6 @@ function u(e) {
68
78
  }[e] || "default";
69
79
  }
70
80
  //#endregion
71
- export { n as cn, a as formatCurrency, o as formatDate, r as formatHours, s as formatRelativeTime, i as formatTimerDuration, c as getInitials, u as getPriorityColor, l as getStatusColor };
81
+ export { n as cn, o as formatCurrency, s as formatDate, r as formatDuration, i as formatHours, l as formatRelativeTime, c as formatTime, a as formatTimerDuration, u as getInitials, f as getPriorityColor, d as getStatusColor };
72
82
 
73
83
  //# sourceMappingURL=helpers.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.js","names":[],"sources":["../../../src/timecampus/utils/helpers.ts"],"sourcesContent":["/**\n * TimeCampus Utility Helpers\n * Common formatting, styling, and utility functions.\n * @module timecampus\n */\n\nimport { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport type BadgeVariant =\n | \"success\"\n | \"warning\"\n | \"error\"\n | \"info\"\n | \"default\"\n | \"muted\";\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n\nexport function formatDuration(seconds: number): string {\n const h = Math.floor(seconds / 3600);\n const m = Math.floor((seconds % 3600) / 60);\n const s = seconds % 60;\n if (h > 0) return `${h}h ${m}m ${s}s`;\n if (m > 0) return `${m}m ${s}s`;\n return `${s}s`;\n}\n\nexport function formatHours(hours: number): string {\n const h = Math.floor(hours);\n const m = Math.round((hours - h) * 60);\n return `${h}h ${m}m`;\n}\n\nexport function formatTimerDuration(seconds: number): string {\n const h = Math.floor(seconds / 3600);\n const m = Math.floor((seconds % 3600) / 60);\n const s = seconds % 60;\n return `${String(h).padStart(2, \"0\")}:${String(m).padStart(2, \"0\")}:${String(s).padStart(2, \"0\")}`;\n}\n\nexport function formatCurrency(amount: number, currency = \"USD\"): string {\n return new Intl.NumberFormat(\"en-US\", { style: \"currency\", currency }).format(\n amount,\n );\n}\n\nexport function formatNumber(num: number): string {\n return new Intl.NumberFormat(\"en-US\").format(num);\n}\n\nexport function formatDate(date: string | Date): string {\n return new Date(date).toLocaleDateString(\"en-US\", {\n year: \"numeric\",\n month: \"short\",\n day: \"numeric\",\n });\n}\n\nexport function formatTime(date: string | Date): string {\n return new Date(date).toLocaleTimeString(\"en-US\", {\n hour: \"2-digit\",\n minute: \"2-digit\",\n });\n}\n\nexport function formatRelativeTime(date: string | Date): string {\n const now = new Date();\n const d = new Date(date);\n const diffMs = now.getTime() - d.getTime();\n const diffMins = Math.floor(diffMs / 60000);\n const diffHours = Math.floor(diffMs / 3600000);\n const diffDays = Math.floor(diffMs / 86400000);\n if (diffMins < 1) return \"Just now\";\n if (diffMins < 60) return `${diffMins}m ago`;\n if (diffHours < 24) return `${diffHours}h ago`;\n if (diffDays < 7) return `${diffDays}d ago`;\n return formatDate(date);\n}\n\nexport function getInitials(name: string): string {\n return name\n .split(\" \")\n .map((n) => n[0])\n .join(\"\")\n .toUpperCase()\n .slice(0, 2);\n}\n\nexport function truncateText(text: string, maxLength: number): string {\n if (text.length <= maxLength) return text;\n return text.slice(0, maxLength) + \"...\";\n}\n\nexport function generateId(): string {\n return Math.random().toString(36).substring(2) + Date.now().toString(36);\n}\n\nexport function sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nexport function getStatusColor(status: string): BadgeVariant {\n const colors: Record<string, BadgeVariant> = {\n active: \"success\",\n completed: \"info\",\n \"in-progress\": \"warning\",\n pending: \"warning\",\n archived: \"muted\",\n \"on-hold\": \"warning\",\n draft: \"muted\",\n approved: \"success\",\n rejected: \"error\",\n error: \"error\",\n running: \"success\",\n paused: \"warning\",\n cancelled: \"muted\",\n online: \"success\",\n away: \"warning\",\n busy: \"error\",\n offline: \"muted\",\n inactive: \"muted\",\n submitted: \"info\",\n reimbursed: \"success\",\n \"revision-requested\": \"warning\",\n flagged: \"error\",\n };\n return colors[status] || \"default\";\n}\n\nexport function getPriorityColor(priority: string): BadgeVariant {\n const colors: Record<string, BadgeVariant> = {\n low: \"info\",\n medium: \"warning\",\n high: \"warning\",\n urgent: \"error\",\n critical: \"error\",\n };\n return colors[priority] || \"default\";\n}\n"],"mappings":";;;AAiBA,SAAgB,EAAG,GAAG,GAAsB;AAC1C,QAAO,EAAQ,EAAK,EAAO,CAAC;;AAY9B,SAAgB,EAAY,GAAuB;CACjD,IAAM,IAAI,KAAK,MAAM,EAAM;AAE3B,QAAO,GAAG,EAAE,IADF,KAAK,OAAO,IAAQ,KAAK,GAAG,CACpB;;AAGpB,SAAgB,EAAoB,GAAyB;CAC3D,IAAM,IAAI,KAAK,MAAM,IAAU,KAAK,EAC9B,IAAI,KAAK,MAAO,IAAU,OAAQ,GAAG,EACrC,IAAI,IAAU;AACpB,QAAO,GAAG,OAAO,EAAE,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,OAAO,EAAE,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,OAAO,EAAE,CAAC,SAAS,GAAG,IAAI;;AAGlG,SAAgB,EAAe,GAAgB,IAAW,OAAe;AACvE,QAAO,IAAI,KAAK,aAAa,SAAS;EAAE,OAAO;EAAY;EAAU,CAAC,CAAC,OACrE,EACD;;AAOH,SAAgB,EAAW,GAA6B;AACtD,QAAO,IAAI,KAAK,EAAK,CAAC,mBAAmB,SAAS;EAChD,MAAM;EACN,OAAO;EACP,KAAK;EACN,CAAC;;AAUJ,SAAgB,EAAmB,GAA6B;CAC9D,IAAM,oBAAM,IAAI,MAAM,EAChB,IAAI,IAAI,KAAK,EAAK,EAClB,IAAS,EAAI,SAAS,GAAG,EAAE,SAAS,EACpC,IAAW,KAAK,MAAM,IAAS,IAAM,EACrC,IAAY,KAAK,MAAM,IAAS,KAAQ,EACxC,IAAW,KAAK,MAAM,IAAS,MAAS;AAK9C,QAJI,IAAW,IAAU,aACrB,IAAW,KAAW,GAAG,EAAS,SAClC,IAAY,KAAW,GAAG,EAAU,SACpC,IAAW,IAAU,GAAG,EAAS,SAC9B,EAAW,EAAK;;AAGzB,SAAgB,EAAY,GAAsB;AAChD,QAAO,EACJ,MAAM,IAAI,CACV,KAAK,MAAM,EAAE,GAAG,CAChB,KAAK,GAAG,CACR,aAAa,CACb,MAAM,GAAG,EAAE;;AAgBhB,SAAgB,EAAe,GAA8B;AAyB3D,QAxB6C;EAC3C,QAAQ;EACR,WAAW;EACX,eAAe;EACf,SAAS;EACT,UAAU;EACV,WAAW;EACX,OAAO;EACP,UAAU;EACV,UAAU;EACV,OAAO;EACP,SAAS;EACT,QAAQ;EACR,WAAW;EACX,QAAQ;EACR,MAAM;EACN,MAAM;EACN,SAAS;EACT,UAAU;EACV,WAAW;EACX,YAAY;EACZ,sBAAsB;EACtB,SAAS;EACV,CACa,MAAW;;AAG3B,SAAgB,EAAiB,GAAgC;AAQ/D,QAP6C;EAC3C,KAAK;EACL,QAAQ;EACR,MAAM;EACN,QAAQ;EACR,UAAU;EACX,CACa,MAAa"}
1
+ {"version":3,"file":"helpers.js","names":[],"sources":["../../../src/timecampus/utils/helpers.ts"],"sourcesContent":["/**\n * TimeCampus Utility Helpers\n * Common formatting, styling, and utility functions.\n * @module timecampus\n */\n\nimport { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport type BadgeVariant =\n | \"success\"\n | \"warning\"\n | \"error\"\n | \"info\"\n | \"default\"\n | \"muted\";\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n\nexport function formatDuration(seconds: number): string {\n const h = Math.floor(seconds / 3600);\n const m = Math.floor((seconds % 3600) / 60);\n const s = seconds % 60;\n if (h > 0) return `${h}h ${m}m ${s}s`;\n if (m > 0) return `${m}m ${s}s`;\n return `${s}s`;\n}\n\nexport function formatHours(hours: number): string {\n const h = Math.floor(hours);\n const m = Math.round((hours - h) * 60);\n return `${h}h ${m}m`;\n}\n\nexport function formatTimerDuration(seconds: number): string {\n const h = Math.floor(seconds / 3600);\n const m = Math.floor((seconds % 3600) / 60);\n const s = seconds % 60;\n return `${String(h).padStart(2, \"0\")}:${String(m).padStart(2, \"0\")}:${String(s).padStart(2, \"0\")}`;\n}\n\nexport function formatCurrency(amount: number, currency = \"USD\"): string {\n return new Intl.NumberFormat(\"en-US\", { style: \"currency\", currency }).format(\n amount,\n );\n}\n\nexport function formatNumber(num: number): string {\n return new Intl.NumberFormat(\"en-US\").format(num);\n}\n\nexport function formatDate(date: string | Date): string {\n return new Date(date).toLocaleDateString(\"en-US\", {\n year: \"numeric\",\n month: \"short\",\n day: \"numeric\",\n });\n}\n\nexport function formatTime(date: string | Date): string {\n return new Date(date).toLocaleTimeString(\"en-US\", {\n hour: \"2-digit\",\n minute: \"2-digit\",\n });\n}\n\nexport function formatRelativeTime(date: string | Date): string {\n const now = new Date();\n const d = new Date(date);\n const diffMs = now.getTime() - d.getTime();\n const diffMins = Math.floor(diffMs / 60000);\n const diffHours = Math.floor(diffMs / 3600000);\n const diffDays = Math.floor(diffMs / 86400000);\n if (diffMins < 1) return \"Just now\";\n if (diffMins < 60) return `${diffMins}m ago`;\n if (diffHours < 24) return `${diffHours}h ago`;\n if (diffDays < 7) return `${diffDays}d ago`;\n return formatDate(date);\n}\n\nexport function getInitials(name: string): string {\n return name\n .split(\" \")\n .map((n) => n[0])\n .join(\"\")\n .toUpperCase()\n .slice(0, 2);\n}\n\nexport function truncateText(text: string, maxLength: number): string {\n if (text.length <= maxLength) return text;\n return text.slice(0, maxLength) + \"...\";\n}\n\nexport function generateId(): string {\n return Math.random().toString(36).substring(2) + Date.now().toString(36);\n}\n\nexport function sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nexport function getStatusColor(status: string): BadgeVariant {\n const colors: Record<string, BadgeVariant> = {\n active: \"success\",\n completed: \"info\",\n \"in-progress\": \"warning\",\n pending: \"warning\",\n archived: \"muted\",\n \"on-hold\": \"warning\",\n draft: \"muted\",\n approved: \"success\",\n rejected: \"error\",\n error: \"error\",\n running: \"success\",\n paused: \"warning\",\n cancelled: \"muted\",\n online: \"success\",\n away: \"warning\",\n busy: \"error\",\n offline: \"muted\",\n inactive: \"muted\",\n submitted: \"info\",\n reimbursed: \"success\",\n \"revision-requested\": \"warning\",\n flagged: \"error\",\n };\n return colors[status] || \"default\";\n}\n\nexport function getPriorityColor(priority: string): BadgeVariant {\n const colors: Record<string, BadgeVariant> = {\n low: \"info\",\n medium: \"warning\",\n high: \"warning\",\n urgent: \"error\",\n critical: \"error\",\n };\n return colors[priority] || \"default\";\n}\n"],"mappings":";;;AAiBA,SAAgB,EAAG,GAAG,GAAsB;AAC1C,QAAO,EAAQ,EAAK,EAAO,CAAC;;AAG9B,SAAgB,EAAe,GAAyB;CACtD,IAAM,IAAI,KAAK,MAAM,IAAU,KAAK,EAC9B,IAAI,KAAK,MAAO,IAAU,OAAQ,GAAG,EACrC,IAAI,IAAU;AAGpB,QAFI,IAAI,IAAU,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,KAC/B,IAAI,IAAU,GAAG,EAAE,IAAI,EAAE,KACtB,GAAG,EAAE;;AAGd,SAAgB,EAAY,GAAuB;CACjD,IAAM,IAAI,KAAK,MAAM,EAAM;AAE3B,QAAO,GAAG,EAAE,IADF,KAAK,OAAO,IAAQ,KAAK,GAAG,CACpB;;AAGpB,SAAgB,EAAoB,GAAyB;CAC3D,IAAM,IAAI,KAAK,MAAM,IAAU,KAAK,EAC9B,IAAI,KAAK,MAAO,IAAU,OAAQ,GAAG,EACrC,IAAI,IAAU;AACpB,QAAO,GAAG,OAAO,EAAE,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,OAAO,EAAE,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,OAAO,EAAE,CAAC,SAAS,GAAG,IAAI;;AAGlG,SAAgB,EAAe,GAAgB,IAAW,OAAe;AACvE,QAAO,IAAI,KAAK,aAAa,SAAS;EAAE,OAAO;EAAY;EAAU,CAAC,CAAC,OACrE,EACD;;AAOH,SAAgB,EAAW,GAA6B;AACtD,QAAO,IAAI,KAAK,EAAK,CAAC,mBAAmB,SAAS;EAChD,MAAM;EACN,OAAO;EACP,KAAK;EACN,CAAC;;AAGJ,SAAgB,EAAW,GAA6B;AACtD,QAAO,IAAI,KAAK,EAAK,CAAC,mBAAmB,SAAS;EAChD,MAAM;EACN,QAAQ;EACT,CAAC;;AAGJ,SAAgB,EAAmB,GAA6B;CAC9D,IAAM,oBAAM,IAAI,MAAM,EAChB,IAAI,IAAI,KAAK,EAAK,EAClB,IAAS,EAAI,SAAS,GAAG,EAAE,SAAS,EACpC,IAAW,KAAK,MAAM,IAAS,IAAM,EACrC,IAAY,KAAK,MAAM,IAAS,KAAQ,EACxC,IAAW,KAAK,MAAM,IAAS,MAAS;AAK9C,QAJI,IAAW,IAAU,aACrB,IAAW,KAAW,GAAG,EAAS,SAClC,IAAY,KAAW,GAAG,EAAU,SACpC,IAAW,IAAU,GAAG,EAAS,SAC9B,EAAW,EAAK;;AAGzB,SAAgB,EAAY,GAAsB;AAChD,QAAO,EACJ,MAAM,IAAI,CACV,KAAK,MAAM,EAAE,GAAG,CAChB,KAAK,GAAG,CACR,aAAa,CACb,MAAM,GAAG,EAAE;;AAgBhB,SAAgB,EAAe,GAA8B;AAyB3D,QAxB6C;EAC3C,QAAQ;EACR,WAAW;EACX,eAAe;EACf,SAAS;EACT,UAAU;EACV,WAAW;EACX,OAAO;EACP,UAAU;EACV,UAAU;EACV,OAAO;EACP,SAAS;EACT,QAAQ;EACR,WAAW;EACX,QAAQ;EACR,MAAM;EACN,MAAM;EACN,SAAS;EACT,UAAU;EACV,WAAW;EACX,YAAY;EACZ,sBAAsB;EACtB,SAAS;EACV,CACa,MAAW;;AAG3B,SAAgB,EAAiB,GAAgC;AAQ/D,QAP6C;EAC3C,KAAK;EACL,QAAQ;EACR,MAAM;EACN,QAAQ;EACR,UAAU;EACX,CACa,MAAa"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-timecampus",
3
- "version": "2026.626.2",
3
+ "version": "2026.703.2",
4
4
  "description": "Burdenoff TimeCampus Microfrontend - AI-powered time management and productivity platform",
5
5
  "type": "module",
6
6
  "files": [