Skip to content

Commit

Permalink
feat(tasks): add tasks empty states (#6130)
Browse files Browse the repository at this point in the history
  • Loading branch information
pedrobonamin committed Mar 26, 2024
1 parent 852bfca commit 22afbb4
Show file tree
Hide file tree
Showing 5 changed files with 174 additions and 38 deletions.
23 changes: 23 additions & 0 deletions packages/sanity/src/tasks/i18n/resources.ts
Expand Up @@ -41,6 +41,29 @@ const tasksLocaleStrings = defineLocalesResources('tasks', {
'document.footer.open-tasks.text_one': '{{count}} open task',
/** The label used in the button in the footer action in a document with multiple tasks */
'document.footer.open-tasks.text_other': '{{count}} open tasks',

'empty-state.list.assigned.heading': "You haven't been assigned any tasks",
'empty-state.list.assigned.text': "Once you're assigned tasks they'll show up here",
'empty-state.list.create-new': 'Create new task',
'empty-state.list.document.heading': "This document doesn't have any tasks yet",
'empty-state.list.document.text': 'Once a document has connected tasks, they will be shown here.',
'empty-state.list.subscribed.heading': "You haven't subscribed to any tasks",
'empty-state.list.subscribed.text':
'When you create, modify, or comment on a task you will be subscribed automatically',

'empty-state.status.list.closed.assigned.heading': 'No completed tasks',
'empty-state.status.list.closed.assigned.text': 'Your tasks marked done will show up here',
'empty-state.status.list.closed.document.heading': 'No completed tasks',
'empty-state.status.list.closed.subscribed.heading': 'No completed tasks',
'empty-state.status.list.closed.subscribed.text':
'Tasks you subscribe to marked done will show up here',

'empty-state.status.list.open.assigned.heading': "You're all caught up",
'empty-state.status.list.open.assigned.text': 'New tasks assigned to you will show up here',
'empty-state.status.list.open.document.heading': 'No tasks on this document',
'empty-state.status.list.open.subscribed.heading': 'No subscribed tasks',
'empty-state.status.list.open.subscribed.text': 'Tasks you subscribe to will show up here',

/** Text used in the assignee input when there is no user assigned */
'form.input.assignee.no-user-assigned.text': 'Not assigned',
/** Text used in the assignee input when searching and no users are found */
Expand Down
Expand Up @@ -59,7 +59,7 @@ export function DocumentPreview({
{isLoading ? (
<TextSkeleton size={1} muted />
) : (
<Text size={1} as={Link} weight="medium">
<Text size={1} as={Link} weight="medium" style={{maxWidth: '20ch'}} textOverflow="ellipsis">
{value?.title || 'Untitled'}
</Text>
)}
Expand Down
115 changes: 115 additions & 0 deletions packages/sanity/src/tasks/src/tasks/components/list/EmptyStates.tsx
@@ -0,0 +1,115 @@
import {AddIcon} from '@sanity/icons'
import {Box, Flex, Stack, Text} from '@sanity/ui'
import {useCallback} from 'react'
import {useTranslation} from 'react-i18next'
import styled from 'styled-components'

import {Button} from '../../../../../ui-components'
import {tasksLocaleNamespace} from '../../../../i18n'
import {type SidebarTabsIds, useTasksNavigation} from '../../context'
import {type TaskStatus} from '../../types'

const HEADING_BY_STATUS: Record<
TaskStatus,
Record<
SidebarTabsIds,
{
heading: string
text: string
}
>
> = {
open: {
assigned: {
heading: 'empty-state.status.list.open.assigned.heading',
text: 'empty-state.status.list.open.assigned.text',
},
document: {heading: 'empty-state.status.list.open.document.heading', text: ''},
subscribed: {
heading: 'empty-state.status.list.open.subscribed.heading',
text: 'empty-state.status.list.open.subscribed.text',
},
},
closed: {
assigned: {
heading: 'empty-state.status.list.closed.assigned.heading',
text: 'empty-state.status.list.closed.assigned.text',
},
document: {heading: 'empty-state.status.list.closed.document.heading', text: ''},
subscribed: {
heading: 'empty-state.status.list.closed.subscribed.heading',
text: 'empty-state.status.list.closed.subscribed.text',
},
},
}

export function EmptyStatusListState({status}: {status: TaskStatus}) {
const {
state: {activeTabId},
} = useTasksNavigation()
const {t} = useTranslation(tasksLocaleNamespace)
const {heading, text} = HEADING_BY_STATUS[status][activeTabId]
return (
<Stack space={3}>
<Text size={1} weight="semibold">
{t(heading)}
</Text>
<Text size={1}>{t(text)}</Text>
</Stack>
)
}

const EMPTY_TASK_LIST: Record<
SidebarTabsIds,
{
heading: string
text: string
}
> = {
assigned: {
heading: 'empty-state.list.assigned.heading',
text: 'empty-state.list.assigned.text',
},
subscribed: {
heading: 'empty-state.list.subscribed.heading',
text: 'empty-state.list.subscribed.text',
},
document: {
heading: 'empty-state.list.document.heading',
text: 'empty-state.list.document.text',
},
}

const Root = styled.div`
max-width: 268px;
margin: 0 auto;
height: 100%;
margin-top: 40%;
`
export function EmptyTasksListState() {
const {
state: {activeTabId},
setViewMode,
} = useTasksNavigation()
const {heading, text} = EMPTY_TASK_LIST[activeTabId]
const {t} = useTranslation(tasksLocaleNamespace)

const handleTaskCreate = useCallback(() => {
setViewMode({type: 'create'})
}, [setViewMode])
return (
<Root>
<Flex direction={'column'} gap={3} align={'center'} flex={1} justify={'center'}>
<Text size={1} weight="semibold">
{t(heading)}
</Text>
<Box paddingBottom={6} paddingTop={1}>
<Text size={1} align="center">
{t(text)}
</Text>
</Box>
<Button icon={AddIcon} text={t('empty-state.list.create-new')} onClick={handleTaskCreate} />
</Flex>
</Root>
)
}
66 changes: 31 additions & 35 deletions packages/sanity/src/tasks/src/tasks/components/list/TasksList.tsx
@@ -1,12 +1,11 @@
import {ChevronDownIcon} from '@sanity/icons'
import {Box, Flex, MenuDivider, Stack, Text} from '@sanity/ui'
import {Fragment, useMemo} from 'react'
import {useTranslation} from 'sanity'
import styled from 'styled-components'

import {tasksLocaleNamespace} from '../../../../i18n'
import {TASK_STATUS} from '../../constants/TaskStatus'
import {type TaskDocument} from '../../types'
import {type TaskDocument, type TaskStatus} from '../../types'
import {EmptyStatusListState, EmptyTasksListState} from './EmptyStates'
import {TasksListItem} from './TasksListItem'

const EMPTY_ARRAY: [] = []
Expand All @@ -33,7 +32,7 @@ const SummaryBox = styled(Box)`
`

interface TaskListProps {
status: string
status: TaskStatus
tasks: TaskDocument[]
onTaskSelect: (id: string) => void
}
Expand All @@ -56,26 +55,30 @@ function TaskList(props: TaskListProps) {
</SummaryBox>

<Stack space={4} marginTop={4} paddingBottom={5}>
{tasks.map((task, index) => {
const showDivider = index < tasks.length - 1

return (
<Fragment key={task._id}>
<TasksListItem
documentId={task._id}
title={task.title}
dueBy={task.dueBy}
assignedTo={task.assignedTo}
target={task.target}
// eslint-disable-next-line react/jsx-no-bind
onSelect={() => onTaskSelect(task._id)}
status={task.status}
/>

{showDivider && <MenuDivider />}
</Fragment>
)
})}
{tasks?.length > 0 ? (
tasks.map((task, index) => {
const showDivider = index < tasks.length - 1

return (
<Fragment key={task._id}>
<TasksListItem
documentId={task._id}
title={task.title}
dueBy={task.dueBy}
assignedTo={task.assignedTo}
target={task.target}
// eslint-disable-next-line react/jsx-no-bind
onSelect={() => onTaskSelect(task._id)}
status={task.status}
/>

{showDivider && <MenuDivider />}
</Fragment>
)
})
) : (
<EmptyStatusListState status={status} />
)}
</Stack>
</DetailsFlex>
)
Expand Down Expand Up @@ -106,23 +109,16 @@ export function TasksList(props: TasksListProps) {

const hasOpenTasks = tasksByStatus.open?.length > 0
const hasClosedTasks = tasksByStatus.closed?.length > 0
const {t} = useTranslation(tasksLocaleNamespace)

return (
<Stack space={4}>
<Stack space={4} flex={1}>
{!hasOpenTasks && !hasClosedTasks ? (
<Text as="p" size={1} muted>
{t('list.empty.text')}
</Text>
<EmptyTasksListState />
) : (
<>
{hasOpenTasks && (
<TaskList status="open" tasks={tasksByStatus.open} onTaskSelect={onTaskSelect} />
)}
<TaskList status="open" tasks={tasksByStatus.open} onTaskSelect={onTaskSelect} />

{hasClosedTasks && (
<TaskList status="closed" tasks={tasksByStatus.closed} onTaskSelect={onTaskSelect} />
)}
<TaskList status="closed" tasks={tasksByStatus.closed} onTaskSelect={onTaskSelect} />
</>
)}
</Stack>
Expand Down
6 changes: 4 additions & 2 deletions packages/sanity/src/tasks/src/tasks/hooks/useActivityLog.ts
Expand Up @@ -14,13 +14,15 @@ export function useActivityLog(task: TaskDocument): {
const {dataset, token} = client.config()

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

const fetchAndParse = useCallback(
async (newestTaskDocument: TaskDocument) => {
try {
if (!publishedId) return
const transactions: TransactionLogEventWithEffects[] = []

const stream = await getJsonStream(transactionsUrl, token)
Expand Down Expand Up @@ -58,7 +60,7 @@ export function useActivityLog(task: TaskDocument): {
console.error('Failed to fetch and parse activity log', error)
}
},
[transactionsUrl, token],
[transactionsUrl, token, publishedId],
)

useEffect(() => {
Expand Down

0 comments on commit 22afbb4

Please sign in to comment.