@burdenoff/microfe-timecampus 2026.703.6 → 2026.707.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/generated/wspace-operations.js +1023 -1514
- package/dist/generated/wspace-operations.js.map +1 -1
- package/dist/generated/wspace-types.js +12 -18
- package/dist/generated/wspace-types.js.map +1 -1
- package/dist/index.js +4 -4
- package/dist/timecampus/TimeCampusRoot.js +130 -198
- package/dist/timecampus/TimeCampusRoot.js.map +1 -1
- package/dist/timecampus/TimeCampusRoutes.js +96 -164
- package/dist/timecampus/TimeCampusRoutes.js.map +1 -1
- package/dist/timecampus/components/index.js +2 -2
- package/dist/timecampus/pages/application-usage/ApplicationUsagePage.js +7 -7
- package/dist/timecampus/pages/client-portal/ClientPortalPage.js +6 -6
- package/dist/timecampus/pages/expense-tracking/ExpenseTrackingPage.js +8 -8
- package/dist/timecampus/pages/geofencing/GeofencingPage.js +6 -6
- package/dist/timecampus/pages/lifetime/FiniteLifePage.js +91 -91
- package/dist/timecampus/pages/lifetime/FiniteLifePage.js.map +1 -1
- package/dist/timecampus/pages/lifetime/LifeBalancePage.js +18 -18
- package/dist/timecampus/pages/lifetime/LifeTimelinePage.js +7 -7
- package/dist/timecampus/pages/lifetime/ValuesGoalsPage.js +1 -1
- package/dist/timecampus/pages/location-tracking/LocationTrackingPage.js +6 -6
- package/dist/timecampus/pages/projects/ProjectDetailPage.js +7 -7
- package/dist/timecampus/pages/resource-planning/ResourcePlanningPage.js +212 -212
- package/dist/timecampus/pages/resource-planning/ResourcePlanningPage.js.map +1 -1
- package/dist/timecampus/pages/team/TeamMembersPage.js +4 -4
- package/dist/timecampus/pages/time/TimeTrackingPage.js +143 -143
- package/dist/timecampus/pages/time/TimeTrackingPage.js.map +1 -1
- package/dist/timecampus/pages/time-approval/TimeApprovalPage.js +1 -1
- package/dist/timecampus/pages/timeline/TimelinePage.js +1 -1
- package/dist/timecampus/pages/timeline/TimelinePage.js.map +1 -1
- package/dist/timecampus/pages/work-patterns/WorkPatternsPage.js +87 -93
- package/dist/timecampus/pages/work-patterns/WorkPatternsPage.js.map +1 -1
- package/package.json +1 -1
- package/dist/timecampus/pages/applets/AppletStorePage.js +0 -167
- package/dist/timecampus/pages/applets/AppletStorePage.js.map +0 -1
- package/dist/timecampus/pages/applets/InstalledAppletsPage.js +0 -162
- package/dist/timecampus/pages/applets/InstalledAppletsPage.js.map +0 -1
- package/dist/timecampus/pages/application-usage/ApplicationUsageSettingsPage.js +0 -133
- package/dist/timecampus/pages/application-usage/ApplicationUsageSettingsPage.js.map +0 -1
- package/dist/timecampus/pages/channels/ChannelsPage.js +0 -418
- package/dist/timecampus/pages/channels/ChannelsPage.js.map +0 -1
- package/dist/timecampus/pages/email-tracking/EmailSettingsPage.js +0 -142
- package/dist/timecampus/pages/email-tracking/EmailSettingsPage.js.map +0 -1
- package/dist/timecampus/pages/focus-score/FocusScoreSettingsPage.js +0 -119
- package/dist/timecampus/pages/focus-score/FocusScoreSettingsPage.js.map +0 -1
- package/dist/timecampus/pages/geofencing/GeofencingSettingsPage.js +0 -110
- package/dist/timecampus/pages/geofencing/GeofencingSettingsPage.js.map +0 -1
- package/dist/timecampus/pages/industry-solutions/IndustrySolutionsPage.js +0 -83
- package/dist/timecampus/pages/industry-solutions/IndustrySolutionsPage.js.map +0 -1
- package/dist/timecampus/pages/location-tracking/LocationPrivacySettingsPage.js +0 -198
- package/dist/timecampus/pages/location-tracking/LocationPrivacySettingsPage.js.map +0 -1
- package/dist/timecampus/pages/team-activity/TeamActivitySettingsPage.js +0 -124
- package/dist/timecampus/pages/team-activity/TeamActivitySettingsPage.js.map +0 -1
- package/dist/timecampus/pages/templates/TemplateApplicationPage.js +0 -88
- package/dist/timecampus/pages/templates/TemplateApplicationPage.js.map +0 -1
- package/dist/timecampus/pages/templates/TemplateDetailPage.js +0 -135
- package/dist/timecampus/pages/templates/TemplateDetailPage.js.map +0 -1
- package/dist/timecampus/pages/templates/TemplatesPage.js +0 -191
- package/dist/timecampus/pages/templates/TemplatesPage.js.map +0 -1
- package/dist/timecampus/pages/widgets/WidgetDetailPage.js +0 -196
- package/dist/timecampus/pages/widgets/WidgetDetailPage.js.map +0 -1
- package/dist/timecampus/pages/widgets/WidgetsPage.js +0 -297
- package/dist/timecampus/pages/widgets/WidgetsPage.js.map +0 -1
- package/dist/timecampus/pages/work-patterns/WorkPatternsSettingsPage.js +0 -133
- package/dist/timecampus/pages/work-patterns/WorkPatternsSettingsPage.js.map +0 -1
- package/dist/timecampus/pages/workflows/WorkflowsPage.js +0 -311
- package/dist/timecampus/pages/workflows/WorkflowsPage.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WorkPatternsPage.js","names":[],"sources":["../../../../src/timecampus/pages/work-patterns/WorkPatternsPage.tsx"],"sourcesContent":["import {\n TrendingUp,\n Clock,\n Zap,\n Brain,\n AlertTriangle,\n ShieldCheck,\n Lightbulb,\n ArrowRight,\n} from \"lucide-react\";\nimport { GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport {\n PageLayout,\n StatCard,\n ChartCard,\n Heatmap,\n TrendChart,\n StatusBadge,\n EmptyState,\n LoadingSkeleton,\n type HeatmapValue,\n} from \"../../components\";\nimport {\n useTimeCampus,\n useTimeCampusNavigate,\n} from \"../../providers/TimeCampusProvider\";\nimport {\n useListWorkPatternsQuery,\n useGetWorkPatternSummaryQuery,\n type WorkPatternFieldsFragment,\n} from \"@/generated/wspace-operations\";\nimport {\n WorkPatternInsightType,\n type WorkPatternInsight,\n} from \"@/generated/wspace-types\";\n\nconst DAY_LABELS = [\"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\", \"Sun\"] as const;\n\n/** JS `getDay()` is 0=Sun..6=Sat; remap so Monday is index 0. */\nfunction dayOfWeekIndex(d: Date): number {\n return (d.getDay() + 6) % 7;\n}\n\n/** Monday (local midnight) of the week containing `d`. */\nfunction weekStart(d: Date): Date {\n const s = new Date(d);\n s.setDate(d.getDate() - dayOfWeekIndex(d));\n s.setHours(0, 0, 0, 0);\n return s;\n}\n\nfunction shortDate(iso: string): string {\n const d = new Date(iso);\n if (Number.isNaN(d.getTime())) return \"\";\n return d.toLocaleDateString(undefined, { month: \"short\", day: \"numeric\" });\n}\n\n// Self-reported ordinals mapped onto a shared 1 (worst) – 5 (best) scale so\n// energy and mood can be trended together. Only real, fetched enum values map;\n// anything else stays null (a gap in the line, never a fabricated point).\nconst ENERGY_SCORE: Record<string, number> = {\n VERY_LOW: 1,\n LOW: 2,\n MEDIUM: 3,\n HIGH: 4,\n VERY_HIGH: 5,\n};\nconst MOOD_SCORE: Record<string, number> = {\n VERY_NEGATIVE: 1,\n NEGATIVE: 2,\n NEUTRAL: 3,\n POSITIVE: 4,\n VERY_POSITIVE: 5,\n};\n\nconst HIGH_STRESS = new Set([\"HIGH\", \"VERY_HIGH\"]);\nconst LOW_ENERGY = new Set([\"LOW\", \"VERY_LOW\"]);\n\nconst formatHoursValue = (v: number) => `${v.toFixed(1)}h`;\n\ntype BurnoutTone = \"danger\" | \"warning\" | \"success\";\n\nconst INSIGHT_VARIANT: Record<\n WorkPatternInsightType,\n \"success\" | \"error\" | \"warning\" | \"info\" | \"muted\"\n> = {\n [WorkPatternInsightType.Positive]: \"success\",\n [WorkPatternInsightType.Negative]: \"error\",\n [WorkPatternInsightType.Warning]: \"warning\",\n [WorkPatternInsightType.Recommendation]: \"info\",\n [WorkPatternInsightType.Neutral]: \"muted\",\n};\n\nconst INSIGHT_LABEL: Record<WorkPatternInsightType, string> = {\n [WorkPatternInsightType.Positive]: \"Positive\",\n [WorkPatternInsightType.Negative]: \"Negative\",\n [WorkPatternInsightType.Warning]: \"Warning\",\n [WorkPatternInsightType.Recommendation]: \"Tip\",\n [WorkPatternInsightType.Neutral]: \"Neutral\",\n};\n\nexport function WorkPatternsPage() {\n const { workspaceId = \"\" } = useTimeCampus();\n const navigate = useTimeCampusNavigate();\n\n const {\n data: patternsData,\n loading: patternsLoading,\n error: patternsError,\n } = useListWorkPatternsQuery({\n variables: { limit: 30 },\n skip: !workspaceId,\n });\n\n const {\n data: summaryData,\n loading: summaryLoading,\n error: summaryError,\n } = useGetWorkPatternSummaryQuery({\n skip: !workspaceId,\n });\n\n const loading = patternsLoading || summaryLoading;\n const error = patternsError || summaryError;\n const summary = summaryData?.workPatternSummary;\n const patterns = patternsData?.workPatterns ?? [];\n const insights = summary?.insights ?? [];\n\n // Chronological order for time-based visuals; the list itself is newest-first.\n const chronological = [...patterns].sort((a, b) => {\n const ta = new Date(a.date).getTime();\n const tb = new Date(b.date).getTime();\n return (Number.isNaN(ta) ? 0 : ta) - (Number.isNaN(tb) ? 0 : tb);\n });\n\n // Derive stats from the latest pattern entry\n const latestPattern = patterns[0];\n const productivityScore =\n summary?.averageProductivity != null\n ? `${Math.round(summary.averageProductivity)}%`\n : \"--\";\n const focusHours =\n latestPattern?.focusHours != null\n ? `${latestPattern.focusHours.toFixed(1)}h`\n : \"--\";\n const totalHours = latestPattern\n ? (\n latestPattern.focusHours +\n latestPattern.meetingHours +\n latestPattern.breakHours\n ).toFixed(0)\n : \"8\";\n const deepWorkSessions =\n latestPattern?.deepWorkSessions != null\n ? String(latestPattern.deepWorkSessions)\n : \"--\";\n const mostProductiveDay = summary?.mostProductiveDay ?? \"--\";\n\n // ---- Heatmap: focus (productive) hours by weekday × week -----------------\n const weekKeys: number[] = [];\n for (const p of chronological) {\n const d = new Date(p.date);\n if (Number.isNaN(d.getTime())) continue;\n const key = weekStart(d).getTime();\n if (!weekKeys.includes(key)) weekKeys.push(key);\n }\n const selectedWeeks = weekKeys.slice(-6); // most recent 6 weeks\n const columnLabels = selectedWeeks.map((ts) =>\n shortDate(new Date(ts).toISOString()),\n );\n const heatmapData: HeatmapValue[][] = DAY_LABELS.map(() =>\n selectedWeeks.map<HeatmapValue>(() => null),\n );\n for (const p of chronological) {\n const d = new Date(p.date);\n if (Number.isNaN(d.getTime())) continue;\n const weekIdx = selectedWeeks.indexOf(weekStart(d).getTime());\n if (weekIdx < 0) continue;\n heatmapData[dayOfWeekIndex(d)][weekIdx] = p.focusHours;\n }\n\n // ---- Energy & mood trend --------------------------------------------------\n const trendData = chronological.map((p) => ({\n date: shortDate(p.date),\n energy:\n p.energyLevel != null ? (ENERGY_SCORE[p.energyLevel] ?? null) : null,\n mood: p.mood != null ? (MOOD_SCORE[p.mood] ?? null) : null,\n }));\n const hasEnergyMood = trendData.some(\n (d) => d.energy != null || d.mood != null,\n );\n\n // ---- Burnout signal (derived from real stress/energy + backend insights) --\n const recent = patterns.slice(0, 7); // most recent logged days\n const windowSize = recent.length;\n const highStressDays = recent.filter(\n (p: WorkPatternFieldsFragment) =>\n p.stressLevel != null && HIGH_STRESS.has(p.stressLevel),\n ).length;\n const lowEnergyDays = recent.filter(\n (p: WorkPatternFieldsFragment) =>\n p.energyLevel != null && LOW_ENERGY.has(p.energyLevel),\n ).length;\n const warningInsight = insights.find(\n (i: WorkPatternInsight) =>\n i.type === WorkPatternInsightType.Warning ||\n i.type === WorkPatternInsightType.Negative,\n );\n const half = Math.ceil(Math.max(windowSize, 1) / 2);\n\n let burnoutTone: BurnoutTone = \"success\";\n if (windowSize > 0 && highStressDays >= half) burnoutTone = \"danger\";\n else if (highStressDays > 0 || lowEnergyDays >= half || warningInsight)\n burnoutTone = \"warning\";\n if (warningInsight?.type === WorkPatternInsightType.Negative)\n burnoutTone = \"danger\";\n\n const stressEvidence: string[] = [];\n if (highStressDays > 0)\n stressEvidence.push(\n `${highStressDays} of your last ${windowSize} logged day${windowSize === 1 ? \"\" : \"s\"} showed high stress`,\n );\n if (lowEnergyDays > 0)\n stressEvidence.push(`${lowEnergyDays} showed low energy`);\n\n const burnoutHeading =\n warningInsight?.title ??\n (burnoutTone === \"danger\"\n ? \"Sustained overload detected\"\n : burnoutTone === \"warning\"\n ? \"Watch your workload\"\n : \"Balanced workload\");\n const burnoutBody =\n warningInsight?.description ??\n (burnoutTone === \"success\"\n ? \"Your recent stress and energy levels look healthy — keep protecting your focus blocks.\"\n : stressEvidence.length > 0\n ? `${stressEvidence.join(\" and \")}. Protect your deep-work time before it erodes.`\n : \"Recent signals suggest an uneven balance — schedule focus time to stay ahead of overload.\");\n const burnoutRecommendation = warningInsight?.recommendation ?? null;\n\n const burnoutAccent =\n burnoutTone === \"danger\"\n ? \"bg-status-error-bg text-status-error-text\"\n : burnoutTone === \"warning\"\n ? \"bg-status-warning-bg text-status-warning-text\"\n : \"bg-status-success-bg text-status-success-text\";\n\n if (loading) {\n return (\n <PageLayout\n title=\"Work Patterns\"\n description=\"Analyze your work habits and productivity patterns\"\n >\n <LoadingSkeleton variant=\"card\" count={4} />\n </PageLayout>\n );\n }\n\n if (error) {\n return (\n <PageLayout\n title=\"Work Patterns\"\n description=\"Analyze your work habits and productivity patterns\"\n >\n <div className=\"rounded-xl border border-status-error-border/50 bg-status-error-bg/10 p-4 text-sm text-status-error-text\">\n Failed to load work patterns. Please try again later.\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title=\"Work Patterns\"\n description=\"Analyze your work habits and productivity patterns\"\n purpose=\"Learn how you actually work across a typical week — when you focus best, where meetings pile up, and which day is your peak — so you can schedule deep work for the hours you're sharpest.\"\n >\n <div className=\"space-y-6\">\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <StatCard\n featured\n title=\"Productivity Score\"\n value={productivityScore}\n icon={<TrendingUp className=\"h-5 w-5\" />}\n trend={\n summary?.averageProductivity != null\n ? { value: 5, label: \"vs last week\" }\n : undefined\n }\n />\n <StatCard\n title=\"Focus Hours\"\n value={focusHours}\n icon={<Brain className=\"h-5 w-5\" />}\n description={`Out of ${totalHours}h total`}\n />\n <StatCard\n title=\"Peak Day\"\n value={mostProductiveDay}\n icon={<Zap className=\"h-5 w-5\" />}\n description=\"Most productive day\"\n />\n <StatCard\n title=\"Deep Work\"\n value={`${deepWorkSessions} sessions`}\n icon={<Clock className=\"h-5 w-5\" />}\n description=\"90+ min uninterrupted\"\n />\n </div>\n\n {/* Burnout signal — honest, derived from recent stress/energy + insights */}\n <GlassCard\n treatment=\"glass\"\n glow={burnoutTone !== \"success\"}\n className=\"p-5 sm:p-6\"\n >\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-start\">\n <div\n className={`flex h-11 w-11 shrink-0 items-center justify-center rounded-xl ${burnoutAccent}`}\n >\n {burnoutTone === \"success\" ? (\n <ShieldCheck className=\"h-5 w-5\" />\n ) : (\n <AlertTriangle className=\"h-5 w-5\" />\n )}\n </div>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <h3 className=\"text-base font-semibold text-text-primary\">\n Burnout Signal\n </h3>\n <StatusBadge\n variant={\n burnoutTone === \"danger\"\n ? \"error\"\n : burnoutTone === \"warning\"\n ? \"warning\"\n : \"success\"\n }\n label={burnoutHeading}\n />\n </div>\n <p className=\"mt-1.5 text-sm text-text-secondary\">\n {burnoutBody}\n </p>\n {burnoutRecommendation && (\n <p className=\"mt-2 flex items-start gap-1.5 text-sm text-text-primary\">\n <Lightbulb className=\"mt-0.5 h-4 w-4 shrink-0 text-primary\" />\n <span>{burnoutRecommendation}</span>\n </p>\n )}\n <button\n type=\"button\"\n onClick={() => navigate(\"/work-patterns/settings\")}\n className=\"mt-4 inline-flex items-center gap-1.5 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n Adjust focus schedule\n <ArrowRight className=\"h-4 w-4\" />\n </button>\n </div>\n </div>\n </GlassCard>\n\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\n <ChartCard\n title=\"Weekly Productive Hours\"\n description=\"Focused hours logged by weekday across recent weeks\"\n >\n <Heatmap\n data={heatmapData}\n rowLabels={[...DAY_LABELS]}\n columnLabels={columnLabels}\n rowsLabel=\"Day\"\n valueFormat={formatHoursValue}\n legendLabels={[\"Fewer\", \"More\"]}\n height={240}\n emptyLabel=\"No work-pattern days logged yet.\"\n ariaLabel=\"Focused hours by weekday and week\"\n />\n </ChartCard>\n\n <ChartCard\n title=\"Energy & Mood Trends\"\n description=\"Self-reported energy and mood, each on a 1 (low) – 5 (high) scale\"\n >\n {hasEnergyMood ? (\n <TrendChart\n data={trendData}\n xKey=\"date\"\n series={[\n { key: \"energy\", label: \"Energy\" },\n { key: \"mood\", label: \"Mood\" },\n ]}\n type=\"area\"\n height={240}\n emptyLabel=\"No energy or mood check-ins yet.\"\n ariaLabel=\"Energy and mood over time\"\n />\n ) : (\n <EmptyState\n title=\"No energy or mood data yet\"\n description=\"Log how you feel on your work-pattern entries to see the trend here.\"\n />\n )}\n </ChartCard>\n </div>\n\n {insights.length > 0 && (\n <GlassCard treatment=\"glass\" className=\"p-5 sm:p-6\">\n <h3 className=\"mb-4 flex items-center gap-2 text-sm font-semibold text-text-primary\">\n <Lightbulb className=\"h-4 w-4 text-primary\" />\n Insights\n </h3>\n <div className=\"space-y-3\">\n {insights.map((insight: WorkPatternInsight, idx: number) => (\n <div\n key={`${insight.type}-${idx}`}\n className=\"rounded-lg border border-border-subtle bg-bg-surface/40 p-4\"\n >\n <div className=\"flex flex-wrap items-center gap-2\">\n <StatusBadge\n variant={INSIGHT_VARIANT[insight.type]}\n label={INSIGHT_LABEL[insight.type]}\n />\n <p className=\"text-sm font-medium text-text-primary\">\n {insight.title}\n </p>\n </div>\n <p className=\"mt-1.5 text-sm text-text-secondary\">\n {insight.description}\n </p>\n {insight.recommendation && (\n <p className=\"mt-2 flex items-start gap-1.5 text-xs text-text-primary\">\n <ArrowRight className=\"mt-0.5 h-3.5 w-3.5 shrink-0 text-primary\" />\n <span>{insight.recommendation}</span>\n </p>\n )}\n </div>\n ))}\n </div>\n </GlassCard>\n )}\n </div>\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAoCA,IAAM,IAAa;CAAC;CAAO;CAAO;CAAO;CAAO;CAAO;CAAO;CAAM;AAGpE,SAAS,EAAe,GAAiB;AACvC,SAAQ,EAAE,QAAQ,GAAG,KAAK;;AAI5B,SAAS,EAAU,GAAe;CAChC,IAAM,IAAI,IAAI,KAAK,EAAE;AAGrB,QAFA,EAAE,QAAQ,EAAE,SAAS,GAAG,EAAe,EAAE,CAAC,EAC1C,EAAE,SAAS,GAAG,GAAG,GAAG,EAAE,EACf;;AAGT,SAAS,EAAU,GAAqB;CACtC,IAAM,IAAI,IAAI,KAAK,EAAI;AAEvB,QADI,OAAO,MAAM,EAAE,SAAS,CAAC,GAAS,KAC/B,EAAE,mBAAmB,KAAA,GAAW;EAAE,OAAO;EAAS,KAAK;EAAW,CAAC;;AAM5E,IAAM,KAAuC;CAC3C,UAAU;CACV,KAAK;CACL,QAAQ;CACR,MAAM;CACN,WAAW;CACZ,EACK,IAAqC;CACzC,eAAe;CACf,UAAU;CACV,SAAS;CACT,UAAU;CACV,eAAe;CAChB,EAEK,KAAc,IAAI,IAAI,CAAC,QAAQ,YAAY,CAAC,EAC5C,KAAa,IAAI,IAAI,CAAC,OAAO,WAAW,CAAC,EAEzC,MAAoB,MAAc,GAAG,EAAE,QAAQ,EAAE,CAAC,IAIlD,KAGF;EACD,EAAuB,WAAW;EAClC,EAAuB,WAAW;EAClC,EAAuB,UAAU;EACjC,EAAuB,iBAAiB;EACxC,EAAuB,UAAU;CACnC,EAEK,KAAwD;EAC3D,EAAuB,WAAW;EAClC,EAAuB,WAAW;EAClC,EAAuB,UAAU;EACjC,EAAuB,iBAAiB;EACxC,EAAuB,UAAU;CACnC;AAED,SAAgB,IAAmB;CACjC,IAAM,EAAE,iBAAc,OAAO,GAAe,EACtC,IAAW,GAAuB,EAElC,EACJ,MAAM,GACN,SAAS,GACT,OAAO,MACL,GAAyB;EAC3B,WAAW,EAAE,OAAO,IAAI;EACxB,MAAM,CAAC;EACR,CAAC,EAEI,EACJ,MAAM,GACN,SAAS,GACT,OAAO,MACL,EAA8B,EAChC,MAAM,CAAC,GACR,CAAC,EAEI,IAAU,KAAmB,GAC7B,IAAQ,KAAiB,GACzB,IAAU,GAAa,oBACvB,IAAW,GAAc,gBAAgB,EAAE,EAC3C,IAAW,GAAS,YAAY,EAAE,EAGlC,IAAgB,CAAC,GAAG,EAAS,CAAC,MAAM,GAAG,MAAM;EACjD,IAAM,IAAK,IAAI,KAAK,EAAE,KAAK,CAAC,SAAS,EAC/B,IAAK,IAAI,KAAK,EAAE,KAAK,CAAC,SAAS;AACrC,UAAQ,OAAO,MAAM,EAAG,GAAG,IAAI,MAAO,OAAO,MAAM,EAAG,GAAG,IAAI;GAC7D,EAGI,IAAgB,EAAS,IACzB,IACJ,GAAS,uBAAuB,OAE5B,OADA,GAAG,KAAK,MAAM,EAAQ,oBAAoB,CAAC,IAE3C,IACJ,GAAe,cAAc,OAEzB,OADA,GAAG,EAAc,WAAW,QAAQ,EAAE,CAAC,IAEvC,IAAa,KAEb,EAAc,aACd,EAAc,eACd,EAAc,YACd,QAAQ,EAAE,GACZ,KACE,KACJ,GAAe,oBAAoB,OAE/B,OADA,OAAO,EAAc,iBAAiB,EAEtC,IAAoB,GAAS,qBAAqB,MAGlD,IAAqB,EAAE;AAC7B,MAAK,IAAM,KAAK,GAAe;EAC7B,IAAM,IAAI,IAAI,KAAK,EAAE,KAAK;AAC1B,MAAI,OAAO,MAAM,EAAE,SAAS,CAAC,CAAE;EAC/B,IAAM,IAAM,EAAU,EAAE,CAAC,SAAS;AAClC,EAAK,EAAS,SAAS,EAAI,IAAE,EAAS,KAAK,EAAI;;CAEjD,IAAM,IAAgB,EAAS,MAAM,GAAG,EAClC,KAAe,EAAc,KAAK,MACtC,EAAU,IAAI,KAAK,EAAG,CAAC,aAAa,CAAC,CACtC,EACK,IAAgC,EAAW,UAC/C,EAAc,UAAwB,KAAK,CAC5C;AACD,MAAK,IAAM,KAAK,GAAe;EAC7B,IAAM,IAAI,IAAI,KAAK,EAAE,KAAK;AAC1B,MAAI,OAAO,MAAM,EAAE,SAAS,CAAC,CAAE;EAC/B,IAAM,IAAU,EAAc,QAAQ,EAAU,EAAE,CAAC,SAAS,CAAC;AACzD,MAAU,MACd,EAAY,EAAe,EAAE,EAAE,KAAW,EAAE;;CAI9C,IAAM,IAAY,EAAc,KAAK,OAAO;EAC1C,MAAM,EAAU,EAAE,KAAK;EACvB,QACE,EAAE,eAAe,OAA+C,OAAvC,GAAa,EAAE,gBAAgB;EAC1D,MAAM,EAAE,QAAQ,OAAsC,OAA9B,EAAW,EAAE,SAAS;EAC/C,EAAE,EACG,KAAgB,EAAU,MAC7B,MAAM,EAAE,UAAU,QAAQ,EAAE,QAAQ,KACtC,EAGK,IAAS,EAAS,MAAM,GAAG,EAAE,EAC7B,IAAa,EAAO,QACpB,IAAiB,EAAO,QAC3B,MACC,EAAE,eAAe,QAAQ,GAAY,IAAI,EAAE,YAAY,CAC1D,CAAC,QACI,IAAgB,EAAO,QAC1B,MACC,EAAE,eAAe,QAAQ,GAAW,IAAI,EAAE,YAAY,CACzD,CAAC,QACI,IAAiB,EAAS,MAC7B,MACC,EAAE,SAAS,EAAuB,WAClC,EAAE,SAAS,EAAuB,SACrC,EACK,IAAO,KAAK,KAAK,KAAK,IAAI,GAAY,EAAE,GAAG,EAAE,EAE/C,IAA2B;AAI/B,CAHI,IAAa,KAAK,KAAkB,IAAM,IAAc,YACnD,IAAiB,KAAK,KAAiB,KAAQ,OACtD,IAAc,YACZ,GAAgB,SAAS,EAAuB,aAClD,IAAc;CAEhB,IAAM,IAA2B,EAAE;AAKnC,CAJI,IAAiB,KACnB,EAAe,KACb,GAAG,EAAe,gBAAgB,EAAW,aAAa,MAAe,IAAI,KAAK,IAAI,qBACvF,EACC,IAAgB,KAClB,EAAe,KAAK,GAAG,EAAc,oBAAoB;CAE3D,IAAM,KACJ,GAAgB,UACf,MAAgB,WACb,gCACA,MAAgB,YACd,wBACA,sBACF,KACJ,GAAgB,gBACf,MAAgB,YACb,2FACA,EAAe,SAAS,IACtB,GAAG,EAAe,KAAK,QAAQ,CAAC,mDAChC,8FACF,IAAwB,GAAgB,kBAAkB,MAE1D,KACJ,MAAgB,WACZ,8CACA,MAAgB,YACd,kDACA;AA0BR,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;aAA2G;GAEpH,CAAA;EACK,CAAA,GAKf,kBAAC,GAAD;EACE,OAAM;EACN,aAAY;EACZ,SAAQ;YAER,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,GAAD;OACE,UAAA;OACA,OAAM;OACN,OAAO;OACP,MAAM,kBAAC,IAAD,EAAY,WAAU,WAAY,CAAA;OACxC,OACE,GAAS,uBAAuB,OAE5B,KAAA,IADA;QAAE,OAAO;QAAG,OAAO;QAAgB;OAGzC,CAAA;MACF,kBAAC,GAAD;OACE,OAAM;OACN,OAAO;OACP,MAAM,kBAAC,IAAD,EAAO,WAAU,WAAY,CAAA;OACnC,aAAa,UAAU,EAAW;OAClC,CAAA;MACF,kBAAC,GAAD;OACE,OAAM;OACN,OAAO;OACP,MAAM,kBAAC,IAAD,EAAK,WAAU,WAAY,CAAA;OACjC,aAAY;OACZ,CAAA;MACF,kBAAC,GAAD;OACE,OAAM;OACN,OAAO,GAAG,GAAiB;OAC3B,MAAM,kBAAC,IAAD,EAAO,WAAU,WAAY,CAAA;OACnC,aAAY;OACZ,CAAA;MACE;;IAGN,kBAAC,GAAD;KACE,WAAU;KACV,MAAM,MAAgB;KACtB,WAAU;eAEV,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD;OACE,WAAW,kEAAkE;iBAG3E,EADD,MAAgB,YACd,KAEA,GAFD,EAAa,WAAU,WAAY,CAEE;OAEnC,CAAA,EACN,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,MAAD;UAAI,WAAU;oBAA4C;UAErD,CAAA,EACL,kBAAC,GAAD;UACE,SACE,MAAgB,WACZ,UACA,MAAgB,YACd,YACA;UAER,OAAO;UACP,CAAA,CACE;;QACN,kBAAC,KAAD;SAAG,WAAU;mBACV;SACC,CAAA;QACH,KACC,kBAAC,KAAD;SAAG,WAAU;mBAAb,CACE,kBAAC,GAAD,EAAW,WAAU,wCAAyC,CAAA,EAC9D,kBAAC,QAAD,EAAA,UAAO,GAA6B,CAAA,CAClC;;QAEN,kBAAC,UAAD;SACE,MAAK;SACL,eAAe,EAAS,0BAA0B;SAClD,WAAU;mBAHZ,CAIC,yBAEC,kBAAC,GAAD,EAAY,WAAU,WAAY,CAAA,CAC3B;;QACL;SACF;;KACI,CAAA;IAEZ,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD;MACE,OAAM;MACN,aAAY;gBAEZ,kBAAC,GAAD;OACE,MAAM;OACN,WAAW,CAAC,GAAG,EAAW;OACZ;OACd,WAAU;OACV,aAAa;OACb,cAAc,CAAC,SAAS,OAAO;OAC/B,QAAQ;OACR,YAAW;OACX,WAAU;OACV,CAAA;MACQ,CAAA,EAEZ,kBAAC,GAAD;MACE,OAAM;MACN,aAAY;gBAEX,KACC,kBAAC,GAAD;OACE,MAAM;OACN,MAAK;OACL,QAAQ,CACN;QAAE,KAAK;QAAU,OAAO;QAAU,EAClC;QAAE,KAAK;QAAQ,OAAO;QAAQ,CAC/B;OACD,MAAK;OACL,QAAQ;OACR,YAAW;OACX,WAAU;OACV,CAAA,GAEF,kBAAC,IAAD;OACE,OAAM;OACN,aAAY;OACZ,CAAA;MAEM,CAAA,CACR;;IAEL,EAAS,SAAS,KACjB,kBAAC,GAAD;KAAW,WAAU;KAAQ,WAAU;eAAvC,CACE,kBAAC,MAAD;MAAI,WAAU;gBAAd,CACE,kBAAC,GAAD,EAAW,WAAU,wBAAyB,CAAA,EAAA,WAE3C;SACL,kBAAC,OAAD;MAAK,WAAU;gBACZ,EAAS,KAAK,GAA6B,MAC1C,kBAAC,OAAD;OAEE,WAAU;iBAFZ;QAIE,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,GAAD;UACE,SAAS,GAAgB,EAAQ;UACjC,OAAO,GAAc,EAAQ;UAC7B,CAAA,EACF,kBAAC,KAAD;UAAG,WAAU;oBACV,EAAQ;UACP,CAAA,CACA;;QACN,kBAAC,KAAD;SAAG,WAAU;mBACV,EAAQ;SACP,CAAA;QACH,EAAQ,kBACP,kBAAC,KAAD;SAAG,WAAU;mBAAb,CACE,kBAAC,GAAD,EAAY,WAAU,4CAA6C,CAAA,EACnE,kBAAC,QAAD,EAAA,UAAO,EAAQ,gBAAsB,CAAA,CACnC;;QAEF;SArBC,GAAG,EAAQ,KAAK,GAAG,IAqBpB,CACN;MACE,CAAA,CACI;;IAEV;;EACK,CAAA"}
|
|
1
|
+
{"version":3,"file":"WorkPatternsPage.js","names":[],"sources":["../../../../src/timecampus/pages/work-patterns/WorkPatternsPage.tsx"],"sourcesContent":["import {\n TrendingUp,\n Clock,\n Zap,\n Brain,\n AlertTriangle,\n ShieldCheck,\n Lightbulb,\n ArrowRight,\n} from \"lucide-react\";\nimport { GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport {\n PageLayout,\n StatCard,\n ChartCard,\n Heatmap,\n TrendChart,\n StatusBadge,\n EmptyState,\n LoadingSkeleton,\n type HeatmapValue,\n} from \"../../components\";\nimport { useTimeCampus } from \"../../providers/TimeCampusProvider\";\nimport {\n useListWorkPatternsQuery,\n useGetWorkPatternSummaryQuery,\n type WorkPatternFieldsFragment,\n} from \"@/generated/wspace-operations\";\nimport {\n WorkPatternInsightType,\n type WorkPatternInsight,\n} from \"@/generated/wspace-types\";\n\nconst DAY_LABELS = [\"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\", \"Sun\"] as const;\n\n/** JS `getDay()` is 0=Sun..6=Sat; remap so Monday is index 0. */\nfunction dayOfWeekIndex(d: Date): number {\n return (d.getDay() + 6) % 7;\n}\n\n/** Monday (local midnight) of the week containing `d`. */\nfunction weekStart(d: Date): Date {\n const s = new Date(d);\n s.setDate(d.getDate() - dayOfWeekIndex(d));\n s.setHours(0, 0, 0, 0);\n return s;\n}\n\nfunction shortDate(iso: string): string {\n const d = new Date(iso);\n if (Number.isNaN(d.getTime())) return \"\";\n return d.toLocaleDateString(undefined, { month: \"short\", day: \"numeric\" });\n}\n\n// Self-reported ordinals mapped onto a shared 1 (worst) – 5 (best) scale so\n// energy and mood can be trended together. Only real, fetched enum values map;\n// anything else stays null (a gap in the line, never a fabricated point).\nconst ENERGY_SCORE: Record<string, number> = {\n VERY_LOW: 1,\n LOW: 2,\n MEDIUM: 3,\n HIGH: 4,\n VERY_HIGH: 5,\n};\nconst MOOD_SCORE: Record<string, number> = {\n VERY_NEGATIVE: 1,\n NEGATIVE: 2,\n NEUTRAL: 3,\n POSITIVE: 4,\n VERY_POSITIVE: 5,\n};\n\nconst HIGH_STRESS = new Set([\"HIGH\", \"VERY_HIGH\"]);\nconst LOW_ENERGY = new Set([\"LOW\", \"VERY_LOW\"]);\n\nconst formatHoursValue = (v: number) => `${v.toFixed(1)}h`;\n\ntype BurnoutTone = \"danger\" | \"warning\" | \"success\";\n\nconst INSIGHT_VARIANT: Record<\n WorkPatternInsightType,\n \"success\" | \"error\" | \"warning\" | \"info\" | \"muted\"\n> = {\n [WorkPatternInsightType.Positive]: \"success\",\n [WorkPatternInsightType.Negative]: \"error\",\n [WorkPatternInsightType.Warning]: \"warning\",\n [WorkPatternInsightType.Recommendation]: \"info\",\n [WorkPatternInsightType.Neutral]: \"muted\",\n};\n\nconst INSIGHT_LABEL: Record<WorkPatternInsightType, string> = {\n [WorkPatternInsightType.Positive]: \"Positive\",\n [WorkPatternInsightType.Negative]: \"Negative\",\n [WorkPatternInsightType.Warning]: \"Warning\",\n [WorkPatternInsightType.Recommendation]: \"Tip\",\n [WorkPatternInsightType.Neutral]: \"Neutral\",\n};\n\nexport function WorkPatternsPage() {\n const { workspaceId = \"\" } = useTimeCampus();\n const {\n data: patternsData,\n loading: patternsLoading,\n error: patternsError,\n } = useListWorkPatternsQuery({\n variables: { limit: 30 },\n skip: !workspaceId,\n });\n\n const {\n data: summaryData,\n loading: summaryLoading,\n error: summaryError,\n } = useGetWorkPatternSummaryQuery({\n skip: !workspaceId,\n });\n\n const loading = patternsLoading || summaryLoading;\n const error = patternsError || summaryError;\n const summary = summaryData?.workPatternSummary;\n const patterns = patternsData?.workPatterns ?? [];\n const insights = summary?.insights ?? [];\n\n // Chronological order for time-based visuals; the list itself is newest-first.\n const chronological = [...patterns].sort((a, b) => {\n const ta = new Date(a.date).getTime();\n const tb = new Date(b.date).getTime();\n return (Number.isNaN(ta) ? 0 : ta) - (Number.isNaN(tb) ? 0 : tb);\n });\n\n // Derive stats from the latest pattern entry\n const latestPattern = patterns[0];\n const productivityScore =\n summary?.averageProductivity != null\n ? `${Math.round(summary.averageProductivity)}%`\n : \"--\";\n const focusHours =\n latestPattern?.focusHours != null\n ? `${latestPattern.focusHours.toFixed(1)}h`\n : \"--\";\n const totalHours = latestPattern\n ? (\n latestPattern.focusHours +\n latestPattern.meetingHours +\n latestPattern.breakHours\n ).toFixed(0)\n : \"8\";\n const deepWorkSessions =\n latestPattern?.deepWorkSessions != null\n ? String(latestPattern.deepWorkSessions)\n : \"--\";\n const mostProductiveDay = summary?.mostProductiveDay ?? \"--\";\n\n // ---- Heatmap: focus (productive) hours by weekday × week -----------------\n const weekKeys: number[] = [];\n for (const p of chronological) {\n const d = new Date(p.date);\n if (Number.isNaN(d.getTime())) continue;\n const key = weekStart(d).getTime();\n if (!weekKeys.includes(key)) weekKeys.push(key);\n }\n const selectedWeeks = weekKeys.slice(-6); // most recent 6 weeks\n const columnLabels = selectedWeeks.map((ts) =>\n shortDate(new Date(ts).toISOString()),\n );\n const heatmapData: HeatmapValue[][] = DAY_LABELS.map(() =>\n selectedWeeks.map<HeatmapValue>(() => null),\n );\n for (const p of chronological) {\n const d = new Date(p.date);\n if (Number.isNaN(d.getTime())) continue;\n const weekIdx = selectedWeeks.indexOf(weekStart(d).getTime());\n if (weekIdx < 0) continue;\n heatmapData[dayOfWeekIndex(d)][weekIdx] = p.focusHours;\n }\n\n // ---- Energy & mood trend --------------------------------------------------\n const trendData = chronological.map((p) => ({\n date: shortDate(p.date),\n energy:\n p.energyLevel != null ? (ENERGY_SCORE[p.energyLevel] ?? null) : null,\n mood: p.mood != null ? (MOOD_SCORE[p.mood] ?? null) : null,\n }));\n const hasEnergyMood = trendData.some(\n (d) => d.energy != null || d.mood != null,\n );\n\n // ---- Burnout signal (derived from real stress/energy + backend insights) --\n const recent = patterns.slice(0, 7); // most recent logged days\n const windowSize = recent.length;\n const highStressDays = recent.filter(\n (p: WorkPatternFieldsFragment) =>\n p.stressLevel != null && HIGH_STRESS.has(p.stressLevel),\n ).length;\n const lowEnergyDays = recent.filter(\n (p: WorkPatternFieldsFragment) =>\n p.energyLevel != null && LOW_ENERGY.has(p.energyLevel),\n ).length;\n const warningInsight = insights.find(\n (i: WorkPatternInsight) =>\n i.type === WorkPatternInsightType.Warning ||\n i.type === WorkPatternInsightType.Negative,\n );\n const half = Math.ceil(Math.max(windowSize, 1) / 2);\n\n let burnoutTone: BurnoutTone = \"success\";\n if (windowSize > 0 && highStressDays >= half) burnoutTone = \"danger\";\n else if (highStressDays > 0 || lowEnergyDays >= half || warningInsight)\n burnoutTone = \"warning\";\n if (warningInsight?.type === WorkPatternInsightType.Negative)\n burnoutTone = \"danger\";\n\n const stressEvidence: string[] = [];\n if (highStressDays > 0)\n stressEvidence.push(\n `${highStressDays} of your last ${windowSize} logged day${windowSize === 1 ? \"\" : \"s\"} showed high stress`,\n );\n if (lowEnergyDays > 0)\n stressEvidence.push(`${lowEnergyDays} showed low energy`);\n\n const burnoutHeading =\n warningInsight?.title ??\n (burnoutTone === \"danger\"\n ? \"Sustained overload detected\"\n : burnoutTone === \"warning\"\n ? \"Watch your workload\"\n : \"Balanced workload\");\n const burnoutBody =\n warningInsight?.description ??\n (burnoutTone === \"success\"\n ? \"Your recent stress and energy levels look healthy — keep protecting your focus blocks.\"\n : stressEvidence.length > 0\n ? `${stressEvidence.join(\" and \")}. Protect your deep-work time before it erodes.`\n : \"Recent signals suggest an uneven balance — schedule focus time to stay ahead of overload.\");\n const burnoutRecommendation = warningInsight?.recommendation ?? null;\n\n const burnoutAccent =\n burnoutTone === \"danger\"\n ? \"bg-status-error-bg text-status-error-text\"\n : burnoutTone === \"warning\"\n ? \"bg-status-warning-bg text-status-warning-text\"\n : \"bg-status-success-bg text-status-success-text\";\n\n if (loading) {\n return (\n <PageLayout\n title=\"Work Patterns\"\n description=\"Analyze your work habits and productivity patterns\"\n >\n <LoadingSkeleton variant=\"card\" count={4} />\n </PageLayout>\n );\n }\n\n if (error) {\n return (\n <PageLayout\n title=\"Work Patterns\"\n description=\"Analyze your work habits and productivity patterns\"\n >\n <div className=\"rounded-xl border border-status-error-border/50 bg-status-error-bg/10 p-4 text-sm text-status-error-text\">\n Failed to load work patterns. Please try again later.\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title=\"Work Patterns\"\n description=\"Analyze your work habits and productivity patterns\"\n purpose=\"Learn how you actually work across a typical week — when you focus best, where meetings pile up, and which day is your peak — so you can schedule deep work for the hours you're sharpest.\"\n >\n <div className=\"space-y-6\">\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <StatCard\n featured\n title=\"Productivity Score\"\n value={productivityScore}\n icon={<TrendingUp className=\"h-5 w-5\" />}\n trend={\n summary?.averageProductivity != null\n ? { value: 5, label: \"vs last week\" }\n : undefined\n }\n />\n <StatCard\n title=\"Focus Hours\"\n value={focusHours}\n icon={<Brain className=\"h-5 w-5\" />}\n description={`Out of ${totalHours}h total`}\n />\n <StatCard\n title=\"Peak Day\"\n value={mostProductiveDay}\n icon={<Zap className=\"h-5 w-5\" />}\n description=\"Most productive day\"\n />\n <StatCard\n title=\"Deep Work\"\n value={`${deepWorkSessions} sessions`}\n icon={<Clock className=\"h-5 w-5\" />}\n description=\"90+ min uninterrupted\"\n />\n </div>\n\n {/* Burnout signal — honest, derived from recent stress/energy + insights */}\n <GlassCard\n treatment=\"glass\"\n glow={burnoutTone !== \"success\"}\n className=\"p-5 sm:p-6\"\n >\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-start\">\n <div\n className={`flex h-11 w-11 shrink-0 items-center justify-center rounded-xl ${burnoutAccent}`}\n >\n {burnoutTone === \"success\" ? (\n <ShieldCheck className=\"h-5 w-5\" />\n ) : (\n <AlertTriangle className=\"h-5 w-5\" />\n )}\n </div>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <h3 className=\"text-base font-semibold text-text-primary\">\n Burnout Signal\n </h3>\n <StatusBadge\n variant={\n burnoutTone === \"danger\"\n ? \"error\"\n : burnoutTone === \"warning\"\n ? \"warning\"\n : \"success\"\n }\n label={burnoutHeading}\n />\n </div>\n <p className=\"mt-1.5 text-sm text-text-secondary\">\n {burnoutBody}\n </p>\n {burnoutRecommendation && (\n <p className=\"mt-2 flex items-start gap-1.5 text-sm text-text-primary\">\n <Lightbulb className=\"mt-0.5 h-4 w-4 shrink-0 text-primary\" />\n <span>{burnoutRecommendation}</span>\n </p>\n )}\n </div>\n </div>\n </GlassCard>\n\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\n <ChartCard\n title=\"Weekly Productive Hours\"\n description=\"Focused hours logged by weekday across recent weeks\"\n >\n <Heatmap\n data={heatmapData}\n rowLabels={[...DAY_LABELS]}\n columnLabels={columnLabels}\n rowsLabel=\"Day\"\n valueFormat={formatHoursValue}\n legendLabels={[\"Fewer\", \"More\"]}\n height={240}\n emptyLabel=\"No work-pattern days logged yet.\"\n ariaLabel=\"Focused hours by weekday and week\"\n />\n </ChartCard>\n\n <ChartCard\n title=\"Energy & Mood Trends\"\n description=\"Self-reported energy and mood, each on a 1 (low) – 5 (high) scale\"\n >\n {hasEnergyMood ? (\n <TrendChart\n data={trendData}\n xKey=\"date\"\n series={[\n { key: \"energy\", label: \"Energy\" },\n { key: \"mood\", label: \"Mood\" },\n ]}\n type=\"area\"\n height={240}\n emptyLabel=\"No energy or mood check-ins yet.\"\n ariaLabel=\"Energy and mood over time\"\n />\n ) : (\n <EmptyState\n title=\"No energy or mood data yet\"\n description=\"Log how you feel on your work-pattern entries to see the trend here.\"\n />\n )}\n </ChartCard>\n </div>\n\n {insights.length > 0 && (\n <GlassCard treatment=\"glass\" className=\"p-5 sm:p-6\">\n <h3 className=\"mb-4 flex items-center gap-2 text-sm font-semibold text-text-primary\">\n <Lightbulb className=\"h-4 w-4 text-primary\" />\n Insights\n </h3>\n <div className=\"space-y-3\">\n {insights.map((insight: WorkPatternInsight, idx: number) => (\n <div\n key={`${insight.type}-${idx}`}\n className=\"rounded-lg border border-border-subtle bg-bg-surface/40 p-4\"\n >\n <div className=\"flex flex-wrap items-center gap-2\">\n <StatusBadge\n variant={INSIGHT_VARIANT[insight.type]}\n label={INSIGHT_LABEL[insight.type]}\n />\n <p className=\"text-sm font-medium text-text-primary\">\n {insight.title}\n </p>\n </div>\n <p className=\"mt-1.5 text-sm text-text-secondary\">\n {insight.description}\n </p>\n {insight.recommendation && (\n <p className=\"mt-2 flex items-start gap-1.5 text-xs text-text-primary\">\n <ArrowRight className=\"mt-0.5 h-3.5 w-3.5 shrink-0 text-primary\" />\n <span>{insight.recommendation}</span>\n </p>\n )}\n </div>\n ))}\n </div>\n </GlassCard>\n )}\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
import { StatCard as e } from "../../components/StatCard.js";
|
|
2
|
-
import { LoadingSkeleton as t } from "../../components/LoadingSkeleton.js";
|
|
3
|
-
import { useListConfigurationsQuery as n, useSetConfigurationMutation as r } from "../../../generated/wspace-operations.js";
|
|
4
|
-
import { PageLayout as i } from "../../components/PageLayout.js";
|
|
5
|
-
import { StatusBadge as a } from "../../components/StatusBadge.js";
|
|
6
|
-
import { EmptyState as o } from "../../components/EmptyState.js";
|
|
7
|
-
import { ChartCard as s } from "../../components/ChartCard.js";
|
|
8
|
-
import { DonutChart as c } from "../../components/charts/DonutChart.js";
|
|
9
|
-
import "../../components/index.js";
|
|
10
|
-
import { TimeCampusConfigScope as l } from "../../../generated/wspace-types.js";
|
|
11
|
-
import { useState as u } from "react";
|
|
12
|
-
import { Boxes as d, LayoutGrid as f, Puzzle as p, Store as m, Tags as h } from "lucide-react";
|
|
13
|
-
import { Fragment as g, jsx as _, jsxs as v } from "react/jsx-runtime";
|
|
14
|
-
import { GlassCard as y } from "@burdenoff/fe-libs/ui";
|
|
15
|
-
//#region src/timecampus/pages/applets/AppletStorePage.tsx
|
|
16
|
-
function b() {
|
|
17
|
-
let { data: b, loading: x, error: S, refetch: C } = n({ variables: { scope: l.Workspace } }), [w, { error: T }] = r(), [E, D] = u(null), O = b?.timeCampusConfigurations ?? [], k = O.filter((e) => e.key.startsWith("applet.store.")), A = new Set(O.filter((e) => e.key.startsWith("applet.installed.")).map((e) => e.key.replace("applet.installed.", ""))), j = k.reduce((e, t) => {
|
|
18
|
-
let n = (t.value ?? {}).category ?? "general";
|
|
19
|
-
return e[n] = (e[n] ?? 0) + 1, e;
|
|
20
|
-
}, {}), M = Object.entries(j).sort((e, t) => t[1] - e[1]).map(([e, t]) => ({
|
|
21
|
-
name: e,
|
|
22
|
-
value: t
|
|
23
|
-
})), N = k.filter((e) => A.has(e.key.replace("applet.store.", ""))).length, P = async (e) => {
|
|
24
|
-
let t = e.key.replace("applet.store.", ""), n = e.value ?? {}, r = n.name ?? t, i = e.description ?? n.description ?? void 0;
|
|
25
|
-
D(e.id);
|
|
26
|
-
try {
|
|
27
|
-
await w({ variables: { input: {
|
|
28
|
-
key: `applet.installed.${t}`,
|
|
29
|
-
scope: l.Workspace,
|
|
30
|
-
value: {
|
|
31
|
-
name: r,
|
|
32
|
-
category: n.category ?? "general",
|
|
33
|
-
description: i,
|
|
34
|
-
version: n.version ?? "1.0.0",
|
|
35
|
-
author: n.author ?? "TimeCampus",
|
|
36
|
-
isActive: !0,
|
|
37
|
-
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
38
|
-
},
|
|
39
|
-
description: i
|
|
40
|
-
} } }), await C();
|
|
41
|
-
} finally {
|
|
42
|
-
D(null);
|
|
43
|
-
}
|
|
44
|
-
};
|
|
45
|
-
return x ? /* @__PURE__ */ _(i, {
|
|
46
|
-
title: "Applet Store",
|
|
47
|
-
description: "Browse and install applets to extend TimeCampus",
|
|
48
|
-
children: /* @__PURE__ */ _(t, {
|
|
49
|
-
variant: "card",
|
|
50
|
-
count: 6
|
|
51
|
-
})
|
|
52
|
-
}) : S ? /* @__PURE__ */ _(i, {
|
|
53
|
-
title: "Applet Store",
|
|
54
|
-
description: "Browse and install applets to extend TimeCampus",
|
|
55
|
-
children: /* @__PURE__ */ _("div", {
|
|
56
|
-
className: "rounded-lg border border-status-error-bg/40 bg-status-error-bg p-4 text-sm text-status-error-text",
|
|
57
|
-
children: "Failed to load applet store data. Please try again later."
|
|
58
|
-
})
|
|
59
|
-
}) : /* @__PURE__ */ _(i, {
|
|
60
|
-
title: "Applet Store",
|
|
61
|
-
description: "Browse and install applets to extend TimeCampus",
|
|
62
|
-
purpose: "Discover optional add-ons that extend TimeCampus — integrations, extra trackers and views — and install the ones your workflow needs instead of living with a one-size-fits-all setup.",
|
|
63
|
-
children: k.length === 0 ? /* @__PURE__ */ _(o, {
|
|
64
|
-
illustration: "empty-generic",
|
|
65
|
-
title: "No applets available yet",
|
|
66
|
-
description: "Store applets are managed as workspace configurations with the 'applet.store.' key prefix — once published, they'll appear here to browse and install."
|
|
67
|
-
}) : /* @__PURE__ */ v("div", {
|
|
68
|
-
className: "space-y-6",
|
|
69
|
-
children: [
|
|
70
|
-
T && /* @__PURE__ */ _("div", {
|
|
71
|
-
className: "rounded-lg border border-status-error-bg/40 bg-status-error-bg p-3 text-sm text-status-error-text",
|
|
72
|
-
children: "Couldn't install that applet. Please try again."
|
|
73
|
-
}),
|
|
74
|
-
/* @__PURE__ */ v("div", {
|
|
75
|
-
className: "grid grid-cols-1 gap-4 sm:grid-cols-3",
|
|
76
|
-
children: [
|
|
77
|
-
/* @__PURE__ */ _(e, {
|
|
78
|
-
featured: !0,
|
|
79
|
-
title: "Available",
|
|
80
|
-
value: k.length,
|
|
81
|
-
icon: /* @__PURE__ */ _(f, { className: "h-5 w-5" }),
|
|
82
|
-
description: "Add-ons you can install"
|
|
83
|
-
}),
|
|
84
|
-
/* @__PURE__ */ _(e, {
|
|
85
|
-
title: "Categories",
|
|
86
|
-
value: Object.keys(j).length,
|
|
87
|
-
icon: /* @__PURE__ */ _(h, { className: "h-5 w-5" }),
|
|
88
|
-
description: "Ways to extend",
|
|
89
|
-
iconBgColor: "bg-status-info-bg text-status-info-text"
|
|
90
|
-
}),
|
|
91
|
-
/* @__PURE__ */ _(e, {
|
|
92
|
-
title: "Installed",
|
|
93
|
-
value: N,
|
|
94
|
-
icon: /* @__PURE__ */ _(d, { className: "h-5 w-5" }),
|
|
95
|
-
description: "Already in your workspace",
|
|
96
|
-
iconBgColor: "bg-status-success-bg text-status-success-text"
|
|
97
|
-
})
|
|
98
|
-
]
|
|
99
|
-
}),
|
|
100
|
-
/* @__PURE__ */ v("div", {
|
|
101
|
-
className: "grid grid-cols-1 gap-6 lg:grid-cols-3",
|
|
102
|
-
children: [/* @__PURE__ */ _(s, {
|
|
103
|
-
title: "By category",
|
|
104
|
-
description: "How the catalogue breaks down",
|
|
105
|
-
className: "lg:col-span-1",
|
|
106
|
-
children: /* @__PURE__ */ _(c, {
|
|
107
|
-
data: M,
|
|
108
|
-
centerLabel: "Applets",
|
|
109
|
-
centerValue: k.length,
|
|
110
|
-
height: 240,
|
|
111
|
-
ariaLabel: "Store applets grouped by category",
|
|
112
|
-
emptyLabel: "No applets to summarise yet."
|
|
113
|
-
})
|
|
114
|
-
}), /* @__PURE__ */ _("div", {
|
|
115
|
-
className: "lg:col-span-2",
|
|
116
|
-
children: /* @__PURE__ */ _("div", {
|
|
117
|
-
className: "grid grid-cols-1 gap-4 sm:grid-cols-2",
|
|
118
|
-
children: k.map((e) => {
|
|
119
|
-
let t = e.value ?? {}, n = t.name ?? e.key.replace("applet.store.", ""), r = e.description ?? t.description ?? "", i = t.category ?? "general", o = A.has(e.key.replace("applet.store.", "")), s = E === e.id;
|
|
120
|
-
return /* @__PURE__ */ v(y, {
|
|
121
|
-
treatment: "glass",
|
|
122
|
-
className: "flex flex-col p-5",
|
|
123
|
-
children: [/* @__PURE__ */ v("div", {
|
|
124
|
-
className: "flex items-start gap-3",
|
|
125
|
-
children: [/* @__PURE__ */ _("div", {
|
|
126
|
-
className: "flex h-12 w-12 shrink-0 items-center justify-center rounded-xl bg-action-primary-bg/10 text-primary",
|
|
127
|
-
children: /* @__PURE__ */ _(p, { className: "h-6 w-6" })
|
|
128
|
-
}), /* @__PURE__ */ v("div", {
|
|
129
|
-
className: "min-w-0 flex-1",
|
|
130
|
-
children: [/* @__PURE__ */ _("p", {
|
|
131
|
-
className: "truncate text-sm font-semibold text-text-primary",
|
|
132
|
-
children: n
|
|
133
|
-
}), r && /* @__PURE__ */ _("p", {
|
|
134
|
-
className: "mt-1 line-clamp-2 text-xs text-text-secondary",
|
|
135
|
-
children: r
|
|
136
|
-
})]
|
|
137
|
-
})]
|
|
138
|
-
}), /* @__PURE__ */ v("div", {
|
|
139
|
-
className: "mt-4 flex items-center justify-between gap-2 border-t border-border-seam pt-3",
|
|
140
|
-
children: [/* @__PURE__ */ _("span", {
|
|
141
|
-
className: "inline-flex rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs font-medium capitalize text-text-secondary",
|
|
142
|
-
children: i
|
|
143
|
-
}), o ? /* @__PURE__ */ _(a, {
|
|
144
|
-
variant: "success",
|
|
145
|
-
label: "Installed",
|
|
146
|
-
className: "gap-1"
|
|
147
|
-
}) : /* @__PURE__ */ _("button", {
|
|
148
|
-
type: "button",
|
|
149
|
-
onClick: () => void P(e),
|
|
150
|
-
disabled: s,
|
|
151
|
-
className: "inline-flex items-center gap-1 rounded-md bg-action-primary-bg px-3 py-1.5 text-xs font-medium text-action-primary-text hover:bg-action-primary-bg/90 disabled:opacity-50",
|
|
152
|
-
children: s ? "Installing..." : /* @__PURE__ */ v(g, { children: [/* @__PURE__ */ _(m, { className: "h-3 w-3" }), "Get it"] })
|
|
153
|
-
})]
|
|
154
|
-
})]
|
|
155
|
-
}, e.id);
|
|
156
|
-
})
|
|
157
|
-
})
|
|
158
|
-
})]
|
|
159
|
-
})
|
|
160
|
-
]
|
|
161
|
-
})
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
//#endregion
|
|
165
|
-
export { b as AppletStorePage };
|
|
166
|
-
|
|
167
|
-
//# sourceMappingURL=AppletStorePage.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"AppletStorePage.js","names":[],"sources":["../../../../src/timecampus/pages/applets/AppletStorePage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport {\n Puzzle,\n Store as StoreIcon,\n LayoutGrid,\n Boxes,\n Tags,\n} from \"lucide-react\";\nimport { GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport {\n PageLayout,\n EmptyState,\n LoadingSkeleton,\n StatCard,\n StatusBadge,\n ChartCard,\n DonutChart,\n} from \"../../components\";\nimport {\n useListConfigurationsQuery,\n useSetConfigurationMutation,\n type ConfigurationFieldsFragment,\n} from \"@/generated/wspace-operations\";\nimport { TimeCampusConfigScope } from \"@/generated/wspace-types\";\n\nexport function AppletStorePage() {\n const { data, loading, error, refetch } = useListConfigurationsQuery({\n variables: { scope: TimeCampusConfigScope.Workspace },\n });\n\n const [setConfiguration, { error: installError }] =\n useSetConfigurationMutation();\n const [installingId, setInstallingId] = useState<string | null>(null);\n\n const configurations: ConfigurationFieldsFragment[] =\n data?.timeCampusConfigurations ?? [];\n\n // Filter configurations that represent store/available applets (key starts with 'applet.store.')\n const storeApplets = configurations.filter((c: ConfigurationFieldsFragment) =>\n c.key.startsWith(\"applet.store.\"),\n );\n\n // Suffixes of applets already installed — the store dedupes against these so\n // \"Get it\" flips to \"Installed\" without inventing any status.\n const installedSuffixes = new Set(\n configurations\n .filter((c: ConfigurationFieldsFragment) =>\n c.key.startsWith(\"applet.installed.\"),\n )\n .map((c) => c.key.replace(\"applet.installed.\", \"\")),\n );\n\n // Real, derived aggregates over the catalogue the page already loads.\n const categoryCounts = storeApplets.reduce<Record<string, number>>(\n (acc, applet) => {\n const val = (applet.value as Record<string, unknown>) ?? {};\n const category = (val.category as string) ?? \"general\";\n acc[category] = (acc[category] ?? 0) + 1;\n return acc;\n },\n {},\n );\n const categoryMix = Object.entries(categoryCounts)\n .sort((a, b) => b[1] - a[1])\n .map(([category, count]) => ({ name: category, value: count }));\n const installedFromStore = storeApplets.filter((applet) =>\n installedSuffixes.has(applet.key.replace(\"applet.store.\", \"\")),\n ).length;\n\n const handleInstall = async (applet: ConfigurationFieldsFragment) => {\n const suffix = applet.key.replace(\"applet.store.\", \"\");\n const val = (applet.value as Record<string, unknown>) ?? {};\n const appletName = (val.name as string) ?? suffix;\n const appletDescription =\n applet.description ?? (val.description as string) ?? undefined;\n setInstallingId(applet.id);\n try {\n await setConfiguration({\n variables: {\n input: {\n key: `applet.installed.${suffix}`,\n scope: TimeCampusConfigScope.Workspace,\n value: {\n name: appletName,\n category: (val.category as string) ?? \"general\",\n description: appletDescription,\n version: (val.version as string) ?? \"1.0.0\",\n author: (val.author as string) ?? \"TimeCampus\",\n isActive: true,\n installedAt: new Date().toISOString(),\n },\n description: appletDescription,\n },\n },\n });\n await refetch();\n } finally {\n setInstallingId(null);\n }\n };\n\n if (loading) {\n return (\n <PageLayout\n title=\"Applet Store\"\n description=\"Browse and install applets to extend TimeCampus\"\n >\n <LoadingSkeleton variant=\"card\" count={6} />\n </PageLayout>\n );\n }\n\n if (error) {\n return (\n <PageLayout\n title=\"Applet Store\"\n description=\"Browse and install applets to extend TimeCampus\"\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 applet store data. Please try again later.\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title=\"Applet Store\"\n description=\"Browse and install applets to extend TimeCampus\"\n purpose=\"Discover optional add-ons that extend TimeCampus — integrations, extra trackers and views — and install the ones your workflow needs instead of living with a one-size-fits-all setup.\"\n >\n {storeApplets.length === 0 ? (\n <EmptyState\n illustration=\"empty-generic\"\n title=\"No applets available yet\"\n description=\"Store applets are managed as workspace configurations with the 'applet.store.' key prefix — once published, they'll appear here to browse and install.\"\n />\n ) : (\n <div className=\"space-y-6\">\n {installError && (\n <div className=\"rounded-lg border border-status-error-bg/40 bg-status-error-bg p-3 text-sm text-status-error-text\">\n Couldn't install that applet. Please try again.\n </div>\n )}\n\n {/* Catalogue summary — derived from the applets below */}\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <StatCard\n featured\n title=\"Available\"\n value={storeApplets.length}\n icon={<LayoutGrid className=\"h-5 w-5\" />}\n description=\"Add-ons you can install\"\n />\n <StatCard\n title=\"Categories\"\n value={Object.keys(categoryCounts).length}\n icon={<Tags className=\"h-5 w-5\" />}\n description=\"Ways to extend\"\n iconBgColor=\"bg-status-info-bg text-status-info-text\"\n />\n <StatCard\n title=\"Installed\"\n value={installedFromStore}\n icon={<Boxes className=\"h-5 w-5\" />}\n description=\"Already in your workspace\"\n iconBgColor=\"bg-status-success-bg text-status-success-text\"\n />\n </div>\n\n {/* Category mix ring + catalogue cards */}\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-3\">\n <ChartCard\n title=\"By category\"\n description=\"How the catalogue breaks down\"\n className=\"lg:col-span-1\"\n >\n <DonutChart\n data={categoryMix}\n centerLabel=\"Applets\"\n centerValue={storeApplets.length}\n height={240}\n ariaLabel=\"Store applets grouped by category\"\n emptyLabel=\"No applets to summarise yet.\"\n />\n </ChartCard>\n\n <div className=\"lg:col-span-2\">\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n {storeApplets.map((applet: ConfigurationFieldsFragment) => {\n const val = (applet.value as Record<string, unknown>) ?? {};\n const name =\n (val.name as string) ??\n applet.key.replace(\"applet.store.\", \"\");\n const description =\n applet.description ?? (val.description as string) ?? \"\";\n const category = (val.category as string) ?? \"general\";\n const isInstalled = installedSuffixes.has(\n applet.key.replace(\"applet.store.\", \"\"),\n );\n const isInstalling = installingId === applet.id;\n\n return (\n <GlassCard\n key={applet.id}\n treatment=\"glass\"\n className=\"flex flex-col p-5\"\n >\n <div className=\"flex items-start gap-3\">\n <div className=\"flex h-12 w-12 shrink-0 items-center justify-center rounded-xl bg-action-primary-bg/10 text-primary\">\n <Puzzle className=\"h-6 w-6\" />\n </div>\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-semibold text-text-primary\">\n {name}\n </p>\n {description && (\n <p className=\"mt-1 line-clamp-2 text-xs text-text-secondary\">\n {description}\n </p>\n )}\n </div>\n </div>\n <div className=\"mt-4 flex items-center justify-between gap-2 border-t border-border-seam pt-3\">\n <span className=\"inline-flex rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs font-medium capitalize text-text-secondary\">\n {category}\n </span>\n {isInstalled ? (\n <StatusBadge\n variant=\"success\"\n label=\"Installed\"\n className=\"gap-1\"\n />\n ) : (\n <button\n type=\"button\"\n onClick={() => void handleInstall(applet)}\n disabled={isInstalling}\n className=\"inline-flex items-center gap-1 rounded-md bg-action-primary-bg px-3 py-1.5 text-xs font-medium text-action-primary-text hover:bg-action-primary-bg/90 disabled:opacity-50\"\n >\n {isInstalling ? (\n \"Installing...\"\n ) : (\n <>\n <StoreIcon className=\"h-3 w-3\" />\n Get it\n </>\n )}\n </button>\n )}\n </div>\n </GlassCard>\n );\n })}\n </div>\n </div>\n </div>\n </div>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
import { StatCard as e } from "../../components/StatCard.js";
|
|
2
|
-
import { LoadingSkeleton as t } from "../../components/LoadingSkeleton.js";
|
|
3
|
-
import { useDeleteConfigurationMutation as n, useListConfigurationsQuery as r } from "../../../generated/wspace-operations.js";
|
|
4
|
-
import { PageLayout as i } from "../../components/PageLayout.js";
|
|
5
|
-
import { StatusBadge as a } from "../../components/StatusBadge.js";
|
|
6
|
-
import { EmptyState as o } from "../../components/EmptyState.js";
|
|
7
|
-
import { ChartCard as s } from "../../components/ChartCard.js";
|
|
8
|
-
import { DonutChart as c } from "../../components/charts/DonutChart.js";
|
|
9
|
-
import "../../components/index.js";
|
|
10
|
-
import { TimeCampusConfigScope as l } from "../../../generated/wspace-types.js";
|
|
11
|
-
import { useState as u } from "react";
|
|
12
|
-
import { ArrowUpCircle as d, Boxes as f, CheckCircle2 as p, Puzzle as m, Trash2 as h } from "lucide-react";
|
|
13
|
-
import { jsx as g, jsxs as _ } from "react/jsx-runtime";
|
|
14
|
-
import { GlassCard as v } from "@burdenoff/fe-libs/ui";
|
|
15
|
-
//#region src/timecampus/pages/applets/InstalledAppletsPage.tsx
|
|
16
|
-
function y() {
|
|
17
|
-
let { data: y, loading: b, error: x, refetch: S } = r({ variables: { scope: l.Workspace } }), [C] = n(), [w, T] = u(null), E = async (e) => {
|
|
18
|
-
T(e);
|
|
19
|
-
try {
|
|
20
|
-
await C({ variables: { id: e } }), await S();
|
|
21
|
-
} finally {
|
|
22
|
-
T(null);
|
|
23
|
-
}
|
|
24
|
-
}, D = (y?.timeCampusConfigurations ?? []).filter((e) => e.key.startsWith("applet.installed.")), O = D.filter((e) => (e.value ?? {}).isActive ?? !0).length, k = D.filter((e) => (e.value ?? {}).updateAvailable ?? !1).length, A = D.reduce((e, t) => {
|
|
25
|
-
let n = (t.value ?? {}).category ?? "general";
|
|
26
|
-
return e[n] = (e[n] ?? 0) + 1, e;
|
|
27
|
-
}, {}), j = Object.entries(A).sort((e, t) => t[1] - e[1]).map(([e, t]) => ({
|
|
28
|
-
name: e,
|
|
29
|
-
value: t
|
|
30
|
-
}));
|
|
31
|
-
return b ? /* @__PURE__ */ g(i, {
|
|
32
|
-
title: "Installed Applets",
|
|
33
|
-
description: "Manage your installed applets and integrations",
|
|
34
|
-
children: /* @__PURE__ */ g(t, {
|
|
35
|
-
variant: "card",
|
|
36
|
-
count: 6
|
|
37
|
-
})
|
|
38
|
-
}) : x ? /* @__PURE__ */ g(i, {
|
|
39
|
-
title: "Installed Applets",
|
|
40
|
-
description: "Manage your installed applets and integrations",
|
|
41
|
-
children: /* @__PURE__ */ g("div", {
|
|
42
|
-
className: "rounded-lg border border-status-error-bg/40 bg-status-error-bg p-4 text-sm text-status-error-text",
|
|
43
|
-
children: "Failed to load installed applets. Please try again later."
|
|
44
|
-
})
|
|
45
|
-
}) : /* @__PURE__ */ g(i, {
|
|
46
|
-
title: "Installed Applets",
|
|
47
|
-
description: "Manage your installed applets and integrations",
|
|
48
|
-
purpose: "See and manage every add-on running in your workspace — enable, configure or update them in one place so you stay in control of how TimeCampus is extended.",
|
|
49
|
-
children: D.length === 0 ? /* @__PURE__ */ g(o, {
|
|
50
|
-
illustration: "empty-generic",
|
|
51
|
-
title: "No applets installed yet",
|
|
52
|
-
description: "Browse the applet store to add integrations and trackers — installed applets are stored as workspace configurations and appear here."
|
|
53
|
-
}) : /* @__PURE__ */ _("div", {
|
|
54
|
-
className: "space-y-6",
|
|
55
|
-
children: [/* @__PURE__ */ _("div", {
|
|
56
|
-
className: "grid grid-cols-1 gap-4 sm:grid-cols-3",
|
|
57
|
-
children: [
|
|
58
|
-
/* @__PURE__ */ g(e, {
|
|
59
|
-
featured: !0,
|
|
60
|
-
title: "Installed",
|
|
61
|
-
value: D.length,
|
|
62
|
-
icon: /* @__PURE__ */ g(f, { className: "h-5 w-5" }),
|
|
63
|
-
description: "Add-ons in this workspace"
|
|
64
|
-
}),
|
|
65
|
-
/* @__PURE__ */ g(e, {
|
|
66
|
-
title: "Active",
|
|
67
|
-
value: O,
|
|
68
|
-
icon: /* @__PURE__ */ g(p, { className: "h-5 w-5" }),
|
|
69
|
-
description: "Currently enabled",
|
|
70
|
-
iconBgColor: "bg-status-success-bg text-status-success-text"
|
|
71
|
-
}),
|
|
72
|
-
/* @__PURE__ */ g(e, {
|
|
73
|
-
title: "Updates",
|
|
74
|
-
value: k,
|
|
75
|
-
icon: /* @__PURE__ */ g(d, { className: "h-5 w-5" }),
|
|
76
|
-
description: "Ready to update",
|
|
77
|
-
iconBgColor: "bg-status-info-bg text-status-info-text"
|
|
78
|
-
})
|
|
79
|
-
]
|
|
80
|
-
}), /* @__PURE__ */ _("div", {
|
|
81
|
-
className: "grid grid-cols-1 gap-6 lg:grid-cols-3",
|
|
82
|
-
children: [/* @__PURE__ */ g(s, {
|
|
83
|
-
title: "By category",
|
|
84
|
-
description: "How your add-ons break down",
|
|
85
|
-
className: "lg:col-span-1",
|
|
86
|
-
children: /* @__PURE__ */ g(c, {
|
|
87
|
-
data: j,
|
|
88
|
-
centerLabel: "Installed",
|
|
89
|
-
centerValue: D.length,
|
|
90
|
-
height: 240,
|
|
91
|
-
ariaLabel: "Installed applets grouped by category",
|
|
92
|
-
emptyLabel: "No applets to summarise yet."
|
|
93
|
-
})
|
|
94
|
-
}), /* @__PURE__ */ g("div", {
|
|
95
|
-
className: "lg:col-span-2",
|
|
96
|
-
children: /* @__PURE__ */ g("div", {
|
|
97
|
-
className: "grid grid-cols-1 gap-4 sm:grid-cols-2",
|
|
98
|
-
children: D.map((e) => {
|
|
99
|
-
let t = e.value ?? {}, n = t.name ?? e.key.replace("applet.installed.", ""), r = t.category ?? "general", i = t.version ?? "1.0.0", o = t.author ?? "Unknown", s = t.isActive ?? !0, c = t.updateAvailable ?? !1;
|
|
100
|
-
return /* @__PURE__ */ _(v, {
|
|
101
|
-
treatment: "glass",
|
|
102
|
-
glow: s,
|
|
103
|
-
className: "flex flex-col p-5",
|
|
104
|
-
children: [/* @__PURE__ */ _("div", {
|
|
105
|
-
className: "flex items-start justify-between gap-3",
|
|
106
|
-
children: [/* @__PURE__ */ _("div", {
|
|
107
|
-
className: "flex min-w-0 items-center gap-3",
|
|
108
|
-
children: [/* @__PURE__ */ g("div", {
|
|
109
|
-
className: "flex h-11 w-11 shrink-0 items-center justify-center rounded-xl bg-action-primary-bg/10 text-primary",
|
|
110
|
-
children: /* @__PURE__ */ g(m, { className: "h-5 w-5" })
|
|
111
|
-
}), /* @__PURE__ */ _("div", {
|
|
112
|
-
className: "min-w-0",
|
|
113
|
-
children: [/* @__PURE__ */ _("div", {
|
|
114
|
-
className: "flex items-center gap-2",
|
|
115
|
-
children: [/* @__PURE__ */ g("p", {
|
|
116
|
-
className: "truncate text-sm font-semibold text-text-primary",
|
|
117
|
-
children: n
|
|
118
|
-
}), c && /* @__PURE__ */ g(a, {
|
|
119
|
-
variant: "info",
|
|
120
|
-
label: "Update"
|
|
121
|
-
})]
|
|
122
|
-
}), /* @__PURE__ */ _("p", {
|
|
123
|
-
className: "mt-0.5 truncate text-xs text-text-secondary",
|
|
124
|
-
children: [
|
|
125
|
-
"v",
|
|
126
|
-
i,
|
|
127
|
-
" · by ",
|
|
128
|
-
o
|
|
129
|
-
]
|
|
130
|
-
})]
|
|
131
|
-
})]
|
|
132
|
-
}), /* @__PURE__ */ g("button", {
|
|
133
|
-
type: "button",
|
|
134
|
-
"aria-label": `Uninstall ${n}`,
|
|
135
|
-
disabled: w === e.id,
|
|
136
|
-
onClick: () => void E(e.id),
|
|
137
|
-
className: "rounded-md p-1.5 text-text-secondary hover:bg-bg-sunken hover:text-status-error-text disabled:opacity-50",
|
|
138
|
-
children: /* @__PURE__ */ g(h, { className: "h-4 w-4" })
|
|
139
|
-
})]
|
|
140
|
-
}), /* @__PURE__ */ _("div", {
|
|
141
|
-
className: "mt-4 flex items-center justify-between border-t border-border-seam pt-3",
|
|
142
|
-
children: [/* @__PURE__ */ g(a, {
|
|
143
|
-
status: s ? "active" : "inactive",
|
|
144
|
-
dot: s,
|
|
145
|
-
label: s ? "Active" : "Disabled"
|
|
146
|
-
}), /* @__PURE__ */ g("span", {
|
|
147
|
-
className: "inline-flex rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs font-medium capitalize text-text-secondary",
|
|
148
|
-
children: r
|
|
149
|
-
})]
|
|
150
|
-
})]
|
|
151
|
-
}, e.id);
|
|
152
|
-
})
|
|
153
|
-
})
|
|
154
|
-
})]
|
|
155
|
-
})]
|
|
156
|
-
})
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
//#endregion
|
|
160
|
-
export { y as InstalledAppletsPage };
|
|
161
|
-
|
|
162
|
-
//# sourceMappingURL=InstalledAppletsPage.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"InstalledAppletsPage.js","names":[],"sources":["../../../../src/timecampus/pages/applets/InstalledAppletsPage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport {\n Puzzle,\n Trash2,\n Boxes,\n CheckCircle2,\n ArrowUpCircle,\n} from \"lucide-react\";\nimport { GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport {\n PageLayout,\n EmptyState,\n LoadingSkeleton,\n StatCard,\n StatusBadge,\n ChartCard,\n DonutChart,\n} from \"../../components\";\nimport {\n useListConfigurationsQuery,\n useDeleteConfigurationMutation,\n type ConfigurationFieldsFragment,\n} from \"@/generated/wspace-operations\";\nimport { TimeCampusConfigScope } from \"@/generated/wspace-types\";\n\nexport function InstalledAppletsPage() {\n const { data, loading, error, refetch } = useListConfigurationsQuery({\n variables: { scope: TimeCampusConfigScope.Workspace },\n });\n const [deleteConfiguration] = useDeleteConfigurationMutation();\n const [uninstallingId, setUninstallingId] = useState<string | null>(null);\n\n const handleUninstall = async (id: string) => {\n setUninstallingId(id);\n try {\n await deleteConfiguration({ variables: { id } });\n await refetch();\n } finally {\n setUninstallingId(null);\n }\n };\n\n const configurations: ConfigurationFieldsFragment[] =\n data?.timeCampusConfigurations ?? [];\n\n // Filter configurations that represent installed applets (key starts with 'applet.installed.')\n const applets = configurations.filter((c: ConfigurationFieldsFragment) =>\n c.key.startsWith(\"applet.installed.\"),\n );\n\n // Real, derived aggregates over the installed applets the page already loads.\n const activeCount = applets.filter((applet) => {\n const val = (applet.value as Record<string, unknown>) ?? {};\n return (val.isActive as boolean) ?? true;\n }).length;\n const updatesCount = applets.filter((applet) => {\n const val = (applet.value as Record<string, unknown>) ?? {};\n return (val.updateAvailable as boolean) ?? false;\n }).length;\n const categoryCounts = applets.reduce<Record<string, number>>(\n (acc, applet) => {\n const val = (applet.value as Record<string, unknown>) ?? {};\n const category = (val.category as string) ?? \"general\";\n acc[category] = (acc[category] ?? 0) + 1;\n return acc;\n },\n {},\n );\n const categoryMix = Object.entries(categoryCounts)\n .sort((a, b) => b[1] - a[1])\n .map(([category, count]) => ({ name: category, value: count }));\n\n if (loading) {\n return (\n <PageLayout\n title=\"Installed Applets\"\n description=\"Manage your installed applets and integrations\"\n >\n <LoadingSkeleton variant=\"card\" count={6} />\n </PageLayout>\n );\n }\n\n if (error) {\n return (\n <PageLayout\n title=\"Installed Applets\"\n description=\"Manage your installed applets and integrations\"\n >\n <div className=\"rounded-lg border border-status-error-bg/40 bg-status-error-bg p-4 text-sm text-status-error-text\">\n Failed to load installed applets. Please try again later.\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title=\"Installed Applets\"\n description=\"Manage your installed applets and integrations\"\n purpose=\"See and manage every add-on running in your workspace — enable, configure or update them in one place so you stay in control of how TimeCampus is extended.\"\n >\n {applets.length === 0 ? (\n <EmptyState\n illustration=\"empty-generic\"\n title=\"No applets installed yet\"\n description=\"Browse the applet store to add integrations and trackers — installed applets are stored as workspace configurations and appear here.\"\n />\n ) : (\n <div className=\"space-y-6\">\n {/* KPI summary — derived from the installed applets below */}\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <StatCard\n featured\n title=\"Installed\"\n value={applets.length}\n icon={<Boxes className=\"h-5 w-5\" />}\n description=\"Add-ons in this workspace\"\n />\n <StatCard\n title=\"Active\"\n value={activeCount}\n icon={<CheckCircle2 className=\"h-5 w-5\" />}\n description=\"Currently enabled\"\n iconBgColor=\"bg-status-success-bg text-status-success-text\"\n />\n <StatCard\n title=\"Updates\"\n value={updatesCount}\n icon={<ArrowUpCircle className=\"h-5 w-5\" />}\n description=\"Ready to update\"\n iconBgColor=\"bg-status-info-bg text-status-info-text\"\n />\n </div>\n\n {/* Category mix ring + applet cards */}\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-3\">\n <ChartCard\n title=\"By category\"\n description=\"How your add-ons break down\"\n className=\"lg:col-span-1\"\n >\n <DonutChart\n data={categoryMix}\n centerLabel=\"Installed\"\n centerValue={applets.length}\n height={240}\n ariaLabel=\"Installed applets grouped by category\"\n emptyLabel=\"No applets to summarise yet.\"\n />\n </ChartCard>\n\n <div className=\"lg:col-span-2\">\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n {applets.map((applet: ConfigurationFieldsFragment) => {\n const val = (applet.value as Record<string, unknown>) ?? {};\n const name =\n (val.name as string) ??\n applet.key.replace(\"applet.installed.\", \"\");\n const category = (val.category as string) ?? \"general\";\n const version = (val.version as string) ?? \"1.0.0\";\n const author = (val.author as string) ?? \"Unknown\";\n const isActive = (val.isActive as boolean) ?? true;\n const hasUpdate = (val.updateAvailable as boolean) ?? false;\n\n return (\n <GlassCard\n key={applet.id}\n treatment=\"glass\"\n glow={isActive}\n className=\"flex flex-col p-5\"\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-11 w-11 shrink-0 items-center justify-center rounded-xl bg-action-primary-bg/10 text-primary\">\n <Puzzle className=\"h-5 w-5\" />\n </div>\n <div className=\"min-w-0\">\n <div className=\"flex items-center gap-2\">\n <p className=\"truncate text-sm font-semibold text-text-primary\">\n {name}\n </p>\n {hasUpdate && (\n <StatusBadge variant=\"info\" label=\"Update\" />\n )}\n </div>\n <p className=\"mt-0.5 truncate text-xs text-text-secondary\">\n v{version} · by {author}\n </p>\n </div>\n </div>\n <button\n type=\"button\"\n aria-label={`Uninstall ${name}`}\n disabled={uninstallingId === applet.id}\n onClick={() => void handleUninstall(applet.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\n <div className=\"mt-4 flex items-center justify-between border-t border-border-seam pt-3\">\n <StatusBadge\n status={isActive ? \"active\" : \"inactive\"}\n dot={isActive}\n label={isActive ? \"Active\" : \"Disabled\"}\n />\n <span className=\"inline-flex rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs font-medium capitalize text-text-secondary\">\n {category}\n </span>\n </div>\n </GlassCard>\n );\n })}\n </div>\n </div>\n </div>\n </div>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|