Skip to content

Commit

Permalink
feat: replace notes sidebar with sheet for better UX (#3109)
Browse files Browse the repository at this point in the history
Co-authored-by: Alon Peretz <[email protected]>
  • Loading branch information
r4zendev and alonp99 authored Feb 28, 2025
1 parent b283c09 commit e330709
Show file tree
Hide file tree
Showing 14 changed files with 257 additions and 245 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export const SheetOverlay = React.forwardRef<
>(({ className, ...props }, ref) => (
<SheetPrimitive.Overlay
className={ctw(
'fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
'fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
className,
)}
{...props}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { cva } from 'class-variance-authority';
import styles from './Sheet.module.css';

export const sheetVariants = cva(
'fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out animate-in animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500',
'fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500',
{
variants: {
side: {
Expand Down
57 changes: 21 additions & 36 deletions apps/backoffice-v2/src/domains/notes/Notes.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,18 @@
import * as React from 'react';
import { ctw } from '@ballerine/ui';
import { Link } from 'react-router-dom';
import { Loader2, X } from 'lucide-react';
import { Link } from 'react-router-dom';

import { Note } from './Note';
import type { TNoteableType, TNotes } from './types';
import { Form } from '@/common/components/organisms/Form/Form';
import { Button } from '@/common/components/atoms/Button/Button';
import { useNotesLogic } from '@/domains/notes/hooks/useNotesLogic';
import { FormItem } from '@/common/components/organisms/Form/Form.Item';
import { FormField } from '@/common/components/organisms/Form/Form.Field';
import { Separator } from '@/common/components/atoms/Separator/Separator';
import { Form } from '@/common/components/organisms/Form/Form';
import { FormControl } from '@/common/components/organisms/Form/Form.Control';
import { FormField } from '@/common/components/organisms/Form/Form.Field';
import { FormItem } from '@/common/components/organisms/Form/Form.Item';
import { FormMessage } from '@/common/components/organisms/Form/Form.Message';
import { MinimalTiptapEditor } from '@/common/components/organisms/TextEditor';
import {
Sidebar,
SidebarContent,
SidebarGroup,
SidebarHeader,
} from '@/common/components/organisms/Sidebar/Sidebar';
import { useNotesLogic } from '@/domains/notes/hooks/useNotesLogic';
import { Note } from './Note';
import type { TNoteableType, TNotes } from './types';

export const Notes = ({
notes,
Expand All @@ -33,23 +26,15 @@ export const Notes = ({
noteableType: TNoteableType;
};
}) => {
const { form, users, onSubmit, isLoading, updateIsNotesOpen } = useNotesLogic();
const { form, users, onSubmit, isLoading } = useNotesLogic();

return (
<Sidebar side={`right`} className={`bg-slate-50`}>
<SidebarHeader className={`h-12 flex-row items-center justify-between border-b p-4`}>
<div className={`flex h-full w-full flex-col bg-slate-50`}>
<div className={`h-12 flex-row items-center justify-between border-b p-4`}>
<span className={`text-sm font-medium`}>Notes</span>
<Link
className={`relative`}
to={{
search: updateIsNotesOpen(),
}}
>
<X className="d-4" />
</Link>
</SidebarHeader>
<SidebarContent className={`flex flex-col gap-1 border-none`}>
<SidebarGroup className={`p-4`}>
</div>
<div className={`flex flex-col gap-1 border-none`}>
<div className={`p-4`}>
<Form {...form}>
<form
className={`flex flex-col`}
Expand Down Expand Up @@ -96,20 +81,20 @@ export const Notes = ({
</Button>
</form>
</Form>
</SidebarGroup>
<SidebarGroup className={`p-0`}>
<Separator />
</SidebarGroup>
<SidebarGroup className={`space-y-4 p-4`}>
</div>

<Separator />

<div className={`space-y-4 p-4`}>
{(notes || []).map(note => (
<Note
key={note.id}
{...note}
user={(users || []).find(user => user.id === note.createdBy)}
/>
))}
</SidebarGroup>
</SidebarContent>
</Sidebar>
</div>
</div>
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,7 @@ export const NotesButton = ({ numberOfNotes = 0 }: INotesButtonProps) => {
return (
<div className={`flex items-center space-x-2`}>
<span className={`me-2 text-sm leading-6`}>Notes</span>
<Link
className={`relative`}
to={{
search: updateIsNotesOpen(),
}}
>
<Link className={`relative`} to={{ search: updateIsNotesOpen() }} replace>
<SquarePen className={`d-5`} />
{numberOfNotes > 0 && (
<div
Expand Down
27 changes: 27 additions & 0 deletions apps/backoffice-v2/src/domains/notes/NotesSheet.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { FunctionComponent, ComponentProps } from 'react';

import { Sheet } from '@/common/components/atoms/Sheet/Sheet';
import { SheetContent } from '@/common/components/atoms/Sheet/Sheet.Content';
import { SheetTrigger } from '@/common/components/atoms/Sheet/Sheet.Trigger';
import { Notes } from './Notes';

export type NotesSheetProps = ComponentProps<typeof Sheet> &
ComponentProps<typeof Notes> & { children: React.ReactNode };

export const NotesSheet: FunctionComponent<NotesSheetProps> = ({
open,
onOpenChange,
defaultOpen,
modal = false,
children,
...notesProps
}) => {
return (
<Sheet open={open} onOpenChange={onOpenChange} modal={modal} defaultOpen={defaultOpen}>
<SheetTrigger asChild>{children}</SheetTrigger>
<SheetContent onPointerDownOutside={e => e.preventDefault()} className="p-0">
<Notes {...notesProps} />
</SheetContent>
</Sheet>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ import { useMutation, useQueryClient } from '@tanstack/react-query';
import { HttpError } from '@/common/errors/http-error';
import { createNote } from '@/domains/notes/hooks/fetchers';
import { TNoteableType } from '@/domains/notes/types';
import { notesQueryKey } from '../../query-keys';

export const useCreateNoteMutation = ({
onSuccess,
disableToast = false,
}: {
onSuccess?: <TData>(data: TData) => void;
disableToast: boolean;
disableToast?: boolean;
}) => {
const queryClient = useQueryClient();

Expand Down Expand Up @@ -40,8 +41,10 @@ export const useCreateNoteMutation = ({
content,
parentNoteId,
}),
onSuccess: data => {
void queryClient.invalidateQueries();
onSuccess: (data, { noteableId, noteableType }) => {
void queryClient.invalidateQueries(
notesQueryKey.byNoteable({ noteableId, noteableType }).queryKey,
);

if (!disableToast) {
toast.success(t(`toast:note_created.success`));
Expand Down
73 changes: 26 additions & 47 deletions apps/backoffice-v2/src/pages/Entity/Entity.page.tsx
Original file line number Diff line number Diff line change
@@ -1,62 +1,41 @@
import { Case } from './components/Case/Case';
import { Notes } from '@/domains/notes/Notes';
import { TWorkflowById } from '@/domains/workflows/fetchers';
import { BlocksVariant } from '@/lib/blocks/variants/BlocksVariant/BlocksVariant';
import { useEntityLogic } from '@/pages/Entity/hooks/useEntityLogic/useEntityLogic';
import { SidebarInset, SidebarProvider } from '@/common/components/organisms/Sidebar/Sidebar';
import { Case } from './components/Case/Case';

export const Entity = () => {
const { workflow, notes, selectedEntity, isNotesOpen } = useEntityLogic();
const { workflow, selectedEntity } = useEntityLogic();

if (!workflow || !selectedEntity) {
return null;
}

// Selected entity
return (
<SidebarProvider
open={isNotesOpen}
style={{
'--sidebar-width': '25rem',
'--sidebar-width-mobile': '20rem',
}}
>
<SidebarInset>
<Case key={workflow.id}>
{/* Reject and approve header */}
<Case.Actions
numberOfNotes={notes?.length ?? 0}
id={workflow.id}
fullName={selectedEntity.name}
showResolutionButtons={
workflow.workflowDefinition?.config?.workflowLevelResolution ??
workflow.context?.entity?.type === 'business'
}
workflow={workflow as TWorkflowById}
/>
<Case.Content key={selectedEntity?.id}>
{workflow.workflowDefinition && (
<BlocksVariant
workflowDefinition={{
version: workflow.workflowDefinition?.version,
variant: workflow.workflowDefinition?.variant,
config: workflow.workflowDefinition?.config,
name: workflow.workflowDefinition?.name,
}}
/>
)}
</Case.Content>
</Case>
</SidebarInset>
<Notes
notes={notes ?? []}
noteData={{
entityId: workflow.entity.id,
entityType: `Business`,
noteableId: workflow.id,
noteableType: `Workflow`,
}}
<Case key={workflow.id}>
{/* Reject and approve header */}
<Case.Actions
id={workflow.id}
entityId={selectedEntity.id}
fullName={selectedEntity.name}
showResolutionButtons={
workflow.workflowDefinition?.config?.workflowLevelResolution ??
workflow.context?.entity?.type === 'business'
}
workflow={workflow as TWorkflowById}
/>
</SidebarProvider>
<Case.Content key={selectedEntity?.id}>
{workflow.workflowDefinition && (
<BlocksVariant
workflowDefinition={{
version: workflow.workflowDefinition?.version,
variant: workflow.workflowDefinition?.variant,
config: workflow.workflowDefinition?.config,
name: workflow.workflowDefinition?.name,
}}
/>
)}
</Case.Content>
</Case>
);
};
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
import { StateTag } from '@ballerine/common';
import { Badge } from '@ballerine/ui';
import { FunctionComponent, useMemo } from 'react';
import { StateTag } from '@ballerine/common';

import { tagToBadgeData } from './consts';
import { ctw } from '@/common/utils/ctw/ctw';
import { IActionsProps } from './interfaces';
import { NotesButton } from '@/common/components/molecules/NotesButton/NotesButton';
import { useCaseActionsLogic } from './hooks/useCaseActionsLogic/useCaseActionsLogic';
import { AssignDropdown } from '@/common/components/atoms/AssignDropdown/AssignDropdown';
import { CaseOptions } from '@/pages/Entity/components/Case/components/CaseOptions/CaseOptions';
import { ActionsVariant } from '@/pages/Entity/components/Case/actions-variants/ActionsVariant/ActionsVariant';
import { Avatar } from '@/common/components/atoms/Avatar';
import { stringToRGB } from '@/common/utils/string-to-rgb/string-to-rgb';
import { createInitials } from '@/common/utils/create-initials/create-initials';
import { ctw } from '@/common/utils/ctw/ctw';
import { stringToRGB } from '@/common/utils/string-to-rgb/string-to-rgb';
import { NotesButton } from '@/domains/notes/NotesButton';
import { NotesSheet } from '@/domains/notes/NotesSheet';
import { ActionsVariant } from '@/pages/Entity/components/Case/actions-variants/ActionsVariant/ActionsVariant';
import { CaseOptions } from '@/pages/Entity/components/Case/components/CaseOptions/CaseOptions';
import { tagToBadgeData } from './consts';
import { useCaseActionsLogic } from './hooks/useCaseActionsLogic/useCaseActionsLogic';
import { IActionsProps } from './interfaces';

/**
* @description To be used by {@link Case}. Displays the entity's full name, avatar, and handles the reject/approve mutation.
*
* @param props
* @param props.id - The id of the entity, passed into the reject/approve mutation.
* @param props.entityId - The id of the selected entity to be used in the notes.
* @param props.fullName - The full name of the entity.
* @param props.showResolutionButtons - Whether to show the reject/approve buttons.
*
Expand All @@ -28,8 +30,8 @@ import { createInitials } from '@/common/utils/create-initials/create-initials';
*/
export const Actions: FunctionComponent<IActionsProps> = ({
id,
entityId,
fullName,
numberOfNotes,
showResolutionButtons,
}) => {
const {
Expand All @@ -42,6 +44,9 @@ export const Actions: FunctionComponent<IActionsProps> = ({
workflowDefinition,
isWorkflowCompleted,
avatarUrl,
notes,
isNotesOpen,
setIsNotesOpen,
} = useCaseActionsLogic({ workflowId: id, fullName });

const entityInitials = createInitials(fullName);
Expand Down Expand Up @@ -102,7 +107,20 @@ export const Actions: FunctionComponent<IActionsProps> = ({
</Badge>
</div>
)}
<NotesButton numberOfNotes={numberOfNotes} />
<NotesSheet
open={isNotesOpen}
onOpenChange={setIsNotesOpen}
modal={false}
notes={notes ?? []}
noteData={{
entityId,
entityType: `Business`,
noteableId: id,
noteableType: `Workflow`,
}}
>
<NotesButton numberOfNotes={notes?.length ?? 0} />
</NotesSheet>
</div>
</div>
{showResolutionButtons && workflowDefinition && (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import { useCallback, useMemo } from 'react';
import { useParams } from 'react-router-dom';

import { IUseActions } from './interfaces';
import { tagToBadgeData } from '../../consts';
import { useCaseState } from '../useCaseState/useCaseState';
import { useCaseDecision } from '../useCaseDecision/useCaseDecision';
import { useDebounce } from '@/common/hooks/useDebounce/useDebounce';
import { useFilterId } from '@/common/hooks/useFilterId/useFilterId';
import { useSerializedSearchParams } from '@/common/hooks/useSerializedSearchParams/useSerializedSearchParams';
import { createInitials } from '@/common/utils/create-initials/create-initials';
import { useUsersQuery } from '@/domains/users/hooks/queries/useUsersQuery/useUsersQuery';
import { useWorkflowByIdQuery } from '@/domains/workflows/hooks/queries/useWorkflowByIdQuery/useWorkflowByIdQuery';
import { useAuthenticatedUserQuery } from '@/domains/auth/hooks/queries/useAuthenticatedUserQuery/useAuthenticatedUserQuery';
import { useNotesByNoteable } from '@/domains/notes/hooks/queries/useNotesByNoteable/useNotesByNoteable';
import { useUsersQuery } from '@/domains/users/hooks/queries/useUsersQuery/useUsersQuery';
import { useAssignWorkflowMutation } from '@/domains/workflows/hooks/mutations/useAssignWorkflowMutation/useAssignWorkflowMutation';
import { useWorkflowByIdQuery } from '@/domains/workflows/hooks/queries/useWorkflowByIdQuery/useWorkflowByIdQuery';
import { tagToBadgeData } from '../../consts';
import { useCaseDecision } from '../useCaseDecision/useCaseDecision';
import { useCaseState } from '../useCaseState/useCaseState';
import { IUseActions } from './interfaces';

export const useCaseActionsLogic = ({ workflowId, fullName }: IUseActions) => {
const filterId = useFilterId();
Expand All @@ -19,6 +22,12 @@ export const useCaseActionsLogic = ({ workflowId, fullName }: IUseActions) => {
filterId,
});

const { entityId } = useParams();
const { data: notes } = useNotesByNoteable({ noteableId: entityId, noteableType: 'Workflow' });

const [{ isNotesOpen }, setSearchParams] = useSerializedSearchParams();
const setIsNotesOpen = (open: boolean) => setSearchParams({ isNotesOpen: open });

const { mutate: mutateAssignWorkflow, isLoading: isLoadingAssignWorkflow } =
useAssignWorkflowMutation({ workflowRuntimeId: workflowId });

Expand Down Expand Up @@ -79,5 +88,8 @@ export const useCaseActionsLogic = ({ workflowId, fullName }: IUseActions) => {
workflowDefinition: workflow?.workflowDefinition,
isWorkflowCompleted,
avatarUrl: workflow?.entity?.avatarUrl || '',
notes,
isNotesOpen: isNotesOpen === 'true',
setIsNotesOpen,
};
};
Loading

0 comments on commit e330709

Please sign in to comment.