@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.
- package/dist/generated/wspace-types.js +28 -0
- package/dist/generated/wspace-types.js.map +1 -0
- package/dist/index.js +4 -4
- package/dist/timecampus/components/Modal.js +65 -0
- package/dist/timecampus/components/Modal.js.map +1 -0
- package/dist/timecampus/components/StatusBadge.js +28 -0
- package/dist/timecampus/components/StatusBadge.js.map +1 -0
- package/dist/timecampus/components/Toast.js +87 -0
- package/dist/timecampus/components/Toast.js.map +1 -0
- package/dist/timecampus/index.js +1 -1
- package/dist/timecampus/pages/HomePage.js +143 -135
- package/dist/timecampus/pages/HomePage.js.map +1 -1
- package/dist/timecampus/pages/applets/AppletStorePage.js +14 -19
- package/dist/timecampus/pages/applets/AppletStorePage.js.map +1 -1
- package/dist/timecampus/pages/applets/InstalledAppletsPage.js +49 -37
- package/dist/timecampus/pages/applets/InstalledAppletsPage.js.map +1 -1
- package/dist/timecampus/pages/application-usage/ApplicationUsagePage.js +315 -87
- package/dist/timecampus/pages/application-usage/ApplicationUsagePage.js.map +1 -1
- package/dist/timecampus/pages/channels/ChannelsPage.js +340 -46
- package/dist/timecampus/pages/channels/ChannelsPage.js.map +1 -1
- package/dist/timecampus/pages/client-portal/ClientPortalPage.js +366 -118
- package/dist/timecampus/pages/client-portal/ClientPortalPage.js.map +1 -1
- package/dist/timecampus/pages/dashboard/ReportsPage.js +70 -37
- package/dist/timecampus/pages/dashboard/ReportsPage.js.map +1 -1
- package/dist/timecampus/pages/email-tracking/EmailTrackingPage.js +366 -60
- package/dist/timecampus/pages/email-tracking/EmailTrackingPage.js.map +1 -1
- package/dist/timecampus/pages/expense-tracking/ExpenseTrackingPage.js +461 -87
- package/dist/timecampus/pages/expense-tracking/ExpenseTrackingPage.js.map +1 -1
- package/dist/timecampus/pages/geofencing/GeofencingPage.js +314 -55
- package/dist/timecampus/pages/geofencing/GeofencingPage.js.map +1 -1
- package/dist/timecampus/pages/industry-solutions/IndustrySolutionsPage.js +30 -27
- package/dist/timecampus/pages/industry-solutions/IndustrySolutionsPage.js.map +1 -1
- package/dist/timecampus/pages/location-tracking/LocationTrackingPage.js +269 -74
- package/dist/timecampus/pages/location-tracking/LocationTrackingPage.js.map +1 -1
- package/dist/timecampus/pages/meetings/ActionItemFormModal.js +127 -0
- package/dist/timecampus/pages/meetings/ActionItemFormModal.js.map +1 -0
- package/dist/timecampus/pages/meetings/MeetingCreatePage.js +62 -52
- package/dist/timecampus/pages/meetings/MeetingCreatePage.js.map +1 -1
- package/dist/timecampus/pages/meetings/MeetingDetailPage.js +361 -74
- package/dist/timecampus/pages/meetings/MeetingDetailPage.js.map +1 -1
- package/dist/timecampus/pages/meetings/MeetingFormModal.js +156 -0
- package/dist/timecampus/pages/meetings/MeetingFormModal.js.map +1 -0
- package/dist/timecampus/pages/meetings/MeetingsPage.js +175 -74
- package/dist/timecampus/pages/meetings/MeetingsPage.js.map +1 -1
- package/dist/timecampus/pages/meetings/ParticipantFormModal.js +105 -0
- package/dist/timecampus/pages/meetings/ParticipantFormModal.js.map +1 -0
- package/dist/timecampus/pages/pomodoro/PomodoroPage.js +307 -67
- package/dist/timecampus/pages/pomodoro/PomodoroPage.js.map +1 -1
- package/dist/timecampus/pages/projects/ProjectCreatePage.js +51 -48
- package/dist/timecampus/pages/projects/ProjectCreatePage.js.map +1 -1
- package/dist/timecampus/pages/projects/ProjectDetailPage.js +248 -51
- package/dist/timecampus/pages/projects/ProjectDetailPage.js.map +1 -1
- package/dist/timecampus/pages/projects/ProjectsPage.js +354 -87
- package/dist/timecampus/pages/projects/ProjectsPage.js.map +1 -1
- package/dist/timecampus/pages/projects/TasksPage.js +367 -65
- package/dist/timecampus/pages/projects/TasksPage.js.map +1 -1
- package/dist/timecampus/pages/resource-planning/ResourcePlanningPage.js +674 -104
- package/dist/timecampus/pages/resource-planning/ResourcePlanningPage.js.map +1 -1
- package/dist/timecampus/pages/team/TeamAnalyticsPage.js +46 -34
- package/dist/timecampus/pages/team/TeamAnalyticsPage.js.map +1 -1
- package/dist/timecampus/pages/team/TeamMembersPage.js +93 -69
- package/dist/timecampus/pages/team/TeamMembersPage.js.map +1 -1
- package/dist/timecampus/pages/team/TeamPage.js +4 -3
- package/dist/timecampus/pages/team/TeamPage.js.map +1 -1
- package/dist/timecampus/pages/templates/TemplateDetailPage.js +72 -53
- package/dist/timecampus/pages/templates/TemplateDetailPage.js.map +1 -1
- package/dist/timecampus/pages/templates/TemplatesPage.js +40 -35
- package/dist/timecampus/pages/templates/TemplatesPage.js.map +1 -1
- package/dist/timecampus/pages/time/TimeEntriesPage.js +484 -97
- package/dist/timecampus/pages/time/TimeEntriesPage.js.map +1 -1
- package/dist/timecampus/pages/time/TimeTrackingPage.js +210 -99
- package/dist/timecampus/pages/time/TimeTrackingPage.js.map +1 -1
- package/dist/timecampus/pages/time/TimerPage.js +229 -97
- package/dist/timecampus/pages/time/TimerPage.js.map +1 -1
- package/dist/timecampus/pages/time-approval/TimeApprovalPage.js +315 -110
- package/dist/timecampus/pages/time-approval/TimeApprovalPage.js.map +1 -1
- package/dist/timecampus/pages/widgets/WidgetDetailPage.js +158 -76
- package/dist/timecampus/pages/widgets/WidgetDetailPage.js.map +1 -1
- package/dist/timecampus/pages/widgets/WidgetsPage.js +181 -52
- package/dist/timecampus/pages/widgets/WidgetsPage.js.map +1 -1
- package/dist/timecampus/pages/workflows/WorkflowsPage.js +212 -61
- package/dist/timecampus/pages/workflows/WorkflowsPage.js.map +1 -1
- package/dist/timecampus/store/timecampusStore.js +13 -1
- package/dist/timecampus/store/timecampusStore.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WidgetsPage.js","names":[],"sources":["../../../../src/timecampus/pages/widgets/WidgetsPage.tsx"],"sourcesContent":["import { LayoutGrid, Plus, Star } from \"lucide-react\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { EmptyState } from \"../../components/EmptyState\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport { useTimeCampus } from \"../../providers/TimeCampusProvider\";\nimport {\n useListConfigurationsQuery,\n type ConfigurationFieldsFragment,\n} from \"@/generated/wspace-operations\";\n\nexport function WidgetsPage() {\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 widgets (key starts with 'widget.')\n const widgets = configurations.filter((c: ConfigurationFieldsFragment) =>\n c.key.startsWith(\"widget.\"),\n );\n\n if (loading) {\n return (\n <PageLayout\n title=\"Widgets\"\n description=\"Customize your dashboard with widgets\"\n >\n <LoadingSkeleton variant=\"card\" count={6} />\n </PageLayout>\n );\n }\n\n if (error) {\n return (\n <PageLayout\n title=\"Widgets\"\n description=\"Customize your dashboard with widgets\"\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 widget configurations. Please try again later.\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title=\"Widgets\"\n description=\"Customize your dashboard with widgets\"\n purpose=\"Build a dashboard that shows what you care about — pick and arrange widgets for timers, projects, focus and team stats so your most important numbers are the first thing you see.\"\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 Widget\n </button>\n }\n >\n {widgets.length === 0 ? (\n <EmptyState\n illustration=\"empty-generic\"\n title=\"No widgets configured yet\"\n description=\"Add your first widget to start customising your dashboard — widgets 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 Widget\n </button>\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {widgets.map((widget: ConfigurationFieldsFragment) => {\n const val = widget.value as Record<string, unknown>;\n const name =\n (val.name as string) ?? widget.key.replace(\"widget.\", \"\");\n const category = (val.category as string) ?? \"General\";\n const size = (val.size as string) ?? \"medium\";\n const isInstalled = (val.isInstalled as boolean) ?? false;\n const rating = (val.rating as number) ?? 0;\n const downloads = (val.downloads as number) ?? 0;\n\n return (\n <div\n key={widget.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 <div className=\"flex h-10 w-10 items-center justify-center rounded-lg bg-action-primary-bg/10\">\n <LayoutGrid className=\"h-5 w-5 text-primary\" />\n </div>\n <div>\n <p className=\"text-sm font-semibold text-text-primary\">\n {name}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {category} · {size}\n </p>\n </div>\n </div>\n {isInstalled && (\n <span className=\"inline-flex rounded-full bg-status-success-bg px-2 py-0.5 text-xs font-medium text-status-success-text\">\n Installed\n </span>\n )}\n </div>\n <div className=\"mt-3 flex items-center justify-between text-xs text-text-secondary\">\n <div className=\"flex items-center gap-1\">\n <Star className=\"h-3 w-3 fill-amber-400 text-status-warning-text\" />\n <span>{rating}</span>\n </div>\n <span>{downloads} downloads</span>\n </div>\n </div>\n );\n })}\n </div>\n )}\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;AAUA,SAAgB,IAAc;CAC5B,IAAM,EAAE,iBAAc,OAAO,GAAe,EAEtC,EAAE,SAAM,YAAS,aAAU,EAA2B,EAC1D,WAAW,EAAE,OAAO,aAAoB,EACzC,CAAC,EAKI,KAHiB,GAAM,kBAAkB,EAAE,EAGlB,QAAQ,MACrC,EAAE,IAAI,WAAW,UAAU,CAC5B;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;EACR,SACE,kBAAC,UAAD;GAAQ,WAAU;aAAlB,CACE,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA,EAAA,gBAErB;;YAGV,EAAQ,WAAW,IAClB,kBAAC,GAAD;GACE,cAAa;GACb,OAAM;GACN,aAAY;GACZ,QACE,kBAAC,UAAD;IAAQ,WAAU;cAAlB,CACE,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA,EAAA,gBAErB;;GAEX,CAAA,GAEF,kBAAC,OAAD;GAAK,WAAU;aACZ,EAAQ,KAAK,MAAwC;IACpD,IAAM,IAAM,EAAO,OACb,IACH,EAAI,QAAmB,EAAO,IAAI,QAAQ,WAAW,GAAG,EACrD,IAAY,EAAI,YAAuB,WACvC,IAAQ,EAAI,QAAmB,UAC/B,IAAe,EAAI,eAA2B,IAC9C,IAAU,EAAI,UAAqB,GACnC,IAAa,EAAI,aAAwB;AAE/C,WACE,kBAAC,OAAD;KAEE,WAAU;eAFZ,CAIE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,OAAD;QAAK,WAAU;kBACb,kBAAC,GAAD,EAAY,WAAU,wBAAyB,CAAA;QAC3C,CAAA,EACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;QAAG,WAAU;kBACV;QACC,CAAA,EACJ,kBAAC,KAAD;QAAG,WAAU;kBAAb;SACG;SAAS;SAAW;SACnB;UACA,EAAA,CAAA,CACF;UACL,KACC,kBAAC,QAAD;OAAM,WAAU;iBAAyG;OAElH,CAAA,CAEL;SACN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD,EAAM,WAAU,mDAAoD,CAAA,EACpE,kBAAC,QAAD,EAAA,UAAO,GAAc,CAAA,CACjB;UACN,kBAAC,QAAD,EAAA,UAAA,CAAO,GAAU,aAAiB,EAAA,CAAA,CAC9B;QACF;OA9BC,EAAO,GA8BR;KAER;GACE,CAAA;EAEG,CAAA"}
|
|
1
|
+
{"version":3,"file":"WidgetsPage.js","names":[],"sources":["../../../../src/timecampus/pages/widgets/WidgetsPage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { LayoutGrid, Plus, Trash2 } from \"lucide-react\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { EmptyState } from \"../../components/EmptyState\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport { Modal } from \"../../components/Modal\";\nimport { useTimeCampusNavigate } from \"../../providers/TimeCampusProvider\";\nimport {\n useListConfigurationsQuery,\n useSetConfigurationMutation,\n useDeleteConfigurationMutation,\n type ConfigurationFieldsFragment,\n} from \"@/generated/wspace-operations\";\nimport { TimeCampusConfigScope } from \"@/generated/wspace-types\";\n\nconst WIDGET_TYPES = [\n { value: \"timer\", label: \"Active timer\" },\n { value: \"project-progress\", label: \"Project progress\" },\n { value: \"focus-score\", label: \"Focus score\" },\n { value: \"team-stats\", label: \"Team stats\" },\n { value: \"billable-hours\", label: \"Billable hours\" },\n { value: \"recent-entries\", label: \"Recent time entries\" },\n];\n\nfunction widgetTypeLabel(value: string): string {\n return WIDGET_TYPES.find((t) => t.value === value)?.label ?? value;\n}\n\nexport function WidgetsPage() {\n const navigate = useTimeCampusNavigate();\n\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 [type, setType] = useState(WIDGET_TYPES[0].value);\n const [description, setDescription] = useState(\"\");\n const [deletingId, setDeletingId] = useState<string | null>(null);\n\n const configurations = data?.configurations ?? [];\n const widgets = configurations.filter((c: ConfigurationFieldsFragment) =>\n c.key.startsWith(\"widget.\"),\n );\n\n const resetForm = () => {\n setName(\"\");\n setType(WIDGET_TYPES[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: `widget.${id}`,\n scope: TimeCampusConfigScope.Workspace,\n value: {\n name: name.trim(),\n type,\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 handleDelete = async (id: string) => {\n setDeletingId(id);\n try {\n await deleteConfiguration({ variables: { id } });\n await refetch();\n } finally {\n setDeletingId(null);\n }\n };\n\n if (loading) {\n return (\n <PageLayout\n title=\"Widgets\"\n description=\"Customize your dashboard with widgets\"\n >\n <LoadingSkeleton variant=\"card\" count={6} />\n </PageLayout>\n );\n }\n\n if (error) {\n return (\n <PageLayout\n title=\"Widgets\"\n description=\"Customize your dashboard with widgets\"\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 widget configurations. Please try again later.\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title=\"Widgets\"\n description=\"Customize your dashboard with widgets\"\n purpose=\"Build a dashboard that shows what you care about — define widgets for timers, projects, focus and team stats so your most important numbers are the first thing you see. Each widget you create is saved to your workspace configuration.\"\n actions={\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 Widget\n </button>\n }\n >\n {widgets.length === 0 ? (\n <EmptyState\n illustration=\"empty-generic\"\n title=\"No widgets configured yet\"\n description=\"Add your first widget to start customising your dashboard — widgets are stored as workspace configurations and appear here.\"\n action={\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 Widget\n </button>\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {widgets.map((widget: ConfigurationFieldsFragment) => {\n const val = widget.value as Record<string, unknown>;\n const widgetName =\n (val.name as string) ?? widget.key.replace(\"widget.\", \"\");\n const widgetType = (val.type as string) ?? \"timer\";\n const widgetDescription =\n (val.description as string) ?? widget.description ?? \"\";\n\n return (\n <div\n key={widget.id}\n role=\"button\"\n tabIndex={0}\n onClick={() => navigate(`/widgets/${widget.id}`)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n navigate(`/widgets/${widget.id}`);\n }\n }}\n className=\"cursor-pointer rounded-xl border border-border-seam bg-gloss-card p-5 shadow-pop transition-colors hover:bg-bg-sunken/50\"\n >\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"flex min-w-0 items-center gap-3\">\n <div className=\"flex h-10 w-10 items-center justify-center rounded-lg bg-action-primary-bg/10\">\n <LayoutGrid className=\"h-5 w-5 text-primary\" />\n </div>\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-semibold text-text-primary\">\n {widgetName}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {widgetTypeLabel(widgetType)}\n </p>\n </div>\n </div>\n <button\n type=\"button\"\n aria-label={`Delete ${widgetName}`}\n disabled={deletingId === widget.id}\n onClick={(e) => {\n e.stopPropagation();\n void handleDelete(widget.id);\n }}\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 {widgetDescription && (\n <p className=\"mt-3 line-clamp-2 text-xs text-text-secondary\">\n {widgetDescription}\n </p>\n )}\n </div>\n );\n })}\n </div>\n )}\n\n <Modal\n open={modalOpen}\n onClose={() => setModalOpen(false)}\n title=\"Create Widget\"\n description=\"Define a dashboard widget 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 Widget\"}\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 widget. 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 Widget Name\n </label>\n <input\n type=\"text\"\n placeholder=\"e.g. This week's billable hours\"\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 Metric\n </label>\n <select\n value={type}\n onChange={(e) => setType(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 {WIDGET_TYPES.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 widget shows\"\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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"}
|
|
@@ -1,90 +1,241 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
1
|
+
import { getStatusColor as e } from "../../utils/helpers.js";
|
|
2
|
+
import { LoadingSkeleton as t } from "../../components/LoadingSkeleton.js";
|
|
3
|
+
import { useDeleteConfigurationMutation as n, useListConfigurationsQuery as r, useSetConfigurationMutation as i } from "../../../generated/wspace-operations.js";
|
|
4
|
+
import { PageLayout as a } from "../../components/PageLayout.js";
|
|
5
|
+
import { EmptyState as o } from "../../components/EmptyState.js";
|
|
6
|
+
import { TimeCampusConfigScope as s } from "../../../generated/wspace-types.js";
|
|
7
|
+
import { Modal as c } from "../../components/Modal.js";
|
|
8
|
+
import { useState as l } from "react";
|
|
9
|
+
import { GitBranch as u, Plus as d, Power as f, Trash2 as p, Zap as m } from "lucide-react";
|
|
10
|
+
import { Fragment as h, jsx as g, jsxs as _ } from "react/jsx-runtime";
|
|
11
|
+
import { GlassCard as v } from "@burdenoff/fe-libs/ui";
|
|
10
12
|
//#region src/timecampus/pages/workflows/WorkflowsPage.tsx
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
var y = [
|
|
14
|
+
{
|
|
15
|
+
value: "timer.started",
|
|
16
|
+
label: "When a timer starts"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
value: "timer.stopped",
|
|
20
|
+
label: "When a timer stops"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
value: "entry.created",
|
|
24
|
+
label: "When a time entry is created"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
value: "timesheet.due",
|
|
28
|
+
label: "When a timesheet is due"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
value: "budget.overrun",
|
|
32
|
+
label: "When a project runs over budget"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
value: "manual",
|
|
36
|
+
label: "Manually"
|
|
37
|
+
}
|
|
38
|
+
];
|
|
39
|
+
function b(e) {
|
|
40
|
+
return y.find((t) => t.value === e)?.label ?? e;
|
|
41
|
+
}
|
|
42
|
+
function x() {
|
|
43
|
+
let { data: x, loading: S, error: C, refetch: w } = r({ variables: { scope: s.Workspace } }), [T, { loading: E, error: D }] = i(), [O] = n(), [k, A] = l(!1), [j, M] = l(""), [N, P] = l(y[0].value), [F, I] = l(""), [L, R] = l(null), z = (x?.configurations ?? []).filter((e) => e.key.startsWith("workflow.")), B = () => {
|
|
44
|
+
M(""), P(y[0].value), I("");
|
|
45
|
+
}, V = () => {
|
|
46
|
+
B(), A(!0);
|
|
47
|
+
}, H = async () => {
|
|
48
|
+
if (!j.trim()) return;
|
|
49
|
+
let e = typeof crypto < "u" && "randomUUID" in crypto ? crypto.randomUUID() : `${Date.now()}`, t = F.trim();
|
|
50
|
+
await T({ variables: { input: {
|
|
51
|
+
key: `workflow.${e}`,
|
|
52
|
+
scope: s.Workspace,
|
|
53
|
+
value: {
|
|
54
|
+
name: j.trim(),
|
|
55
|
+
trigger: N,
|
|
56
|
+
status: "active",
|
|
57
|
+
description: t || void 0,
|
|
58
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
59
|
+
},
|
|
60
|
+
description: t || void 0
|
|
61
|
+
} } }), await w(), A(!1), B();
|
|
62
|
+
}, U = async (e) => {
|
|
63
|
+
R(e.id);
|
|
64
|
+
try {
|
|
65
|
+
let t = e.value ?? {}, n = t.status === "active" ? "inactive" : "active";
|
|
66
|
+
await T({ variables: { input: {
|
|
67
|
+
key: e.key,
|
|
68
|
+
scope: s.Workspace,
|
|
69
|
+
value: {
|
|
70
|
+
...t,
|
|
71
|
+
status: n
|
|
72
|
+
},
|
|
73
|
+
description: e.description ?? void 0
|
|
74
|
+
} } }), await w();
|
|
75
|
+
} finally {
|
|
76
|
+
R(null);
|
|
77
|
+
}
|
|
78
|
+
}, W = async (e) => {
|
|
79
|
+
R(e);
|
|
80
|
+
try {
|
|
81
|
+
await O({ variables: { id: e } }), await w();
|
|
82
|
+
} finally {
|
|
83
|
+
R(null);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
if (S) return /* @__PURE__ */ g(a, {
|
|
14
87
|
title: "Workflows",
|
|
15
88
|
description: "Automate repetitive tasks",
|
|
16
|
-
children: /* @__PURE__ */
|
|
89
|
+
children: /* @__PURE__ */ g(t, {
|
|
17
90
|
variant: "list",
|
|
18
91
|
count: 5
|
|
19
92
|
})
|
|
20
|
-
})
|
|
93
|
+
});
|
|
94
|
+
if (C) return /* @__PURE__ */ g(a, {
|
|
21
95
|
title: "Workflows",
|
|
22
96
|
description: "Automate repetitive tasks",
|
|
23
|
-
children: /* @__PURE__ */
|
|
97
|
+
children: /* @__PURE__ */ g("div", {
|
|
24
98
|
className: "rounded-lg border border-status-error-bg/40 bg-status-error-bg p-4 text-sm text-status-error-text",
|
|
25
99
|
children: "Failed to load workflow configurations. Please try again later."
|
|
26
100
|
})
|
|
27
|
-
})
|
|
101
|
+
});
|
|
102
|
+
let G = /* @__PURE__ */ _("button", {
|
|
103
|
+
type: "button",
|
|
104
|
+
onClick: V,
|
|
105
|
+
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",
|
|
106
|
+
children: [/* @__PURE__ */ g(d, { className: "h-4 w-4" }), "Create Workflow"]
|
|
107
|
+
});
|
|
108
|
+
return /* @__PURE__ */ _(a, {
|
|
28
109
|
title: "Workflows",
|
|
29
110
|
description: "Automate repetitive tasks",
|
|
30
|
-
purpose: "Put the busywork on autopilot — auto-start timers, submit timesheets,
|
|
31
|
-
actions:
|
|
32
|
-
|
|
33
|
-
children: [/* @__PURE__ */ l(s, { className: "h-4 w-4" }), "Create Workflow"]
|
|
34
|
-
}),
|
|
35
|
-
children: g.length === 0 ? /* @__PURE__ */ l(a, {
|
|
111
|
+
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.",
|
|
112
|
+
actions: G,
|
|
113
|
+
children: [z.length === 0 ? /* @__PURE__ */ g(o, {
|
|
36
114
|
illustration: "empty-generic",
|
|
37
115
|
title: "No workflows configured yet",
|
|
38
116
|
description: "Create your first automation to handle repetitive time-tracking tasks — workflows are stored as workspace configurations and appear here.",
|
|
39
|
-
action:
|
|
40
|
-
|
|
41
|
-
children: [/* @__PURE__ */ l(s, { className: "h-4 w-4" }), "Create Workflow"]
|
|
42
|
-
})
|
|
43
|
-
}) : /* @__PURE__ */ l(d, {
|
|
117
|
+
action: G
|
|
118
|
+
}) : /* @__PURE__ */ g(v, {
|
|
44
119
|
treatment: "glass",
|
|
45
120
|
glow: !0,
|
|
46
121
|
className: "overflow-hidden",
|
|
47
|
-
children: /* @__PURE__ */
|
|
48
|
-
className: "divide-y divide-border",
|
|
49
|
-
children:
|
|
50
|
-
let n =
|
|
51
|
-
return /* @__PURE__ */
|
|
52
|
-
className: "flex items-center justify-between p-4 hover:bg-bg-sunken/50
|
|
53
|
-
children: [/* @__PURE__ */
|
|
54
|
-
className: "flex items-center gap-3",
|
|
55
|
-
children: [/* @__PURE__ */
|
|
122
|
+
children: /* @__PURE__ */ g("div", {
|
|
123
|
+
className: "divide-y divide-border-seam",
|
|
124
|
+
children: z.map((t) => {
|
|
125
|
+
let n = t.value ?? {}, r = n.name ?? t.key.replace("workflow.", ""), i = n.status ?? "inactive", a = n.trigger ?? "manual";
|
|
126
|
+
return /* @__PURE__ */ _("div", {
|
|
127
|
+
className: "flex items-center justify-between p-4 transition-colors hover:bg-bg-sunken/50",
|
|
128
|
+
children: [/* @__PURE__ */ _("div", {
|
|
129
|
+
className: "flex min-w-0 items-center gap-3",
|
|
130
|
+
children: [/* @__PURE__ */ g("div", {
|
|
56
131
|
className: "flex h-9 w-9 items-center justify-center rounded-lg bg-action-primary-bg/10",
|
|
57
|
-
children: /* @__PURE__ */
|
|
58
|
-
}), /* @__PURE__ */
|
|
59
|
-
className: "
|
|
60
|
-
children:
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}), /* @__PURE__ */ u("div", {
|
|
72
|
-
className: "flex items-center gap-4",
|
|
73
|
-
children: [/* @__PURE__ */ u("div", {
|
|
74
|
-
className: "text-right text-xs text-text-secondary",
|
|
75
|
-
children: [/* @__PURE__ */ u("p", { children: [d, " runs"] }), /* @__PURE__ */ u("p", { children: [f, "% success"] })]
|
|
76
|
-
}), /* @__PURE__ */ l("span", {
|
|
77
|
-
className: `inline-flex rounded-full px-2 py-0.5 text-xs font-medium ${t(i)}`,
|
|
78
|
-
children: i
|
|
132
|
+
children: /* @__PURE__ */ g(u, { className: "h-4 w-4 text-primary" })
|
|
133
|
+
}), /* @__PURE__ */ _("div", {
|
|
134
|
+
className: "min-w-0",
|
|
135
|
+
children: [/* @__PURE__ */ g("p", {
|
|
136
|
+
className: "truncate text-sm font-medium text-text-primary",
|
|
137
|
+
children: r
|
|
138
|
+
}), /* @__PURE__ */ _("p", {
|
|
139
|
+
className: "text-xs text-text-secondary",
|
|
140
|
+
children: [
|
|
141
|
+
/* @__PURE__ */ g(m, { className: "inline h-3 w-3" }),
|
|
142
|
+
" ",
|
|
143
|
+
b(a)
|
|
144
|
+
]
|
|
145
|
+
})]
|
|
79
146
|
})]
|
|
147
|
+
}), /* @__PURE__ */ _("div", {
|
|
148
|
+
className: "flex items-center gap-2",
|
|
149
|
+
children: [
|
|
150
|
+
/* @__PURE__ */ g("span", {
|
|
151
|
+
className: `inline-flex rounded-full px-2 py-0.5 text-xs font-medium ${e(i)}`,
|
|
152
|
+
children: i
|
|
153
|
+
}),
|
|
154
|
+
/* @__PURE__ */ g("button", {
|
|
155
|
+
type: "button",
|
|
156
|
+
"aria-label": i === "active" ? `Deactivate ${r}` : `Activate ${r}`,
|
|
157
|
+
disabled: L === t.id,
|
|
158
|
+
onClick: () => void U(t),
|
|
159
|
+
className: "rounded-md p-1.5 text-text-secondary hover:bg-bg-sunken hover:text-text-primary disabled:opacity-50",
|
|
160
|
+
children: /* @__PURE__ */ g(f, { className: "h-4 w-4" })
|
|
161
|
+
}),
|
|
162
|
+
/* @__PURE__ */ g("button", {
|
|
163
|
+
type: "button",
|
|
164
|
+
"aria-label": `Delete ${r}`,
|
|
165
|
+
disabled: L === t.id,
|
|
166
|
+
onClick: () => void W(t.id),
|
|
167
|
+
className: "rounded-md p-1.5 text-text-secondary hover:bg-bg-sunken hover:text-status-error-text disabled:opacity-50",
|
|
168
|
+
children: /* @__PURE__ */ g(p, { className: "h-4 w-4" })
|
|
169
|
+
})
|
|
170
|
+
]
|
|
80
171
|
})]
|
|
81
|
-
},
|
|
172
|
+
}, t.id);
|
|
82
173
|
})
|
|
83
174
|
})
|
|
84
|
-
})
|
|
175
|
+
}), /* @__PURE__ */ _(c, {
|
|
176
|
+
open: k,
|
|
177
|
+
onClose: () => A(!1),
|
|
178
|
+
title: "Create Workflow",
|
|
179
|
+
description: "Define an automation rule for your workspace.",
|
|
180
|
+
footer: /* @__PURE__ */ _(h, { children: [/* @__PURE__ */ g("button", {
|
|
181
|
+
type: "button",
|
|
182
|
+
onClick: () => A(!1),
|
|
183
|
+
className: "rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50",
|
|
184
|
+
children: "Cancel"
|
|
185
|
+
}), /* @__PURE__ */ g("button", {
|
|
186
|
+
type: "button",
|
|
187
|
+
onClick: H,
|
|
188
|
+
disabled: E || !j.trim(),
|
|
189
|
+
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",
|
|
190
|
+
children: E ? "Creating..." : "Create Workflow"
|
|
191
|
+
})] }),
|
|
192
|
+
children: [D && /* @__PURE__ */ g("div", {
|
|
193
|
+
className: "mb-4 rounded-lg border border-status-error-bg/40 bg-status-error-bg p-3 text-sm text-status-error-text",
|
|
194
|
+
children: "Failed to create workflow. Please try again."
|
|
195
|
+
}), /* @__PURE__ */ _("div", {
|
|
196
|
+
className: "space-y-4",
|
|
197
|
+
children: [
|
|
198
|
+
/* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
|
|
199
|
+
className: "block text-sm font-medium text-text-primary",
|
|
200
|
+
children: "Workflow Name"
|
|
201
|
+
}), /* @__PURE__ */ g("input", {
|
|
202
|
+
type: "text",
|
|
203
|
+
placeholder: "e.g. Auto-submit my weekly timesheet",
|
|
204
|
+
value: j,
|
|
205
|
+
onChange: (e) => M(e.target.value),
|
|
206
|
+
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"
|
|
207
|
+
})] }),
|
|
208
|
+
/* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
|
|
209
|
+
className: "block text-sm font-medium text-text-primary",
|
|
210
|
+
children: "Trigger"
|
|
211
|
+
}), /* @__PURE__ */ g("select", {
|
|
212
|
+
value: N,
|
|
213
|
+
onChange: (e) => P(e.target.value),
|
|
214
|
+
className: "mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary",
|
|
215
|
+
children: y.map((e) => /* @__PURE__ */ g("option", {
|
|
216
|
+
value: e.value,
|
|
217
|
+
children: e.label
|
|
218
|
+
}, e.value))
|
|
219
|
+
})] }),
|
|
220
|
+
/* @__PURE__ */ _("div", { children: [/* @__PURE__ */ _("label", {
|
|
221
|
+
className: "block text-sm font-medium text-text-primary",
|
|
222
|
+
children: ["Description", /* @__PURE__ */ g("span", {
|
|
223
|
+
className: "ml-1 font-normal text-text-secondary",
|
|
224
|
+
children: "(optional)"
|
|
225
|
+
})]
|
|
226
|
+
}), /* @__PURE__ */ g("textarea", {
|
|
227
|
+
placeholder: "What this workflow should do",
|
|
228
|
+
rows: 3,
|
|
229
|
+
value: F,
|
|
230
|
+
onChange: (e) => I(e.target.value),
|
|
231
|
+
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"
|
|
232
|
+
})] })
|
|
233
|
+
]
|
|
234
|
+
})]
|
|
235
|
+
})]
|
|
85
236
|
});
|
|
86
237
|
}
|
|
87
238
|
//#endregion
|
|
88
|
-
export {
|
|
239
|
+
export { x as WorkflowsPage };
|
|
89
240
|
|
|
90
241
|
//# sourceMappingURL=WorkflowsPage.js.map
|
|
@@ -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} ·\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:
|
|
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"}
|
package/package.json
CHANGED