Skip to content

Commit 86ed350

Browse files
committed
feat(kanban): ✨ show deadline in kanban
1 parent 5fabaec commit 86ed350

File tree

7 files changed

+62
-33
lines changed

7 files changed

+62
-33
lines changed

src/Agenda3/components/kanban/Column.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,19 @@ import { useEffect, useImperativeHandle, useRef } from 'react'
66
import React from 'react'
77
import { ReactSortable } from 'react-sortablejs'
88

9-
import { updateBlockDateInfo } from '@/Agenda3/helpers/block'
109
import { track } from '@/Agenda3/helpers/umami'
1110
import useAgendaEntities from '@/Agenda3/hooks/useAgendaEntities'
1211
import { appAtom } from '@/Agenda3/models/app'
1312
import { DEFAULT_ESTIMATED_TIME } from '@/constants/agenda'
14-
import type { AgendaTaskWithStart } from '@/types/task'
13+
import type { AgendaTaskWithStartOrDeadline } from '@/types/task'
1514
import { cn, replaceDateInfo } from '@/util/util'
1615

1716
import AddTaskCard from './AddTaskCard'
1817
import ColumnTitle from './ColumnTitle'
1918
import type { KanBanItem } from './KanBan'
2019
import TaskCard from './taskCard/TaskCard'
2120

