Skip to content

Commit d208952

Browse files
authored
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
1 parent 88f1aa0 commit d208952

39 files changed

+5224
-722
lines changed

apps/web/i18n.lock

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ checksums:
1515
A%20powerful%2C%20flexible%20kanban%20app%20that%20helps%20you%20organise%20work%2C%20track%20progress%2C%20and%20deliver%20results%E2%80%94all%20in%20one%20place./singular: d405b83b0d631cb72f4347c10bcbb643
1616
Account/singular: 01215c12fb1cdb93bd0c84c1382bef56
1717
Account%20deleted/singular: a25da96a1579c4491be0a95669ef18a4
18+
Actions/singular: c46571856723b03262fd33f511116298
1819
Activity/singular: 1948763de8e531483a798b68195e297e
1920
Activity%20log/singular: 3ec6755040306fdc9ce801bcf6ab28e1
2021
Activity%20logs/singular: 8b1f0bb96a905646ecfad1cfdfa42168
@@ -91,6 +92,7 @@ checksums:
9192
Card%20title/singular: 7c34f59f4005e6cb3a6ff546ea0b96e3
9293
Change%20Password/singular: a552fc5c4189ebc3e2e6018edda7d18f
9394
Change%20your%20language%20preferences./singular: 293d49fc3c75e9c425b64bd7126e6b46
95+
changed%20the%20due%20date%20to%20%3C0%3E%7BformattedDate%7D%3C%2F0%3E/singular: 32189aa87452ab18f90f02742bfa9910
9496
Check%20out%20some%20of%20our%20favorite%20open%20source%20projects./singular: b0d0be4f63c16552b3aa795af29240d1
9597
Check%20your%20inbox/singular: e9a430fcd298def74212238df0f680d6
9698
Checklist%20name/singular: 5eb5de823f7ca5a4d97bb41e6a3f675a
@@ -175,6 +177,11 @@ checksums:
175177
Don't%20have%20an%20account%3F%20%3C0%3E%3C1%3ESign%20up%3C%2F1%3E%3C%2F0%3E/singular: 6ae8282ab8b4a01b4ce87b119e13714b
176178
Done/singular: ffd408fa29d5bc9039ef8ea1b9b699bb
177179
Download%20failed/singular: 142b391cca3e05f2af9617092f7358c5
180+
Due%20date/singular: b098b52aff38516c5838c2ac4419f958
181+
Due%20next%20month/singular: 223ab6f1211e1dfdf39ea68d25fe380b
182+
Due%20next%20week/singular: 2f8fac5719df18d25a466ee913dc6487
183+
Due%20today/singular: a14cb9dd0003485894d328bb803c80e5
184+
Due%20tomorrow/singular: 0b6c8ad7aba0873b7e212d5f1949ca97
178185
Edit%20board%20URL/singular: d8276dfc0189f371ec7d80a2047c07aa
179186
Edit%20comment/singular: 7e4b46525fcb6b47b71798e31c46e374
180187
Edit%20label/singular: 0309e0be1512b1e0b0ceb87c69a53d03
@@ -231,6 +238,7 @@ checksums:
231238
Free%2C%20forever/singular: ae4f2f81e88a50b5f250ef8e8e77e74b
232239
Full-time/singular: fe6e201f6676ae354111435cf6c76910
233240
Fun/singular: 395bdc48e943762b6cde649f4a96771d
241+
General/singular: b891e8f15579fc5d97bcaf3637f5ae59
234242
Get%20started/singular: 5c783951b0100a168bdd2161ff294833
235243
Get%20Started/singular: 1d5f030c4ec9c869e647ae060518b948
236244
Get%20started%20by%20creating%20a%20new%20%7B0%7D/singular: 4ca5ef637c08104e9dbb5ff9f49f0680
@@ -242,6 +250,10 @@ checksums:
242250
GitHub/singular: 6e1cf3c00fa6fbe24afcc78ea3b5f3e4
243251
Go%20Home/singular: 6251589da1964d55afabdfd64c84c335
244252
Go%20to%20app/singular: 896d0441384dcd2bfb3b23d61ff1944d
253+
Go%20to%20boards/singular: dc1362de207d67fe84fb0caf60262e73
254+
Go%20to%20members/singular: 445f4efbc4b1e7509f4fd79ebfbb1476
255+
Go%20to%20settings/singular: 24a7f96880650c9b37099d69f4b7e2a9
256+
Go%20to%20templates/singular: e4e58e33d637282d141df466d729bc7c
245257
High%20Priority/singular: 5d231ff8254aabc875f194c4b4f49c97
246258
Hired/singular: e5a9b1bd409b007141fe3d7890022f9a
247259
Host%20Kan%20on%20your%20own%20infrastructure.%20Ideal%20for%20organisations%20that%20need%20complete%20control%20over%20their%20data./singular: 8e7ae0783d60ef4624d3caf9bfc3747f
@@ -285,6 +297,7 @@ checksums:
285297
Kanban%20is%20better%20with%20a%20team.%20Perfect%20for%20small%20and%20growing%20teams%20looking%20to%20collaborate./singular: a77bee43046b260797c8936ad23e9223
286298
Kanban%20reimagined/singular: 613ccfdd9f54c66cbf68cfa313498766
287299
Keep%20in%20mind%20that%20this%20action%20is%20irreversible./singular: 79702b2ad3be71cb8b87f1122a60c199
300+
Keyboard%20Shortcuts/singular: ef00d7494b69def6841620bd6554d040
288301
Labels/singular: 6f15627a90002323eac018274b6922d6
289302
Labels%20%26%20filters/singular: e1ceda0c6cc32fb04cb9423582ad8fe4
290303
Labels%20%26%20Filters/singular: 8f0bdd6084516f8241cb613178114390
@@ -318,6 +331,7 @@ checksums:
318331
moved%20the%20card%20from%20%3C0%3E%7B0%7D%3C%2F0%3E%20to%3C1%3E%7B1%7D%3C%2F1%3E/singular: 11edf0427766c26db541d46379dc3c16
319332
moved%20the%20card%20to%20another%20list/singular: 2e8c41bfafb42fa299ce56e4300205ff
320333
Name/singular: 9368b5a047572b6051f334af5aa76819
334+
Navigation/singular: 0373afd8238db1c49f4be4fa6cdf5cd3
321335
Need%20help%3F/singular: 04e7322f2d3ffb2d73ff2f64b71637c8
322336
New/singular: 126d036fae5fb6b629728ecb97e6195b
323337
New%20%7B0%7D/singular: 67b6a22a65e0956f2091d00e6966652b
@@ -338,18 +352,23 @@ checksums:
338352
No%20authentication%20methods%20are%20currently%20available/singular: f718c896810da3612202887f9a4374fa
339353
No%20boards%20found/singular: e044088d2c51b6bdf660fafa86ee1e17
340354
No%20credit%20card%20required/singular: 2090aa4171dc0b60735069f89b592883
355+
No%20dates/singular: bd0ec82b3b1f4a5fa89d362b71f8141d
341356
No%20download%20URL%20available%20for%20this%20attachment./singular: e367d39420b2242f9d2fd749c87f446a
357+
No%20keyboard%20shortcuts%20registered./singular: 7f1ed5d777cade7d62303e9e591bbf63
342358
No%20lists/singular: cedf633d99c77ff4356e089f2d98c0a6
343359
No%20results%20found%20for%20%22%7BdebouncedQuery%7D%22./singular: 5db6294712528cd897b15ae36f4fd834
344360
Offer/singular: 82b4e0c9a3f5b4bd93590847de7c32a1
345361
Onboarding/singular: 52b23f9c62ff199d4c09920e7641829e
346362
Once%20you%20delete%20your%20account%2C%20there%20is%20no%20going%20back.%20This%20action%20cannot%20be%20undone./singular: 9cf7aa6ef30890e5124e266c081bae1c
347363
Once%20you%20delete%20your%20workspace%2C%20there%20is%20no%20going%20back.%20This%20action%20cannot%20be%20undone./singular: c9ce6a516ed1f361653e8f8db3dd5ffd
364+
Open%20command%20menu/singular: cf00563ea1a994e7c36a761d56664acc
365+
Open%20keyboard%20shortcuts/singular: 638ae0999fd03d2c611064274859422e
348366
Open%20source/singular: 81a747dc664de1a3389b495c8f84843b
349367
Open%20Source%20Friends/singular: ffe3d75e5d03b8469c9dcb019dceb164
350368
or/singular: 7b133c38bec0d5ee23cc6bcf9a8de50b
351369
Organize%20and%20find%20cards%20quickly%20with%20powerful%20filtering%20tools./singular: 1b9898c4b21e9dff413b4f76dc59db56
352370
OSS%20Friends/singular: 706e10666dfe26130c17fedb5366a25d
371+
Overdue/singular: 24caaa2b5d7a2447ab7664e3771cf98c
353372
Own%20your%20data/singular: cc2178dac4bdf6b07f030cfc2a7510e6
354373
Owned%20by%20Atlassian/singular: ace4ed076a5318ad48c296fa09afed69
355374
Part-time/singular: 213d63da450f35dabb3ab0e35e29feed
@@ -393,6 +412,7 @@ checksums:
393412
removed%20a%20label%20from%20the%20card/singular: f83ec18850a2a8a3f8242a740a04df06
394413
removed%20a%20member%20from%20the%20card/singular: 0bd7561bb79358fde3d595c3e60a315a
395414
removed%20label%20%3C0%3E%7B0%7D%3C%2F0%3E/singular: 7e0936b15831e65754588b8512f62fcb
415+
removed%20the%20due%20date/singular: 000c084844718540fcad542dd8caf8b4
396416
renamed%20a%20checklist/singular: 4d208de1857740e63469007c5b8b491a
397417
renamed%20checklist%20%3C0%3E%7B0%7D%3C%2F0%3E/singular: e95d119ef65b0af6c0a96bef182be671
398418
renamed%20checklist%20item%20to%20%3C0%3E%7B0%7D%3C%2F0%3E/singular: 50f55f71f9245890afee434d7adc2140
@@ -417,12 +437,15 @@ checksums:
417437
Self-hostable/singular: 3e3597145cc17f03b8b5646b80d59592
418438
Send%20feedback/singular: 9631cc08d49da04475b30a0d320ce97c
419439
Senior/singular: 3fff865dc00435f82896fc302ea45630
440+
Set%20due%20date/singular: 3cb81c4829857556f5d117c9a23f7bcc
441+
set%20the%20due%20date/singular: b5d9a1edd988f455041aee9fa7fb3d63
420442
Settings/singular: 8df6777277469c1fd88cc18dde2f1cc3
421443
Settings%20%7C%20Account/singular: 050e18406849ec057edac877c297c3e1
422444
Settings%20%7C%20API/singular: 85101e4b802a09ad9e3f01ff116f0894
423445
Settings%20%7C%20Billing/singular: e44cba741d5414035a0b499c5766c203
424446
Settings%20%7C%20Integrations/singular: d04992e28016452f6d3d7dcc0b592415
425447
Settings%20%7C%20Workspace/singular: 5d0bacf7ff696da940f232df45edfd39
448+
Shortcuts/singular: db3330ed3240c398054f3be23c52851f
426449
Sign%20in/singular: cb8757c7450e17de1e226e82fb0fa4a2
427450
Sign%20In/singular: ec7b8f314fe9bc6591006707484ede61
428451
Sign%20Up/singular: 0dd2ae69be4618c1f9e615774a4509ca
@@ -497,6 +520,7 @@ checksums:
497520
Unable%20to%20update%20checklist/singular: 44c10f49f448909ceca481ed9b4b07e7
498521
Unable%20to%20update%20checklist%20item/singular: 5c86b3523531c30d58856e3ecde0cb55
499522
Unable%20to%20update%20comment/singular: 05e798ed6b0f3fee41f3e8832eb699ea
523+
Unable%20to%20update%20due%20date/singular: af20d7482cb639a8c8264c681a18abed
500524
Unable%20to%20update%20labels/singular: dca2bdc3dcf74bc9d95e05156039a291
501525
Unable%20to%20update%20list/singular: 14aa802f91b9b4c05236c8c75afb33da
502526
Unable%20to%20update%20members/singular: 9a851a6b0c75ee16d25cf2ff4b67b255
@@ -515,6 +539,7 @@ checksums:
515539
Update%20label/singular: 97880b503dfe956941c5f23025a1c102
516540
updated%20a%20checklist%20item/singular: 198e1d1d503f4b69b752dd84d0cc0e9d
517541
updated%20the%20description/singular: 70fd604b6dc957a75be90ea77572d76d
542+
updated%20the%20due%20date/singular: 7636c200d50fed58a4295ed1e9b5c1e0
518543
updated%20the%20title/singular: 98cf8f12923ec1a58e767e3a40863b21
519544
updated%20the%20title%20to%20%3C0%3E%7B0%7D%3C%2F0%3E/singular: aca196d729401d8adc5c8ca9d558840e
520545
Upgrade%20to%20Pro/singular: 972773025e763ddf53273df6d37a729a
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import {
2+
addMonths,
3+
eachDayOfInterval,
4+
endOfMonth,
5+
endOfWeek,
6+
format,
7+
isSameDay,
8+
isToday,
9+
startOfMonth,
10+
startOfWeek,
11+
subMonths,
12+
} from "date-fns";
13+
import { useMemo, useState } from "react";
14+
import { HiChevronLeft, HiChevronRight } from "react-icons/hi2";
15+
import { twMerge } from "tailwind-merge";
16+
17+
interface DateSelectorProps {
18+
selectedDate?: Date | null;
19+
onDateSelect?: (date: Date | undefined) => void;
20+
}
21+
22+
const DateSelector = ({ selectedDate, onDateSelect }: DateSelectorProps) => {
23+
const [currentMonth, setCurrentMonth] = useState(() => {
24+
return selectedDate ? startOfMonth(selectedDate) : startOfMonth(new Date());
25+
});
26+
27+
const monthName = format(currentMonth, "MMMM");
28+
const year = format(currentMonth, "yyyy");
29+
30+
const dayHeaders = useMemo(() => {
31+
const weekStart = startOfWeek(new Date(), { weekStartsOn: 1 }); // Monday
32+
return eachDayOfInterval({
33+
start: weekStart,
34+
end: new Date(weekStart.getTime() + 6 * 24 * 60 * 60 * 1000),
35+
}).map((date) => format(date, "EEEEEE")); // Shortest localized day name
36+
}, []);
37+
38+
const days = useMemo(() => {
39+
const monthStart = startOfMonth(currentMonth);
40+
const monthEnd = endOfMonth(currentMonth);
41+
const calendarStart = startOfWeek(monthStart, { weekStartsOn: 1 }); // Monday
42+
const calendarEnd = endOfWeek(monthEnd, { weekStartsOn: 1 }); // Monday
43+
44+
return eachDayOfInterval({ start: calendarStart, end: calendarEnd }).map(
45+
(date) => {
46+
const dateString = format(date, "yyyy-MM-dd");
47+
return {
48+
date: dateString,
49+
isToday: isToday(date),
50+
isSelected: selectedDate ? isSameDay(date, selectedDate) : false,
51+
isCurrentMonth: date >= monthStart && date <= monthEnd,
52+
dateObj: date,
53+
};
54+
},
55+
);
56+
}, [currentMonth, selectedDate]);
57+
58+
const handlePreviousMonth = () => {
59+
setCurrentMonth(subMonths(currentMonth, 1));
60+
};
61+
62+
const handleNextMonth = () => {
63+
setCurrentMonth(addMonths(currentMonth, 1));
64+
};
65+
66+
const handleDateClick = (date: Date, e: React.MouseEvent) => {
67+
e.stopPropagation();
68+
// If clicking the same date that's already selected, unselect it
69+
if (selectedDate && isSameDay(date, selectedDate)) {
70+
onDateSelect?.(undefined);
71+
} else {
72+
onDateSelect?.(date);
73+
}
74+
};
75+
76+
return (
77+
<div className="w-[250px] p-4">
78+
<div className="flex items-center text-light-1000 dark:text-dark-1000">
79+
<button
80+
type="button"
81+
onClick={handlePreviousMonth}
82+
className="flex flex-none items-center justify-center p-1.5 text-light-700 hover:text-light-900 dark:text-dark-700 dark:hover:text-dark-1000"
83+
>
84+
<span className="sr-only">Previous month</span>
85+
<HiChevronLeft aria-hidden="true" className="h-4 w-4" />
86+
</button>
87+
<div className="flex-1 text-center text-sm font-semibold">
88+
{monthName} {year}
89+
</div>
90+
<button
91+
type="button"
92+
onClick={handleNextMonth}
93+
className="flex flex-none items-center justify-center p-1.5 text-light-700 hover:text-light-900 dark:text-dark-700 dark:hover:text-dark-1000"
94+
>
95+
<span className="sr-only">Next month</span>
96+
<HiChevronRight aria-hidden="true" className="h-4 w-4" />
97+
</button>
98+
</div>
99+
<div className="mt-6 grid grid-cols-7 text-center text-xs/6 text-light-950 dark:text-dark-950">
100+
{dayHeaders.map((day, index) => (
101+
<div key={index}>{day}</div>
102+
))}
103+
</div>
104+
<div className="isolate mt-2 grid grid-cols-7 text-sm">
105+
{days.map((day) => (
106+
<button
107+
key={day.date}
108+
type="button"
109+
onClick={(e) => handleDateClick(day.dateObj, e)}
110+
className={twMerge(
111+
"flex aspect-square items-center justify-center rounded-lg focus:z-10",
112+
day.isSelected
113+
? "bg-light-1000 hover:bg-light-1000 dark:bg-dark-1000 dark:hover:bg-dark-1000"
114+
: "bg-transparent hover:bg-light-200 dark:bg-transparent dark:hover:bg-dark-200",
115+
)}
116+
>
117+
<time
118+
dateTime={day.date}
119+
className={twMerge(
120+
"mx-auto flex size-7 items-center justify-center rounded-full text-light-900 dark:text-dark-900",
121+
day.isCurrentMonth
122+
? "text-light-900 dark:text-dark-900"
123+
: "text-light-700 dark:text-dark-600",
124+
day.isSelected && "text-light-50 dark:text-dark-50",
125+
)}
126+
>
127+
{day.date.split("-").pop()?.replace(/^0/, "")}
128+
</time>
129+
</button>
130+
))}
131+
</div>
132+
</div>
133+
);
134+
};
135+
136+
export default DateSelector;

