feat: a home page with life
Greeting and summary of the day, the coming week, the latest across my projects, who is online now, the week in numbers (mine and the team's, with a streak), sprints under way, «Retomar», project cards with their activity, what to pick up when nothing is mine and first steps for newcomers. REST (GET /api/me/home), GraphQL (myHome) and MCP (get_my_week). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,7 @@ export * from './lib/facades/insights.facade';
|
||||
export * from './lib/facades/my-views.facade';
|
||||
export * from './lib/facades/presence.facade';
|
||||
export * from './lib/facades/portfolio.facade';
|
||||
export * from './lib/facades/my-home.facade';
|
||||
export * from './lib/facades/backlog.facade';
|
||||
export * from './lib/facades/list.facade';
|
||||
export * from './lib/facades/propose.facade';
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { type } from '@ngrx/signals';
|
||||
import { eventGroup } from '@ngrx/signals/events';
|
||||
import { Home } from '@task-stream/web/tracker/domain';
|
||||
|
||||
export const myHomePageEvents = eventGroup({
|
||||
source: 'My Home Page',
|
||||
events: {
|
||||
refreshed: type<void>(),
|
||||
},
|
||||
});
|
||||
|
||||
export const myHomeApiEvents = eventGroup({
|
||||
source: 'My Home API',
|
||||
events: {
|
||||
loaded: type<Home>(),
|
||||
loadFailed: type<string>(),
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,23 @@
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { injectDispatch } from '@ngrx/signals/events';
|
||||
import { myHomePageEvents } from '../events/my-home.events';
|
||||
import { MyHomeStore } from '../stores/my-home.store';
|
||||
|
||||
/**
|
||||
* The home page beyond «my work»: agenda, activity, the week, sprints,
|
||||
* project cards, suggestions, first steps and who is on what now.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class MyHomeFacade {
|
||||
private readonly store = inject(MyHomeStore);
|
||||
private readonly dispatch = injectDispatch(myHomePageEvents);
|
||||
|
||||
/** Null until the first load. */
|
||||
readonly home = this.store.home;
|
||||
readonly error = this.store.error;
|
||||
|
||||
/** Loads it (again, to refresh). */
|
||||
refresh(): void {
|
||||
this.dispatch.refreshed();
|
||||
}
|
||||
}
|
||||
50
libs/web/tracker/application/src/lib/stores/my-home.store.ts
Normal file
50
libs/web/tracker/application/src/lib/stores/my-home.store.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { inject } from '@angular/core';
|
||||
import { mapResponse } from '@ngrx/operators';
|
||||
import { signalStore, withState } from '@ngrx/signals';
|
||||
import {
|
||||
Events,
|
||||
on,
|
||||
withEventHandlers,
|
||||
withReducer,
|
||||
} from '@ngrx/signals/events';
|
||||
import { Home, ProjectsGateway } from '@task-stream/web/tracker/domain';
|
||||
import { exhaustMap } from 'rxjs';
|
||||
import { myHomeApiEvents, myHomePageEvents } from '../events/my-home.events';
|
||||
import { describeError } from '../notifications';
|
||||
|
||||
interface MyHomeState {
|
||||
home: Home | null;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The home page beyond «my work». Root-scoped, so coming back shows the
|
||||
* last reading at once while it refreshes.
|
||||
*/
|
||||
export const MyHomeStore = signalStore(
|
||||
{ providedIn: 'root' },
|
||||
withState<MyHomeState>({ home: null, error: null }),
|
||||
withReducer(
|
||||
on(myHomeApiEvents.loaded, ({ payload }) => ({
|
||||
home: payload,
|
||||
error: null,
|
||||
})),
|
||||
on(myHomeApiEvents.loadFailed, ({ payload }) => ({ error: payload })),
|
||||
),
|
||||
withEventHandlers(
|
||||
(_store, events = inject(Events), projects = inject(ProjectsGateway)) => ({
|
||||
// A refresh while one is under way waits for it instead of piling up.
|
||||
load$: events.on(myHomePageEvents.refreshed).pipe(
|
||||
exhaustMap(() =>
|
||||
projects.myHome().pipe(
|
||||
mapResponse({
|
||||
next: (home) => myHomeApiEvents.loaded(home),
|
||||
error: (error) =>
|
||||
myHomeApiEvents.loadFailed(describeError(error)),
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
}),
|
||||
),
|
||||
);
|
||||
Reference in New Issue
Block a user