22-
export type ColumnProps = { day: Dayjs; tasks: AgendaTaskWithStart[]; allKanbanItems: KanBanItem[] }
21+
export type ColumnProps = { day: Dayjs; tasks: AgendaTaskWithStartOrDeadline[]; allKanbanItems: KanBanItem[] }
2322
const Column = ({ day, tasks, allKanbanItems }: ColumnProps, ref) => {
2423
const columnContainerRef = useRef<HTMLDivElement>(null)
2524
// bind draggable
@@ -47,6 +46,8 @@ const Column = ({ day, tasks, allKanbanItems }: ColumnProps, ref) => {
4746
console.log('[faiz:] === kanban onAdd', sortableEvent)
4847
const id = sortableEvent?.item?.dataset?.id
4948
const task = allKanbanItems.find((task) => task.id === id)
49+
// TODO: 支持拖拽修改 deadline
50+
if (!task?.start) return logseq.UI.showMsg('Drag task without start date is not supported', 'error')
5051
if (!task || !id) return logseq.UI.showMsg('task id not found', 'error')
5152
let startDay = day
5253
// remain time info

src/Agenda3/components/kanban/KanBan.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { DATE_FORMATTER_FOR_KEY, separateTasksInDay, transformTasksToKanbanTasks
66
import { recentTasksAtom } from '@/Agenda3/models/entities/tasks'
77
import { settingsAtom } from '@/Agenda3/models/settings'
88
import { getRecentDaysRange } from '@/constants/agenda'
9-
import type { AgendaTaskWithStart } from '@/types/task'
9+
import type { AgendaTaskWithStartOrDeadline } from '@/types/task'
1010
import { genDays } from '@/util/util'
1111

1212
import Column, { type ColumnHandle } from './Column'
@@ -66,7 +66,7 @@ const KanBan = (props, ref) => {
6666
)
6767
}
6868

69-
export type KanBanItem = AgendaTaskWithStart & {
69+
export type KanBanItem = AgendaTaskWithStartOrDeadline & {
7070
filtered?: boolean
7171
}
7272
export default forwardRef<KanBanHandle>(KanBan)

src/Agenda3/components/kanban/taskCard/TaskCard.tsx

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,27 +12,28 @@ import { logseqAtom } from '@/Agenda3/models/logseq'
1212
import { settingsAtom } from '@/Agenda3/models/settings'
1313
import { DEFAULT_ESTIMATED_TIME } from '@/constants/agenda'
1414
import type { AgendaEntity } from '@/types/entity'
15-
import type { AgendaTaskWithStart } from '@/types/task'
15+
import type { AgendaTaskWithStartOrDeadline } from '@/types/task'
1616
import { cn } from '@/util/util'
1717

1818
import Group from '../../Group'
1919
import TaskModal from '../../modals/TaskModal'
2020
import Toolbar from './Toolbar'
2121

22-
const TaskCard = ({ task }: { task: AgendaTaskWithStart }) => {
22+
const TaskCard = ({ task }: { task: AgendaTaskWithStartOrDeadline }) => {
2323
const currentGraph = useAtomValue(logseqAtom).currentGraph
2424
const settings = useAtomValue(settingsAtom)
2525
const groupType = settings.selectedFilters?.length ? 'filter' : 'page'
2626

2727
const [editTaskModal, setEditTaskModal] = useState<{
2828
open: boolean
29-
task?: AgendaTaskWithStart
29+
task?: AgendaTaskWithStartOrDeadline
3030
}>({
3131
open: false,
3232
})
3333

3434
const editDisabled = task.rrule || task.recurringPast
3535
const isMultipleDays = task.allDay && task.end
36+
const noStart = !task.start
3637
const estimatedTime = task.estimatedTime ?? DEFAULT_ESTIMATED_TIME
3738

3839
const { updateEntity, deleteEntity } = useAgendaEntities()
@@ -48,7 +49,7 @@ const TaskCard = ({ task }: { task: AgendaTaskWithStart }) => {
4849
const onRemoveDate = async (taskId: string) => {
4950
updateEntity({ type: 'task-remove-date', id: taskId, data: null })
5051
}
51-
const onClickTask = (e: React.MouseEvent, task: AgendaTaskWithStart) => {
52+
const onClickTask = (e: React.MouseEvent, task: AgendaTaskWithStartOrDeadline) => {
5253
if (e.ctrlKey) {
5354
navToLogseqBlock(task, currentGraph)
5455
console.log(task)
@@ -65,7 +66,7 @@ const TaskCard = ({ task }: { task: AgendaTaskWithStart }) => {
6566
{
6667
'bg-[#edeef0] opacity-80 dark:bg-[#2f2f33]': task.status === 'done',
6768
// 循环任务及多天任务不能拖拽
68-
'droppable-task-element': !editDisabled && !isMultipleDays,
69+
'droppable-task-element': !editDisabled && !isMultipleDays && !noStart,
6970
},
7071
)}
7172
data-event={JSON.stringify({
@@ -117,7 +118,11 @@ const TaskCard = ({ task }: { task: AgendaTaskWithStart }) => {
117118
<Toolbar task={task} groupType={groupType} onClickMark={onClickTaskMark} />
118119

119120
{/* ========= Title ========= */}
120-
<div className={cn('my-0.5 text-gray-600 dark:text-gray-100', { 'line-through': task.status === 'done' })}>
121+
<div
122+
className={cn('my-0.5 select-none break-words text-gray-600 dark:text-gray-100', {
123+
'line-through': task.status === 'done',
124+
})}
125+
>
121126
{task.showTitle}
122127
</div>
123128

src/Agenda3/components/kanban/taskCard/Toolbar.tsx

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
import { Tooltip } from 'antd'
2+
import dayjs from 'dayjs'
23
import { useAtomValue } from 'jotai'
4+
import { CgSandClock } from 'react-icons/cg'
35
import { GoGoal } from 'react-icons/go'
46
import { IoIosCheckmarkCircle, IoIosCheckmarkCircleOutline } from 'react-icons/io'
57
import { IoRepeatOutline } from 'react-icons/io5'
68

79
import { minutesToHHmm } from '@/Agenda3/helpers/fullCalendar'
810
import { navToLogseqBlock } from '@/Agenda3/helpers/logseq'
11+
import { getDaysBetween } from '@/Agenda3/helpers/util'
912
import { logseqAtom } from '@/Agenda3/models/logseq'
1013
import { DEFAULT_ESTIMATED_TIME } from '@/constants/agenda'
1114
import type { AgendaEntity } from '@/types/entity'
12-
import type { AgendaTaskWithStart } from '@/types/task'
15+
import type { AgendaTaskWithStart, AgendaTaskWithStartOrDeadline } from '@/types/task'
1316

1417
import LogseqLogo from '../../icons/LogseqLogo'
1518

@@ -18,7 +21,7 @@ const Toolbar = ({
1821
groupType,
1922
onClickMark,
2023
}: {
21-
task: AgendaTaskWithStart
24+
task: AgendaTaskWithStartOrDeadline
2225
groupType: 'page' | 'filter'
2326
onClickMark: (event: React.MouseEvent, task: AgendaEntity, status: AgendaEntity['status']) => void
2427
}) => {
@@ -41,7 +44,7 @@ const Toolbar = ({
4144
/>
4245
)}
4346
{/* Timer */}
44-
{task.allDay ? null : (
47+
{task.allDay || !task.start ? null : (
4548
<span
4649
className="rounded px-1 py-0.5 text-[10px] text-white opacity-70"
4750
style={{
@@ -59,6 +62,13 @@ const Toolbar = ({
5962
<GoGoal className="text-gray-300" />
6063
</Tooltip>
6164
) : null}
65+
{/* Deadline icon */}
66+
{task.deadline ? (
67+
<div className="flex items-center rounded bg-zinc-500/10 px-1 py-0.5 text-[10px] font-normal text-zinc-400 dark:bg-white/20">
68+
<CgSandClock className="text-[10px]" />
69+
<span>{getDaysBetween(dayjs(), task.deadline.value)}</span>
70+
</div>
71+
) : null}
6272
{/* Logseq icon */}
6373
<div
6474
className="cursor-pointer text-gray-300 opacity-0 transition-opacity group-hover/card:opacity-100 dark:text-gray-400"
@@ -70,10 +80,12 @@ const Toolbar = ({
7080
<LogseqLogo />
7181
</div>
7282
</div>
73-
<div className="rounded bg-gray-100 px-1 py-0.5 text-[10px] text-gray-400 dark:bg-zinc-800">
74-
{task.status === 'done' ? <span>{minutesToHHmm(task.actualTime ?? estimatedTime)} / </span> : null}
75-
{minutesToHHmm(estimatedTime)}
76-
</div>
83+
{task.start ? (
84+
<div className="rounded bg-gray-100 px-1 py-0.5 text-[10px] text-gray-400 dark:bg-zinc-800">
85+
{task.status === 'done' ? <span>{minutesToHHmm(task.actualTime ?? estimatedTime)} / </span> : null}
86+
{minutesToHHmm(estimatedTime)}
87+
</div>
88+
) : null}
7789
</div>
7890
)
7991
}

src/Agenda3/components/timebox/TimeBox.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ const TimeBox = ({ onChangeType }: { onChangeType?: () => void }) => {
5454
const recentTasks = useAtomValue(recentTasksAtom)
5555
const now = dayjs()
5656
const calendarEvents = recentTasks
57+
// TODO: 补充 deadline 任务的处理后移除次过滤器
58+
.filter((task) => task.start)
5759
.map((task) =>
5860
transformAgendaTaskToCalendarEvent(task, {
5961
showFirstEventInCycleOnly: settings.viewOptions?.showFirstEventInCycleOnly,
@@ -148,7 +150,7 @@ const TimeBox = ({ onChangeType }: { onChangeType?: () => void }) => {
148150
}}
149151
>
150152
<div className="group flex h-[44px] items-center justify-between">
151-
<div className="flex cursor-default items-center gap-1.5 px-2 py-1">
153+
<div className="flex cursor-default items-center gap-1.5 px-2 py-1">
152154
<MdSchedule className="text-lg" /> Time Box
153155
</div>
154156
<div className="mr-4 flex opacity-0 transition-opacity group-hover/root:opacity-100">
@@ -224,7 +226,7 @@ const TimeBox = ({ onChangeType }: { onChangeType?: () => void }) => {
224226
<div className="flex gap-1 text-gray-500 dark:text-gray-300">
225227
{day.format('ddd')}
226228
<span
227-
className={cn('h-6 w-6 rounded ', {
229+
className={cn('h-6 w-6 rounded ', {
228230
'bg-blue-400 text-white dark:bg-blue-600 dark:text-gray-100': isToday,
229231
})}
230232
>

src/Agenda3/helpers/task.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import type { Filter, Settings } from '@/Agenda3/models/settings'
88
import { DEFAULT_ESTIMATED_TIME, getRecentDaysRange } from '@/constants/agenda'
99
import type { AgendaEntity, AgendaEntityDeadline, AgendaEntityPage } from '@/types/entity'
1010
import type { RRule } from '@/types/fullcalendar'
11-
import type { AgendaTaskWithStart } from '@/types/task'
11+
import type { AgendaTaskWithStart, AgendaTaskWithStartOrDeadline } from '@/types/task'
1212
import { fillBlockReference } from '@/util/schedule'
1313
import { genDays } from '@/util/util'
1414

@@ -343,11 +343,16 @@ export const DATE_FORMATTER_FOR_KEY = 'YYYYMMDD'
343343
/**
344344
* separate tasks day by day
345345
*/
346-
export const separateTasksInDay = (tasks: AgendaTaskWithStart[]): Map<string, AgendaTaskWithStart[]> => {
346+
export const separateTasksInDay = (
347+
tasks: AgendaTaskWithStartOrDeadline[],
348+
): Map<string, AgendaTaskWithStartOrDeadline[]> => {
347349
// separate tasks in day based on scheduled date
348-
const tasksMap = new Map<string, AgendaTaskWithStart[]>()
350+
const tasksMap = new Map<string, AgendaTaskWithStartOrDeadline[]>()
349351
tasks.forEach((task) => {
350-
const taskDayStr = task.start.format(DATE_FORMATTER_FOR_KEY)
352+
if (!task.start && !task.deadline?.value) return
353+
const taskDayStr = task.start
354+
? task.start.format(DATE_FORMATTER_FOR_KEY)
355+
: task.deadline!.value.format(DATE_FORMATTER_FOR_KEY)
351356
if (tasksMap.has(taskDayStr)) {
352357
tasksMap.get(taskDayStr)?.push(task)
353358
} else {
@@ -364,7 +369,7 @@ export const separateTasksInDay = (tasks: AgendaTaskWithStart[]): Map<string, Ag
364369
* adapt task to kanban
365370
*/
366371
export const transformTasksToKanbanTasks = (
367-
tasks: AgendaTaskWithStart[],
372+
tasks: AgendaTaskWithStartOrDeadline[],
368373
options: { showFirstEventInCycleOnly?: boolean } = {},
369374
): KanBanItem[] => {
370375
const { showFirstEventInCycleOnly = false } = options
@@ -374,7 +379,7 @@ export const transformTasksToKanbanTasks = (
374379
const { allDay, end, start } = task
375380

376381
// splitting multi-days task into single-day tasks
377-
if (allDay && end) {
382+
if (allDay && end && start) {
378383
const days = genDays(start, end)
379384
return days.map((day) => {
380385
const isPast = day.isBefore(today, 'day')

src/Agenda3/models/entities/tasks.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,19 +32,23 @@ export const tasksWithStartOrDeadlineAtom = atom<AgendaTaskWithStartOrDeadline[]
3232
} as AgendaTaskWithStartOrDeadline
3333
})
3434
})
35-
export const recentTasksAtom = atom<AgendaTaskWithStart[]>((get) => {
36-
const allTasks = get(tasksWithStartAtom)
35+
export const recentTasksAtom = atom<AgendaTaskWithStartOrDeadline[]>((get) => {
36+
const allTasks = get(tasksWithStartOrDeadlineAtom)
3737

3838
const today = dayjs()
3939
const [rangeStart, rangeEnd] = RECENT_DAYS_RANGE
4040
const startDay = today.subtract(rangeStart, 'day')
4141
const endDay = today.add(rangeEnd, 'day')
4242
return allTasks.filter((task) => {
43-
return (
44-
task.start?.isBetween(startDay, endDay, 'day', '[]') ||
45-
task.end?.isBetween(startDay, endDay, 'day', '[]') ||
46-
(task.start?.isBefore(startDay, 'day') && task.end?.isAfter(endDay, 'day'))
47-
)
43+
if (task.start) {
44+
return (
45+
task.start?.isBetween(startDay, endDay, 'day', '[]') ||
46+
task.end?.isBetween(startDay, endDay, 'day', '[]') ||
47+
(task.start?.isBefore(startDay, 'day') && task.end?.isAfter(endDay, 'day'))
48+
)
49+
}
50+
if (task.deadline?.value) return task.deadline.value?.isBetween(startDay, endDay, 'day', '[]')
51+
return false
4852
})
4953
})
5054

0 commit comments

Comments
 (0)