apps/web/src/hooks/useLocalisation.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
import type { Locale as DateFnsLocale } from "date-fns";
12
import { useLingui } from "@lingui/react";
3+
import { de, enGB, es, fr, it, nl, pl, ru } from "date-fns/locale";
24

35
import type { Locale } from "~/locales";
46
import { useLinguiContext } from "~/providers/lingui";
@@ -8,16 +10,29 @@ export function useLocalisation() {
810
const { i18n } = useLingui();
911
const { locale, setLocale, availableLocales } = useLinguiContext();
1012

13+
const dateLocaleMap: Partial<Record<Locale, DateFnsLocale>> = {
14+
en: enGB,
15+
fr,
16+
de,
17+
es,
18+
it,
19+
nl,
20+
ru,
21+
pl,
22+
};
23+
24+
const currentDateLocale = dateLocaleMap[locale] ?? enGB;
25+
1126
const handleSetLocale = async (newLocale: Locale) => {
1227
await activateLocale(newLocale);
1328
setLocale(newLocale);
1429
};
1530

1631
return {
1732
locale,
33+
dateLocale: currentDateLocale,
1834
setLocale: handleSetLocale,
1935
availableLocales,
20-
formatDate: i18n.date,
2136
formatNumber: i18n.number,
2237
};
2338
}

0 commit comments

Comments
 (0)