Skip to content

Commit 22afbb4

Browse files
authoredMar 26, 2024··
feat(tasks): add tasks empty states (#6130)
1 parent 852bfca commit 22afbb4

File tree

5 files changed

+174
-38
lines changed

5 files changed

+174
-38
lines changed
 

‎packages/sanity/src/tasks/i18n/resources.ts

+23
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,29 @@ const tasksLocaleStrings = defineLocalesResources('tasks', {
4141
'document.footer.open-tasks.text_one': '{{count}} open task',
4242
/** The label used in the button in the footer action in a document with multiple tasks */
4343
'document.footer.open-tasks.text_other': '{{count}} open tasks',
44+
45+
'empty-state.list.assigned.heading': "You haven't been assigned any tasks",
46+
'empty-state.list.assigned.text': "Once you're assigned tasks they'll show up here",
47+
'empty-state.list.create-new': 'Create new task',
48+
'empty-state.list.document.heading': "This document doesn't have any tasks yet",
49+
'empty-state.list.document.text': 'Once a document has connected tasks, they will be shown here.',
50+
'empty-state.list.subscribed.heading': "You haven't subscribed to any tasks",
51+
'empty-state.list.subscribed.text':
52+
'When you create, modify, or comment on a task you will be subscribed automatically',
53+
54+
'empty-state.status.list.closed.assigned.heading': 'No completed tasks',
55+
'empty-state.status.list.closed.assigned.text': 'Your tasks marked done will show up here',
56+
'empty-state.status.list.closed.document.heading': 'No completed tasks',
57+
'empty-state.status.list.closed.subscribed.heading': 'No completed tasks',
58+
'empty-state.status.list.closed.subscribed.text':
59+
'Tasks you subscribe to marked done will show up here',
60+
61+
'empty-state.status.list.open.assigned.heading': "You're all caught up",
62+
'empty-state.status.list.open.assigned.text': 'New tasks assigned to you will show up here',
63+
'empty-state.status.list.open.document.heading': 'No tasks on this document',
64+
'empty-state.status.list.open.subscribed.heading': 'No subscribed tasks',
65+
'empty-state.status.list.open.subscribed.text': 'Tasks you subscribe to will show up here',
66+
4467
/** Text used in the assignee input when there is no user assigned */
4568
'form.input.assignee.no-user-assigned.text': 'Not assigned',
4669
/** Text used in the assignee input when searching and no users are found */

‎packages/sanity/src/tasks/src/tasks/components/list/DocumentPreview.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ export function DocumentPreview({
5959
{isLoading ? (
6060
<TextSkeleton size={1} muted />
6161
) : (
62-
<Text size={1} as={Link} weight="medium">
62+
<Text size={1} as={Link} weight="medium" style={{maxWidth: '20ch'}} textOverflow="ellipsis">
6363
{value?.title || 'Untitled'}
6464
</Text>
6565
)}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import {AddIcon} from '@sanity/icons'
2+
import {Box, Flex, Stack, Text} from '@sanity/ui'
3+
import {useCallback} from 'react'
4+
import {useTranslation} from 'react-i18next'
5+
import styled from 'styled-components'
6+
7+
import {Button} from '../../../../../ui-components'
8+
import {tasksLocaleNamespace} from '../../../../i18n'
9+
import {type SidebarTabsIds, useTasksNavigation} from '../../context'
10+
import {type TaskStatus} from '../../types'
11+
12+
const HEADING_BY_STATUS: Record<
13+
TaskStatus,
14+
Record<
15+
SidebarTabsIds,
16+
{
17+
heading: string
18+
text: string
19+
}
20+
>
21+
> = {
22+
open: {
23+
assigned: {
24+
heading: 'empty-state.status.list.open.assigned.heading',
25+
text: 'empty-state.status.list.open.assigned.text',
26+
},
27+
document: {heading: 'empty-state.status.list.open.document.heading', text: ''},
28+
subscribed: {
29+
heading: 'empty-state.status.list.open.subscribed.heading',
30+
text: 'empty-state.status.list.open.subscribed.text',
31+
},
32+
},
33+
closed: {
34+
assigned: {
35+
heading: 'empty-state.status.list.closed.assigned.heading',
36+
text: 'empty-state.status.list.closed.assigned.text',
37+
},
38+
document: {heading: 'empty-state.status.list.closed.document.heading', text: ''},
39+
subscribed: {
40+
heading: 'empty-state.status.list.closed.subscribed.heading',
41+
text: 'empty-state.status.list.closed.subscribed.text',
42+
},
43+
},
44+
}
45+
46+
export function EmptyStatusListState({status}: {status: TaskStatus}) {
47+
const {
48+
state: {activeTabId},
49+
} = useTasksNavigation()
50+
const {t} = useTranslation(tasksLocaleNamespace)
51+
const {heading, text} = HEADING_BY_STATUS[status][activeTabId]
52+
return (
53+
<Stack space={3}>
54+
<Text size={1} weight="semibold">
55+
{t(heading)}
56+
</Text>
57+
<Text size={1}>{t(text)}</Text>
58+
</Stack>
59+
)
60+
}
61+
62+
const EMPTY_TASK_LIST: Record<
63+
SidebarTabsIds,
64+
{
65+
heading: string
66+
text: string
67+
}
68+
> = {
69+
assigned: {
70+
heading: 'empty-state.list.assigned.heading',
71+
text: 'empty-state.list.assigned.text',
72+
},
73+
subscribed: {
74+
heading: 'empty-state.list.subscribed.heading',
75+
text: 'empty-state.list.subscribed.text',
76+
},
77+
document: {
78+
heading: 'empty-state.list.document.heading',
79+
text: 'empty-state.list.document.text',
80+
},
81+
}
82+
83+
const Root = styled.div`
84+
max-width: 268px;
85+
margin: 0 auto;
86+
height: 100%;
87+
margin-top: 40%;
88+
`
89+
export function EmptyTasksListState() {
90+
const {
91+
state: {activeTabId},
92+
setViewMode,
93+
} = useTasksNavigation()
94+
const {heading, text} = EMPTY_TASK_LIST[activeTabId]
95+
const {t} = useTranslation(tasksLocaleNamespace)
96+
97+
const handleTaskCreate = useCallback(() => {
98+
setViewMode({type: 'create'})
99+
}, [setViewMode])
100+
return (
101+
<Root>
102+
<Flex direction={'column'} gap={3} align={'center'} flex={1} justify={'center'}>
103+
<Text size={1} weight="semibold">
104+
{t(heading)}
105+
</Text>
106+
<Box paddingBottom={6} paddingTop={1}>
107+
<Text size={1} align="center">
108+
{t(text)}
109+
</Text>
110+
</Box>
111+
<Button icon={AddIcon} text={t('empty-state.list.create-new')} onClick={handleTaskCreate} />
112+
</Flex>
113+
</Root>
114+
)
115+
}

‎packages/sanity/src/tasks/src/tasks/components/list/TasksList.tsx

+31-35
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
import {ChevronDownIcon} from '@sanity/icons'
22
import {Box, Flex, MenuDivider, Stack, Text} from '@sanity/ui'
33
import {Fragment, useMemo} from 'react'
4-
import {useTranslation} from 'sanity'
54
import styled from 'styled-components'
65

7-
import {tasksLocaleNamespace} from '../../../../i18n'
86
import {TASK_STATUS} from '../../constants/TaskStatus'
9-
import {type TaskDocument} from '../../types'
7+
import {type TaskDocument, type TaskStatus} from '../../types'
8+
import {EmptyStatusListState, EmptyTasksListState} from './EmptyStates'
109
import {TasksListItem} from './TasksListItem'
1110

1211
const EMPTY_ARRAY: [] = []
@@ -33,7 +32,7 @@ const SummaryBox = styled(Box)`
3332
`
3433

3534
interface TaskListProps {
36-
status: string
35+
status: TaskStatus
3736
tasks: TaskDocument[]
3837
onTaskSelect: (id: string) => void
3938
}
@@ -56,26 +55,30 @@ function TaskList(props: TaskListProps) {
5655
</SummaryBox>
5756

5857
<Stack space={4} marginTop={4} paddingBottom={5}>
59-
{tasks.map((task, index) => {
60-
const showDivider = index < tasks.length - 1
61-
62-
return (
63-
<Fragment key={task._id}>
64-
<TasksListItem
65-
documentId={task._id}
66-
title={task.title}
67-
dueBy={task.dueBy}
68-
assignedTo={task.assignedTo}
69-
target={task.target}
70-
// eslint-disable-next-line react/jsx-no-bind
71-
onSelect={() => onTaskSelect(task._id)}
72-
status={task.status}
73-
/>
74-
75-
{showDivider && <MenuDivider />}
76-
</Fragment>
77-
)
78-
})}
58+
{tasks?.length > 0 ? (
59+
tasks.map((task, index) => {
60+
const showDivider = index < tasks.length - 1
61+
62+
return (
63+
<Fragment key={task._id}>
64+
<TasksListItem
65+
documentId={task._id}
66+
title={task.title}
67+
dueBy={task.dueBy}
68+
assignedTo={task.assignedTo}
69+
target={task.target}
70+
// eslint-disable-next-line react/jsx-no-bind
71+
onSelect={() => onTaskSelect(task._id)}
72+
status={task.status}
73+
/>
74+
75+
{showDivider && <MenuDivider />}
76+
</Fragment>
77+
)
78+
})
79+
) : (
80+
<EmptyStatusListState status={status} />
81+
)}
7982
</Stack>
8083
</DetailsFlex>
8184
)
@@ -106,23 +109,16 @@ export function TasksList(props: TasksListProps) {
106109

107110
const hasOpenTasks = tasksByStatus.open?.length > 0
108111
const hasClosedTasks = tasksByStatus.closed?.length > 0
109-
const {t} = useTranslation(tasksLocaleNamespace)
110112

111113
return (
112-
<Stack space={4}>
114+
<Stack space={4} flex={1}>
113115
{!hasOpenTasks && !hasClosedTasks ? (
114-
<Text as="p" size={1} muted>
115-
{t('list.empty.text')}
116-
</Text>
116+
<EmptyTasksListState />
117117
) : (
118118
<>
119-
{hasOpenTasks && (
120-
<TaskList status="open" tasks={tasksByStatus.open} onTaskSelect={onTaskSelect} />
121-
)}
119+
<TaskList status="open" tasks={tasksByStatus.open} onTaskSelect={onTaskSelect} />
122120

123-
{hasClosedTasks && (
124-
<TaskList status="closed" tasks={tasksByStatus.closed} onTaskSelect={onTaskSelect} />
125-
)}
121+
<TaskList status="closed" tasks={tasksByStatus.closed} onTaskSelect={onTaskSelect} />
126122
</>
127123
)}
128124
</Stack>

‎packages/sanity/src/tasks/src/tasks/hooks/useActivityLog.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,15 @@ export function useActivityLog(task: TaskDocument): {
1414
const {dataset, token} = client.config()
1515

1616
const queryParams = `tag=sanity.studio.tasks.history&effectFormat=mendoza&excludeContent=true&includeIdentifiedDocumentsOnly=true&reverse=true`
17+
const publishedId = getPublishedId(task?._id ?? '')
1718
const transactionsUrl = client.getUrl(
18-
`/data/history/${dataset}/transactions/${getPublishedId(task._id)}?${queryParams}`,
19+
`/data/history/${dataset}/transactions/${publishedId}?${queryParams}`,
1920
)
2021

2122
const fetchAndParse = useCallback(
2223
async (newestTaskDocument: TaskDocument) => {
2324
try {
25+
if (!publishedId) return
2426
const transactions: TransactionLogEventWithEffects[] = []
2527

2628
const stream = await getJsonStream(transactionsUrl, token)
@@ -58,7 +60,7 @@ export function useActivityLog(task: TaskDocument): {
5860
console.error('Failed to fetch and parse activity log', error)
5961
}
6062
},
61-
[transactionsUrl, token],
63+
[transactionsUrl, token, publishedId],
6264
)
6365

6466
useEffect(() => {

0 commit comments

Comments
 (0)
Please sign in to comment.