Feat/editable fields update (#743)
* Removed console log * Used current scope as default parent scope for fields * Finished editable fields on people show page * Added stories * Console log * Lint
This commit is contained in:
@ -2160,7 +2160,7 @@ export type GetPersonQueryVariables = Exact<{
|
|||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|
||||||
export type GetPersonQuery = { __typename?: 'Query', findUniquePerson: { __typename?: 'Person', id: string, firstName: string, lastName: string, displayName: string, email: string, createdAt: string, _commentThreadCount: number, company?: { __typename?: 'Company', id: string } | null } };
|
export type GetPersonQuery = { __typename?: 'Query', findUniquePerson: { __typename?: 'Person', id: string, firstName: string, lastName: string, displayName: string, email: string, createdAt: string, city: string, phone: string, _commentThreadCount: number, company?: { __typename?: 'Company', id: string, name: string, domainName: string } | null } };
|
||||||
|
|
||||||
export type UpdatePeopleMutationVariables = Exact<{
|
export type UpdatePeopleMutationVariables = Exact<{
|
||||||
id?: InputMaybe<Scalars['String']>;
|
id?: InputMaybe<Scalars['String']>;
|
||||||
@ -3629,9 +3629,13 @@ export const GetPersonDocument = gql`
|
|||||||
displayName
|
displayName
|
||||||
email
|
email
|
||||||
createdAt
|
createdAt
|
||||||
|
city
|
||||||
|
phone
|
||||||
_commentThreadCount
|
_commentThreadCount
|
||||||
company {
|
company {
|
||||||
id
|
id
|
||||||
|
name
|
||||||
|
domainName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,6 @@ import styled from '@emotion/styled';
|
|||||||
|
|
||||||
import { CompanyAccountOwnerPicker } from '@/companies/components/CompanyAccountOwnerPicker';
|
import { CompanyAccountOwnerPicker } from '@/companies/components/CompanyAccountOwnerPicker';
|
||||||
import { useEditableField } from '@/ui/editable-field/hooks/useEditableField';
|
import { useEditableField } from '@/ui/editable-field/hooks/useEditableField';
|
||||||
import { HotkeyScope } from '@/ui/hotkey/types/HotkeyScope';
|
|
||||||
import { Company, User } from '~/generated/graphql';
|
import { Company, User } from '~/generated/graphql';
|
||||||
|
|
||||||
const CompanyAccountOwnerPickerContainer = styled.div`
|
const CompanyAccountOwnerPickerContainer = styled.div`
|
||||||
@ -17,7 +16,6 @@ export type OwnProps = {
|
|||||||
};
|
};
|
||||||
onSubmit?: () => void;
|
onSubmit?: () => void;
|
||||||
onCancel?: () => void;
|
onCancel?: () => void;
|
||||||
parentHotkeyScope?: HotkeyScope;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function CompanyAccountOwnerPickerFieldEditMode({
|
export function CompanyAccountOwnerPickerFieldEditMode({
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { EditableField } from '@/ui/editable-field/components/EditableField';
|
import { EditableField } from '@/ui/editable-field/components/EditableField';
|
||||||
import { EditableFieldEditModeDate } from '@/ui/editable-field/components/EditableFieldEditModeDate';
|
|
||||||
import { FieldContext } from '@/ui/editable-field/states/FieldContext';
|
import { FieldContext } from '@/ui/editable-field/states/FieldContext';
|
||||||
|
import { EditableFieldEditModeDate } from '@/ui/editable-field/variants/components/EditableFieldEditModeDate';
|
||||||
import { IconCalendar } from '@/ui/icon';
|
import { IconCalendar } from '@/ui/icon';
|
||||||
import { RecoilScope } from '@/ui/recoil-scope/components/RecoilScope';
|
import { RecoilScope } from '@/ui/recoil-scope/components/RecoilScope';
|
||||||
import { Company, useUpdateCompanyMutation } from '~/generated/graphql';
|
import { Company, useUpdateCompanyMutation } from '~/generated/graphql';
|
||||||
@ -56,6 +56,7 @@ export function CompanyCreatedAtEditableField({ company }: OwnProps) {
|
|||||||
? formatToHumanReadableDate(parseDate(internalValue).toJSDate())
|
? formatToHumanReadableDate(parseDate(internalValue).toJSDate())
|
||||||
: 'No date'
|
: 'No date'
|
||||||
}
|
}
|
||||||
|
isDisplayModeContentEmpty={!(internalValue !== '')}
|
||||||
/>
|
/>
|
||||||
</RecoilScope>
|
</RecoilScope>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -56,6 +56,7 @@ export function CompanyDomainNameEditableField({ company }: OwnProps) {
|
|||||||
}
|
}
|
||||||
displayModeContent={<FieldDisplayURL URL={internalValue} />}
|
displayModeContent={<FieldDisplayURL URL={internalValue} />}
|
||||||
useEditButton
|
useEditButton
|
||||||
|
isDisplayModeContentEmpty={!(internalValue !== '')}
|
||||||
/>
|
/>
|
||||||
</RecoilScope>
|
</RecoilScope>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -70,6 +70,7 @@ export function CompanyEmployeesEditableField({ company }: OwnProps) {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
displayModeContent={internalValue}
|
displayModeContent={internalValue}
|
||||||
|
isDisplayModeContentEmpty={!(internalValue && internalValue !== '0')}
|
||||||
/>
|
/>
|
||||||
</RecoilScope>
|
</RecoilScope>
|
||||||
);
|
);
|
||||||
|
|||||||
84
front/src/modules/people/components/PersonPropertyBox.tsx
Normal file
84
front/src/modules/people/components/PersonPropertyBox.tsx
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
import {
|
||||||
|
IconCalendar,
|
||||||
|
IconMail,
|
||||||
|
IconMap,
|
||||||
|
IconPhone,
|
||||||
|
} from '@tabler/icons-react';
|
||||||
|
|
||||||
|
import { PropertyBox } from '@/ui/editable-field/property-box/components/PropertyBox';
|
||||||
|
import { DateEditableField } from '@/ui/editable-field/variants/components/DateEditableField';
|
||||||
|
import { PhoneEditableField } from '@/ui/editable-field/variants/components/PhoneEditableField';
|
||||||
|
import { TextEditableField } from '@/ui/editable-field/variants/components/TextEditableField';
|
||||||
|
import { Company, Person, useUpdatePeopleMutation } from '~/generated/graphql';
|
||||||
|
|
||||||
|
import { PeopleCompanyEditableField } from '../editable-field/components/PeopleCompanyEditableField';
|
||||||
|
|
||||||
|
type OwnProps = {
|
||||||
|
person: Pick<
|
||||||
|
Person,
|
||||||
|
'id' | 'city' | 'email' | 'displayName' | 'phone' | 'createdAt'
|
||||||
|
> & {
|
||||||
|
company?: Pick<Company, 'id' | 'name' | 'domainName'> | null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export function PersonPropertyBox({ person }: OwnProps) {
|
||||||
|
const [updatePerson] = useUpdatePeopleMutation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PropertyBox extraPadding={true}>
|
||||||
|
<TextEditableField
|
||||||
|
value={person.email}
|
||||||
|
icon={<IconMail />}
|
||||||
|
placeholder={'Email'}
|
||||||
|
onSubmit={(newEmail) => {
|
||||||
|
updatePerson({
|
||||||
|
variables: {
|
||||||
|
id: person.id,
|
||||||
|
email: newEmail,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<PhoneEditableField
|
||||||
|
value={person.phone}
|
||||||
|
icon={<IconPhone />}
|
||||||
|
placeholder={'Phone'}
|
||||||
|
onSubmit={(newPhone) => {
|
||||||
|
updatePerson({
|
||||||
|
variables: {
|
||||||
|
id: person.id,
|
||||||
|
phone: newPhone,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<DateEditableField
|
||||||
|
value={person.createdAt}
|
||||||
|
icon={<IconCalendar />}
|
||||||
|
onSubmit={(newDate) => {
|
||||||
|
updatePerson({
|
||||||
|
variables: {
|
||||||
|
id: person.id,
|
||||||
|
createdAt: newDate,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<PeopleCompanyEditableField people={person} />
|
||||||
|
<TextEditableField
|
||||||
|
value={person.city}
|
||||||
|
icon={<IconMap />}
|
||||||
|
placeholder={'City'}
|
||||||
|
onSubmit={(newCity) => {
|
||||||
|
updatePerson({
|
||||||
|
variables: {
|
||||||
|
id: person.id,
|
||||||
|
city: newCity,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</PropertyBox>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,47 @@
|
|||||||
|
import { IconBuildingSkyscraper } from '@tabler/icons-react';
|
||||||
|
|
||||||
|
import { CompanyChip } from '@/companies/components/CompanyChip';
|
||||||
|
import { EditableField } from '@/ui/editable-field/components/EditableField';
|
||||||
|
import { FieldContext } from '@/ui/editable-field/states/FieldContext';
|
||||||
|
import { RecoilScope } from '@/ui/recoil-scope/components/RecoilScope';
|
||||||
|
import { RelationPickerHotkeyScope } from '@/ui/relation-picker/types/RelationPickerHotkeyScope';
|
||||||
|
import { Company, Person } from '~/generated/graphql';
|
||||||
|
import { getLogoUrlFromDomainName } from '~/utils';
|
||||||
|
|
||||||
|
import { PeopleCompanyEditableFieldEditMode } from './PeopleCompanyEditableFieldEditMode';
|
||||||
|
|
||||||
|
export type OwnProps = {
|
||||||
|
people: Pick<Person, 'id'> & {
|
||||||
|
company?: Pick<Company, 'id' | 'name' | 'domainName'> | null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export function PeopleCompanyEditableField({ people }: OwnProps) {
|
||||||
|
return (
|
||||||
|
<RecoilScope SpecificContext={FieldContext}>
|
||||||
|
<RecoilScope>
|
||||||
|
<EditableField
|
||||||
|
customEditHotkeyScope={{
|
||||||
|
scope: RelationPickerHotkeyScope.RelationPicker,
|
||||||
|
}}
|
||||||
|
iconLabel={<IconBuildingSkyscraper />}
|
||||||
|
editModeContent={
|
||||||
|
<PeopleCompanyEditableFieldEditMode people={people} />
|
||||||
|
}
|
||||||
|
displayModeContent={
|
||||||
|
people.company ? (
|
||||||
|
<CompanyChip
|
||||||
|
id={people.company.id}
|
||||||
|
name={people.company.name}
|
||||||
|
picture={getLogoUrlFromDomainName(people.company.domainName)}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
isDisplayModeContentEmpty={!people.company}
|
||||||
|
/>
|
||||||
|
</RecoilScope>
|
||||||
|
</RecoilScope>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,51 @@
|
|||||||
|
import { useFilteredSearchCompanyQuery } from '@/companies/queries';
|
||||||
|
import { useEditableField } from '@/ui/editable-field/hooks/useEditableField';
|
||||||
|
import { useRecoilScopedState } from '@/ui/recoil-scope/hooks/useRecoilScopedState';
|
||||||
|
import { SingleEntitySelect } from '@/ui/relation-picker/components/SingleEntitySelect';
|
||||||
|
import { relationPickerSearchFilterScopedState } from '@/ui/relation-picker/states/relationPickerSearchFilterScopedState';
|
||||||
|
import { EntityForSelect } from '@/ui/relation-picker/types/EntityForSelect';
|
||||||
|
import { Company, Person, useUpdatePeopleMutation } from '~/generated/graphql';
|
||||||
|
|
||||||
|
export type OwnProps = {
|
||||||
|
people: Pick<Person, 'id'> & { company?: Pick<Company, 'id'> | null };
|
||||||
|
};
|
||||||
|
|
||||||
|
export function PeopleCompanyEditableFieldEditMode({ people }: OwnProps) {
|
||||||
|
const { closeEditableField } = useEditableField();
|
||||||
|
|
||||||
|
const [searchFilter] = useRecoilScopedState(
|
||||||
|
relationPickerSearchFilterScopedState,
|
||||||
|
);
|
||||||
|
const [updatePeople] = useUpdatePeopleMutation();
|
||||||
|
|
||||||
|
const companies = useFilteredSearchCompanyQuery({
|
||||||
|
searchFilter,
|
||||||
|
selectedIds: people.company?.id ? [people.company.id] : [],
|
||||||
|
});
|
||||||
|
|
||||||
|
async function handleEntitySelected(entity: EntityForSelect) {
|
||||||
|
await updatePeople({
|
||||||
|
variables: {
|
||||||
|
...people,
|
||||||
|
companyId: entity.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
closeEditableField();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCancel() {
|
||||||
|
closeEditableField();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SingleEntitySelect
|
||||||
|
onEntitySelected={handleEntitySelected}
|
||||||
|
entities={{
|
||||||
|
entitiesToSelect: companies.entitiesToSelect,
|
||||||
|
selectedEntity: companies.selectedEntities[0],
|
||||||
|
loading: companies.loading,
|
||||||
|
}}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -11,9 +11,13 @@ export const GET_PERSON = gql`
|
|||||||
displayName
|
displayName
|
||||||
email
|
email
|
||||||
createdAt
|
createdAt
|
||||||
|
city
|
||||||
|
phone
|
||||||
_commentThreadCount
|
_commentThreadCount
|
||||||
company {
|
company {
|
||||||
id
|
id
|
||||||
|
name
|
||||||
|
domainName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { formatToHumanReadableDate } from '~/utils';
|
import { formatToHumanReadableDate } from '~/utils';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
value: Date;
|
value: Date | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function InplaceInputDateDisplayMode({ value }: OwnProps) {
|
export function InplaceInputDateDisplayMode({ value }: OwnProps) {
|
||||||
|
|||||||
@ -16,7 +16,6 @@ export const EditableFieldNormalModeOuterContainer = styled.div<
|
|||||||
padding: ${({ theme }) => theme.spacing(1)};
|
padding: ${({ theme }) => theme.spacing(1)};
|
||||||
|
|
||||||
${(props) => {
|
${(props) => {
|
||||||
console.log(props.isDisplayModeContentEmpty);
|
|
||||||
if (props.isDisplayModeContentEmpty) {
|
if (props.isDisplayModeContentEmpty) {
|
||||||
return css`
|
return css`
|
||||||
min-width: 50px;
|
min-width: 50px;
|
||||||
|
|||||||
@ -1,8 +1,13 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
import { useRecoilCallback } from 'recoil';
|
||||||
|
|
||||||
|
import { currentHotkeyScopeState } from '@/ui/hotkey/states/internal/currentHotkeyScopeState';
|
||||||
import { HotkeyScope } from '@/ui/hotkey/types/HotkeyScope';
|
import { HotkeyScope } from '@/ui/hotkey/types/HotkeyScope';
|
||||||
import { isSameHotkeyScope } from '@/ui/hotkey/utils/isSameHotkeyScope';
|
import { isSameHotkeyScope } from '@/ui/hotkey/utils/isSameHotkeyScope';
|
||||||
|
import { useContextScopeId } from '@/ui/recoil-scope/hooks/useContextScopeId';
|
||||||
import { useRecoilScopedState } from '@/ui/recoil-scope/hooks/useRecoilScopedState';
|
import { useRecoilScopedState } from '@/ui/recoil-scope/hooks/useRecoilScopedState';
|
||||||
|
import { getSnapshotScopedState } from '@/ui/recoil-scope/utils/getSnapshotScopedState';
|
||||||
|
import { getSnapshotState } from '@/ui/recoil-scope/utils/getSnapshotState';
|
||||||
|
|
||||||
import { customEditHotkeyScopeForFieldScopedState } from '../states/customEditHotkeyScopeForFieldScopedState';
|
import { customEditHotkeyScopeForFieldScopedState } from '../states/customEditHotkeyScopeForFieldScopedState';
|
||||||
import { FieldContext } from '../states/FieldContext';
|
import { FieldContext } from '../states/FieldContext';
|
||||||
@ -21,8 +26,7 @@ export function useBindFieldHotkeyScope({
|
|||||||
FieldContext,
|
FieldContext,
|
||||||
);
|
);
|
||||||
|
|
||||||
const [parentHotkeyScopeForField, setParentHotkeyScopeForField] =
|
const fieldContextScopeId = useContextScopeId(FieldContext);
|
||||||
useRecoilScopedState(parentHotkeyScopeForFieldScopedState, FieldContext);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
@ -37,16 +41,35 @@ export function useBindFieldHotkeyScope({
|
|||||||
setCustomEditHotkeyScopeForField,
|
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(() => {
|
useEffect(() => {
|
||||||
if (
|
setParentHotkeyScopeForField(parentHotkeyScope);
|
||||||
parentHotkeyScope &&
|
}, [parentHotkeyScope, setParentHotkeyScopeForField]);
|
||||||
!isSameHotkeyScope(parentHotkeyScope, parentHotkeyScopeForField)
|
|
||||||
) {
|
|
||||||
setParentHotkeyScopeForField(parentHotkeyScope);
|
|
||||||
}
|
|
||||||
}, [
|
|
||||||
parentHotkeyScope,
|
|
||||||
parentHotkeyScopeForField,
|
|
||||||
setParentHotkeyScopeForField,
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,6 @@ import { isFieldInEditModeScopedState } from '../states/isFieldInEditModeScopedS
|
|||||||
import { parentHotkeyScopeForFieldScopedState } from '../states/parentHotkeyScopeForFieldScopedState';
|
import { parentHotkeyScopeForFieldScopedState } from '../states/parentHotkeyScopeForFieldScopedState';
|
||||||
import { EditableFieldHotkeyScope } from '../types/EditableFieldHotkeyScope';
|
import { EditableFieldHotkeyScope } from '../types/EditableFieldHotkeyScope';
|
||||||
|
|
||||||
// TODO: use atoms for hotkey scopes
|
|
||||||
export function useEditableField() {
|
export function useEditableField() {
|
||||||
const [isFieldInEditMode, setIsFieldInEditMode] = useRecoilScopedState(
|
const [isFieldInEditMode, setIsFieldInEditMode] = useRecoilScopedState(
|
||||||
isFieldInEditModeScopedState,
|
isFieldInEditModeScopedState,
|
||||||
|
|||||||
@ -0,0 +1,65 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
import { InplaceInputDateDisplayMode } from '@/ui/display/component/InplaceInputDateDisplayMode';
|
||||||
|
import { EditableField } from '@/ui/editable-field/components/EditableField';
|
||||||
|
import { FieldContext } from '@/ui/editable-field/states/FieldContext';
|
||||||
|
import { RecoilScope } from '@/ui/recoil-scope/components/RecoilScope';
|
||||||
|
import { parseDate } from '~/utils/date-utils';
|
||||||
|
|
||||||
|
import { EditableFieldEditModeDate } from './EditableFieldEditModeDate';
|
||||||
|
|
||||||
|
type OwnProps = {
|
||||||
|
icon?: React.ReactNode;
|
||||||
|
value: string | null | undefined;
|
||||||
|
onSubmit?: (newValue: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function DateEditableField({ icon, value, onSubmit }: OwnProps) {
|
||||||
|
const [internalValue, setInternalValue] = useState(value);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setInternalValue(value);
|
||||||
|
}, [value]);
|
||||||
|
|
||||||
|
async function handleChange(newValue: string) {
|
||||||
|
setInternalValue(newValue);
|
||||||
|
|
||||||
|
onSubmit?.(newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleSubmit() {
|
||||||
|
if (!internalValue) return;
|
||||||
|
|
||||||
|
onSubmit?.(internalValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleCancel() {
|
||||||
|
setInternalValue(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const internalDateValue = internalValue
|
||||||
|
? parseDate(internalValue).toJSDate()
|
||||||
|
: null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RecoilScope SpecificContext={FieldContext}>
|
||||||
|
<EditableField
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
iconLabel={icon}
|
||||||
|
editModeContent={
|
||||||
|
<EditableFieldEditModeDate
|
||||||
|
value={internalValue ?? ''}
|
||||||
|
onChange={(newValue: string) => {
|
||||||
|
handleChange(newValue);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
displayModeContent={
|
||||||
|
<InplaceInputDateDisplayMode value={internalDateValue} />
|
||||||
|
}
|
||||||
|
isDisplayModeContentEmpty={!(internalValue !== '')}
|
||||||
|
/>
|
||||||
|
</RecoilScope>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -2,7 +2,7 @@ import { HotkeyScope } from '@/ui/hotkey/types/HotkeyScope';
|
|||||||
import { InplaceInputDate } from '@/ui/inplace-input/components/InplaceInputDate';
|
import { InplaceInputDate } from '@/ui/inplace-input/components/InplaceInputDate';
|
||||||
import { parseDate } from '~/utils/date-utils';
|
import { parseDate } from '~/utils/date-utils';
|
||||||
|
|
||||||
import { useEditableField } from '../hooks/useEditableField';
|
import { useEditableField } from '../../hooks/useEditableField';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
value: string;
|
value: string;
|
||||||
@ -0,0 +1,66 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
import { InplaceInputPhoneDisplayMode } from '@/ui/display/component/InplaceInputPhoneDisplayMode';
|
||||||
|
import { EditableField } from '@/ui/editable-field/components/EditableField';
|
||||||
|
import { FieldContext } from '@/ui/editable-field/states/FieldContext';
|
||||||
|
import { InplaceInputText } from '@/ui/inplace-input/components/InplaceInputText';
|
||||||
|
import { RecoilScope } from '@/ui/recoil-scope/components/RecoilScope';
|
||||||
|
|
||||||
|
type OwnProps = {
|
||||||
|
icon?: React.ReactNode;
|
||||||
|
placeholder?: string;
|
||||||
|
value: string | null | undefined;
|
||||||
|
onSubmit?: (newValue: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function PhoneEditableField({
|
||||||
|
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 (
|
||||||
|
<RecoilScope SpecificContext={FieldContext}>
|
||||||
|
<EditableField
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
iconLabel={icon}
|
||||||
|
editModeContent={
|
||||||
|
<InplaceInputText
|
||||||
|
placeholder={placeholder ?? ''}
|
||||||
|
autoFocus
|
||||||
|
value={internalValue ?? ''}
|
||||||
|
onChange={(newValue: string) => {
|
||||||
|
handleChange(newValue);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
displayModeContent={
|
||||||
|
<InplaceInputPhoneDisplayMode value={internalValue ?? ''} />
|
||||||
|
}
|
||||||
|
isDisplayModeContentEmpty={!(internalValue !== '')}
|
||||||
|
useEditButton
|
||||||
|
/>
|
||||||
|
</RecoilScope>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
import type { Meta, StoryObj } from '@storybook/react';
|
||||||
|
import { IconCalendar } from '@tabler/icons-react';
|
||||||
|
|
||||||
|
import { getRenderWrapperForComponent } from '~/testing/renderWrappers';
|
||||||
|
|
||||||
|
import { DateEditableField } from '../DateEditableField';
|
||||||
|
|
||||||
|
const meta: Meta<typeof DateEditableField> = {
|
||||||
|
title: 'UI/EditableField/DateEditableField',
|
||||||
|
component: DateEditableField,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof DateEditableField>;
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
render: getRenderWrapperForComponent(
|
||||||
|
<DateEditableField
|
||||||
|
value={new Date().toISOString()}
|
||||||
|
icon={<IconCalendar />}
|
||||||
|
/>,
|
||||||
|
),
|
||||||
|
};
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
import type { Meta, StoryObj } from '@storybook/react';
|
||||||
|
import { IconCurrencyDollar } from '@tabler/icons-react';
|
||||||
|
|
||||||
|
import { getRenderWrapperForComponent } from '~/testing/renderWrappers';
|
||||||
|
|
||||||
|
import { NumberEditableField } from '../NumberEditableField';
|
||||||
|
|
||||||
|
const meta: Meta<typeof NumberEditableField> = {
|
||||||
|
title: 'UI/EditableField/NumberEditableField',
|
||||||
|
component: NumberEditableField,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof NumberEditableField>;
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
render: getRenderWrapperForComponent(
|
||||||
|
<NumberEditableField
|
||||||
|
value={10}
|
||||||
|
icon={<IconCurrencyDollar />}
|
||||||
|
placeholder="Number"
|
||||||
|
/>,
|
||||||
|
),
|
||||||
|
};
|
||||||
@ -0,0 +1,27 @@
|
|||||||
|
import { BrowserRouter } from 'react-router-dom';
|
||||||
|
import type { Meta, StoryObj } from '@storybook/react';
|
||||||
|
import { IconPhone } from '@tabler/icons-react';
|
||||||
|
|
||||||
|
import { getRenderWrapperForComponent } from '~/testing/renderWrappers';
|
||||||
|
|
||||||
|
import { PhoneEditableField } from '../PhoneEditableField';
|
||||||
|
|
||||||
|
const meta: Meta<typeof PhoneEditableField> = {
|
||||||
|
title: 'UI/EditableField/PhoneEditableField',
|
||||||
|
component: PhoneEditableField,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof PhoneEditableField>;
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
render: getRenderWrapperForComponent(
|
||||||
|
<BrowserRouter>
|
||||||
|
<PhoneEditableField
|
||||||
|
value={'+33714446494'}
|
||||||
|
icon={<IconPhone />}
|
||||||
|
placeholder="Phone"
|
||||||
|
/>
|
||||||
|
</BrowserRouter>,
|
||||||
|
),
|
||||||
|
};
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
import type { Meta, StoryObj } from '@storybook/react';
|
||||||
|
import { IconUser } from '@tabler/icons-react';
|
||||||
|
|
||||||
|
import { getRenderWrapperForComponent } from '~/testing/renderWrappers';
|
||||||
|
|
||||||
|
import { TextEditableField } from '../TextEditableField';
|
||||||
|
|
||||||
|
const meta: Meta<typeof TextEditableField> = {
|
||||||
|
title: 'UI/EditableField/TextEditableField',
|
||||||
|
component: TextEditableField,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof TextEditableField>;
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
render: getRenderWrapperForComponent(
|
||||||
|
<TextEditableField
|
||||||
|
value={'John Doe'}
|
||||||
|
icon={<IconUser />}
|
||||||
|
placeholder="Name"
|
||||||
|
/>,
|
||||||
|
),
|
||||||
|
};
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilCallback } from 'recoil';
|
||||||
|
|
||||||
import { currentHotkeyScopeState } from '../states/internal/currentHotkeyScopeState';
|
import { currentHotkeyScopeState } from '../states/internal/currentHotkeyScopeState';
|
||||||
import { CustomHotkeyScopes } from '../types/CustomHotkeyScope';
|
import { CustomHotkeyScopes } from '../types/CustomHotkeyScope';
|
||||||
@ -13,8 +13,6 @@ export function usePreviousHotkeyScope() {
|
|||||||
|
|
||||||
const setHotkeyScope = useSetHotkeyScope();
|
const setHotkeyScope = useSetHotkeyScope();
|
||||||
|
|
||||||
const currentHotkeyScope = useRecoilValue(currentHotkeyScopeState);
|
|
||||||
|
|
||||||
function goBackToPreviousHotkeyScope() {
|
function goBackToPreviousHotkeyScope() {
|
||||||
if (previousHotkeyScope) {
|
if (previousHotkeyScope) {
|
||||||
setHotkeyScope(
|
setHotkeyScope(
|
||||||
@ -24,13 +22,19 @@ export function usePreviousHotkeyScope() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setHotkeyScopeAndMemorizePreviousScope(
|
const setHotkeyScopeAndMemorizePreviousScope = useRecoilCallback(
|
||||||
scope: string,
|
({ snapshot }) =>
|
||||||
customScopes?: CustomHotkeyScopes,
|
(scope: string, customScopes?: CustomHotkeyScopes) => {
|
||||||
) {
|
const currentHotkeyScope = snapshot
|
||||||
setPreviousHotkeyScope(currentHotkeyScope);
|
.getLoadable(currentHotkeyScopeState)
|
||||||
setHotkeyScope(scope, customScopes);
|
.valueOrThrow();
|
||||||
}
|
|
||||||
|
setHotkeyScope(scope, customScopes);
|
||||||
|
|
||||||
|
setPreviousHotkeyScope(currentHotkeyScope);
|
||||||
|
},
|
||||||
|
[setPreviousHotkeyScope],
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
setHotkeyScopeAndMemorizePreviousScope,
|
setHotkeyScopeAndMemorizePreviousScope,
|
||||||
|
|||||||
@ -19,12 +19,12 @@ function isCustomScopesEqual(
|
|||||||
export function useSetHotkeyScope() {
|
export function useSetHotkeyScope() {
|
||||||
return useRecoilCallback(
|
return useRecoilCallback(
|
||||||
({ snapshot, set }) =>
|
({ snapshot, set }) =>
|
||||||
async (HotkeyScopeToSet: string, customScopes?: CustomHotkeyScopes) => {
|
async (hotkeyScopeToSet: string, customScopes?: CustomHotkeyScopes) => {
|
||||||
const currentHotkeyScope = await snapshot.getPromise(
|
const currentHotkeyScope = await snapshot.getPromise(
|
||||||
currentHotkeyScopeState,
|
currentHotkeyScopeState,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (currentHotkeyScope.scope === HotkeyScopeToSet) {
|
if (currentHotkeyScope.scope === hotkeyScopeToSet) {
|
||||||
if (!isDefined(customScopes)) {
|
if (!isDefined(customScopes)) {
|
||||||
if (
|
if (
|
||||||
isCustomScopesEqual(
|
isCustomScopesEqual(
|
||||||
@ -47,7 +47,7 @@ export function useSetHotkeyScope() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
set(currentHotkeyScopeState, {
|
set(currentHotkeyScopeState, {
|
||||||
scope: HotkeyScopeToSet,
|
scope: hotkeyScopeToSet,
|
||||||
customScopes: {
|
customScopes: {
|
||||||
commandMenu: customScopes?.commandMenu ?? true,
|
commandMenu: customScopes?.commandMenu ?? true,
|
||||||
goto: customScopes?.goto ?? false,
|
goto: customScopes?.goto ?? false,
|
||||||
|
|||||||
@ -48,7 +48,7 @@ export const DatePickerContainer = ({ children }: DatePickerContainerProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
value: Date;
|
value: Date | null | undefined;
|
||||||
onChange: (newDate: Date) => void;
|
onChange: (newDate: Date) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -56,7 +56,7 @@ export function InplaceInputDate({ onChange, value }: OwnProps) {
|
|||||||
return (
|
return (
|
||||||
<InplaceInputContainer>
|
<InplaceInputContainer>
|
||||||
<DatePicker
|
<DatePicker
|
||||||
date={value}
|
date={value ?? new Date()}
|
||||||
onChangeHandler={onChange}
|
onChangeHandler={onChange}
|
||||||
customInput={<DateDisplay />}
|
customInput={<DateDisplay />}
|
||||||
customCalendarContainer={DatePickerContainer}
|
customCalendarContainer={DatePickerContainer}
|
||||||
|
|||||||
12
front/src/modules/ui/recoil-scope/hooks/useContextScopeId.ts
Normal file
12
front/src/modules/ui/recoil-scope/hooks/useContextScopeId.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { Context, useContext } from 'react';
|
||||||
|
|
||||||
|
export function useContextScopeId(SpecificContext: Context<string | null>) {
|
||||||
|
const recoilScopeId = useContext(SpecificContext);
|
||||||
|
|
||||||
|
if (!recoilScopeId)
|
||||||
|
throw new Error(
|
||||||
|
`Using useContextScopedId outside of the specified context : ${SpecificContext.displayName}, verify that you are using a RecoilScope with the specific context you want to use.`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return recoilScopeId;
|
||||||
|
}
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
import { RecoilState, Snapshot } from 'recoil';
|
||||||
|
|
||||||
|
export function getSnapshotScopedState<T>({
|
||||||
|
snapshot,
|
||||||
|
state,
|
||||||
|
contextScopeId,
|
||||||
|
}: {
|
||||||
|
snapshot: Snapshot;
|
||||||
|
state: (scopeId: string) => RecoilState<T>;
|
||||||
|
contextScopeId: string;
|
||||||
|
}) {
|
||||||
|
return snapshot.getLoadable(state(contextScopeId)).valueOrThrow();
|
||||||
|
}
|
||||||
11
front/src/modules/ui/recoil-scope/utils/getSnapshotState.ts
Normal file
11
front/src/modules/ui/recoil-scope/utils/getSnapshotState.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { RecoilState, Snapshot } from 'recoil';
|
||||||
|
|
||||||
|
export function getSnapshotState<T>({
|
||||||
|
snapshot,
|
||||||
|
state,
|
||||||
|
}: {
|
||||||
|
snapshot: Snapshot;
|
||||||
|
state: RecoilState<T>;
|
||||||
|
}) {
|
||||||
|
return snapshot.getLoadable(state).valueOrThrow();
|
||||||
|
}
|
||||||
@ -25,6 +25,7 @@ export function EditableCellDateEditMode({
|
|||||||
|
|
||||||
function handleDateChange(newDate: Date) {
|
function handleDateChange(newDate: Date) {
|
||||||
onChange(newDate);
|
onChange(newDate);
|
||||||
|
|
||||||
closeEditableCell();
|
closeEditableCell();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -112,7 +112,6 @@ export function PasswordLogin() {
|
|||||||
}
|
}
|
||||||
navigate('/auth/create/workspace');
|
navigate('/auth/create/workspace');
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.log('err', err);
|
|
||||||
enqueueSnackBar(err?.message, {
|
enqueueSnackBar(err?.message, {
|
||||||
variant: 'error',
|
variant: 'error',
|
||||||
});
|
});
|
||||||
|
|||||||
@ -99,7 +99,6 @@ export const EditNote: Story = {
|
|||||||
graphql.query(
|
graphql.query(
|
||||||
getOperationName(GET_COMMENT_THREAD) ?? '',
|
getOperationName(GET_COMMENT_THREAD) ?? '',
|
||||||
(req, res, ctx) => {
|
(req, res, ctx) => {
|
||||||
console.log('coucou');
|
|
||||||
return res(
|
return res(
|
||||||
ctx.data({
|
ctx.data({
|
||||||
findManyCommentThreads: [mockedCommentThreads[0]],
|
findManyCommentThreads: [mockedCommentThreads[0]],
|
||||||
|
|||||||
@ -2,10 +2,9 @@ import { useParams } from 'react-router-dom';
|
|||||||
import { useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
|
|
||||||
import { Timeline } from '@/activities/timeline/components/Timeline';
|
import { Timeline } from '@/activities/timeline/components/Timeline';
|
||||||
|
import { PersonPropertyBox } from '@/people/components/PersonPropertyBox';
|
||||||
import { usePersonQuery } from '@/people/queries';
|
import { usePersonQuery } from '@/people/queries';
|
||||||
import { PropertyBox } from '@/ui/editable-field/property-box/components/PropertyBox';
|
import { IconUser } from '@/ui/icon';
|
||||||
import { PropertyBoxItem } from '@/ui/editable-field/property-box/components/PropertyBoxItem';
|
|
||||||
import { IconLink, IconUser } from '@/ui/icon';
|
|
||||||
import { WithTopBarContainer } from '@/ui/layout/components/WithTopBarContainer';
|
import { WithTopBarContainer } from '@/ui/layout/components/WithTopBarContainer';
|
||||||
import { ShowPageLeftContainer } from '@/ui/layout/show-page/components/ShowPageLeftContainer';
|
import { ShowPageLeftContainer } from '@/ui/layout/show-page/components/ShowPageLeftContainer';
|
||||||
import { ShowPageRightContainer } from '@/ui/layout/show-page/components/ShowPageRightContainer';
|
import { ShowPageRightContainer } from '@/ui/layout/show-page/components/ShowPageRightContainer';
|
||||||
@ -33,14 +32,7 @@ export function PersonShow() {
|
|||||||
title={person?.displayName ?? 'No name'}
|
title={person?.displayName ?? 'No name'}
|
||||||
date={person?.createdAt ?? ''}
|
date={person?.createdAt ?? ''}
|
||||||
/>
|
/>
|
||||||
<PropertyBox extraPadding={true}>
|
{person && <PersonPropertyBox person={person} />}
|
||||||
<>
|
|
||||||
<PropertyBoxItem
|
|
||||||
icon={<IconLink />}
|
|
||||||
value={person?.firstName ?? 'No First name'}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
</PropertyBox>
|
|
||||||
</ShowPageLeftContainer>
|
</ShowPageLeftContainer>
|
||||||
<ShowPageRightContainer>
|
<ShowPageRightContainer>
|
||||||
<Timeline
|
<Timeline
|
||||||
|
|||||||
Reference in New Issue
Block a user