Show Entity task/note tabs (#1282)
* - show task tab - tab bar * - add notes tab * - fixed unused style * - add button - fixed company edit note test * - fixed merge & dropdown * - added Tests - refactored directory structure activities - moved Task/Note Pages to corresponding modules - fixed TabList * lint
This commit is contained in:
87
front/src/modules/activities/notes/components/NoteCard.tsx
Normal file
87
front/src/modules/activities/notes/components/NoteCard.tsx
Normal file
@ -0,0 +1,87 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { ActivityRelationEditableField } from '@/activities/editable-fields/components/ActivityRelationEditableField';
|
||||
import { useOpenActivityRightDrawer } from '@/activities/hooks/useOpenActivityRightDrawer';
|
||||
import { Activity, ActivityTarget } from '~/generated/graphql';
|
||||
|
||||
const StyledCard = styled.div`
|
||||
align-items: flex-start;
|
||||
background: ${({ theme }) => theme.background.secondary};
|
||||
border: 1px solid ${({ theme }) => theme.border.color.medium};
|
||||
border-radius: ${({ theme }) => theme.border.radius.md};
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 300px;
|
||||
justify-content: space-between;
|
||||
`;
|
||||
|
||||
const StyledCardDetailsContainer = styled.div`
|
||||
align-items: flex-start;
|
||||
align-self: stretch;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: ${({ theme }) => theme.spacing(2)};
|
||||
height: calc(100% - 45px);
|
||||
justify-content: start;
|
||||
padding: ${({ theme }) => theme.spacing(2)};
|
||||
width: calc(100% - ${({ theme }) => theme.spacing(4)});
|
||||
`;
|
||||
|
||||
const StyledNoteTitle = styled.div`
|
||||
color: ${({ theme }) => theme.font.color.primary};
|
||||
font-weight: ${({ theme }) => theme.font.weight.medium};
|
||||
`;
|
||||
|
||||
const StyledCardContent = styled.div`
|
||||
align-self: stretch;
|
||||
color: ${({ theme }) => theme.font.color.secondary};
|
||||
line-break: anywhere;
|
||||
margin-top: ${({ theme }) => theme.spacing(2)};
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledFooter = styled.div`
|
||||
align-items: center;
|
||||
align-self: stretch;
|
||||
border-top: 1px solid ${({ theme }) => theme.border.color.light};
|
||||
color: ${({ theme }) => theme.font.color.primary};
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: ${({ theme }) => theme.spacing(1)};
|
||||
justify-content: center;
|
||||
padding: ${({ theme }) => theme.spacing(2)};
|
||||
width: calc(100% - ${({ theme }) => theme.spacing(4)});
|
||||
`;
|
||||
|
||||
export function NoteCard({
|
||||
note,
|
||||
}: {
|
||||
note: Pick<
|
||||
Activity,
|
||||
'id' | 'title' | 'body' | 'type' | 'completedAt' | 'dueAt'
|
||||
> & {
|
||||
activityTargets?: Array<Pick<ActivityTarget, 'id'>> | null;
|
||||
};
|
||||
}) {
|
||||
const openActivityRightDrawer = useOpenActivityRightDrawer();
|
||||
const body = JSON.parse(note.body ?? '{}')[0]
|
||||
?.content.map((x: any) => x.text)
|
||||
.join('\n');
|
||||
|
||||
return (
|
||||
<StyledCard>
|
||||
<StyledCardDetailsContainer
|
||||
onClick={() => openActivityRightDrawer(note.id)}
|
||||
>
|
||||
<StyledNoteTitle>{note.title ?? 'Task Title'}</StyledNoteTitle>
|
||||
<StyledCardContent>{body}</StyledCardContent>
|
||||
</StyledCardDetailsContainer>
|
||||
<StyledFooter>
|
||||
<ActivityRelationEditableField activity={note} />
|
||||
</StyledFooter>
|
||||
</StyledCard>
|
||||
);
|
||||
}
|
||||
70
front/src/modules/activities/notes/components/NoteList.tsx
Normal file
70
front/src/modules/activities/notes/components/NoteList.tsx
Normal file
@ -0,0 +1,70 @@
|
||||
import { ReactElement } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { NoteForList } from '../../types/NoteForList';
|
||||
|
||||
import { NoteCard } from './NoteCard';
|
||||
|
||||
type OwnProps = {
|
||||
title: string;
|
||||
notes: NoteForList[];
|
||||
button?: ReactElement | false;
|
||||
};
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
align-items: flex-start;
|
||||
align-self: stretch;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding: 8px 24px;
|
||||
`;
|
||||
|
||||
const StyledTitleBar = styled.h3`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: ${({ theme }) => theme.spacing(4)};
|
||||
margin-top: ${({ theme }) => theme.spacing(4)};
|
||||
place-items: center;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledTitle = styled.h3`
|
||||
color: ${({ theme }) => theme.font.color.primary};
|
||||
font-weight: ${({ theme }) => theme.font.weight.semiBold};
|
||||
`;
|
||||
|
||||
const StyledCount = styled.span`
|
||||
color: ${({ theme }) => theme.font.color.light};
|
||||
margin-left: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const StyledNoteContainer = styled.div`
|
||||
display: grid;
|
||||
gap: ${({ theme }) => theme.spacing(4)};
|
||||
grid-auto-rows: 1fr;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export function NoteList({ title, notes, button }: OwnProps) {
|
||||
return (
|
||||
<>
|
||||
{notes && notes.length > 0 && (
|
||||
<StyledContainer>
|
||||
<StyledTitleBar>
|
||||
<StyledTitle>
|
||||
{title} <StyledCount>{notes.length}</StyledCount>
|
||||
</StyledTitle>
|
||||
{button}
|
||||
</StyledTitleBar>
|
||||
<StyledNoteContainer>
|
||||
{notes.map((note) => (
|
||||
<NoteCard key={note.id} note={note} />
|
||||
))}
|
||||
</StyledNoteContainer>
|
||||
</StyledContainer>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
91
front/src/modules/activities/notes/components/Notes.tsx
Normal file
91
front/src/modules/activities/notes/components/Notes.tsx
Normal file
@ -0,0 +1,91 @@
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { IconNotes } from '@tabler/icons-react';
|
||||
|
||||
import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateActivityDrawer';
|
||||
import { NoteList } from '@/activities/notes/components/NoteList';
|
||||
import { useNotes } from '@/activities/notes/hooks/useNotes';
|
||||
import { ActivityTargetableEntity } from '@/activities/types/ActivityTargetableEntity';
|
||||
import {
|
||||
Button,
|
||||
ButtonSize,
|
||||
ButtonVariant,
|
||||
} from '@/ui/button/components/Button';
|
||||
import { ActivityType } from '~/generated/graphql';
|
||||
|
||||
const StyledTaskGroupEmptyContainer = styled.div`
|
||||
align-items: center;
|
||||
align-self: stretch;
|
||||
display: flex;
|
||||
flex: 1 0 0;
|
||||
flex-direction: column;
|
||||
gap: ${({ theme }) => theme.spacing(2)};
|
||||
justify-content: center;
|
||||
padding-bottom: ${({ theme }) => theme.spacing(16)};
|
||||
padding-left: ${({ theme }) => theme.spacing(4)};
|
||||
padding-right: ${({ theme }) => theme.spacing(4)};
|
||||
padding-top: ${({ theme }) => theme.spacing(3)};
|
||||
`;
|
||||
|
||||
const StyledEmptyTaskGroupTitle = styled.div`
|
||||
color: ${({ theme }) => theme.font.color.secondary};
|
||||
font-size: ${({ theme }) => theme.font.size.xxl};
|
||||
font-weight: ${({ theme }) => theme.font.weight.semiBold};
|
||||
line-height: ${({ theme }) => theme.text.lineHeight.md};
|
||||
`;
|
||||
|
||||
const StyledEmptyTaskGroupSubTitle = styled.div`
|
||||
color: ${({ theme }) => theme.font.color.extraLight};
|
||||
font-size: ${({ theme }) => theme.font.size.xxl};
|
||||
font-weight: ${({ theme }) => theme.font.weight.semiBold};
|
||||
line-height: ${({ theme }) => theme.text.lineHeight.md};
|
||||
margin-bottom: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const StyledNotesContainer = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
`;
|
||||
|
||||
export function Notes({ entity }: { entity: ActivityTargetableEntity }) {
|
||||
const { notes } = useNotes(entity);
|
||||
const theme = useTheme();
|
||||
|
||||
const openCreateActivity = useOpenCreateActivityDrawer();
|
||||
|
||||
if (notes?.length === 0) {
|
||||
return (
|
||||
<StyledTaskGroupEmptyContainer>
|
||||
<StyledEmptyTaskGroupTitle>No note yet</StyledEmptyTaskGroupTitle>
|
||||
<StyledEmptyTaskGroupSubTitle>Create one:</StyledEmptyTaskGroupSubTitle>
|
||||
<Button
|
||||
icon={<IconNotes size={theme.icon.size.sm} />}
|
||||
title="New note"
|
||||
variant={ButtonVariant.Secondary}
|
||||
onClick={() => openCreateActivity(ActivityType.Note, [entity])}
|
||||
/>
|
||||
</StyledTaskGroupEmptyContainer>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledNotesContainer>
|
||||
<NoteList
|
||||
title="All"
|
||||
notes={notes ?? []}
|
||||
button={
|
||||
<Button
|
||||
icon={<IconNotes size={theme.icon.size.md} />}
|
||||
size={ButtonSize.Small}
|
||||
variant={ButtonVariant.Secondary}
|
||||
title="Add note"
|
||||
onClick={() => openCreateActivity(ActivityType.Note, [entity])}
|
||||
></Button>
|
||||
}
|
||||
/>
|
||||
</StyledNotesContainer>
|
||||
);
|
||||
}
|
||||
27
front/src/modules/activities/notes/hooks/useNotes.ts
Normal file
27
front/src/modules/activities/notes/hooks/useNotes.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { ActivityType, useGetActivitiesQuery } from '~/generated/graphql';
|
||||
|
||||
import { ActivityTargetableEntity } from '../../types/ActivityTargetableEntity';
|
||||
|
||||
export function useNotes(entity: ActivityTargetableEntity) {
|
||||
const { data: notesData } = useGetActivitiesQuery({
|
||||
variables: {
|
||||
where: {
|
||||
type: { equals: ActivityType.Note },
|
||||
activityTargets: {
|
||||
some: {
|
||||
OR: [
|
||||
{ companyId: { equals: entity.id } },
|
||||
{ personId: { equals: entity.id } },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const notes = notesData?.findManyActivities;
|
||||
|
||||
return {
|
||||
notes,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user