(
+ genericEntityFieldFamilySelector({
+ entityId: currentEditableFieldEntityId ?? '',
+ fieldName: currentEditableFieldDefinition
+ ? currentEditableFieldDefinition.metadata.fieldName
+ : '',
+ }),
+ );
+
+ const [internalValue, setInternalValue] = useState(fieldValue);
+
+ const updateField = useUpdateGenericEntityField();
+
+ const wrapperRef = useRef(null);
+
+ useRegisterCloseFieldHandlers(wrapperRef, handleSubmit, onCancel);
+
+ function handleSubmit() {
+ if (internalValue === fieldValue) return;
+
+ setFieldValue(internalValue);
+
+ if (currentEditableFieldEntityId && updateField) {
+ updateField(
+ currentEditableFieldEntityId,
+ currentEditableFieldDefinition,
+ internalValue,
+ );
+ }
+ }
+
+ function onCancel() {
+ setFieldValue(fieldValue);
+ }
+
+ function handleChange(newValue: string) {
+ setInternalValue(newValue);
+ }
+
+ return (
+
+ {
+ handleChange(newValue);
+ }}
+ />
+
+ );
+}
diff --git a/front/src/modules/ui/editable-field/components/GenericEditableRelationField.tsx b/front/src/modules/ui/editable-field/components/GenericEditableRelationField.tsx
index ae2c42ec2..596b60dea 100644
--- a/front/src/modules/ui/editable-field/components/GenericEditableRelationField.tsx
+++ b/front/src/modules/ui/editable-field/components/GenericEditableRelationField.tsx
@@ -34,7 +34,7 @@ export function GenericEditableRelationField() {
);
}
+ case Entity.User: {
+ return (
+
+ );
+ }
+ case Entity.Company: {
+ return (
+
+ );
+ }
default:
console.warn(
`Unknown relation type: "${currentEditableFieldDefinition.metadata.relationType}"
diff --git a/front/src/modules/ui/editable-field/components/GenericEditableRelationFieldEditMode.tsx b/front/src/modules/ui/editable-field/components/GenericEditableRelationFieldEditMode.tsx
index 9b4c08c4f..dc3650e5d 100644
--- a/front/src/modules/ui/editable-field/components/GenericEditableRelationFieldEditMode.tsx
+++ b/front/src/modules/ui/editable-field/components/GenericEditableRelationFieldEditMode.tsx
@@ -2,10 +2,11 @@ import { useContext } from 'react';
import styled from '@emotion/styled';
import { useRecoilState } from 'recoil';
+import { CompanyPicker } from '@/companies/components/CompanyPicker';
import { PeoplePicker } from '@/people/components/PeoplePicker';
-import { ViewFieldRelationValue } from '@/ui/editable-field/types/ViewField';
import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect';
import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect';
+import { UserPicker } from '@/users/components/UserPicker';
import { useEditableField } from '../hooks/useEditableField';
import { useUpdateGenericEntityField } from '../hooks/useUpdateGenericEntityField';
@@ -13,7 +14,10 @@ import { EditableFieldDefinitionContext } from '../states/EditableFieldDefinitio
import { EditableFieldEntityIdContext } from '../states/EditableFieldEntityIdContext';
import { genericEntityFieldFamilySelector } from '../states/genericEntityFieldFamilySelector';
import { FieldDefinition } from '../types/FieldDefinition';
-import { FieldRelationMetadata } from '../types/FieldMetadata';
+import {
+ FieldRelationMetadata,
+ FieldRelationValue,
+} from '../types/FieldMetadata';
const RelationPickerContainer = styled.div`
left: 0px;
@@ -28,7 +32,7 @@ function RelationPicker({
handleCancel,
}: {
fieldDefinition: FieldDefinition;
- fieldValue: ViewFieldRelationValue;
+ fieldValue: FieldRelationValue;
handleEntitySubmit: (newRelationId: EntityForSelect | null) => void;
handleCancel: () => void;
}) {
@@ -36,7 +40,25 @@ function RelationPicker({
case Entity.Person: {
return (
+ );
+ }
+ case Entity.User: {
+ return (
+
+ );
+ }
+ case Entity.Company: {
+ return (
+
@@ -46,7 +68,7 @@ function RelationPicker({
console.warn(
`Unknown relation type: "${fieldDefinition.metadata.relationType}" in GenericEditableRelationField`,
);
- return <> >;
+ return <>>;
}
}
diff --git a/front/src/modules/ui/editable-field/components/GenericEditableTextField.tsx b/front/src/modules/ui/editable-field/components/GenericEditableTextField.tsx
new file mode 100644
index 000000000..038d1484d
--- /dev/null
+++ b/front/src/modules/ui/editable-field/components/GenericEditableTextField.tsx
@@ -0,0 +1,41 @@
+import { useContext } from 'react';
+import { useRecoilValue } from 'recoil';
+
+import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
+
+import { EditableFieldDefinitionContext } from '../states/EditableFieldDefinitionContext';
+import { EditableFieldEntityIdContext } from '../states/EditableFieldEntityIdContext';
+import { FieldContext } from '../states/FieldContext';
+import { genericEntityFieldFamilySelector } from '../states/genericEntityFieldFamilySelector';
+import { FieldDefinition } from '../types/FieldDefinition';
+import { FieldNumberMetadata } from '../types/FieldMetadata';
+
+import { EditableField } from './EditableField';
+import { GenericEditableTextFieldEditMode } from './GenericEditableTextFieldEditMode';
+
+export function GenericEditableTextField() {
+ const currentEditableFieldEntityId = useContext(EditableFieldEntityIdContext);
+ const currentEditableFieldDefinition = useContext(
+ EditableFieldDefinitionContext,
+ ) as FieldDefinition;
+
+ const fieldValue = useRecoilValue(
+ genericEntityFieldFamilySelector({
+ entityId: currentEditableFieldEntityId ?? '',
+ fieldName: currentEditableFieldDefinition
+ ? currentEditableFieldDefinition.metadata.fieldName
+ : '',
+ }),
+ );
+
+ return (
+
+ }
+ displayModeContent={fieldValue}
+ isDisplayModeContentEmpty={!fieldValue}
+ />
+
+ );
+}
diff --git a/front/src/modules/ui/editable-field/components/GenericEditableTextFieldEditMode.tsx b/front/src/modules/ui/editable-field/components/GenericEditableTextFieldEditMode.tsx
new file mode 100644
index 000000000..815fb2497
--- /dev/null
+++ b/front/src/modules/ui/editable-field/components/GenericEditableTextFieldEditMode.tsx
@@ -0,0 +1,72 @@
+import { useContext, useRef, useState } from 'react';
+import { useRecoilState } from 'recoil';
+
+import { TextInputEdit } from '@/ui/input/text/components/TextInputEdit';
+
+import { useRegisterCloseFieldHandlers } from '../hooks/useRegisterCloseFieldHandlers';
+import { useUpdateGenericEntityField } from '../hooks/useUpdateGenericEntityField';
+import { EditableFieldDefinitionContext } from '../states/EditableFieldDefinitionContext';
+import { EditableFieldEntityIdContext } from '../states/EditableFieldEntityIdContext';
+import { genericEntityFieldFamilySelector } from '../states/genericEntityFieldFamilySelector';
+import { FieldDefinition } from '../types/FieldDefinition';
+import { FieldTextMetadata } from '../types/FieldMetadata';
+
+export function GenericEditableTextFieldEditMode() {
+ const currentEditableFieldEntityId = useContext(EditableFieldEntityIdContext);
+ const currentEditableFieldDefinition = useContext(
+ EditableFieldDefinitionContext,
+ ) as FieldDefinition;
+
+ // TODO: we could use a hook that would return the field value with the right type
+ const [fieldValue, setFieldValue] = useRecoilState(
+ genericEntityFieldFamilySelector({
+ entityId: currentEditableFieldEntityId ?? '',
+ fieldName: currentEditableFieldDefinition
+ ? currentEditableFieldDefinition.metadata.fieldName
+ : '',
+ }),
+ );
+
+ const [internalValue, setInternalValue] = useState(fieldValue);
+
+ const updateField = useUpdateGenericEntityField();
+
+ const wrapperRef = useRef(null);
+
+ useRegisterCloseFieldHandlers(wrapperRef, handleSubmit, onCancel);
+
+ function handleSubmit() {
+ if (internalValue === fieldValue) return;
+
+ setFieldValue(internalValue);
+
+ if (currentEditableFieldEntityId && updateField) {
+ updateField(
+ currentEditableFieldEntityId,
+ currentEditableFieldDefinition,
+ internalValue,
+ );
+ }
+ }
+
+ function onCancel() {
+ setFieldValue(fieldValue);
+ }
+
+ function handleChange(newValue: string) {
+ setInternalValue(newValue);
+ }
+
+ return (
+
+ {
+ handleChange(newValue);
+ }}
+ />
+
+ );
+}
diff --git a/front/src/modules/ui/editable-field/components/GenericEditableURLField.tsx b/front/src/modules/ui/editable-field/components/GenericEditableURLField.tsx
new file mode 100644
index 000000000..322290d8f
--- /dev/null
+++ b/front/src/modules/ui/editable-field/components/GenericEditableURLField.tsx
@@ -0,0 +1,43 @@
+import { useContext } from 'react';
+import { useRecoilValue } from 'recoil';
+
+import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
+
+import { EditableFieldDefinitionContext } from '../states/EditableFieldDefinitionContext';
+import { EditableFieldEntityIdContext } from '../states/EditableFieldEntityIdContext';
+import { FieldContext } from '../states/FieldContext';
+import { genericEntityFieldFamilySelector } from '../states/genericEntityFieldFamilySelector';
+import { FieldDefinition } from '../types/FieldDefinition';
+import { FieldNumberMetadata } from '../types/FieldMetadata';
+
+import { EditableField } from './EditableField';
+import { FieldDisplayURL } from './FieldDisplayURL';
+import { GenericEditableURLFieldEditMode } from './GenericEditableURLFieldEditMode';
+
+export function GenericEditableURLField() {
+ const currentEditableFieldEntityId = useContext(EditableFieldEntityIdContext);
+ const currentEditableFieldDefinition = useContext(
+ EditableFieldDefinitionContext,
+ ) as FieldDefinition;
+
+ const fieldValue = useRecoilValue(
+ genericEntityFieldFamilySelector({
+ entityId: currentEditableFieldEntityId ?? '',
+ fieldName: currentEditableFieldDefinition
+ ? currentEditableFieldDefinition.metadata.fieldName
+ : '',
+ }),
+ );
+
+ return (
+
+ }
+ displayModeContent={}
+ isDisplayModeContentEmpty={!fieldValue}
+ />
+
+ );
+}
diff --git a/front/src/modules/ui/editable-field/components/GenericEditableURLFieldEditMode.tsx b/front/src/modules/ui/editable-field/components/GenericEditableURLFieldEditMode.tsx
new file mode 100644
index 000000000..a0e3b229f
--- /dev/null
+++ b/front/src/modules/ui/editable-field/components/GenericEditableURLFieldEditMode.tsx
@@ -0,0 +1,74 @@
+import { useContext, useRef, useState } from 'react';
+import { useRecoilState } from 'recoil';
+
+import { TextInputEdit } from '@/ui/input/text/components/TextInputEdit';
+
+import { useRegisterCloseFieldHandlers } from '../hooks/useRegisterCloseFieldHandlers';
+import { useUpdateGenericEntityField } from '../hooks/useUpdateGenericEntityField';
+import { EditableFieldDefinitionContext } from '../states/EditableFieldDefinitionContext';
+import { EditableFieldEntityIdContext } from '../states/EditableFieldEntityIdContext';
+import { genericEntityFieldFamilySelector } from '../states/genericEntityFieldFamilySelector';
+import { FieldDefinition } from '../types/FieldDefinition';
+import { FieldURLMetadata } from '../types/FieldMetadata';
+
+// This one is very similar to GenericEditableTextFieldEditMode
+// We could probably merge them since FieldURLMetadata is basically a FieldTextMetadata
+export function GenericEditableURLFieldEditMode() {
+ const currentEditableFieldEntityId = useContext(EditableFieldEntityIdContext);
+ const currentEditableFieldDefinition = useContext(
+ EditableFieldDefinitionContext,
+ ) as FieldDefinition;
+
+ // TODO: we could use a hook that would return the field value with the right type
+ const [fieldValue, setFieldValue] = useRecoilState(
+ genericEntityFieldFamilySelector({
+ entityId: currentEditableFieldEntityId ?? '',
+ fieldName: currentEditableFieldDefinition
+ ? currentEditableFieldDefinition.metadata.fieldName
+ : '',
+ }),
+ );
+
+ const [internalValue, setInternalValue] = useState(fieldValue);
+
+ const updateField = useUpdateGenericEntityField();
+
+ const wrapperRef = useRef(null);
+
+ useRegisterCloseFieldHandlers(wrapperRef, handleSubmit, onCancel);
+
+ function handleSubmit() {
+ if (internalValue === fieldValue) return;
+
+ setFieldValue(internalValue);
+
+ if (currentEditableFieldEntityId && updateField) {
+ updateField(
+ currentEditableFieldEntityId,
+ currentEditableFieldDefinition,
+ internalValue,
+ );
+ }
+ }
+
+ function onCancel() {
+ setFieldValue(fieldValue);
+ }
+
+ function handleChange(newValue: string) {
+ setInternalValue(newValue);
+ }
+
+ return (
+
+ {
+ handleChange(newValue);
+ }}
+ />
+
+ );
+}
diff --git a/front/src/modules/ui/editable-field/hooks/useBindFieldHotkeyScope.ts b/front/src/modules/ui/editable-field/hooks/useBindFieldHotkeyScope.ts
deleted file mode 100644
index 2552fa7ba..000000000
--- a/front/src/modules/ui/editable-field/hooks/useBindFieldHotkeyScope.ts
+++ /dev/null
@@ -1,75 +0,0 @@
-import { useEffect } from 'react';
-import { useRecoilCallback } from 'recoil';
-
-import { currentHotkeyScopeState } from '@/ui/utilities/hotkey/states/internal/currentHotkeyScopeState';
-import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
-import { isSameHotkeyScope } from '@/ui/utilities/hotkey/utils/isSameHotkeyScope';
-import { useContextScopeId } from '@/ui/utilities/recoil-scope/hooks/useContextScopeId';
-import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
-import { getSnapshotScopedState } from '@/ui/utilities/recoil-scope/utils/getSnapshotScopedState';
-import { getSnapshotState } from '@/ui/utilities/recoil-scope/utils/getSnapshotState';
-
-import { customEditHotkeyScopeForFieldScopedState } from '../states/customEditHotkeyScopeForFieldScopedState';
-import { FieldContext } from '../states/FieldContext';
-import { parentHotkeyScopeForFieldScopedState } from '../states/parentHotkeyScopeForFieldScopedState';
-
-export function useBindFieldHotkeyScope({
- customEditHotkeyScope,
- parentHotkeyScope,
-}: {
- customEditHotkeyScope?: HotkeyScope;
- parentHotkeyScope?: HotkeyScope;
-}) {
- const [customEditHotkeyScopeForField, setCustomEditHotkeyScopeForField] =
- useRecoilScopedState(
- customEditHotkeyScopeForFieldScopedState,
- FieldContext,
- );
-
- const fieldContextScopeId = useContextScopeId(FieldContext);
-
- useEffect(() => {
- if (
- customEditHotkeyScope &&
- !isSameHotkeyScope(customEditHotkeyScope, customEditHotkeyScopeForField)
- ) {
- setCustomEditHotkeyScopeForField(customEditHotkeyScope);
- }
- }, [
- customEditHotkeyScope,
- customEditHotkeyScopeForField,
- setCustomEditHotkeyScopeForField,
- ]);
-
- const setParentHotkeyScopeForField = useRecoilCallback(
- ({ snapshot, set }) =>
- (parentHotkeyScopeToSet: HotkeyScope | null | undefined) => {
- const currentHotkeyScope = getSnapshotState({
- snapshot,
- state: currentHotkeyScopeState,
- });
-
- const parentHotkeyScopeForField = getSnapshotScopedState({
- snapshot,
- state: parentHotkeyScopeForFieldScopedState,
- contextScopeId: fieldContextScopeId,
- });
-
- if (!parentHotkeyScopeToSet) {
- set(
- parentHotkeyScopeForFieldScopedState(fieldContextScopeId),
- currentHotkeyScope,
- );
- } else if (
- !isSameHotkeyScope(parentHotkeyScopeToSet, parentHotkeyScopeForField)
- ) {
- setParentHotkeyScopeForField(parentHotkeyScopeToSet);
- }
- },
- [fieldContextScopeId],
- );
-
- useEffect(() => {
- setParentHotkeyScopeForField(parentHotkeyScope);
- }, [parentHotkeyScope, setParentHotkeyScopeForField]);
-}
diff --git a/front/src/modules/ui/editable-field/hooks/useEditableField.ts b/front/src/modules/ui/editable-field/hooks/useEditableField.ts
index b676c1b43..4db05c696 100644
--- a/front/src/modules/ui/editable-field/hooks/useEditableField.ts
+++ b/front/src/modules/ui/editable-field/hooks/useEditableField.ts
@@ -1,10 +1,9 @@
-import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
+import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
+import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
-import { customEditHotkeyScopeForFieldScopedState } from '../states/customEditHotkeyScopeForFieldScopedState';
import { FieldContext } from '../states/FieldContext';
import { isFieldInEditModeScopedState } from '../states/isFieldInEditModeScopedState';
-import { parentHotkeyScopeForFieldScopedState } from '../states/parentHotkeyScopeForFieldScopedState';
import { EditableFieldHotkeyScope } from '../types/EditableFieldHotkeyScope';
export function useEditableField() {
@@ -13,39 +12,29 @@ export function useEditableField() {
FieldContext,
);
- const [customEditHotkeyScopeForField] = useRecoilScopedState(
- customEditHotkeyScopeForFieldScopedState,
- FieldContext,
- );
-
- const [parentHotkeyScopeForField] = useRecoilScopedState(
- parentHotkeyScopeForFieldScopedState,
- FieldContext,
- );
-
- const setHotkeyScope = useSetHotkeyScope();
+ const {
+ setHotkeyScopeAndMemorizePreviousScope,
+ goBackToPreviousHotkeyScope,
+ } = usePreviousHotkeyScope();
function closeEditableField() {
setIsFieldInEditMode(false);
- if (parentHotkeyScopeForField) {
- setHotkeyScope(
- parentHotkeyScopeForField.scope,
- parentHotkeyScopeForField.customScopes,
- );
- }
+ goBackToPreviousHotkeyScope();
}
- function openEditableField() {
+ function openEditableField(customEditHotkeyScopeForField?: HotkeyScope) {
setIsFieldInEditMode(true);
if (customEditHotkeyScopeForField) {
- setHotkeyScope(
+ setHotkeyScopeAndMemorizePreviousScope(
customEditHotkeyScopeForField.scope,
customEditHotkeyScopeForField.customScopes,
);
} else {
- setHotkeyScope(EditableFieldHotkeyScope.EditableField);
+ setHotkeyScopeAndMemorizePreviousScope(
+ EditableFieldHotkeyScope.EditableField,
+ );
}
}
diff --git a/front/src/modules/ui/editable-field/hooks/useUpdateGenericEntityField.ts b/front/src/modules/ui/editable-field/hooks/useUpdateGenericEntityField.ts
index 545bc86d7..1e6b2e1aa 100644
--- a/front/src/modules/ui/editable-field/hooks/useUpdateGenericEntityField.ts
+++ b/front/src/modules/ui/editable-field/hooks/useUpdateGenericEntityField.ts
@@ -1,7 +1,5 @@
import { useContext } from 'react';
-import { isFieldChip } from '@/ui/editable-field/types/guards/isFieldChip';
-
import { EditableFieldMutationContext } from '../states/EditableFieldMutationContext';
import { FieldDefinition } from '../types/FieldDefinition';
import {
@@ -27,6 +25,7 @@ import {
FieldURLMetadata,
FieldURLValue,
} from '../types/FieldMetadata';
+import { isFieldChip } from '../types/guards/isFieldChip';
import { isFieldChipValue } from '../types/guards/isFieldChipValue';
import { isFieldDate } from '../types/guards/isFieldDate';
import { isFieldDateValue } from '../types/guards/isFieldDateValue';
@@ -53,31 +52,30 @@ export function useUpdateGenericEntityField() {
const [updateEntity] = useUpdateEntityMutation();
return function updateEntityField<
- MetadataType extends FieldMetadata,
- ValueType extends MetadataType extends FieldDoubleTextMetadata
+ ValueType extends FieldMetadata extends FieldDoubleTextMetadata
? FieldDoubleTextValue
- : MetadataType extends FieldTextMetadata
+ : FieldMetadata extends FieldTextMetadata
? FieldTextValue
- : MetadataType extends FieldPhoneMetadata
+ : FieldMetadata extends FieldPhoneMetadata
? FieldPhoneValue
- : MetadataType extends FieldURLMetadata
+ : FieldMetadata extends FieldURLMetadata
? FieldURLValue
- : MetadataType extends FieldNumberMetadata
+ : FieldMetadata extends FieldNumberMetadata
? FieldNumberValue
- : MetadataType extends FieldDateMetadata
+ : FieldMetadata extends FieldDateMetadata
? FieldDateValue
- : MetadataType extends FieldChipMetadata
+ : FieldMetadata extends FieldChipMetadata
? FieldChipValue
- : MetadataType extends FieldDoubleTextChipMetadata
+ : FieldMetadata extends FieldDoubleTextChipMetadata
? FieldDoubleTextChipValue
- : MetadataType extends FieldRelationMetadata
+ : FieldMetadata extends FieldRelationMetadata
? FieldRelationValue
- : MetadataType extends FieldProbabilityMetadata
+ : FieldMetadata extends FieldProbabilityMetadata
? FieldProbabilityValue
: unknown,
>(
currentEntityId: string,
- field: FieldDefinition,
+ field: FieldDefinition,
newFieldValue: ValueType,
) {
const newFieldValueUnknown = newFieldValue as unknown;
diff --git a/front/src/modules/ui/editable-field/property-box/components/PropertyBoxItem.tsx b/front/src/modules/ui/editable-field/property-box/components/PropertyBoxItem.tsx
deleted file mode 100644
index 13cc1ce13..000000000
--- a/front/src/modules/ui/editable-field/property-box/components/PropertyBoxItem.tsx
+++ /dev/null
@@ -1,58 +0,0 @@
-import { ReactNode } from 'react';
-import styled from '@emotion/styled';
-
-const StyledPropertyBoxItem = styled.div`
- align-items: center;
- display: flex;
- gap: ${({ theme }) => theme.spacing(2)};
- padding: ${({ theme }) => theme.spacing(1)};
-`;
-
-const StyledIconContainer = styled.div`
- align-items: center;
- display: flex;
-
- svg {
- align-items: center;
- display: flex;
- height: 16px;
- justify-content: center;
- width: 16px;
- }
-`;
-
-const StyledValueContainer = styled.div`
- align-content: flex-start;
- align-items: center;
- color: ${({ theme }) => theme.font.color.primary};
- display: flex;
- flex: 1 0 0;
- flex-wrap: wrap;
-`;
-
-const StyledLabelAndIconContainer = styled.div`
- align-items: center;
- color: ${({ theme }) => theme.font.color.tertiary};
- display: flex;
- gap: ${({ theme }) => theme.spacing(1)};
-`;
-
-export function PropertyBoxItem({
- icon,
- label,
- value,
-}: {
- icon: ReactNode;
- label?: string;
- value: ReactNode;
-}) {
- return (
-
-
- {icon}
- {label}
-
- {value}
-
- );
-}
diff --git a/front/src/modules/ui/editable-field/states/EditableFieldDefinitionContext.ts b/front/src/modules/ui/editable-field/states/EditableFieldDefinitionContext.ts
index 2ce04ca7b..6a804aeb7 100644
--- a/front/src/modules/ui/editable-field/states/EditableFieldDefinitionContext.ts
+++ b/front/src/modules/ui/editable-field/states/EditableFieldDefinitionContext.ts
@@ -1,8 +1,8 @@
import { createContext } from 'react';
import { FieldDefinition } from '../types/FieldDefinition';
-import { ViewFieldMetadata } from '../types/ViewField';
+import { FieldMetadata } from '../types/FieldMetadata';
export const EditableFieldDefinitionContext = createContext<
- FieldDefinition
->({} as FieldDefinition);
+ FieldDefinition
+>({} as FieldDefinition);
diff --git a/front/src/modules/ui/editable-field/types/FieldDefinition.ts b/front/src/modules/ui/editable-field/types/FieldDefinition.ts
index 0cb858308..41cf30e36 100644
--- a/front/src/modules/ui/editable-field/types/FieldDefinition.ts
+++ b/front/src/modules/ui/editable-field/types/FieldDefinition.ts
@@ -1,9 +1,9 @@
-import { FieldMetadata } from './FieldMetadata';
+import { FieldMetadata, FieldType } from './FieldMetadata';
export type FieldDefinition = {
id: string;
label: string;
icon?: JSX.Element;
- type: string;
+ type: FieldType;
metadata: T;
};
diff --git a/front/src/modules/ui/editable-field/types/FieldMetadata.ts b/front/src/modules/ui/editable-field/types/FieldMetadata.ts
index 0b2e598b3..b7afa92f9 100644
--- a/front/src/modules/ui/editable-field/types/FieldMetadata.ts
+++ b/front/src/modules/ui/editable-field/types/FieldMetadata.ts
@@ -2,6 +2,7 @@ import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelec
import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect';
export type FieldType =
+ | 'unknown'
| 'text'
| 'relation'
| 'chip'
@@ -14,41 +15,36 @@ export type FieldType =
| 'probability';
export type FieldTextMetadata = {
- type: 'text';
placeHolder: string;
fieldName: string;
};
export type FieldPhoneMetadata = {
- type: 'phone';
placeHolder: string;
fieldName: string;
};
export type FieldURLMetadata = {
- type: 'url';
placeHolder: string;
fieldName: string;
};
export type FieldDateMetadata = {
- type: 'date';
fieldName: string;
};
export type FieldNumberMetadata = {
- type: 'number';
fieldName: string;
+ placeHolder: string;
};
export type FieldRelationMetadata = {
- type: 'relation';
relationType: Entity;
fieldName: string;
+ useEditButton?: boolean;
};
export type FieldChipMetadata = {
- type: 'chip';
relationType: Entity;
contentFieldName: string;
urlFieldName: string;
@@ -56,7 +52,6 @@ export type FieldChipMetadata = {
};
export type FieldDoubleTextMetadata = {
- type: 'double-text';
firstValueFieldName: string;
firstValuePlaceholder: string;
secondValueFieldName: string;
@@ -64,7 +59,6 @@ export type FieldDoubleTextMetadata = {
};
export type FieldDoubleTextChipMetadata = {
- type: 'double-text-chip';
firstValueFieldName: string;
firstValuePlaceholder: string;
secondValueFieldName: string;
@@ -74,11 +68,10 @@ export type FieldDoubleTextChipMetadata = {
};
export type FieldProbabilityMetadata = {
- type: 'probability';
fieldName: string;
};
-export type FieldMetadata = { type: FieldType } & (
+export type FieldMetadata =
| FieldTextMetadata
| FieldRelationMetadata
| FieldChipMetadata
@@ -88,8 +81,7 @@ export type FieldMetadata = { type: FieldType } & (
| FieldURLMetadata
| FieldNumberMetadata
| FieldDateMetadata
- | FieldProbabilityMetadata
-);
+ | FieldProbabilityMetadata;
export type FieldTextValue = string;
diff --git a/front/src/modules/ui/editable-field/types/ViewField.ts b/front/src/modules/ui/editable-field/types/ViewField.ts
index 24b04bdce..f1386e15c 100644
--- a/front/src/modules/ui/editable-field/types/ViewField.ts
+++ b/front/src/modules/ui/editable-field/types/ViewField.ts
@@ -46,6 +46,7 @@ export type ViewFieldRelationMetadata = {
type: 'relation';
relationType: Entity;
fieldName: string;
+ useEditButton?: boolean;
};
export type ViewFieldChipMetadata = {
diff --git a/front/src/modules/ui/editable-field/variants/components/TextEditableField.tsx b/front/src/modules/ui/editable-field/variants/components/TextEditableField.tsx
deleted file mode 100644
index 7bc3f2ecb..000000000
--- a/front/src/modules/ui/editable-field/variants/components/TextEditableField.tsx
+++ /dev/null
@@ -1,64 +0,0 @@
-import { useEffect, useState } from 'react';
-
-import { EditableField } from '@/ui/editable-field/components/EditableField';
-import { FieldContext } from '@/ui/editable-field/states/FieldContext';
-import { TextInputEdit } from '@/ui/input/text/components/TextInputEdit';
-import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
-
-import { OverflowingTextWithTooltip } from '../../../tooltip/OverflowingTextWithTooltip';
-
-type OwnProps = {
- icon?: React.ReactNode;
- placeholder?: string;
- value: string | null | undefined;
- onSubmit?: (newValue: string) => void;
-};
-
-export function TextEditableField({
- icon,
- placeholder,
- value,
- onSubmit,
-}: OwnProps) {
- const [internalValue, setInternalValue] = useState(value);
-
- useEffect(() => {
- setInternalValue(value);
- }, [value]);
-
- async function handleChange(newValue: string) {
- setInternalValue(newValue);
- }
-
- async function handleSubmit() {
- if (!internalValue) return;
-
- onSubmit?.(internalValue);
- }
-
- async function handleCancel() {
- setInternalValue(value);
- }
-
- return (
-
- {
- handleChange(newValue);
- }}
- />
- }
- displayModeContent={}
- isDisplayModeContentEmpty={!(internalValue !== '')}
- />
-
- );
-}
diff --git a/front/src/modules/ui/editable-field/variants/components/__stories__/TextEditableField.stories.tsx b/front/src/modules/ui/editable-field/variants/components/__stories__/TextEditableField.stories.tsx
deleted file mode 100644
index fbffa6855..000000000
--- a/front/src/modules/ui/editable-field/variants/components/__stories__/TextEditableField.stories.tsx
+++ /dev/null
@@ -1,31 +0,0 @@
-import type { Meta, StoryObj } from '@storybook/react';
-import { IconUser } from '@tabler/icons-react';
-
-import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
-
-import { TextEditableField } from '../TextEditableField';
-
-const meta: Meta = {
- title: 'UI/EditableField/TextEditableField',
- component: TextEditableField,
- decorators: [ComponentDecorator],
- argTypes: {
- icon: {
- type: 'boolean',
- mapping: {
- true: ,
- false: undefined,
- },
- },
- },
- args: {
- value: 'John Doe',
- icon: true,
- placeholder: 'Name',
- },
-};
-
-export default meta;
-type Story = StoryObj;
-
-export const Default: Story = {};
diff --git a/front/src/modules/ui/filter-n-sort/components/DropdownMenuContainer.tsx b/front/src/modules/ui/filter-n-sort/components/DropdownMenuContainer.tsx
index 430c73959..b1f01e1f6 100644
--- a/front/src/modules/ui/filter-n-sort/components/DropdownMenuContainer.tsx
+++ b/front/src/modules/ui/filter-n-sort/components/DropdownMenuContainer.tsx
@@ -28,7 +28,7 @@ export function DropdownMenuContainer({
});
return (
-
+
{children}
);
diff --git a/front/src/modules/ui/input/relation-picker/types/EntityTypeForSelect.ts b/front/src/modules/ui/input/relation-picker/types/EntityTypeForSelect.ts
index e27c21f6f..80d845d6b 100644
--- a/front/src/modules/ui/input/relation-picker/types/EntityTypeForSelect.ts
+++ b/front/src/modules/ui/input/relation-picker/types/EntityTypeForSelect.ts
@@ -1,4 +1,5 @@
-import { CommentableType, PipelineProgressableType } from '~/generated/graphql';
+import { ActivityTargetableEntityType } from '@/activities/types/ActivityTargetableEntity';
+import { PipelineProgressableType } from '~/generated/graphql';
export enum Entity {
Company = 'Company',
@@ -7,6 +8,6 @@ export enum Entity {
}
export type EntityTypeForSelect =
- | CommentableType
+ | ActivityTargetableEntityType
| PipelineProgressableType
| Entity;
diff --git a/front/src/modules/ui/link/components/SocialLink.tsx b/front/src/modules/ui/link/components/SocialLink.tsx
index dd4acb392..04603b314 100644
--- a/front/src/modules/ui/link/components/SocialLink.tsx
+++ b/front/src/modules/ui/link/components/SocialLink.tsx
@@ -30,16 +30,25 @@ export function SocialLink({ children, href, onClick, type }: OwnProps) {
let displayValue = children;
if (type === 'linkedin') {
- const splitUrl = href.split('/');
- const splitName = splitUrl[4].split('-');
- displayValue = splitName[2]
- ? `${splitName[0]}-${splitName[1]}`
- : splitName[0];
+ const matches = href.match(
+ /(?:https?:\/\/)?(?:www.)?linkedin.com\/(?:in|company)\/([-a-zA-Z0-9@:%_+.~#?&//=]*)/,
+ );
+ if (matches && matches[1]) {
+ displayValue = matches[1];
+ } else {
+ displayValue = 'LinkedIn';
+ }
}
if (type === 'twitter') {
- const splitUrl = href.split('/');
- displayValue = `@${splitUrl[3]}`;
+ const matches = href.match(
+ /(?:https?:\/\/)?(?:www.)?twitter.com\/([-a-zA-Z0-9@:%_+.~#?&//=]*)/,
+ );
+ if (matches && matches[1]) {
+ displayValue = `@${matches[1]}`;
+ } else {
+ displayValue = '@twitter';
+ }
}
return (
diff --git a/front/src/modules/ui/table/components/EntityTable.tsx b/front/src/modules/ui/table/components/EntityTable.tsx
index d4e4d3af3..d3a07d393 100644
--- a/front/src/modules/ui/table/components/EntityTable.tsx
+++ b/front/src/modules/ui/table/components/EntityTable.tsx
@@ -1,16 +1,19 @@
import { useRef } from 'react';
import styled from '@emotion/styled';
-import { useRecoilValue } from 'recoil';
+import type {
+ ViewFieldDefinition,
+ ViewFieldMetadata,
+} from '@/ui/editable-field/types/ViewField';
import { SelectedSortType, SortType } from '@/ui/filter-n-sort/types/interface';
import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect';
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { useLeaveTableFocus } from '../hooks/useLeaveTableFocus';
import { useMapKeyboardToSoftFocus } from '../hooks/useMapKeyboardToSoftFocus';
+import { useResetTableRowSelection } from '../hooks/useResetTableRowSelection';
import { useSetRowSelectedState } from '../hooks/useSetRowSelectedState';
-import { EntityUpdateMutationHookContext } from '../states/EntityUpdateMutationHookContext';
-import { tableRowIdsState } from '../states/tableRowIdsState';
+import { EntityUpdateMutationContext } from '../states/EntityUpdateMutationHookContext';
import { TableHeader } from '../table-header/components/TableHeader';
import { EntityTableBody } from './EntityTableBody';
@@ -25,6 +28,8 @@ const StyledTable = styled.table`
margin-right: ${({ theme }) => theme.table.horizontalCellMargin};
table-layout: fixed;
+ width: calc(100% - ${({ theme }) => theme.table.horizontalCellMargin} * 2);
+
th {
border: 1px solid ${({ theme }) => theme.border.color.light};
border-collapse: collapse;
@@ -90,28 +95,24 @@ type OwnProps = {
viewName: string;
viewIcon?: React.ReactNode;
availableSorts?: Array>;
+ onColumnsChange?: (columns: ViewFieldDefinition[]) => void;
onSortsUpdate?: (sorts: Array>) => void;
onRowSelectionChange?: (rowSelection: string[]) => void;
- useUpdateEntityMutation: any;
+ updateEntityMutation: any;
};
export function EntityTable({
viewName,
viewIcon,
availableSorts,
+ onColumnsChange,
onSortsUpdate,
- useUpdateEntityMutation,
+ updateEntityMutation,
}: OwnProps) {
const tableBodyRef = useRef(null);
- const rowIds = useRecoilValue(tableRowIdsState);
const setRowSelectedState = useSetRowSelectedState();
-
- function resetSelections() {
- for (const rowId of rowIds) {
- setRowSelectedState(rowId, false);
- }
- }
+ const resetTableRowSelection = useResetTableRowSelection();
useMapKeyboardToSoftFocus();
@@ -125,28 +126,29 @@ export function EntityTable({
});
return (
-
+
-
+
-
+
);
}
diff --git a/front/src/modules/ui/table/components/EntityTableColumnMenu.tsx b/front/src/modules/ui/table/components/EntityTableColumnMenu.tsx
index 3afc80d99..770789b65 100644
--- a/front/src/modules/ui/table/components/EntityTableColumnMenu.tsx
+++ b/front/src/modules/ui/table/components/EntityTableColumnMenu.tsx
@@ -1,6 +1,7 @@
import { cloneElement, ComponentProps, useRef } from 'react';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
+import { useRecoilValue } from 'recoil';
import { IconButton } from '@/ui/button/components/IconButton';
import { DropdownMenu } from '@/ui/dropdown/components/DropdownMenu';
@@ -9,32 +10,27 @@ import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMen
import { IconPlus } from '@/ui/icon';
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
-import type {
- ViewFieldDefinition,
- ViewFieldMetadata,
-} from '../../editable-field/types/ViewField';
+import { hiddenTableColumnsState } from '../states/tableColumnsState';
const StyledColumnMenu = styled(DropdownMenu)`
font-weight: ${({ theme }) => theme.font.weight.regular};
`;
type EntityTableColumnMenuProps = {
- onAddViewField: (
- viewFieldDefinition: ViewFieldDefinition,
- ) => void;
+ onAddColumn: (columnId: string) => void;
onClickOutside?: () => void;
- viewFieldDefinitions: ViewFieldDefinition[];
} & ComponentProps<'div'>;
export const EntityTableColumnMenu = ({
- onAddViewField,
+ onAddColumn,
onClickOutside = () => undefined,
- viewFieldDefinitions,
...props
}: EntityTableColumnMenuProps) => {
const ref = useRef(null);
const theme = useTheme();
+ const hiddenColumns = useRecoilValue(hiddenTableColumnsState);
+
useListenClickOutside({
refs: [ref],
callback: onClickOutside,
@@ -43,21 +39,21 @@ export const EntityTableColumnMenu = ({
return (
- {viewFieldDefinitions.map((viewFieldDefinition) => (
+ {hiddenColumns.map((column) => (
}
- onClick={() => onAddViewField(viewFieldDefinition)}
+ onClick={() => onAddColumn(column.id)}
/>
}
>
- {viewFieldDefinition.columnIcon &&
- cloneElement(viewFieldDefinition.columnIcon, {
+ {column.columnIcon &&
+ cloneElement(column.columnIcon, {
size: theme.icon.size.md,
})}
- {viewFieldDefinition.columnLabel}
+ {column.columnLabel}
))}
diff --git a/front/src/modules/ui/table/components/EntityTableHeader.tsx b/front/src/modules/ui/table/components/EntityTableHeader.tsx
index e999449cc..89b4db82f 100644
--- a/front/src/modules/ui/table/components/EntityTableHeader.tsx
+++ b/front/src/modules/ui/table/components/EntityTableHeader.tsx
@@ -1,5 +1,4 @@
import { useCallback, useState } from 'react';
-import { getOperationName } from '@apollo/client/utilities';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { useRecoilCallback, useRecoilState, useRecoilValue } from 'recoil';
@@ -11,21 +10,14 @@ import type {
} from '@/ui/editable-field/types/ViewField';
import { IconPlus } from '@/ui/icon';
import { useTrackPointer } from '@/ui/utilities/pointer-event/hooks/useTrackPointer';
-import { GET_VIEW_FIELDS } from '@/views/queries/select';
-import { currentViewIdState } from '@/views/states/currentViewIdState';
-import {
- useCreateViewFieldMutation,
- useUpdateViewFieldMutation,
-} from '~/generated/graphql';
-import { toViewFieldInput } from '../hooks/useLoadViewFields';
import { resizeFieldOffsetState } from '../states/resizeFieldOffsetState';
import {
- addableViewFieldDefinitionsState,
- columnWidthByViewFieldIdState,
- viewFieldsState,
- visibleViewFieldsState,
-} from '../states/viewFieldsState';
+ hiddenTableColumnsState,
+ tableColumnsByIdState,
+ tableColumnsState,
+ visibleTableColumnsState,
+} from '../states/tableColumnsState';
import { ColumnHead } from './ColumnHead';
import { EntityTableColumnMenu } from './EntityTableColumnMenu';
@@ -86,17 +78,18 @@ const StyledEntityTableColumnMenu = styled(EntityTableColumnMenu)`
z-index: ${({ theme }) => theme.lastLayerZIndex};
`;
-export function EntityTableHeader() {
+export type EntityTableHeaderProps = {
+ onColumnsChange?: (columns: ViewFieldDefinition[]) => void;
+};
+
+export function EntityTableHeader({ onColumnsChange }: EntityTableHeaderProps) {
const theme = useTheme();
- const [{ objectName }, setViewFieldsState] = useRecoilState(viewFieldsState);
- const currentViewId = useRecoilValue(currentViewIdState);
- const viewFields = useRecoilValue(visibleViewFieldsState);
- const columnWidths = useRecoilValue(columnWidthByViewFieldIdState);
- const addableViewFieldDefinitions = useRecoilValue(
- addableViewFieldDefinitionsState,
- );
+ const [columns, setColumns] = useRecoilState(tableColumnsState);
const [offset, setOffset] = useRecoilState(resizeFieldOffsetState);
+ const columnsById = useRecoilValue(tableColumnsByIdState);
+ const hiddenColumns = useRecoilValue(hiddenTableColumnsState);
+ const visibleColumns = useRecoilValue(visibleTableColumnsState);
const [initialPointerPositionX, setInitialPointerPositionX] = useState<
number | null
@@ -104,9 +97,6 @@ export function EntityTableHeader() {
const [resizedFieldId, setResizedFieldId] = useState(null);
const [isColumnMenuOpen, setIsColumnMenuOpen] = useState(false);
- const [createViewFieldMutation] = useCreateViewFieldMutation();
- const [updateViewFieldMutation] = useUpdateViewFieldMutation();
-
const handleResizeHandlerStart = useCallback((positionX: number) => {
setInitialPointerPositionX(positionX);
}, []);
@@ -126,37 +116,28 @@ export function EntityTableHeader() {
const nextWidth = Math.round(
Math.max(
- columnWidths[resizedFieldId] +
+ columnsById[resizedFieldId].columnSize +
snapshot.getLoadable(resizeFieldOffsetState).valueOrThrow(),
COLUMN_MIN_WIDTH,
),
);
- if (nextWidth !== columnWidths[resizedFieldId]) {
- // Optimistic update to avoid "bouncing width" visual effect on resize.
- setViewFieldsState((previousState) => ({
- ...previousState,
- viewFields: previousState.viewFields.map((viewField) =>
- viewField.id === resizedFieldId
- ? { ...viewField, columnSize: nextWidth }
- : viewField,
- ),
- }));
+ if (nextWidth !== columnsById[resizedFieldId].columnSize) {
+ const nextColumns = columns.map((column) =>
+ column.id === resizedFieldId
+ ? { ...column, columnSize: nextWidth }
+ : column,
+ );
- updateViewFieldMutation({
- variables: {
- data: { sizeInPx: nextWidth },
- where: { id: resizedFieldId },
- },
- refetchQueries: [getOperationName(GET_VIEW_FIELDS) ?? ''],
- });
+ setColumns(nextColumns);
+ onColumnsChange?.(nextColumns);
}
set(resizeFieldOffsetState, 0);
setInitialPointerPositionX(null);
setResizedFieldId(null);
},
- [resizedFieldId, columnWidths, setResizedFieldId],
+ [resizedFieldId, columnsById, setResizedFieldId],
);
useTrackPointer({
@@ -170,26 +151,18 @@ export function EntityTableHeader() {
setIsColumnMenuOpen((previousValue) => !previousValue);
}, []);
- const handleAddViewField = useCallback(
- (viewFieldDefinition: ViewFieldDefinition) => {
+ const handleAddColumn = useCallback(
+ (columnId: string) => {
setIsColumnMenuOpen(false);
- if (!objectName) return;
+ const nextColumns = columns.map((column) =>
+ column.id === columnId ? { ...column, isVisible: true } : column,
+ );
- createViewFieldMutation({
- variables: {
- data: {
- ...toViewFieldInput(objectName, {
- ...viewFieldDefinition,
- columnOrder: viewFields.length + 1,
- }),
- view: { connect: { id: currentViewId } },
- },
- },
- refetchQueries: [getOperationName(GET_VIEW_FIELDS) ?? ''],
- });
+ setColumns(nextColumns);
+ onColumnsChange?.(nextColumns);
},
- [createViewFieldMutation, currentViewId, objectName, viewFields.length],
+ [columns, onColumnsChange, setColumns],
);
return (
@@ -205,31 +178,31 @@ export function EntityTableHeader() {
- {viewFields.map((viewField) => (
+ {visibleColumns.map((column) => (
{
- setResizedFieldId(viewField.id);
+ setResizedFieldId(column.id);
}}
/>
))}
- {addableViewFieldDefinitions.length > 0 && (
+ {hiddenColumns.length > 0 && (
{isColumnMenuOpen && (
)}
diff --git a/front/src/modules/ui/table/components/EntityTableRow.tsx b/front/src/modules/ui/table/components/EntityTableRow.tsx
index c39b7f190..b71306a9f 100644
--- a/front/src/modules/ui/table/components/EntityTableRow.tsx
+++ b/front/src/modules/ui/table/components/EntityTableRow.tsx
@@ -1,8 +1,8 @@
import styled from '@emotion/styled';
import { useRecoilValue } from 'recoil';
+import { visibleTableColumnsState } from '../states/tableColumnsState';
import { ViewFieldContext } from '../states/ViewFieldContext';
-import { visibleViewFieldsState } from '../states/viewFieldsState';
import { CheckboxCell } from './CheckboxCell';
import { EntityTableCell } from './EntityTableCell';
@@ -13,7 +13,7 @@ const StyledRow = styled.tr<{ selected: boolean }>`
`;
export function EntityTableRow({ rowId }: { rowId: string }) {
- const viewFields = useRecoilValue(visibleViewFieldsState);
+ const columns = useRecoilValue(visibleTableColumnsState);
return (
- {viewFields.map((viewField, columnIndex) => {
+ {columns.map((column, columnIndex) => {
return (
-
+
);
diff --git a/front/src/modules/ui/table/components/GenericEntityTableData.tsx b/front/src/modules/ui/table/components/GenericEntityTableData.tsx
index f28457147..58aae7899 100644
--- a/front/src/modules/ui/table/components/GenericEntityTableData.tsx
+++ b/front/src/modules/ui/table/components/GenericEntityTableData.tsx
@@ -1,34 +1,21 @@
import { defaultOrderBy } from '@/people/queries';
-import {
- ViewFieldDefinition,
- ViewFieldMetadata,
-} from '@/ui/editable-field/types/ViewField';
import { FilterDefinition } from '@/ui/filter-n-sort/types/FilterDefinition';
import { useSetEntityTableData } from '@/ui/table/hooks/useSetEntityTableData';
-import { useLoadViewFields } from '../hooks/useLoadViewFields';
-
export function GenericEntityTableData({
- objectName,
useGetRequest,
getRequestResultKey,
orderBy = defaultOrderBy,
whereFilters,
- viewFieldDefinitions,
filterDefinitionArray,
}: {
- objectName: 'company' | 'person';
useGetRequest: any;
getRequestResultKey: string;
orderBy?: any;
whereFilters?: any;
- viewFieldDefinitions: ViewFieldDefinition[];
filterDefinitionArray: FilterDefinition[];
}) {
const setEntityTableData = useSetEntityTableData();
-
- useLoadViewFields({ objectName, viewFieldDefinitions });
-
useGetRequest({
variables: { orderBy, where: whereFilters },
onCompleted: (data: any) => {
diff --git a/front/src/modules/ui/table/editable-cell/components/HoverableMenuItem.tsx b/front/src/modules/ui/table/editable-cell/components/HoverableMenuItem.tsx
deleted file mode 100644
index 33888c329..000000000
--- a/front/src/modules/ui/table/editable-cell/components/HoverableMenuItem.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import styled from '@emotion/styled';
-
-export const HoverableMenuItem = styled.div`
- align-items: center;
- background: ${({ theme }) => theme.background.primary};
- border-radius: ${({ theme }) => theme.border.radius.sm};
- box-sizing: border-box;
- cursor: pointer;
- display: flex;
- height: 100%;
- position: relative;
- transition: background 0.1s ease;
- user-select: none;
- width: 100%;
-
- &:hover {
- background: ${({ theme }) => theme.background.transparent.light};
- }
-`;
diff --git a/front/src/modules/ui/table/editable-cell/hooks/useEditableCell.ts b/front/src/modules/ui/table/editable-cell/hooks/useEditableCell.ts
index 8a514f1a8..8da2ee7a5 100644
--- a/front/src/modules/ui/table/editable-cell/hooks/useEditableCell.ts
+++ b/front/src/modules/ui/table/editable-cell/hooks/useEditableCell.ts
@@ -1,5 +1,6 @@
import { useContext } from 'react';
+import { useDragSelect } from '@/ui/utilities/drag-select/hooks/useDragSelect';
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
@@ -17,17 +18,20 @@ export function useEditableCell() {
const { setCurrentCellInEditMode } = useCurrentCellEditMode();
const setHotkeyScope = useSetHotkeyScope();
+ const { setDragSelectionStartEnabled } = useDragSelect();
const closeCurrentCellInEditMode = useCloseCurrentCellInEditMode();
const customCellHotkeyScope = useContext(CellHotkeyScopeContext);
function closeEditableCell() {
+ setDragSelectionStartEnabled(true);
closeCurrentCellInEditMode();
setHotkeyScope(TableHotkeyScope.TableSoftFocus);
}
function openEditableCell() {
+ setDragSelectionStartEnabled(false);
setCurrentCellInEditMode();
if (customCellHotkeyScope) {
diff --git a/front/src/modules/ui/table/hooks/useCurrentEntityId.ts b/front/src/modules/ui/table/hooks/useCurrentEntityId.ts
index 05040206d..ae7940735 100644
--- a/front/src/modules/ui/table/hooks/useCurrentEntityId.ts
+++ b/front/src/modules/ui/table/hooks/useCurrentEntityId.ts
@@ -2,11 +2,6 @@ import { useContext } from 'react';
import { RowIdContext } from '../states/RowIdContext';
-export type TableDimensions = {
- numberOfColumns: number;
- numberOfRows: number;
-};
-
export function useCurrentRowEntityId() {
const currentEntityId = useContext(RowIdContext);
diff --git a/front/src/modules/ui/table/hooks/useInitializeEntityTable.ts b/front/src/modules/ui/table/hooks/useInitializeEntityTable.ts
deleted file mode 100644
index 1e7de1c14..000000000
--- a/front/src/modules/ui/table/hooks/useInitializeEntityTable.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-import { useEffect } from 'react';
-import { useRecoilState, useRecoilValue } from 'recoil';
-
-import { entityTableDimensionsState } from '../states/entityTableDimensionsState';
-import { tableRowIdsState } from '../states/tableRowIdsState';
-
-import { useResetTableRowSelection } from './useResetTableRowSelection';
-
-export type TableDimensions = {
- numberOfColumns: number;
- numberOfRows: number;
-};
-
-export function useInitializeEntityTable({
- numberOfColumns,
-}: {
- numberOfColumns: number;
-}) {
- const resetTableRowSelection = useResetTableRowSelection();
-
- const tableRowIds = useRecoilValue(tableRowIdsState);
-
- useEffect(() => {
- resetTableRowSelection();
- }, [resetTableRowSelection]);
-
- const [, setTableDimensions] = useRecoilState(entityTableDimensionsState);
-
- useEffect(() => {
- setTableDimensions({
- numberOfColumns,
- numberOfRows: tableRowIds?.length,
- });
- }, [tableRowIds, numberOfColumns, setTableDimensions]);
-}
diff --git a/front/src/modules/ui/table/hooks/useInitializeEntityTableFilters.ts b/front/src/modules/ui/table/hooks/useInitializeEntityTableFilters.ts
deleted file mode 100644
index 130e5e6fb..000000000
--- a/front/src/modules/ui/table/hooks/useInitializeEntityTableFilters.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import { useEffect } from 'react';
-
-import { availableFiltersScopedState } from '@/ui/filter-n-sort/states/availableFiltersScopedState';
-import { FilterDefinition } from '@/ui/filter-n-sort/types/FilterDefinition';
-import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
-
-import { TableContext } from '../states/TableContext';
-
-export function useInitializeEntityTableFilters({
- availableFilters,
-}: {
- availableFilters: FilterDefinition[];
-}) {
- const [, setAvailableFilters] = useRecoilScopedState(
- availableFiltersScopedState,
- TableContext,
- );
-
- useEffect(() => {
- setAvailableFilters(availableFilters);
- }, [setAvailableFilters, availableFilters]);
-}
diff --git a/front/src/modules/ui/table/hooks/useLoadViewFields.ts b/front/src/modules/ui/table/hooks/useLoadViewFields.ts
deleted file mode 100644
index f7cbb5640..000000000
--- a/front/src/modules/ui/table/hooks/useLoadViewFields.ts
+++ /dev/null
@@ -1,96 +0,0 @@
-import { getOperationName } from '@apollo/client/utilities';
-import { useRecoilValue, useSetRecoilState } from 'recoil';
-
-import type {
- ViewFieldDefinition,
- ViewFieldMetadata,
- ViewFieldTextMetadata,
-} from '@/ui/editable-field/types/ViewField';
-import { GET_VIEW_FIELDS } from '@/views/queries/select';
-import { currentViewIdState } from '@/views/states/currentViewIdState';
-import {
- SortOrder,
- useCreateViewFieldsMutation,
- useGetViewFieldsQuery,
-} from '~/generated/graphql';
-
-import { entityTableDimensionsState } from '../states/entityTableDimensionsState';
-import { viewFieldsState } from '../states/viewFieldsState';
-
-const DEFAULT_VIEW_FIELD_METADATA: ViewFieldTextMetadata = {
- type: 'text',
- placeHolder: '',
- fieldName: '',
-};
-
-export const toViewFieldInput = (
- objectName: 'company' | 'person',
- viewFieldDefinition: ViewFieldDefinition,
-) => ({
- fieldName: viewFieldDefinition.columnLabel,
- index: viewFieldDefinition.columnOrder,
- isVisible: viewFieldDefinition.isVisible ?? true,
- objectName,
- sizeInPx: viewFieldDefinition.columnSize,
-});
-
-export const useLoadViewFields = ({
- objectName,
- viewFieldDefinitions,
-}: {
- objectName: 'company' | 'person';
- viewFieldDefinitions: ViewFieldDefinition[];
-}) => {
- const currentViewId = useRecoilValue(currentViewIdState);
- const setEntityTableDimensions = useSetRecoilState(
- entityTableDimensionsState,
- );
- const setViewFieldsState = useSetRecoilState(viewFieldsState);
-
- const [createViewFieldsMutation] = useCreateViewFieldsMutation();
-
- useGetViewFieldsQuery({
- variables: {
- orderBy: { index: SortOrder.Asc },
- where: {
- objectName: { equals: objectName },
- viewId: { equals: currentViewId ?? null },
- },
- },
- onCompleted: (data) => {
- if (data.viewFields.length) {
- const viewFields = data.viewFields.map<
- ViewFieldDefinition
- >((viewField) => ({
- ...(viewFieldDefinitions.find(
- ({ columnLabel }) => viewField.fieldName === columnLabel,
- ) || { metadata: DEFAULT_VIEW_FIELD_METADATA }),
- id: viewField.id,
- columnLabel: viewField.fieldName,
- columnOrder: viewField.index,
- columnSize: viewField.sizeInPx,
- isVisible: viewField.isVisible,
- }));
-
- setViewFieldsState({ objectName, viewFields });
- setEntityTableDimensions((prevState) => ({
- ...prevState,
- numberOfColumns: data.viewFields.length,
- }));
-
- return;
- }
-
- // Populate if empty
- createViewFieldsMutation({
- variables: {
- data: viewFieldDefinitions.map((viewFieldDefinition) => ({
- ...toViewFieldInput(objectName, viewFieldDefinition),
- viewId: currentViewId,
- })),
- },
- refetchQueries: [getOperationName(GET_VIEW_FIELDS) ?? ''],
- });
- },
- });
-};
diff --git a/front/src/modules/ui/table/hooks/useMoveSoftFocus.ts b/front/src/modules/ui/table/hooks/useMoveSoftFocus.ts
index 800bda596..9903a1f87 100644
--- a/front/src/modules/ui/table/hooks/useMoveSoftFocus.ts
+++ b/front/src/modules/ui/table/hooks/useMoveSoftFocus.ts
@@ -1,8 +1,8 @@
import { useRecoilCallback } from 'recoil';
-import { numberOfTableColumnsSelectorState } from '../states/numberOfTableColumnsSelectorState';
-import { numberOfTableRowsSelectorState } from '../states/numberOfTableRowsSelectorState';
+import { numberOfTableRowsState } from '../states/numberOfTableRowsState';
import { softFocusPositionState } from '../states/softFocusPositionState';
+import { numberOfTableColumnsState } from '../states/tableColumnsState';
import { useSetSoftFocusPosition } from './useSetSoftFocusPosition';
@@ -39,7 +39,7 @@ export function useMoveSoftFocus() {
.valueOrThrow();
const numberOfTableRows = snapshot
- .getLoadable(numberOfTableRowsSelectorState)
+ .getLoadable(numberOfTableRowsState)
.valueOrThrow();
let newRowNumber = softFocusPosition.row + 1;
@@ -64,11 +64,11 @@ export function useMoveSoftFocus() {
.valueOrThrow();
const numberOfTableColumns = snapshot
- .getLoadable(numberOfTableColumnsSelectorState)
+ .getLoadable(numberOfTableColumnsState)
.valueOrThrow();
const numberOfTableRows = snapshot
- .getLoadable(numberOfTableRowsSelectorState)
+ .getLoadable(numberOfTableRowsState)
.valueOrThrow();
const currentColumnNumber = softFocusPosition.column;
@@ -112,7 +112,7 @@ export function useMoveSoftFocus() {
.valueOrThrow();
const numberOfTableColumns = snapshot
- .getLoadable(numberOfTableColumnsSelectorState)
+ .getLoadable(numberOfTableColumnsState)
.valueOrThrow();
const currentColumnNumber = softFocusPosition.column;
diff --git a/front/src/modules/ui/table/hooks/useSetEntityTableData.ts b/front/src/modules/ui/table/hooks/useSetEntityTableData.ts
index 69765571c..983f16837 100644
--- a/front/src/modules/ui/table/hooks/useSetEntityTableData.ts
+++ b/front/src/modules/ui/table/hooks/useSetEntityTableData.ts
@@ -3,13 +3,14 @@ import { useRecoilCallback } from 'recoil';
import { availableFiltersScopedState } from '@/ui/filter-n-sort/states/availableFiltersScopedState';
import { FilterDefinition } from '@/ui/filter-n-sort/types/FilterDefinition';
import { useResetTableRowSelection } from '@/ui/table/hooks/useResetTableRowSelection';
-import { entityTableDimensionsState } from '@/ui/table/states/entityTableDimensionsState';
-import { isFetchingEntityTableDataState } from '@/ui/table/states/isFetchingEntityTableDataState';
import { TableContext } from '@/ui/table/states/TableContext';
import { tableEntitiesFamilyState } from '@/ui/table/states/tableEntitiesFamilyState';
import { tableRowIdsState } from '@/ui/table/states/tableRowIdsState';
import { useContextScopeId } from '@/ui/utilities/recoil-scope/hooks/useContextScopeId';
+import { isFetchingEntityTableDataState } from '../states/isFetchingEntityTableDataState';
+import { numberOfTableRowsState } from '../states/numberOfTableRowsState';
+
export function useSetEntityTableData() {
const resetTableRowSelection = useResetTableRowSelection();
@@ -43,10 +44,7 @@ export function useSetEntityTableData() {
resetTableRowSelection();
- set(entityTableDimensionsState, (prevState) => ({
- ...prevState,
- numberOfRows: entityIds.length,
- }));
+ set(numberOfTableRowsState, entityIds.length);
set(availableFiltersScopedState(tableContextScopeId), filters);
diff --git a/front/src/modules/ui/table/hooks/useSetTableRowIds.ts b/front/src/modules/ui/table/hooks/useSetTableRowIds.ts
new file mode 100644
index 000000000..9751b56e7
--- /dev/null
+++ b/front/src/modules/ui/table/hooks/useSetTableRowIds.ts
@@ -0,0 +1,19 @@
+import { useRecoilCallback } from 'recoil';
+
+import { tableRowIdsState } from '../states/tableRowIdsState';
+
+export function useSetTableRowIds() {
+ return useRecoilCallback(
+ ({ set, snapshot }) =>
+ (rowIds: string[]) => {
+ const currentRowIds = snapshot
+ .getLoadable(tableRowIdsState)
+ .valueOrThrow();
+
+ if (JSON.stringify(rowIds) !== JSON.stringify(currentRowIds)) {
+ set(tableRowIdsState, rowIds);
+ }
+ },
+ [],
+ );
+}
diff --git a/front/src/modules/ui/table/hooks/useUpdateEntityField.ts b/front/src/modules/ui/table/hooks/useUpdateEntityField.ts
index 5dcd1fbdc..4fbe45c75 100644
--- a/front/src/modules/ui/table/hooks/useUpdateEntityField.ts
+++ b/front/src/modules/ui/table/hooks/useUpdateEntityField.ts
@@ -17,7 +17,6 @@ import { isViewFieldText } from '@/ui/editable-field/types/guards/isViewFieldTex
import { isViewFieldTextValue } from '@/ui/editable-field/types/guards/isViewFieldTextValue';
import { isViewFieldURL } from '@/ui/editable-field/types/guards/isViewFieldURL';
import { isViewFieldURLValue } from '@/ui/editable-field/types/guards/isViewFieldURLValue';
-import { EntityUpdateMutationHookContext } from '@/ui/table/states/EntityUpdateMutationHookContext';
import { isViewFieldChipValue } from '../../editable-field/types/guards/isViewFieldChipValue';
import {
@@ -42,11 +41,10 @@ import {
ViewFieldURLMetadata,
ViewFieldURLValue,
} from '../../editable-field/types/ViewField';
+import { EntityUpdateMutationContext } from '../states/EntityUpdateMutationHookContext';
export function useUpdateEntityField() {
- const useUpdateEntityMutation = useContext(EntityUpdateMutationHookContext);
-
- const [updateEntity] = useUpdateEntityMutation();
+ const updateEntity = useContext(EntityUpdateMutationContext);
return function updatePeopleField<
MetadataType extends ViewFieldMetadata,
diff --git a/front/src/modules/ui/table/hooks/useUpsertEntityTableItem.ts b/front/src/modules/ui/table/hooks/useUpsertEntityTableItem.ts
new file mode 100644
index 000000000..bbf8c3146
--- /dev/null
+++ b/front/src/modules/ui/table/hooks/useUpsertEntityTableItem.ts
@@ -0,0 +1,19 @@
+import { useRecoilCallback } from 'recoil';
+
+import { tableEntitiesFamilyState } from '@/ui/table/states/tableEntitiesFamilyState';
+
+export function useUpsertEntityTableItem() {
+ return useRecoilCallback(
+ ({ set, snapshot }) =>
+ (entity: T) => {
+ const currentEntity = snapshot
+ .getLoadable(tableEntitiesFamilyState(entity.id))
+ .valueOrThrow();
+
+ if (JSON.stringify(currentEntity) !== JSON.stringify(entity)) {
+ set(tableEntitiesFamilyState(entity.id), entity);
+ }
+ },
+ [],
+ );
+}
diff --git a/front/src/modules/ui/table/hooks/useUpsertTableRowId.ts b/front/src/modules/ui/table/hooks/useUpsertTableRowId.ts
new file mode 100644
index 000000000..0a8d519af
--- /dev/null
+++ b/front/src/modules/ui/table/hooks/useUpsertTableRowId.ts
@@ -0,0 +1,17 @@
+import { useRecoilCallback } from 'recoil';
+
+import { tableRowIdsState } from '../states/tableRowIdsState';
+
+export function useUpsertTableRowId() {
+ return useRecoilCallback(
+ ({ set, snapshot }) =>
+ (rowId: string) => {
+ const currentRowIds = snapshot
+ .getLoadable(tableRowIdsState)
+ .valueOrThrow();
+
+ set(tableRowIdsState, Array.from(new Set([rowId, ...currentRowIds])));
+ },
+ [],
+ );
+}
diff --git a/front/src/modules/views/components/OptionsDropdownButton.tsx b/front/src/modules/ui/table/options/components/TableOptionsDropdownButton.tsx
similarity index 64%
rename from front/src/modules/views/components/OptionsDropdownButton.tsx
rename to front/src/modules/ui/table/options/components/TableOptionsDropdownButton.tsx
index d574f13a6..c5ebb98be 100644
--- a/front/src/modules/views/components/OptionsDropdownButton.tsx
+++ b/front/src/modules/ui/table/options/components/TableOptionsDropdownButton.tsx
@@ -1,7 +1,6 @@
import { useCallback, useState } from 'react';
-import { getOperationName } from '@apollo/client/utilities';
import { useTheme } from '@emotion/react';
-import { useRecoilValue } from 'recoil';
+import { useRecoilState, useRecoilValue } from 'recoil';
import { IconButton } from '@/ui/button/components/IconButton';
import { DropdownMenuHeader } from '@/ui/dropdown/components/DropdownMenuHeader';
@@ -16,16 +15,15 @@ import DropdownButton from '@/ui/filter-n-sort/components/DropdownButton';
import { FiltersHotkeyScope } from '@/ui/filter-n-sort/types/FiltersHotkeyScope';
import { IconChevronLeft, IconMinus, IconPlus, IconTag } from '@/ui/icon';
import {
- hiddenViewFieldsState,
- visibleViewFieldsState,
-} from '@/ui/table/states/viewFieldsState';
-import { useUpdateViewFieldMutation } from '~/generated/graphql';
+ hiddenTableColumnsState,
+ tableColumnsState,
+ visibleTableColumnsState,
+} from '@/ui/table/states/tableColumnsState';
-import { GET_VIEW_FIELDS } from '../queries/select';
+import { TableOptionsDropdownSection } from './TableOptionsDropdownSection';
-import { OptionsDropdownSection } from './OptionsDropdownSection';
-
-type OptionsDropdownButtonProps = {
+type TableOptionsDropdownButtonProps = {
+ onColumnsChange?: (columns: ViewFieldDefinition[]) => void;
HotkeyScope: FiltersHotkeyScope;
};
@@ -33,51 +31,52 @@ enum Option {
Properties = 'Properties',
}
-export const OptionsDropdownButton = ({
+export const TableOptionsDropdownButton = ({
+ onColumnsChange,
HotkeyScope,
-}: OptionsDropdownButtonProps) => {
+}: TableOptionsDropdownButtonProps) => {
const theme = useTheme();
const [isUnfolded, setIsUnfolded] = useState(false);
const [selectedOption, setSelectedOption] = useState |