feat: «Mi día», the few tickets I pick for today
A home panel with the tickets picked for today (10 at most), crossed out
as they get done and counted («1 de 3 terminados»), my open tickets to
add with one click, and what was left unfinished on my last day to carry
over. «☀ Mi día» on a ticket adds it or takes it out. The day is the
person's own (YYYY-MM-DD from the browser); private, no history (Mongo
`my_days`).
API: myDay(day), addToMyDay, removeFromMyDay; GET /api/me/day,
PUT/DELETE /api/me/day/{issue}. MCP: my_day.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -20,6 +20,7 @@ import {
|
||||
PinsService,
|
||||
TreeFollowsService,
|
||||
IssueRemindersService,
|
||||
MyDayService,
|
||||
PresenceService,
|
||||
WorkflowsService,
|
||||
} from '@task-stream/api/tracker/application';
|
||||
@@ -54,6 +55,7 @@ import {
|
||||
IssuePageGql,
|
||||
IssueParentGql,
|
||||
IssueReminderGql,
|
||||
MyDayGql,
|
||||
ReminderOnIssueGql,
|
||||
PresenceGql,
|
||||
} from './types';
|
||||
@@ -69,6 +71,7 @@ export class IssueResolver {
|
||||
private readonly pins: PinsService,
|
||||
private readonly treeFollows: TreeFollowsService,
|
||||
private readonly reminders: IssueRemindersService,
|
||||
private readonly myDayService: MyDayService,
|
||||
) {}
|
||||
|
||||
@Mutation(() => [PresenceGql], {
|
||||
@@ -442,6 +445,40 @@ export class IssueResolver {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Query(() => MyDayGql, {
|
||||
description:
|
||||
'«Mi día»: the tickets I picked for today, and what was left on my last day',
|
||||
})
|
||||
myDay(
|
||||
@CurrentUser() me: User,
|
||||
@Args('day', { description: 'Today, YYYY-MM-DD in my time zone' })
|
||||
day: string,
|
||||
) {
|
||||
return this.myDayService.get(me.id, day);
|
||||
}
|
||||
|
||||
@Mutation(() => Boolean, {
|
||||
description: 'Add tickets to «Mi día» (10 at most)',
|
||||
})
|
||||
async addToMyDay(
|
||||
@CurrentUser() me: User,
|
||||
@Args('day') day: string,
|
||||
@Args('issueIds', { type: () => [ID] }) issueIds: string[],
|
||||
): Promise<boolean> {
|
||||
await this.myDayService.add(me.id, day, issueIds);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Mutation(() => Boolean, { description: 'Take a ticket out of «Mi día»' })
|
||||
async removeFromMyDay(
|
||||
@CurrentUser() me: User,
|
||||
@Args('day') day: string,
|
||||
@Args('issueId', { type: () => ID }) issueId: string,
|
||||
): Promise<boolean> {
|
||||
await this.myDayService.remove(me.id, day, issueId);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Query(() => [ReminderOnIssueGql], {
|
||||
description: 'My waiting reminders on tickets, soonest first',
|
||||
})
|
||||
|
||||
@@ -1393,3 +1393,22 @@ export class ReminderOnIssueGql {
|
||||
@Field(() => IssueReminderGql) reminder!: IssueReminderGql;
|
||||
@Field(() => IssueGql) issue!: IssueGql;
|
||||
}
|
||||
|
||||
@ObjectType('MyDayLeftover', {
|
||||
description: 'What was left unfinished on the latest earlier day',
|
||||
})
|
||||
export class MyDayLeftoverGql {
|
||||
@Field() day!: string;
|
||||
@Field(() => [IssueGql]) issues!: IssueGql[];
|
||||
}
|
||||
|
||||
@ObjectType('MyDay', {
|
||||
description: '«Mi día»: the tickets I picked for today',
|
||||
})
|
||||
export class MyDayGql {
|
||||
@Field({ description: 'YYYY-MM-DD, in my time zone' }) day!: string;
|
||||
@Field(() => [IssueGql], { description: 'In order, done ones included' })
|
||||
issues!: IssueGql[];
|
||||
@Field(() => MyDayLeftoverGql, { nullable: true })
|
||||
leftover!: MyDayLeftoverGql | null;
|
||||
}
|
||||
|
||||
@@ -1552,6 +1552,12 @@ export class PushEndpointDto {
|
||||
endpoint!: string;
|
||||
}
|
||||
|
||||
export class MyDayQueryDto {
|
||||
@ApiProperty({ example: '2026-10-20', description: 'Today, in my time zone' })
|
||||
@Matches(/^\d{4}-\d{2}-\d{2}$/)
|
||||
day!: string;
|
||||
}
|
||||
|
||||
export class SetReminderDto {
|
||||
@ApiProperty({
|
||||
format: 'date-time',
|
||||
|
||||
@@ -1427,3 +1427,15 @@ export class ReminderOnIssueDto {
|
||||
@ApiProperty({ type: IssueReminderDto }) reminder!: IssueReminderDto;
|
||||
@ApiProperty({ type: () => IssueDto }) issue!: IssueDto;
|
||||
}
|
||||
|
||||
export class MyDayLeftoverDto {
|
||||
@ApiProperty() day!: string;
|
||||
@ApiProperty({ type: () => [IssueDto] }) issues!: IssueDto[];
|
||||
}
|
||||
|
||||
export class MyDayDto {
|
||||
@ApiProperty({ description: 'YYYY-MM-DD, in my time zone' }) day!: string;
|
||||
@ApiProperty({ type: () => [IssueDto] }) issues!: IssueDto[];
|
||||
@ApiProperty({ type: MyDayLeftoverDto, nullable: true })
|
||||
leftover!: MyDayLeftoverDto | null;
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ import {
|
||||
PinsService,
|
||||
TreeFollowsService,
|
||||
IssueRemindersService,
|
||||
MyDayService,
|
||||
PresenceService,
|
||||
ProjectsService,
|
||||
} from '@task-stream/api/tracker/application';
|
||||
@@ -49,6 +50,7 @@ import {
|
||||
CreateIssueDto,
|
||||
DecisionsQueryDto,
|
||||
SetReminderDto,
|
||||
MyDayQueryDto,
|
||||
IssueSearchQueryDto,
|
||||
PresenceBodyDto,
|
||||
LinkIssueDto,
|
||||
@@ -69,6 +71,7 @@ import {
|
||||
CommentPageDto,
|
||||
DecisionDto,
|
||||
IssueReminderDto,
|
||||
MyDayDto,
|
||||
ReminderOnIssueDto,
|
||||
EpicDto,
|
||||
IssueDto,
|
||||
@@ -106,6 +109,7 @@ export class IssuesController {
|
||||
private readonly pins: PinsService,
|
||||
private readonly treeFollows: TreeFollowsService,
|
||||
private readonly reminders: IssueRemindersService,
|
||||
private readonly myDayService: MyDayService,
|
||||
) {}
|
||||
|
||||
@Get('me/home')
|
||||
@@ -210,6 +214,59 @@ export class IssuesController {
|
||||
await this.pins.pin(me.id, await this.refs.issueId(me.id, issue), false);
|
||||
}
|
||||
|
||||
@Get('me/day')
|
||||
@ApiOperation({
|
||||
summary: '«Mi día»: the tickets I picked for today',
|
||||
description: 'And what was left unfinished on my latest earlier day.',
|
||||
})
|
||||
@ApiOkResponse({ type: MyDayDto })
|
||||
@ApiReadErrors()
|
||||
async myDay(@CurrentUser() me: User, @Query() query: MyDayQueryDto) {
|
||||
const myDay = await this.myDayService.get(me.id, query.day);
|
||||
return {
|
||||
day: myDay.day,
|
||||
issues: await this.refs.issueDtos(myDay.issues, viewerOf(me)),
|
||||
leftover: myDay.leftover && {
|
||||
day: myDay.leftover.day,
|
||||
issues: await this.refs.issueDtos(myDay.leftover.issues, viewerOf(me)),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@Put('me/day/:issue')
|
||||
@HttpCode(204)
|
||||
@ISSUE
|
||||
@ApiOperation({ summary: 'Add a ticket to «Mi día» (10 at most)' })
|
||||
@ApiNoContentResponse()
|
||||
@ApiCommandErrors()
|
||||
async addToMyDay(
|
||||
@CurrentUser() me: User,
|
||||
@Param('issue') issue: string,
|
||||
@Query() query: MyDayQueryDto,
|
||||
) {
|
||||
await this.myDayService.add(me.id, query.day, [
|
||||
await this.refs.issueId(me.id, issue),
|
||||
]);
|
||||
}
|
||||
|
||||
@Delete('me/day/:issue')
|
||||
@HttpCode(204)
|
||||
@ISSUE
|
||||
@ApiOperation({ summary: 'Take a ticket out of «Mi día»' })
|
||||
@ApiNoContentResponse()
|
||||
@ApiCommandErrors()
|
||||
async removeFromMyDay(
|
||||
@CurrentUser() me: User,
|
||||
@Param('issue') issue: string,
|
||||
@Query() query: MyDayQueryDto,
|
||||
) {
|
||||
await this.myDayService.remove(
|
||||
me.id,
|
||||
query.day,
|
||||
await this.refs.issueId(me.id, issue),
|
||||
);
|
||||
}
|
||||
|
||||
@Get('me/reminders')
|
||||
@ApiOperation({ summary: 'My waiting reminders on tickets, soonest first' })
|
||||
@ApiOkResponse({ type: [ReminderOnIssueDto] })
|
||||
|
||||
Reference in New Issue
Block a user