Add dueDate and assignee on notes (#988)

* Add dueDate and assignee on notes

* Fix tests

* Fix tests
This commit is contained in:
Charles Bochet
2023-07-29 15:36:21 -07:00
committed by GitHub
parent d9f6ae8663
commit 8601ed04ae
46 changed files with 875 additions and 205 deletions

View File

@ -15,11 +15,11 @@ const StyledRawLink = styled(RawLink)`
`;
type OwnProps = {
value: string;
value: string | null;
};
export function InplaceInputPhoneDisplayMode({ value }: OwnProps) {
return isValidPhoneNumber(value) ? (
return value && isValidPhoneNumber(value) ? (
<StyledRawLink
href={parsePhoneNumber(value, 'FR')?.getURI()}
onClick={(event: MouseEvent<HTMLElement>) => {

View File

@ -15,6 +15,7 @@ const StyledIconContainer = styled.div`
align-items: center;
color: ${({ theme }) => theme.font.color.tertiary};
display: flex;
width: 16px;
svg {
align-items: center;
@ -32,6 +33,12 @@ const StyledLabelAndIconContainer = styled.div`
gap: ${({ theme }) => theme.spacing(1)};
`;
const StyledValueContainer = styled.div`
display: flex;
flex: 1;
max-width: calc(100% - ${({ theme }) => theme.spacing(4)});
`;
const StyledLabel = styled.div<Pick<OwnProps, 'labelFixedWidth'>>`
align-items: center;
@ -39,18 +46,22 @@ const StyledLabel = styled.div<Pick<OwnProps, 'labelFixedWidth'>>`
labelFixedWidth ? `${labelFixedWidth}px` : 'fit-content'};
`;
const StyledEditButtonContainer = styled(motion.div)`
position: absolute;
right: 0;
`;
export const EditableFieldBaseContainer = styled.div`
align-items: center;
box-sizing: border-box;
display: flex;
gap: ${({ theme }) => theme.spacing(1)};
height: 24px;
justify-content: flex-start;
position: relative;
user-select: none;
justify-content: space-between;
position: relative;
user-select: none;
width: 100%;
`;
@ -66,6 +77,7 @@ type OwnProps = {
parentHotkeyScope?: HotkeyScope;
customEditHotkeyScope?: HotkeyScope;
isDisplayModeContentEmpty?: boolean;
isDisplayModeFixHeight?: boolean;
onSubmit?: () => void;
onCancel?: () => void;
};
@ -82,6 +94,7 @@ export function EditableField({
disableHoverEffect,
isDisplayModeContentEmpty,
displayModeContentOnly,
isDisplayModeFixHeight,
onSubmit,
onCancel,
}: OwnProps) {
@ -119,29 +132,32 @@ export function EditableField({
<StyledLabel labelFixedWidth={labelFixedWidth}>{label}</StyledLabel>
)}
</StyledLabelAndIconContainer>
{isFieldInEditMode && !displayModeContentOnly ? (
<EditableFieldEditMode onSubmit={onSubmit} onCancel={onCancel}>
{editModeContent}
</EditableFieldEditMode>
) : (
<EditableFieldDisplayMode
disableHoverEffect={disableHoverEffect}
disableClick={useEditButton}
onClick={handleDisplayModeClick}
isDisplayModeContentEmpty={isDisplayModeContentEmpty}
>
{displayModeContent}
</EditableFieldDisplayMode>
)}
<StyledValueContainer>
{isFieldInEditMode && !displayModeContentOnly ? (
<EditableFieldEditMode onSubmit={onSubmit} onCancel={onCancel}>
{editModeContent}
</EditableFieldEditMode>
) : (
<EditableFieldDisplayMode
disableHoverEffect={disableHoverEffect}
disableClick={useEditButton}
onClick={handleDisplayModeClick}
isDisplayModeContentEmpty={isDisplayModeContentEmpty}
isDisplayModeFixHeight={isDisplayModeFixHeight}
>
{displayModeContent}
</EditableFieldDisplayMode>
)}
</StyledValueContainer>
{showEditButton && (
<motion.div
<StyledEditButtonContainer
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.1 }}
whileHover={{ scale: 1.04 }}
>
<EditableFieldEditButton />
</motion.div>
</StyledEditButtonContainer>
)}
</EditableFieldBaseContainer>
);

View File

@ -4,15 +4,18 @@ import styled from '@emotion/styled';
export const EditableFieldNormalModeOuterContainer = styled.div<
Pick<
OwnProps,
'disableClick' | 'isDisplayModeContentEmpty' | 'disableHoverEffect'
| 'disableClick'
| 'isDisplayModeContentEmpty'
| 'disableHoverEffect'
| 'isDisplayModeFixHeight'
>
>`
align-items: center;
border-radius: ${({ theme }) => theme.border.radius.sm};
display: flex;
height: 100%;
height: 16px;
height: ${({ isDisplayModeFixHeight }) =>
isDisplayModeFixHeight ? '16px' : 'auto'};
min-height: 16px;
overflow: hidden;
@ -67,6 +70,7 @@ type OwnProps = {
onClick?: () => void;
isDisplayModeContentEmpty?: boolean;
disableHoverEffect?: boolean;
isDisplayModeFixHeight?: boolean;
};
export function EditableFieldDisplayMode({
@ -75,6 +79,7 @@ export function EditableFieldDisplayMode({
onClick,
isDisplayModeContentEmpty,
disableHoverEffect,
isDisplayModeFixHeight,
}: React.PropsWithChildren<OwnProps>) {
return (
<EditableFieldNormalModeOuterContainer
@ -82,6 +87,7 @@ export function EditableFieldDisplayMode({
disableClick={disableClick}
isDisplayModeContentEmpty={isDisplayModeContentEmpty}
disableHoverEffect={disableHoverEffect}
isDisplayModeFixHeight={isDisplayModeFixHeight}
>
<EditableFieldNormalModeInnerContainer>
{children}

View File

@ -7,10 +7,12 @@ export const EditableFieldEditModeContainer = styled.div<OwnProps>`
align-items: center;
display: flex;
height: 24px;
margin-left: -${({ theme }) => theme.spacing(1)};
position: relative;
width: inherit;
width: 100%;
z-index: 10;
`;

View File

@ -7,7 +7,7 @@ const StyledPropertyBoxContainer = styled.div`
border-radius: ${({ theme }) => theme.border.radius.sm};
display: flex;
flex-direction: column;
gap: ${({ theme }) => theme.spacing(3)};
gap: ${({ theme }) => theme.spacing(2)};
padding: ${({ theme }) => theme.spacing(3)};
`;

View File

@ -10,11 +10,12 @@ import { EditableFieldEditModeDate } from './EditableFieldEditModeDate';
type OwnProps = {
icon?: React.ReactNode;
label?: string;
value: string | null | undefined;
onSubmit?: (newValue: string) => void;
};
export function DateEditableField({ icon, value, onSubmit }: OwnProps) {
export function DateEditableField({ icon, value, label, onSubmit }: OwnProps) {
const [internalValue, setInternalValue] = useState(value);
useEffect(() => {
@ -47,6 +48,7 @@ export function DateEditableField({ icon, value, onSubmit }: OwnProps) {
onSubmit={handleSubmit}
onCancel={handleCancel}
iconLabel={icon}
label={label}
editModeContent={
<EditableFieldEditModeDate
value={internalValue || new Date().toISOString()}

View File

@ -14,12 +14,14 @@ type OwnProps = {
export const StyledDoubleTextContainer = styled.div`
align-items: center;
display: flex;
justify-content: space-between;
justify-content: center;
text-align: center;
`;
& > input:last-child {
border-left: 1px solid ${({ theme }) => theme.border.color.medium};
padding-left: ${({ theme }) => theme.spacing(2)};
}
const StyledNameInput = styled(StyledInput)`
padding: 0;
text-align: center;
width: auto;
`;
export function InplaceInputDoubleText({
@ -31,7 +33,8 @@ export function InplaceInputDoubleText({
}: OwnProps) {
return (
<StyledDoubleTextContainer>
<StyledInput
<StyledNameInput
size={firstValue.length}
autoFocus
placeholder={firstValuePlaceholder}
value={firstValue}
@ -39,7 +42,8 @@ export function InplaceInputDoubleText({
onChange(event.target.value, secondValue);
}}
/>
<StyledInput
<StyledNameInput
size={secondValue.length}
autoComplete="off"
placeholder={secondValuePlaceholder}
value={secondValue}

View File

@ -43,9 +43,12 @@ const StyledDate = styled.div`
const StyledTitle = styled.div`
color: ${({ theme }) => theme.font.color.primary};
display: flex;
flex-direction: row;
font-size: ${({ theme }) => theme.font.size.xl};
font-weight: ${({ theme }) => theme.font.weight.semiBold};
max-width: 100%;
justify-content: center;
width: 100%;
`;
const StyledTooltip = styled(Tooltip)`

View File

@ -1,3 +1,4 @@
import { useRef } from 'react';
import debounce from 'lodash.debounce';
import { DropdownMenu } from '@/ui/dropdown/components/DropdownMenu';
@ -6,6 +7,7 @@ import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem';
import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer';
import { DropdownMenuSearch } from '@/ui/dropdown/components/DropdownMenuSearch';
import { DropdownMenuSeparator } from '@/ui/dropdown/components/DropdownMenuSeparator';
import { useListenClickOutside } from '@/ui/hooks/useListenClickOutside';
import { Avatar } from '@/users/components/Avatar';
import { isNonEmptyString } from '~/utils/isNonEmptyString';
@ -25,6 +27,7 @@ export function MultipleEntitySelect<
>({
entities,
onChange,
onSubmit,
onSearchFilterChange,
searchFilter,
value,
@ -33,6 +36,8 @@ export function MultipleEntitySelect<
searchFilter: string;
onSearchFilterChange: (newSearchFilter: string) => void;
onChange: (value: Record<string, boolean>) => void;
onCancel?: () => void;
onSubmit?: () => void;
value: Record<string, boolean>;
}) {
const debouncedSetSearchFilter = debounce(onSearchFilterChange, 100, {
@ -53,8 +58,21 @@ export function MultipleEntitySelect<
isNonEmptyString(entity.name),
);
const containerRef = useRef<HTMLDivElement>(null);
useListenClickOutside({
refs: [containerRef],
callback: (event) => {
event.stopImmediatePropagation();
event.stopPropagation();
event.preventDefault();
onSubmit?.();
},
});
return (
<DropdownMenu>
<DropdownMenu ref={containerRef}>
<DropdownMenuSearch
value={searchFilter}
onChange={handleFilterChange}

View File

@ -33,6 +33,13 @@ export function GenericEditableDoubleTextChipCellDisplayMode({
}),
);
const [avatarUrlValue] = useRecoilState<string>(
tableEntityFieldFamilySelector({
entityId: currentRowEntityId ?? '',
fieldName: viewField.metadata.avatarUrlFieldName,
}),
);
const displayName = `${firstValue} ${secondValue}`;
switch (viewField.metadata.entityType) {
@ -40,7 +47,13 @@ export function GenericEditableDoubleTextChipCellDisplayMode({
return <CompanyChip id={currentRowEntityId ?? ''} name={displayName} />;
}
case Entity.Person: {
return <PersonChip id={currentRowEntityId ?? ''} name={displayName} />;
return (
<PersonChip
id={currentRowEntityId ?? ''}
name={displayName}
pictureUrl={avatarUrlValue}
/>
);
}
default:
console.warn(

View File

@ -45,6 +45,7 @@ export function GenericEditableRelationCellDisplayMode({
<UserChip
id={fieldValue?.id ?? ''}
name={fieldValue?.displayName ?? ''}
pictureUrl={fieldValue?.avatarUrl ?? ''}
/>
);
}

View File

@ -68,6 +68,7 @@ export type ViewFieldDoubleTextChipMetadata = {
firstValuePlaceholder: string;
secondValueFieldName: string;
secondValuePlaceholder: string;
avatarUrlFieldName: string;
entityType: Entity;
};