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:
@@ -1,4 +1,15 @@
|
||||
import { and, asc, desc, eq, inArray, isNull, or } from "drizzle-orm";
|
||||
import {
|
||||
and,
|
||||
asc,
|
||||
desc,
|
||||
eq,
|
||||
gte,
|
||||
inArray,
|
||||
isNotNull,
|
||||
isNull,
|
||||
lt,
|
||||
or,
|
||||
} from "drizzle-orm";
|
||||
|
||||
import type { dbClient } from "@kan/db/client";
|
||||
import type { BoardVisibilityStatus } from "@kan/db/schema";
|
||||
@@ -65,6 +76,39 @@ export const getIdByPublicId = async (db: dbClient, boardPublicId: string) => {
|
||||
return board;
|
||||
};
|
||||
|
||||
interface DueDateFilter {
|
||||
startDate?: Date;
|
||||
endDate?: Date;
|
||||
hasNoDueDate?: boolean;
|
||||
}
|
||||
|
||||
const buildDueDateWhere = (filters: DueDateFilter[]) => {
|
||||
if (!filters.length) return undefined;
|
||||
|
||||
const clauses = filters
|
||||
.map((filter) => {
|
||||
const conditions: ReturnType<typeof and>[] = [];
|
||||
|
||||
if (filter.hasNoDueDate) {
|
||||
conditions.push(isNull(cards.dueDate));
|
||||
} else {
|
||||
conditions.push(isNotNull(cards.dueDate));
|
||||
|
||||
if (filter.startDate)
|
||||
conditions.push(gte(cards.dueDate, filter.startDate));
|
||||
|
||||
if (filter.endDate) conditions.push(lt(cards.dueDate, filter.endDate));
|
||||
}
|
||||
|
||||
return conditions.length > 0 ? and(...conditions) : undefined;
|
||||
})
|
||||
.filter((clause): clause is NonNullable<typeof clause> => !!clause);
|
||||
|
||||
if (!clauses.length) return undefined;
|
||||
|
||||
return or(...clauses);
|
||||
};
|
||||
|
||||
export const getByPublicId = async (
|
||||
db: dbClient,
|
||||
boardPublicId: string,
|
||||
@@ -72,6 +116,7 @@ export const getByPublicId = async (
|
||||
members: string[];
|
||||
labels: string[];
|
||||
lists: string[];
|
||||
dueDate: DueDateFilter[];
|
||||
type: "regular" | "template" | undefined;
|
||||
},
|
||||
) => {
|
||||
@@ -164,6 +209,7 @@ export const getByPublicId = async (
|
||||
description: true,
|
||||
listId: true,
|
||||
index: true,
|
||||
dueDate: true,
|
||||
},
|
||||
with: {
|
||||
labels: {
|
||||
@@ -236,6 +282,7 @@ export const getByPublicId = async (
|
||||
where: and(
|
||||
cardIds.length > 0 ? inArray(cards.publicId, cardIds) : undefined,
|
||||
isNull(cards.deletedAt),
|
||||
buildDueDateWhere(filters.dueDate),
|
||||
),
|
||||
orderBy: [asc(cards.index)],
|
||||
},
|
||||
@@ -291,6 +338,7 @@ export const getBySlug = async (
|
||||
members: string[];
|
||||
labels: string[];
|
||||
lists: string[];
|
||||
dueDate: DueDateFilter[];
|
||||
},
|
||||
) => {
|
||||
let cardIds: string[] = [];
|
||||
@@ -353,6 +401,7 @@ export const getBySlug = async (
|
||||
description: true,
|
||||
listId: true,
|
||||
index: true,
|
||||
dueDate: true,
|
||||
},
|
||||
with: {
|
||||
labels: {
|
||||
@@ -405,6 +454,7 @@ export const getBySlug = async (
|
||||
where: and(
|
||||
cardIds.length > 0 ? inArray(cards.publicId, cardIds) : undefined,
|
||||
isNull(cards.deletedAt),
|
||||
buildDueDateWhere(filters.dueDate),
|
||||
),
|
||||
orderBy: [asc(cards.index)],
|
||||
},
|
||||
|
||||
@@ -23,6 +23,7 @@ export const create = async (
|
||||
createdBy: string;
|
||||
listId: number;
|
||||
position: "start" | "end";
|
||||
dueDate?: Date | null;
|
||||
},
|
||||
) => {
|
||||
return db.transaction(async (tx) => {
|
||||
@@ -71,6 +72,7 @@ export const create = async (
|
||||
createdBy: cardInput.createdBy,
|
||||
listId: cardInput.listId,
|
||||
index: index,
|
||||
dueDate: cardInput.dueDate ?? null,
|
||||
})
|
||||
.returning({ id: cards.id, listId: cards.listId });
|
||||
|
||||
@@ -163,6 +165,7 @@ export const update = async (
|
||||
cardInput: {
|
||||
title?: string;
|
||||
description?: string;
|
||||
dueDate?: Date | null;
|
||||
},
|
||||
args: {
|
||||
cardPublicId: string;
|
||||
@@ -173,6 +176,8 @@ export const update = async (
|
||||
.set({
|
||||
title: cardInput.title,
|
||||
description: cardInput.description,
|
||||
dueDate: cardInput.dueDate !== undefined ? cardInput.dueDate : undefined,
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.where(and(eq(cards.publicId, args.cardPublicId), isNull(cards.deletedAt)))
|
||||
.returning({
|
||||
@@ -180,6 +185,7 @@ export const update = async (
|
||||
publicId: cards.publicId,
|
||||
title: cards.title,
|
||||
description: cards.description,
|
||||
dueDate: cards.dueDate,
|
||||
});
|
||||
|
||||
return result;
|
||||
@@ -214,6 +220,7 @@ export const getByPublicId = (db: dbClient, cardPublicId: string) => {
|
||||
title: true,
|
||||
description: true,
|
||||
listId: true,
|
||||
dueDate: true,
|
||||
},
|
||||
where: eq(cards.publicId, cardPublicId),
|
||||
});
|
||||
@@ -397,6 +404,7 @@ export const getWithListAndMembersByPublicId = async (
|
||||
publicId: true,
|
||||
title: true,
|
||||
description: true,
|
||||
dueDate: true,
|
||||
},
|
||||
with: {
|
||||
labels: {
|
||||
@@ -531,6 +539,8 @@ export const getWithListAndMembersByPublicId = async (
|
||||
toTitle: true,
|
||||
fromDescription: true,
|
||||
toDescription: true,
|
||||
fromDueDate: true,
|
||||
toDueDate: true,
|
||||
},
|
||||
with: {
|
||||
fromList: {
|
||||
@@ -780,6 +790,7 @@ export const reorder = async (
|
||||
publicId: true,
|
||||
title: true,
|
||||
description: true,
|
||||
dueDate: true,
|
||||
},
|
||||
where: eq(cards.id, card.id),
|
||||
});
|
||||
|
||||
@@ -22,6 +22,8 @@ export const create = async (
|
||||
commentId?: number;
|
||||
fromComment?: string;
|
||||
toComment?: string;
|
||||
fromDueDate?: Date;
|
||||
toDueDate?: Date;
|
||||
sourceBoardId?: number;
|
||||
},
|
||||
) => {
|
||||
@@ -45,6 +47,8 @@ export const create = async (
|
||||
commentId: activityInput.commentId,
|
||||
fromComment: activityInput.fromComment,
|
||||
toComment: activityInput.toComment,
|
||||
fromDueDate: activityInput.fromDueDate,
|
||||
toDueDate: activityInput.toDueDate,
|
||||
sourceBoardId: activityInput.sourceBoardId,
|
||||
})
|
||||
.returning({ id: cardActivities.id });
|
||||
@@ -68,6 +72,8 @@ export const bulkCreate = async (
|
||||
fromDescription?: string;
|
||||
toDescription?: string;
|
||||
createdBy: string;
|
||||
fromDueDate?: Date;
|
||||
toDueDate?: Date;
|
||||
sourceBoardId?: number;
|
||||
}[],
|
||||
) => {
|
||||
|
||||
Reference in New Issue
Block a user