feat: card due dates (#271)

* feat: add card due dates to schema

* feat: update repo funcs

* feat: update card router to support due dates

* chore: update migration journal

* feat: add date selector

* feat: display date icon and label on cards

* feat: add due date filters

* feat: add due date to new card form

* feat: light mode tweaks

* feat: improve text eligibility on light mode

* feat: reduce selector font size

* feat: display date updates in card activity

* chore: gen translations
This commit is contained in:
Henry
2025-12-05 21:49:45 +00:00
committed by GitHub
parent 88f1aa0ec4
commit d208952d15
39 changed files with 5224 additions and 722 deletions

View File

@@ -13,6 +13,12 @@ import { generateSlug, generateUID } from "@kan/shared/utils";
import { createTRPCRouter, protectedProcedure, publicProcedure } from "../trpc";
import { assertUserInWorkspace } from "../utils/auth";
const dueDateFilterSchema = z.object({
startDate: z.string().datetime().optional(),
endDate: z.string().datetime().optional(),
hasNoDueDate: z.boolean().optional(),
});
export const boardRouter = createTRPCRouter({
all: protectedProcedure
.meta({
@@ -79,6 +85,7 @@ export const boardRouter = createTRPCRouter({
members: z.array(z.string().min(12)).optional(),
labels: z.array(z.string().min(12)).optional(),
lists: z.array(z.string().min(12)).optional(),
dueDate: z.array(dueDateFilterSchema).optional(),
type: z.enum(["regular", "template"]).optional(),
}),
)
@@ -112,6 +119,14 @@ export const boardRouter = createTRPCRouter({
members: input.members ?? [],
labels: input.labels ?? [],
lists: input.lists ?? [],
dueDate:
input.dueDate?.map((filter) => ({
startDate: filter.startDate
? new Date(filter.startDate)
: undefined,
endDate: filter.endDate ? new Date(filter.endDate) : undefined,
hasNoDueDate: filter.hasNoDueDate,
})) ?? [],
type: input.type,
},
);
@@ -145,6 +160,7 @@ export const boardRouter = createTRPCRouter({
members: z.array(z.string().min(12)).optional(),
labels: z.array(z.string().min(12)).optional(),
lists: z.array(z.string().min(12)).optional(),
dueDate: z.array(dueDateFilterSchema).optional(),
}),
)
.output(z.custom<Awaited<ReturnType<typeof boardRepo.getBySlug>>>())
@@ -168,6 +184,14 @@ export const boardRouter = createTRPCRouter({
members: input.members ?? [],
labels: input.labels ?? [],
lists: input.lists ?? [],
dueDate:
input.dueDate?.map((filter) => ({
startDate: filter.startDate
? new Date(filter.startDate)
: undefined,
endDate: filter.endDate ? new Date(filter.endDate) : undefined,
hasNoDueDate: filter.hasNoDueDate,
})) ?? [],
},
);
@@ -239,6 +263,7 @@ export const boardRouter = createTRPCRouter({
members: [],
labels: [],
lists: [],
dueDate: [],
type: sourceBoardInfo.type,
},
);

View File

@@ -32,6 +32,7 @@ export const cardRouter = createTRPCRouter({
labelPublicIds: z.array(z.string().min(12)),
memberPublicIds: z.array(z.string().min(12)),
position: z.enum(["start", "end"]),
dueDate: z.date().nullable().optional(),
}),
)
.output(z.custom<Awaited<ReturnType<typeof cardRepo.create>>>())
@@ -69,6 +70,7 @@ export const cardRouter = createTRPCRouter({
createdBy: userId,
listId: list.id,
position: input.position,
dueDate: input.dueDate ?? null,
});
const newCardId = newCard.id;
@@ -686,6 +688,7 @@ export const cardRouter = createTRPCRouter({
description: z.string().optional(),
index: z.number().optional(),
listPublicId: z.string().min(12).optional(),
dueDate: z.date().nullable().optional(),
}),
)
.output(z.custom<Awaited<ReturnType<typeof cardRepo.update>>>())
@@ -746,15 +749,19 @@ export const cardRouter = createTRPCRouter({
title: string;
description: string | null;
publicId: string;
dueDate: Date | null;
}
| undefined;
if (input.title || input.description) {
const previousDueDate = existingCard.dueDate;
if (input.title || input.description || input.dueDate !== undefined) {
result = await cardRepo.update(
ctx.db,
{
...(input.title && { title: input.title }),
...(input.description && { description: input.description }),
...(input.dueDate !== undefined && { dueDate: input.dueDate }),
},
{ cardPublicId: input.cardPublicId },
);
@@ -796,6 +803,32 @@ export const cardRouter = createTRPCRouter({
});
}
if (
input.dueDate !== undefined &&
previousDueDate?.getTime() !== input.dueDate?.getTime()
) {
let activityType:
| "card.updated.dueDate.added"
| "card.updated.dueDate.updated"
| "card.updated.dueDate.removed";
if (!previousDueDate) {
activityType = "card.updated.dueDate.added";
} else if (!input.dueDate) {
activityType = "card.updated.dueDate.removed";
} else {
activityType = "card.updated.dueDate.updated";
}
activities.push({
type: activityType,
cardId: result.id,
createdBy: userId,
fromDueDate: previousDueDate ?? undefined,
toDueDate: input.dueDate ?? undefined,
});
}
if (newListId && existingCard.listId !== newListId) {
activities.push({
type: "card.updated.list" as const,