diff --git a/front/src/App.tsx b/front/src/App.tsx index 877351f31f..609aa1c41b 100644 --- a/front/src/App.tsx +++ b/front/src/App.tsx @@ -17,6 +17,7 @@ import { SettingsExperience } from '~/pages/settings/SettingsExperience'; import { SettingsProfile } from '~/pages/settings/SettingsProfile'; import { SettingsWorksapce } from '~/pages/settings/SettingsWorkspace'; import { SettingsWorkspaceMembers } from '~/pages/settings/SettingsWorkspaceMembers'; +import { Tasks } from '~/pages/tasks/Tasks'; import { AppInternalHooks } from '~/sync-hooks/AppInternalHooks'; // TEMP FEATURE FLAG FOR VIEW FIELDS @@ -39,6 +40,7 @@ export function App() { } /> } /> } /> + } /> } /> } /> diff --git a/front/src/AppNavbar.tsx b/front/src/AppNavbar.tsx index 7e4167e732..544db676b1 100644 --- a/front/src/AppNavbar.tsx +++ b/front/src/AppNavbar.tsx @@ -5,6 +5,7 @@ import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu'; import { SettingsNavbar } from '@/settings/components/SettingsNavbar'; import { IconBuildingSkyscraper, + IconCheckbox, IconInbox, IconSearch, IconSettings, @@ -45,6 +46,11 @@ export function AppNavbar() { to="/settings/profile" icon={} /> + } + /> | null, activityTargets?: Array<{ __typename?: 'ActivityTarget', id: string, commentableType?: CommentableType | null, commentableId?: string | null }> | null }> }; +export type GetActivitiesQueryVariables = Exact<{ + where: ActivityWhereInput; + orderBy?: InputMaybe | ActivityOrderByWithRelationInput>; +}>; + + +export type GetActivitiesQuery = { __typename?: 'Query', findManyActivities: Array<{ __typename?: 'Activity', id: string, createdAt: string, title?: string | null, body?: string | null, type: ActivityType, completedAt?: string | null, dueAt?: string | null, assignee?: { __typename?: 'User', id: string, firstName?: string | null, lastName?: string | null, displayName: string, avatarUrl?: string | null } | null, author: { __typename?: 'User', id: string, firstName?: string | null, lastName?: string | null, displayName: string }, comments?: Array<{ __typename?: 'Comment', id: string }> | null, activityTargets?: Array<{ __typename?: 'ActivityTarget', id: string, commentableType?: CommentableType | null, commentableId?: string | null }> | null }> }; + export type GetActivityQueryVariables = Exact<{ activityId: Scalars['String']; }>; @@ -2870,6 +2878,69 @@ export function useGetActivitiesByTargetsLazyQuery(baseOptions?: Apollo.LazyQuer export type GetActivitiesByTargetsQueryHookResult = ReturnType; export type GetActivitiesByTargetsLazyQueryHookResult = ReturnType; export type GetActivitiesByTargetsQueryResult = Apollo.QueryResult; +export const GetActivitiesDocument = gql` + query GetActivities($where: ActivityWhereInput!, $orderBy: [ActivityOrderByWithRelationInput!]) { + findManyActivities(orderBy: $orderBy, where: $where) { + id + createdAt + title + body + type + completedAt + dueAt + assignee { + id + firstName + lastName + displayName + avatarUrl + } + author { + id + firstName + lastName + displayName + } + comments { + id + } + activityTargets { + id + commentableType + commentableId + } + } +} + `; + +/** + * __useGetActivitiesQuery__ + * + * To run a query within a React component, call `useGetActivitiesQuery` and pass it any options that fit your needs. + * When your component renders, `useGetActivitiesQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useGetActivitiesQuery({ + * variables: { + * where: // value for 'where' + * orderBy: // value for 'orderBy' + * }, + * }); + */ +export function useGetActivitiesQuery(baseOptions: Apollo.QueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useQuery(GetActivitiesDocument, options); + } +export function useGetActivitiesLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useLazyQuery(GetActivitiesDocument, options); + } +export type GetActivitiesQueryHookResult = ReturnType; +export type GetActivitiesLazyQueryHookResult = ReturnType; +export type GetActivitiesQueryResult = Apollo.QueryResult; export const GetActivityDocument = gql` query GetActivity($activityId: String!) { findManyActivities(where: {id: {equals: $activityId}}) { diff --git a/front/src/modules/activities/components/ActivityTargetChips.tsx b/front/src/modules/activities/components/ActivityTargetChips.tsx new file mode 100644 index 0000000000..5fd3b4ce0c --- /dev/null +++ b/front/src/modules/activities/components/ActivityTargetChips.tsx @@ -0,0 +1,43 @@ +import styled from '@emotion/styled'; + +import { CompanyChip } from '@/companies/components/CompanyChip'; +import { PersonChip } from '@/people/components/PersonChip'; +import { GetCompaniesQuery, GetPeopleQuery } from '~/generated/graphql'; +import { getLogoUrlFromDomainName } from '~/utils'; + +const StyledContainer = styled.div` + display: flex; + flex-wrap: wrap; + gap: ${({ theme }) => theme.spacing(1)}; +`; + +export function ActivityTargetChips({ + targetCompanies, + targetPeople, +}: { + targetCompanies?: GetCompaniesQuery; + targetPeople?: GetPeopleQuery; +}) { + return ( + + {targetCompanies?.companies && + targetCompanies.companies.map((company) => ( + + ))} + {targetPeople?.people && + targetPeople.people.map((person) => ( + + ))} + + ); +} diff --git a/front/src/modules/activities/editable-fields/components/ActivityRelationEditableField.tsx b/front/src/modules/activities/editable-fields/components/ActivityRelationEditableField.tsx index 937053daa5..7c6c805de3 100644 --- a/front/src/modules/activities/editable-fields/components/ActivityRelationEditableField.tsx +++ b/front/src/modules/activities/editable-fields/components/ActivityRelationEditableField.tsx @@ -1,7 +1,4 @@ -import styled from '@emotion/styled'; - -import { CompanyChip } from '@/companies/components/CompanyChip'; -import { PersonChip } from '@/people/components/PersonChip'; +import { ActivityTargetChips } from '@/activities/components/ActivityTargetChips'; import { EditableField } from '@/ui/editable-field/components/EditableField'; import { FieldContext } from '@/ui/editable-field/states/FieldContext'; import { IconArrowUpRight } from '@/ui/icon'; @@ -13,16 +10,9 @@ import { useGetCompaniesQuery, useGetPeopleQuery, } from '~/generated/graphql'; -import { getLogoUrlFromDomainName } from '~/utils'; import { ActivityRelationEditableFieldEditMode } from './ActivityRelationEditableFieldEditMode'; -const StyledDisplayModeContainer = styled.div` - display: flex; - flex-wrap: wrap; - gap: ${({ theme }) => theme.spacing(1)}; -`; - type OwnProps = { activity?: Pick & { activityTargets?: Array< @@ -74,26 +64,10 @@ export function ActivityRelationEditableField({ activity }: OwnProps) { } label="Relations" displayModeContent={ - - {targetCompanies?.companies && - targetCompanies.companies.map((company) => ( - - ))} - {targetPeople?.people && - targetPeople.people.map((person) => ( - - ))} - + } /> diff --git a/front/src/modules/activities/queries/select.ts b/front/src/modules/activities/queries/select.ts index 1b6771d11a..9d29c5dd93 100644 --- a/front/src/modules/activities/queries/select.ts +++ b/front/src/modules/activities/queries/select.ts @@ -52,6 +52,44 @@ export const GET_ACTIVITIES_BY_TARGETS = gql` } `; +export const GET_ACTIVITIES = gql` + query GetActivities( + $where: ActivityWhereInput! + $orderBy: [ActivityOrderByWithRelationInput!] + ) { + findManyActivities(orderBy: $orderBy, where: $where) { + id + createdAt + title + body + type + completedAt + dueAt + assignee { + id + firstName + lastName + displayName + avatarUrl + } + author { + id + firstName + lastName + displayName + } + comments { + id + } + activityTargets { + id + commentableType + commentableId + } + } + } +`; + export const GET_ACTIVITY = gql` query GetActivity($activityId: String!) { findManyActivities(where: { id: { equals: $activityId } }) { diff --git a/front/src/modules/tasks/components/TaskGroups.tsx b/front/src/modules/tasks/components/TaskGroups.tsx new file mode 100644 index 0000000000..d5a340a140 --- /dev/null +++ b/front/src/modules/tasks/components/TaskGroups.tsx @@ -0,0 +1,13 @@ +import { useTasks } from '../hooks/useTasks'; + +import { TaskList } from './TaskList'; + +export function TaskGroups() { + const { todayTasks, otherTasks } = useTasks(); + return ( + <> + + + + ); +} diff --git a/front/src/modules/tasks/components/TaskList.tsx b/front/src/modules/tasks/components/TaskList.tsx new file mode 100644 index 0000000000..966527fa31 --- /dev/null +++ b/front/src/modules/tasks/components/TaskList.tsx @@ -0,0 +1,61 @@ +import styled from '@emotion/styled'; + +import { TaskForList } from '../types/TaskForList'; + +import { TaskRow } from './TaskRow'; + +type OwnProps = { + title: string; + tasks: TaskForList[]; +}; + +const StyledContainer = styled.div` + align-items: flex-start; + align-self: stretch; + display: flex; + flex-direction: column; + justify-content: center; + padding: 8px 24px; +`; + +const StyledTitle = styled.h3` + color: ${({ theme }) => theme.font.color.primary}; + margin-bottom: ${({ theme }) => theme.spacing(4)}; + margin-top: ${({ theme }) => theme.spacing(4)}; +`; + +const StyledCount = styled.span` + color: ${({ theme }) => theme.font.color.light}; + margin-left: ${({ theme }) => theme.spacing(2)}; +`; + +const StyledTaskRows = styled.div` + background-color: ${({ theme }) => theme.background.secondary}; + border: 1px solid ${({ theme }) => theme.border.color.light}; + border-radius: ${({ theme }) => theme.border.radius.md}; + width: 100%; +`; + +const StyledEmptyListMessage = styled.div` + color: ${({ theme }) => theme.font.color.secondary}; + padding: ${({ theme }) => theme.spacing(4)}; +`; + +export function TaskList({ title, tasks }: OwnProps) { + return ( + + + {title} {tasks ? tasks.length : 0} + + {tasks && tasks.length > 0 ? ( + + {tasks.map((task) => ( + + ))} + + ) : ( + No task in this section + )} + + ); +} diff --git a/front/src/modules/tasks/components/TaskRow.tsx b/front/src/modules/tasks/components/TaskRow.tsx new file mode 100644 index 0000000000..d56ed2fa7f --- /dev/null +++ b/front/src/modules/tasks/components/TaskRow.tsx @@ -0,0 +1,105 @@ +import { useTheme } from '@emotion/react'; +import styled from '@emotion/styled'; + +import { ActivityTargetChips } from '@/activities/components/ActivityTargetChips'; +import { useOpenActivityRightDrawer } from '@/activities/hooks/useOpenActivityRightDrawer'; +import { IconCalendar, IconComment } from '@/ui/icon'; +import { + Checkbox, + CheckboxShape, +} from '@/ui/input/checkbox/components/Checkbox'; +import { useGetCompaniesQuery, useGetPeopleQuery } from '~/generated/graphql'; +import { beautifyExactDate } from '~/utils/date-utils'; + +import { TaskForList } from '../types/TaskForList'; + +const StyledContainer = styled.div` + align-items: center; + align-self: stretch; + border-bottom: 1px solid ${({ theme }) => theme.border.color.light}; + cursor: pointer; + display: flex; + height: ${({ theme }) => theme.spacing(12)}; + padding: 0 ${({ theme }) => theme.spacing(4)}; +`; + +const StyledSeparator = styled.div` + flex: 1; +`; + +const StyledTaskTitle = styled.div` + padding: 0 ${({ theme }) => theme.spacing(2)}; +`; + +const StyledCommentIcon = styled.div` + color: ${({ theme }) => theme.font.color.light}; +`; + +const StyledDueDate = styled.div` + color: ${({ theme }) => theme.font.color.secondary}; + gap: ${({ theme }) => theme.spacing(1)}; + padding-left: ${({ theme }) => theme.spacing(2)}; +`; + +export function TaskRow({ task }: { task: TaskForList }) { + const theme = useTheme(); + const openActivityRightDrawer = useOpenActivityRightDrawer(); + const { data: targetPeople } = useGetPeopleQuery({ + variables: { + where: { + id: { + in: task?.activityTargets + ? task?.activityTargets + .filter((target) => target.commentableType === 'Person') + .map((target) => target.commentableId ?? '') + : [], + }, + }, + }, + }); + + const { data: targetCompanies } = useGetCompaniesQuery({ + variables: { + where: { + id: { + in: task?.activityTargets + ? task?.activityTargets + .filter((target) => target.commentableType === 'Company') + .map((target) => target.commentableId ?? '') + : [], + }, + }, + }, + }); + + return ( + { + openActivityRightDrawer(task.id); + }} + > +
{ + e.stopPropagation(); + }} + > + +
+ {task.title} + {task.comments && task.comments.length > 0 && ( + + + + )} + + + + + {task.dueAt && beautifyExactDate(task.dueAt)} + +
+ ); +} diff --git a/front/src/modules/tasks/hooks/useTasks.ts b/front/src/modules/tasks/hooks/useTasks.ts new file mode 100644 index 0000000000..2dd2d5c890 --- /dev/null +++ b/front/src/modules/tasks/hooks/useTasks.ts @@ -0,0 +1,47 @@ +import { activeTabIdScopedState } from '@/ui/tab/states/activeTabIdScopedState'; +import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState'; +import { ActivityType, useGetActivitiesQuery } from '~/generated/graphql'; +import { parseDate } from '~/utils/date-utils'; + +import { TasksContext } from '../states/TasksContext'; + +export function useTasks() { + const [activeTabId] = useRecoilScopedState( + activeTabIdScopedState, + TasksContext, + ); + + const { data, loading } = useGetActivitiesQuery({ + variables: { + where: { + type: { equals: ActivityType.Task }, + completedAt: + activeTabId === 'done' ? { not: { equals: null } } : { equals: null }, + }, + }, + }); + + const todayTasks = data?.findManyActivities.filter((task) => { + if (!task.dueAt) { + return false; + } + const dueDate = parseDate(task.dueAt).toJSDate(); + const today = new Date(); + return dueDate.getDate() === today.getDate(); + }); + + const otherTasks = data?.findManyActivities.filter((task) => { + if (!task.dueAt) { + return false; + } + const dueDate = parseDate(task.dueAt).toJSDate(); + const today = new Date(); + return dueDate.getDate() !== today.getDate(); + }); + + return { + todayTasks, + otherTasks, + loading, + }; +} diff --git a/front/src/modules/tasks/states/TasksContext.ts b/front/src/modules/tasks/states/TasksContext.ts new file mode 100644 index 0000000000..b8dd7264e0 --- /dev/null +++ b/front/src/modules/tasks/states/TasksContext.ts @@ -0,0 +1,3 @@ +import { createContext } from 'react'; + +export const TasksContext = createContext(null); diff --git a/front/src/modules/tasks/types/TaskForList.ts b/front/src/modules/tasks/types/TaskForList.ts new file mode 100644 index 0000000000..14c8f0e357 --- /dev/null +++ b/front/src/modules/tasks/types/TaskForList.ts @@ -0,0 +1,3 @@ +import { GetActivitiesQuery } from '~/generated/graphql'; + +export type TaskForList = GetActivitiesQuery['findManyActivities'][0]; diff --git a/front/src/modules/types/AppPath.ts b/front/src/modules/types/AppPath.ts index 27fa412ed0..7a817a8718 100644 --- a/front/src/modules/types/AppPath.ts +++ b/front/src/modules/types/AppPath.ts @@ -15,6 +15,7 @@ export enum AppPath { CompaniesPage = '/companies', CompanyShowPage = '/companies/:companyId', PersonShowPage = '/person/:personId', + TasksPage = '/tasks', OpportunitiesPage = '/opportunities', SettingsCatchAll = `/settings/*`, diff --git a/front/src/modules/ui/board/components/BoardHeader.tsx b/front/src/modules/ui/board/components/BoardHeader.tsx index 5edab14736..553f663fbd 100644 --- a/front/src/modules/ui/board/components/BoardHeader.tsx +++ b/front/src/modules/ui/board/components/BoardHeader.tsx @@ -6,6 +6,7 @@ import SortAndFilterBar from '@/ui/filter-n-sort/components/SortAndFilterBar'; import { SortDropdownButton } from '@/ui/filter-n-sort/components/SortDropdownButton'; import { FiltersHotkeyScope } from '@/ui/filter-n-sort/types/FiltersHotkeyScope'; import { SelectedSortType, SortType } from '@/ui/filter-n-sort/types/interface'; +import { TopBar } from '@/ui/top-bar/TopBar'; type OwnProps = { viewName: string; @@ -15,24 +16,6 @@ type OwnProps = { context: Context; }; -const StyledContainer = styled.div` - border-bottom: 1px solid ${({ theme }) => theme.border.color.light}; - display: flex; - flex-direction: column; -`; - -const StyledBoardHeader = styled.div` - align-items: center; - color: ${({ theme }) => theme.font.color.secondary}; - display: flex; - flex-direction: row; - font-weight: ${({ theme }) => theme.font.weight.medium}; - height: 40px; - justify-content: space-between; - padding-left: ${({ theme }) => theme.spacing(3)}; - padding-right: ${({ theme }) => theme.spacing(2)}; -`; - const StyledIcon = styled.div` display: flex; margin-left: ${({ theme }) => theme.spacing(1)}; @@ -43,16 +26,6 @@ const StyledIcon = styled.div` } `; -const StyledViewSection = styled.div` - display: flex; -`; - -const StyledFilters = styled.div` - display: flex; - font-weight: ${({ theme }) => theme.font.weight.regular}; - gap: 2px; -`; - export function BoardHeader({ viewName, viewIcon, @@ -83,35 +56,37 @@ export function BoardHeader({ ); return ( - - - + {viewIcon} {viewName} - - - - - isSortSelected={sorts.length > 0} - availableSorts={availableSorts || []} - onSortSelect={sortSelect} - HotkeyScope={FiltersHotkeyScope.FilterDropdownButton} - /> - - - { - innerSetSorts([]); - onSortsUpdate && onSortsUpdate([]); - }} - /> - + + } + rightComponents={[ + , + + isSortSelected={sorts.length > 0} + availableSorts={availableSorts || []} + onSortSelect={sortSelect} + HotkeyScope={FiltersHotkeyScope.FilterDropdownButton} + />, + ]} + bottomComponent={ + { + innerSetSorts([]); + onSortsUpdate && onSortsUpdate([]); + }} + /> + } + /> ); } diff --git a/front/src/modules/ui/icon/index.ts b/front/src/modules/ui/icon/index.ts index 141384deb9..b4f1f53d0f 100644 --- a/front/src/modules/ui/icon/index.ts +++ b/front/src/modules/ui/icon/index.ts @@ -9,6 +9,7 @@ export { IconUser } from '@tabler/icons-react'; export { IconList } from '@tabler/icons-react'; export { IconInbox } from '@tabler/icons-react'; export { IconSearch } from '@tabler/icons-react'; +export { IconArchive } from '@tabler/icons-react'; export { IconSettings } from '@tabler/icons-react'; export { IconLogout } from '@tabler/icons-react'; export { IconColorSwatch } from '@tabler/icons-react'; diff --git a/front/src/modules/ui/tab/components/Tab.tsx b/front/src/modules/ui/tab/components/Tab.tsx index afce4fdfcd..0c61cd5386 100644 --- a/front/src/modules/ui/tab/components/Tab.tsx +++ b/front/src/modules/ui/tab/components/Tab.tsx @@ -3,6 +3,7 @@ import styled from '@emotion/styled'; type OwnProps = { title: string; + icon?: React.ReactNode; active?: boolean; className?: string; onClick?: () => void; @@ -28,10 +29,16 @@ const StyledTab = styled.div<{ active?: boolean }>` } `; -export function Tab({ title, active = false, onClick, className }: OwnProps) { +export function Tab({ + title, + icon, + active = false, + onClick, + className, +}: OwnProps) { return ( - {title} + {icon} {title} ); } diff --git a/front/src/modules/ui/tab/components/TabList.tsx b/front/src/modules/ui/tab/components/TabList.tsx new file mode 100644 index 0000000000..ebfcd26514 --- /dev/null +++ b/front/src/modules/ui/tab/components/TabList.tsx @@ -0,0 +1,47 @@ +import * as React from 'react'; + +import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState'; + +import { activeTabIdScopedState } from '../states/activeTabIdScopedState'; + +import { Tab } from './Tab'; + +type SingleTabProps = { + title: string; + icon?: React.ReactNode; + id: string; +}; + +type OwnProps = { + tabs: SingleTabProps[]; + context: React.Context; +}; + +export function TabList({ tabs, context }: OwnProps) { + const initialActiveTabId = tabs[0].id; + + const [activeTabId, setActiveTabId] = useRecoilScopedState( + activeTabIdScopedState, + context, + ); + + React.useEffect(() => { + setActiveTabId(initialActiveTabId); + }, [initialActiveTabId, setActiveTabId]); + + return ( + <> + {tabs.map((tab) => ( + { + setActiveTabId(tab.id); + }} + /> + ))} + + ); +} diff --git a/front/src/modules/ui/tab/components/__stories__/Tab.stories.tsx b/front/src/modules/ui/tab/components/__stories__/Tab.stories.tsx index 57f5f3d4a4..08a879ed74 100644 --- a/front/src/modules/ui/tab/components/__stories__/Tab.stories.tsx +++ b/front/src/modules/ui/tab/components/__stories__/Tab.stories.tsx @@ -1,4 +1,5 @@ import type { Meta, StoryObj } from '@storybook/react'; +import { IconCheckbox } from '@tabler/icons-react'; import { CatalogDecorator } from '~/testing/decorators/CatalogDecorator'; import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator'; @@ -22,7 +23,7 @@ export const Default: Story = { }; export const Catalog: Story = { - args: { title: 'Tab title' }, + args: { title: 'Tab title', icon: }, argTypes: { active: { control: false }, onClick: { control: false }, diff --git a/front/src/modules/ui/tab/states/activeTabIdScopedState.ts b/front/src/modules/ui/tab/states/activeTabIdScopedState.ts new file mode 100644 index 0000000000..232a7a80e5 --- /dev/null +++ b/front/src/modules/ui/tab/states/activeTabIdScopedState.ts @@ -0,0 +1,6 @@ +import { atomFamily } from 'recoil'; + +export const activeTabIdScopedState = atomFamily({ + key: 'activeTabIdScopedState', + default: null, +}); diff --git a/front/src/modules/ui/table/table-header/components/TableHeader.tsx b/front/src/modules/ui/table/table-header/components/TableHeader.tsx index 833499a813..9ce66ae079 100644 --- a/front/src/modules/ui/table/table-header/components/TableHeader.tsx +++ b/front/src/modules/ui/table/table-header/components/TableHeader.tsx @@ -6,6 +6,7 @@ import SortAndFilterBar from '@/ui/filter-n-sort/components/SortAndFilterBar'; import { SortDropdownButton } from '@/ui/filter-n-sort/components/SortDropdownButton'; import { FiltersHotkeyScope } from '@/ui/filter-n-sort/types/FiltersHotkeyScope'; import { SelectedSortType, SortType } from '@/ui/filter-n-sort/types/interface'; +import { TopBar } from '@/ui/top-bar/TopBar'; import { TableContext } from '../../states/TableContext'; @@ -16,23 +17,6 @@ type OwnProps = { onSortsUpdate?: (sorts: Array>) => void; }; -const StyledContainer = styled.div` - display: flex; - flex-direction: column; -`; - -const StyledTableHeader = styled.div` - align-items: center; - color: ${({ theme }) => theme.font.color.secondary}; - display: flex; - flex-direction: row; - font-weight: ${({ theme }) => theme.font.weight.medium}; - height: 40px; - justify-content: space-between; - padding-left: ${({ theme }) => theme.spacing(3)}; - padding-right: ${({ theme }) => theme.spacing(2)}; -`; - const StyledIcon = styled.div` display: flex; margin-left: ${({ theme }) => theme.spacing(1)}; @@ -43,16 +27,6 @@ const StyledIcon = styled.div` } `; -const StyledViewSection = styled.div` - display: flex; -`; - -const StyledFilters = styled.div` - display: flex; - font-weight: ${({ theme }) => theme.font.weight.regular}; - gap: 2px; -`; - export function TableHeader({ viewName, viewIcon, @@ -82,35 +56,37 @@ export function TableHeader({ ); return ( - - - + {viewIcon} {viewName} - - - - - isSortSelected={sorts.length > 0} - availableSorts={availableSorts || []} - onSortSelect={sortSelect} - HotkeyScope={FiltersHotkeyScope.FilterDropdownButton} - /> - - - { - innerSetSorts([]); - onSortsUpdate && onSortsUpdate([]); - }} - /> - + + } + rightComponents={[ + , + + isSortSelected={sorts.length > 0} + availableSorts={availableSorts || []} + onSortSelect={sortSelect} + HotkeyScope={FiltersHotkeyScope.FilterDropdownButton} + />, + ]} + bottomComponent={ + { + innerSetSorts([]); + onSortsUpdate && onSortsUpdate([]); + }} + /> + } + /> ); } diff --git a/front/src/modules/ui/top-bar/TopBar.tsx b/front/src/modules/ui/top-bar/TopBar.tsx new file mode 100644 index 0000000000..2652302c0e --- /dev/null +++ b/front/src/modules/ui/top-bar/TopBar.tsx @@ -0,0 +1,51 @@ +import { ReactNode } from 'react'; +import styled from '@emotion/styled'; + +type OwnProps = { + leftComponent?: ReactNode; + rightComponents?: ReactNode[]; + bottomComponent?: ReactNode; +}; + +const StyledContainer = styled.div` + display: flex; + flex-direction: column; +`; + +const StyledTableHeader = styled.div` + align-items: center; + color: ${({ theme }) => theme.font.color.secondary}; + display: flex; + flex-direction: row; + font-weight: ${({ theme }) => theme.font.weight.medium}; + height: 40px; + justify-content: space-between; + padding-left: ${({ theme }) => theme.spacing(3)}; + padding-right: ${({ theme }) => theme.spacing(2)}; +`; + +const StyledLeftSection = styled.div` + display: flex; +`; + +const StyledRightSection = styled.div` + display: flex; + font-weight: ${({ theme }) => theme.font.weight.regular}; + gap: 2px; +`; + +export function TopBar({ + leftComponent, + rightComponents, + bottomComponent, +}: OwnProps) { + return ( + + + {leftComponent} + {rightComponents} + + {bottomComponent} + + ); +} diff --git a/front/src/pages/tasks/Tasks.tsx b/front/src/pages/tasks/Tasks.tsx new file mode 100644 index 0000000000..f3dd7b203d --- /dev/null +++ b/front/src/pages/tasks/Tasks.tsx @@ -0,0 +1,51 @@ +import { useTheme } from '@emotion/react'; +import styled from '@emotion/styled'; + +import { TaskGroups } from '@/tasks/components/TaskGroups'; +import { TasksContext } from '@/tasks/states/TasksContext'; +import { IconArchive, IconCheck, IconCheckbox } from '@/ui/icon/index'; +import { WithTopBarContainer } from '@/ui/layout/components/WithTopBarContainer'; +import { TabList } from '@/ui/tab/components/TabList'; +import { TopBar } from '@/ui/top-bar/TopBar'; +import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope'; + +const StyledTasksContainer = styled.div` + display: flex; + flex: 1; + flex-direction: column; + height: 100%; + overflow: auto; +`; + +export function Tasks() { + const theme = useTheme(); + + const TASK_TABS = [ + { + id: 'to-do', + title: 'To do', + icon: , + }, + { + id: 'done', + title: 'Done', + icon: , + }, + ]; + + return ( + } + > + + + } + /> + + + + + ); +}