From d59d5dcddbf1e67ba1dfc1a819c84be7f0b0c3ee Mon Sep 17 00:00:00 2001 From: Kato-101 Date: Thu, 3 Jul 2025 13:42:28 +0800 Subject: [PATCH 01/18] backup --- .../company-detail/CompanyDetail.tsx | 24 ++++ .../company-detail/CompanyDetailGeneral.tsx | 7 ++ .../company-detail/CompanyDetailLayout.tsx | 88 +++++++++++++++ .../company-detail/CompanyDetailSheet.tsx | 45 ++++++++ .../companies/company-edit/PhoneField.tsx | 48 -------- .../companies/components/CompanyColumns.tsx | 22 ++-- .../graphql/queries/getCompanyDetail.ts | 30 +++++ .../companies/hooks/useCompanyDetail.tsx | 27 +++++ .../customers/components/CustomersColumns.tsx | 24 +++- .../components/CustomerDetailSelectTag.tsx | 4 +- .../customer-edit/hooks/useCustomerEdit.tsx | 9 +- .../customers/hooks/useCustomerVariables.tsx | 0 .../contacts/customers/hooks/useCustomers.tsx | 105 +++++++++--------- .../customers/hooks/useEditCustomer.tsx | 1 - .../customers/hooks/useRemoveCustomers.tsx | 4 +- .../contacts/states/companyDetailStates.ts | 3 + .../src/pages/contacts/CompaniesIndexPage.tsx | 2 + .../modules/hotkey/hooks/useScopedHotkeys.tsx | 2 +- .../modules/inputs/components/EmailField.tsx | 25 +++-- .../modules/inputs/components/PhoneField.tsx | 4 +- .../inputs/contexts/EmailFieldsContext.tsx | 3 +- .../inputs/contexts/PhoneFieldsContext.tsx | 3 +- .../modules/inputs/hooks/useEmailFields.tsx | 4 +- .../components/RecordTableCellInline.tsx | 16 ++- 24 files changed, 359 insertions(+), 141 deletions(-) create mode 100644 frontend/core-ui/src/modules/contacts/companies/company-detail/CompanyDetail.tsx create mode 100644 frontend/core-ui/src/modules/contacts/companies/company-detail/CompanyDetailGeneral.tsx create mode 100644 frontend/core-ui/src/modules/contacts/companies/company-detail/CompanyDetailLayout.tsx create mode 100644 frontend/core-ui/src/modules/contacts/companies/company-detail/CompanyDetailSheet.tsx delete mode 100644 frontend/core-ui/src/modules/contacts/companies/company-edit/PhoneField.tsx create mode 100644 frontend/core-ui/src/modules/contacts/companies/graphql/queries/getCompanyDetail.ts create mode 100644 frontend/core-ui/src/modules/contacts/companies/hooks/useCompanyDetail.tsx delete mode 100644 frontend/core-ui/src/modules/contacts/customers/hooks/useCustomerVariables.tsx create mode 100644 frontend/core-ui/src/modules/contacts/states/companyDetailStates.ts diff --git a/frontend/core-ui/src/modules/contacts/companies/company-detail/CompanyDetail.tsx b/frontend/core-ui/src/modules/contacts/companies/company-detail/CompanyDetail.tsx new file mode 100644 index 0000000000..8f7c69755a --- /dev/null +++ b/frontend/core-ui/src/modules/contacts/companies/company-detail/CompanyDetail.tsx @@ -0,0 +1,24 @@ +import { Separator, Sheet } from 'erxes-ui'; +import { + CompanyDetailLayout, + CompanyDetailTabContent, +} from '@/contacts/companies/company-detail/CompanyDetailLayout'; + +export const CompanyDetail = () => { + return ( + +
+ + {/* + + + + + + + */} + +
+
+ ); +}; diff --git a/frontend/core-ui/src/modules/contacts/companies/company-detail/CompanyDetailGeneral.tsx b/frontend/core-ui/src/modules/contacts/companies/company-detail/CompanyDetailGeneral.tsx new file mode 100644 index 0000000000..63499686ce --- /dev/null +++ b/frontend/core-ui/src/modules/contacts/companies/company-detail/CompanyDetailGeneral.tsx @@ -0,0 +1,7 @@ +import React from 'react' + +export const CompanyDetailGeneral = () => { + return ( +
CompanyDetailGeneral
+ ) +} \ No newline at end of file diff --git a/frontend/core-ui/src/modules/contacts/companies/company-detail/CompanyDetailLayout.tsx b/frontend/core-ui/src/modules/contacts/companies/company-detail/CompanyDetailLayout.tsx new file mode 100644 index 0000000000..eae1dac2a0 --- /dev/null +++ b/frontend/core-ui/src/modules/contacts/companies/company-detail/CompanyDetailLayout.tsx @@ -0,0 +1,88 @@ +import { Resizable, Sidebar, Tabs, useQueryState } from 'erxes-ui'; +import { CompanyDetailSheet } from '@/contacts/companies/company-detail/CompanyDetailSheet'; +import * as TabsPrimitive from '@radix-ui/react-tabs'; + +export const CompanyDetailLayout = ({ + children, + actions, +}: { + children: React.ReactNode; + actions?: React.ReactNode; +}) => { + return ( + +
+
+ + + {children} + + {actions} + +
+
+
+ ); +}; + +const CompanyDetailTabs = ({ children }: { children: React.ReactNode }) => { + const [selectedTab, setSelectedTab] = useQueryState('tab'); + + return ( + + + + + General + + + + + + Overview + + + + + + Plugins + + + + + Properties + + + + + + + + + + {children} + + ); +}; + +export const CompanyDetailTabContent = ({ + children, + value, +}: { + children: React.ReactNode; + value: string; +}) => { + return ( + + {children} + + ); +}; diff --git a/frontend/core-ui/src/modules/contacts/companies/company-detail/CompanyDetailSheet.tsx b/frontend/core-ui/src/modules/contacts/companies/company-detail/CompanyDetailSheet.tsx new file mode 100644 index 0000000000..c941dc6dc2 --- /dev/null +++ b/frontend/core-ui/src/modules/contacts/companies/company-detail/CompanyDetailSheet.tsx @@ -0,0 +1,45 @@ +import { IconLayoutSidebarLeftCollapse } from '@tabler/icons-react'; + +import { Button, Sheet, useQueryState, cn } from 'erxes-ui'; +import { useAtomValue } from 'jotai'; +import { renderingCompanyDetailAtom } from '@/contacts/states/companyDetailStates'; +import { usePreviousHotkeyScope } from 'erxes-ui'; +export const CompanyDetailSheet = ({ + children, +}: { + children: React.ReactNode; +}) => { + const [open, setOpen] = useQueryState('contactId'); + const { goBackToPreviousHotkeyScope } = usePreviousHotkeyScope(); + + const activeTab = useAtomValue(renderingCompanyDetailAtom); + + return ( + { + setOpen(null); + goBackToPreviousHotkeyScope(); + }} + > + + + + Company Detail + + + Company Detail + + + {children} + + + ); +}; diff --git a/frontend/core-ui/src/modules/contacts/companies/company-edit/PhoneField.tsx b/frontend/core-ui/src/modules/contacts/companies/company-edit/PhoneField.tsx deleted file mode 100644 index e3667fe72c..0000000000 --- a/frontend/core-ui/src/modules/contacts/companies/company-edit/PhoneField.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { Phone } from 'erxes-ui'; -import { useCompaniesEdit } from '../hooks/useCompaniesEdit'; -import { useToast } from 'erxes-ui'; -import { ApolloError } from '@apollo/client'; -import { CountryCode } from 'libphonenumber-js'; -export const PhoneField = ({ - _id, - primaryPhone, - phones = [], - className, - fieldId, - defaultCountryCode, -}: { - _id: string; - primaryPhone: string; - phones: string[]; - className?: string; - fieldId?: string; - defaultCountryCode?: CountryCode | undefined; -}) => { - const { companiesEdit } = useCompaniesEdit(); - const { toast } = useToast(); - return ( - { - companiesEdit( - { - variables: { _id, primaryPhone: mainPhone, phones: restPhones }, - onError: (e: ApolloError) => { - toast({ - title: 'Error', - description: e.message, - }); - }, - onCompleted, - }, - ['primaryPhone', 'phones'], - ); - }} - /> - ); -}; diff --git a/frontend/core-ui/src/modules/contacts/companies/components/CompanyColumns.tsx b/frontend/core-ui/src/modules/contacts/companies/components/CompanyColumns.tsx index df7b6e4048..ee5373343e 100644 --- a/frontend/core-ui/src/modules/contacts/companies/components/CompanyColumns.tsx +++ b/frontend/core-ui/src/modules/contacts/companies/components/CompanyColumns.tsx @@ -5,9 +5,9 @@ import { IconUser, } from '@tabler/icons-react'; import type { ColumnDef } from '@tanstack/react-table'; - import { Avatar, + Badge, EmailDisplay, EmailListField, Input, @@ -20,6 +20,7 @@ import { RecordTableCellTrigger, RecordTablePopover, TextOverflowTooltip, + useQueryState, } from 'erxes-ui'; import { useCompaniesEdit } from '@/contacts/companies/hooks/useCompaniesEdit'; import { TCompany } from '@/contacts/types/companyType'; @@ -27,13 +28,10 @@ import { ContactsHotKeyScope } from '@/contacts/types/ContactsHotKeyScope'; import { ApolloError } from '@apollo/client'; import { useToast } from 'erxes-ui'; import { SelectMember, SelectTags } from 'ui-modules'; +import { useSetAtom } from 'jotai'; +import { renderingCompanyDetailAtom } from '@/contacts/states/companyDetailStates'; export const companyColumns: ColumnDef[] = [ - { - id: 'more', - cell: () => , - size: 33, - }, RecordTable.checkboxColumn as ColumnDef, { id: 'avatar', @@ -65,9 +63,17 @@ export const companyColumns: ColumnDef[] = [ ), cell: ({ cell }) => { const { primaryName } = cell.row.original; + const setRenderingCompanyDetail = useSetAtom(renderingCompanyDetailAtom); + const [, setCompanyDetail] = useQueryState('companyId'); return ( - {primaryName} + + { + e.stopPropagation(); + setRenderingCompanyDetail(true); + setCompanyDetail(cell.row.original._id); + }}>{primaryName} + @@ -221,7 +227,7 @@ export const companyColumns: ColumnDef[] = [ }); }, }, - ['primaryPhone', 'phones', 'emailValidationStatus'], + ['primaryPhone', 'phones'], ); }} /> diff --git a/frontend/core-ui/src/modules/contacts/companies/graphql/queries/getCompanyDetail.ts b/frontend/core-ui/src/modules/contacts/companies/graphql/queries/getCompanyDetail.ts new file mode 100644 index 0000000000..bf9ed0ef91 --- /dev/null +++ b/frontend/core-ui/src/modules/contacts/companies/graphql/queries/getCompanyDetail.ts @@ -0,0 +1,30 @@ +import { gql } from '@apollo/client'; + + +export const GET_COMPANY_DETAIL = gql`query CompanyDetail($id: String!) { + companyDetail(_id: $id) { + _id + avatar + code + createdAt + primaryEmail + primaryName + primaryPhone + ownerId + customers { + _id + avatar + firstName + lastName + middleName + } + names + emails + owner { + _id + username + } + description + businessType + } +}` \ No newline at end of file diff --git a/frontend/core-ui/src/modules/contacts/companies/hooks/useCompanyDetail.tsx b/frontend/core-ui/src/modules/contacts/companies/hooks/useCompanyDetail.tsx new file mode 100644 index 0000000000..01a027bc2e --- /dev/null +++ b/frontend/core-ui/src/modules/contacts/companies/hooks/useCompanyDetail.tsx @@ -0,0 +1,27 @@ +import { OperationVariables, useQuery } from '@apollo/client'; +import { GET_COMPANY_DETAIL } from '@/contacts/companies/graphql/queries/getCompanyDetail'; +import { renderingCompanyDetailAtom } from '@/contacts/states/companyDetailStates'; +import { useSetAtom } from 'jotai'; +import { useQueryState } from 'erxes-ui'; + +export const useCompanyDetail = (operationVariables?: OperationVariables) => { + const [_id] = useQueryState('contactId'); + const setRendering = useSetAtom(renderingCompanyDetailAtom); + const { data, loading } = useQuery(GET_COMPANY_DETAIL, { + variables: { + id: _id, + }, + skip: !_id, + ...operationVariables, + onCompleted: (data) => { + setRendering(false); + operationVariables?.onCompleted?.(data); + }, + onError: (error) => { + setRendering(false); + operationVariables?.onError?.(error); + }, + }); + + return { companyDetail: data?.companyDetail, loading }; +}; diff --git a/frontend/core-ui/src/modules/contacts/customers/components/CustomersColumns.tsx b/frontend/core-ui/src/modules/contacts/customers/components/CustomersColumns.tsx index 5669dc7460..a277f7237e 100644 --- a/frontend/core-ui/src/modules/contacts/customers/components/CustomersColumns.tsx +++ b/frontend/core-ui/src/modules/contacts/customers/components/CustomersColumns.tsx @@ -138,6 +138,9 @@ export const customersColumns: ColumnDef[] = [ return ( @@ -145,6 +148,23 @@ export const customersColumns: ColumnDef[] = [ { + customersEdit( + { + variables: { + _id, + emailValidationStatus: status === 'verified' ? 'valid' : 'invalid', + }, + onError: (e: ApolloError) => { + toast({ + title: 'Error', + description: e.message, + }); + }, + }, + ['emailValidationStatus'], + ); + }} onValueChange={(newEmails) => { const primaryEmail = newEmails.find((email) => email.isPrimary); let newEmailValidationStatus; @@ -169,7 +189,7 @@ export const customersColumns: ColumnDef[] = [ }); }, }, - ['primaryEmail', 'emails', 'emailValidationStatus'], + ['primaryEmail', 'emails'], ); }} emails={_emails} @@ -248,7 +268,7 @@ export const customersColumns: ColumnDef[] = [ }); }, }, - ['primaryPhone', 'phones', 'emailValidationStatus'], + ['primaryPhone', 'phones'], ); }} /> diff --git a/frontend/core-ui/src/modules/contacts/customers/customer-detail/components/CustomerDetailSelectTag.tsx b/frontend/core-ui/src/modules/contacts/customers/customer-detail/components/CustomerDetailSelectTag.tsx index 512b94aac3..4bea557189 100644 --- a/frontend/core-ui/src/modules/contacts/customers/customer-detail/components/CustomerDetailSelectTag.tsx +++ b/frontend/core-ui/src/modules/contacts/customers/customer-detail/components/CustomerDetailSelectTag.tsx @@ -16,7 +16,7 @@ export const CustomerDetailSelectTag = ({ - ({ @@ -47,7 +47,7 @@ export const CustomerDetailSelectTag = ({ - + ); }; diff --git a/frontend/core-ui/src/modules/contacts/customers/customer-edit/hooks/useCustomerEdit.tsx b/frontend/core-ui/src/modules/contacts/customers/customer-edit/hooks/useCustomerEdit.tsx index dd87d658e8..e811bce7d6 100644 --- a/frontend/core-ui/src/modules/contacts/customers/customer-edit/hooks/useCustomerEdit.tsx +++ b/frontend/core-ui/src/modules/contacts/customers/customer-edit/hooks/useCustomerEdit.tsx @@ -1,6 +1,5 @@ import { OperationVariables, useMutation } from '@apollo/client'; import { CUSTOMERS_EDIT } from '../graphql/mutations/customersEditMutation'; - export const useCustomersEdit = () => { const [_customersEdit, { loading }] = useMutation(CUSTOMERS_EDIT); @@ -17,10 +16,10 @@ export const useCustomersEdit = () => { ...operationVariables, variables, update: (cache, { data: { customersEdit } }) => { - cache.modify({ - id: cache.identify(customersEdit), - fields: fieldsToUpdate, - }); + cache.modify({ + id: cache.identify(customersEdit), + fields: fieldsToUpdate, + }); }, }); }; diff --git a/frontend/core-ui/src/modules/contacts/customers/hooks/useCustomerVariables.tsx b/frontend/core-ui/src/modules/contacts/customers/hooks/useCustomerVariables.tsx deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frontend/core-ui/src/modules/contacts/customers/hooks/useCustomers.tsx b/frontend/core-ui/src/modules/contacts/customers/hooks/useCustomers.tsx index a428396e0a..f4390c1fd6 100644 --- a/frontend/core-ui/src/modules/contacts/customers/hooks/useCustomers.tsx +++ b/frontend/core-ui/src/modules/contacts/customers/hooks/useCustomers.tsx @@ -17,64 +17,70 @@ import { useIsCustomerLeadSessionKey } from './useCustomerLeadSessionKey'; const CUSTOMERS_PER_PAGE = 30; -export const useCustomers = ( - options?: QueryHookOptions>, -) => { + +export const useCustomersVariables = (variables?: QueryHookOptions>['variables']) => { const { isLead } = useIsCustomerLeadSessionKey(); - const setCustomerTotalCount = useSetAtom(customerTotalCountAtom); - // Customer Filter implementation const [{ searchValue, tags, created, updated, lastSeen, brand, birthday }] = - useMultiQueryState<{ - searchValue: string; - tags: string[]; - created: string; - updated: string; - lastSeen: string; - brand: string; - birthday: string; - }>(['searchValue', 'tags', 'created', 'updated', 'lastSeen', 'brand', 'birthday']); - const { sessionKey } = useIsCustomerLeadSessionKey(); + useMultiQueryState<{ + searchValue: string; + tags: string[]; + created: string; + updated: string; + lastSeen: string; + brand: string; + birthday: string; + }>(['searchValue', 'tags', 'created', 'updated', 'lastSeen', 'brand', 'birthday']); +const { sessionKey } = useIsCustomerLeadSessionKey(); - const { cursor } = useRecordTableCursor({ - sessionKey, - }); - - const customersQueryVariables = { - limit: CUSTOMERS_PER_PAGE, - orderBy: { - createdAt: -1, +const { cursor } = useRecordTableCursor({ + sessionKey, +}); + +const customersQueryVariables = { + limit: CUSTOMERS_PER_PAGE, + orderBy: { + createdAt: -1, + }, + cursor, + searchValue: searchValue || undefined, + tagIds: tags || undefined, + brandIds: brand ? [brand] : undefined, + dateFilters: JSON.stringify({ + createdAt: { + gte: parseDateRangeFromString(created)?.from, + lte: parseDateRangeFromString(created)?.to, }, - cursor, - searchValue, - tagIds: tags, - brandIds: [brand], - dateFilters: JSON.stringify({ - createdAt: { - gte: parseDateRangeFromString(created)?.from, - lte: parseDateRangeFromString(created)?.to, - }, - updatedAt: { - gte: parseDateRangeFromString(updated)?.from, - lte: parseDateRangeFromString(updated)?.to, - }, - lastSeenAt: { - gte: parseDateRangeFromString(lastSeen)?.from, - lte: parseDateRangeFromString(lastSeen)?.to, - }, - birthDate: { - gte: parseDateRangeFromString(birthday)?.from, - lte: parseDateRangeFromString(birthday)?.to, - } - }), - type: isLead ? 'lead' : 'customer', - ...options?.variables, - }; + updatedAt: { + gte: parseDateRangeFromString(updated)?.from, + lte: parseDateRangeFromString(updated)?.to, + }, + lastSeenAt: { + gte: parseDateRangeFromString(lastSeen)?.from, + lte: parseDateRangeFromString(lastSeen)?.to, + }, + birthDate: { + gte: parseDateRangeFromString(birthday)?.from, + lte: parseDateRangeFromString(birthday)?.to, + } + }), + type: isLead ? 'lead' : 'customer', + ...variables, +}; +return {customersQueryVariables}; +} + +export const useCustomers = ( + options?: QueryHookOptions>, +) => { + + const setCustomerTotalCount = useSetAtom(customerTotalCountAtom); + // Customer Filter implementation const { data, loading, fetchMore } = useQuery>( GET_CUSTOMERS, { ...options, - variables: customersQueryVariables, + variables: useCustomersVariables(options?.variables)?.customersQueryVariables, }, ); @@ -123,6 +129,5 @@ export const useCustomers = ( handleFetchMore, pageInfo, totalCount, - customersQueryVariables, }; }; diff --git a/frontend/core-ui/src/modules/contacts/customers/hooks/useEditCustomer.tsx b/frontend/core-ui/src/modules/contacts/customers/hooks/useEditCustomer.tsx index 948d855abc..fff411ab1d 100644 --- a/frontend/core-ui/src/modules/contacts/customers/hooks/useEditCustomer.tsx +++ b/frontend/core-ui/src/modules/contacts/customers/hooks/useEditCustomer.tsx @@ -1,5 +1,4 @@ import { MutationHookOptions, useMutation } from '@apollo/client'; - import { EDIT_CUSTOMERS } from '../graphql/mutations/editCustomers'; export const useCustomerEdit = () => { diff --git a/frontend/core-ui/src/modules/contacts/customers/hooks/useRemoveCustomers.tsx b/frontend/core-ui/src/modules/contacts/customers/hooks/useRemoveCustomers.tsx index 59149aebad..2ff67fa0a7 100644 --- a/frontend/core-ui/src/modules/contacts/customers/hooks/useRemoveCustomers.tsx +++ b/frontend/core-ui/src/modules/contacts/customers/hooks/useRemoveCustomers.tsx @@ -2,10 +2,10 @@ import { OperationVariables, useMutation } from '@apollo/client'; import { CUSTOMERS_REMOVE } from '@/contacts/customers/graphql/mutations/removeCustomers'; import { GET_CUSTOMERS } from '@/contacts/customers/graphql/queries/getCustomers'; import { ICustomer } from 'ui-modules'; -import { useCustomers } from '@/contacts/customers/hooks/useCustomers'; +import { useCustomersVariables } from '@/contacts/customers/hooks/useCustomers'; export const useRemoveCustomers = () => { - const { customersQueryVariables } = useCustomers(); + const { customersQueryVariables } = useCustomersVariables(); const [_removeCustomers, { loading }] = useMutation(CUSTOMERS_REMOVE); const removeCustomers = async ( diff --git a/frontend/core-ui/src/modules/contacts/states/companyDetailStates.ts b/frontend/core-ui/src/modules/contacts/states/companyDetailStates.ts new file mode 100644 index 0000000000..c1bd8789ae --- /dev/null +++ b/frontend/core-ui/src/modules/contacts/states/companyDetailStates.ts @@ -0,0 +1,3 @@ +import { atom } from 'jotai'; + +export const renderingCompanyDetailAtom = atom(false); \ No newline at end of file diff --git a/frontend/core-ui/src/pages/contacts/CompaniesIndexPage.tsx b/frontend/core-ui/src/pages/contacts/CompaniesIndexPage.tsx index 9b20d1a8e3..20d3f53690 100644 --- a/frontend/core-ui/src/pages/contacts/CompaniesIndexPage.tsx +++ b/frontend/core-ui/src/pages/contacts/CompaniesIndexPage.tsx @@ -2,6 +2,7 @@ import { CompaniesHeader } from '@/contacts/companies/components/CompaniesHeader import { CompaniesRecordTable } from '@/contacts/companies/components/CompaniesRecordTable'; import { CompaniesFilter } from '@/contacts/companies/components/CompaniesFilter'; import { PageContainer, PageSubHeader } from 'erxes-ui'; +import { CompanyDetail } from '@/contacts/companies/company-detail/CompanyDetail'; export const CompaniesIndexPage = () => { return ( @@ -11,6 +12,7 @@ export const CompaniesIndexPage = () => { + ); }; diff --git a/frontend/libs/erxes-ui/src/modules/hotkey/hooks/useScopedHotkeys.tsx b/frontend/libs/erxes-ui/src/modules/hotkey/hooks/useScopedHotkeys.tsx index 1fe7f3ee0e..9f0320116f 100644 --- a/frontend/libs/erxes-ui/src/modules/hotkey/hooks/useScopedHotkeys.tsx +++ b/frontend/libs/erxes-ui/src/modules/hotkey/hooks/useScopedHotkeys.tsx @@ -6,7 +6,7 @@ import { useScopedHotKeysCallback } from './useScopedHotKeysCallback'; import { isDefined } from 'erxes-ui/utils/isDefined'; import { useHotkeys } from 'react-hotkeys-hook'; -type UseHotkeysOptionsWithoutBuggyOptions = Omit; +export type UseHotkeysOptionsWithoutBuggyOptions = Omit; export const useScopedHotkeys = ( keys: Keys, diff --git a/frontend/libs/erxes-ui/src/modules/inputs/components/EmailField.tsx b/frontend/libs/erxes-ui/src/modules/inputs/components/EmailField.tsx index db4b14af06..7b459ab282 100644 --- a/frontend/libs/erxes-ui/src/modules/inputs/components/EmailField.tsx +++ b/frontend/libs/erxes-ui/src/modules/inputs/components/EmailField.tsx @@ -46,14 +46,16 @@ export const EmailFieldsProvider = ({ recordId, onValueChange, noValidation, + onValidationStatusChange, }: { children: React.ReactNode; recordId: string; onValueChange: (emails: TEmails) => void; noValidation?: boolean; + onValidationStatusChange?: (status: 'verified' | 'unverified') => void; }) => { return ( - + {children} ); @@ -63,11 +65,13 @@ export const EmailListField = ({ recordId, emails, onValueChange, + onValidationStatusChange, noValidation, }: { recordId: string; emails: TEmails; onValueChange: (emails: TEmails) => void; + onValidationStatusChange?: (status: 'verified' | 'unverified') => void; noValidation?: boolean; }) => { const setEmails = useSetAtom(emailsFamilyState(recordId)); @@ -80,7 +84,7 @@ export const EmailListField = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [emails, setEmails]); return ( - +
@@ -142,7 +146,7 @@ const EmailOptions = ({ status, isPrimary, }: IEmailField & { isPrimary?: boolean }) => { - const { recordId, onValueChange, noValidation } = useEmailFields(); + const { recordId, onValueChange, noValidation, onValidationStatusChange } = useEmailFields(); const emails = useAtomValue(emailsFamilyState(recordId)); const setEditingEmail = useSetAtom(editingEmailFamilyState(recordId)); const setShowEmailInput = useSetAtom(showEmailInputFamilyState(recordId)); @@ -161,13 +165,10 @@ const EmailOptions = ({ }; const handleVerificationChange = (value: string) => { - onValueChange?.( - emails.map((e) => { - if (e.email === email) { - return { ...e, status: value as 'verified' | 'unverified' }; - } - return e; - }), + if (noValidation) return; + if (value === status) return; + onValidationStatusChange?.( + value as 'verified' | 'unverified', ); }; const handleDeleteClick = () => { @@ -207,7 +208,7 @@ const EmailOptions = ({ {isPrimary && !noValidation && ( <> - + Verified @@ -330,7 +331,7 @@ const EmailForm = () => { + +
+ + c._id) || []} + onValueChange={(value) => { + customerEdit({ + variables: { + _id, + companies: value, + }, + }); + }} + mode="multiple" + /> +
); }; diff --git a/frontend/core-ui/src/modules/contacts/customers/customer-detail/components/CustomerDetailSelectTag.tsx b/frontend/core-ui/src/modules/contacts/customers/customer-detail/components/CustomerDetailSelectTag.tsx index 4bea557189..42c15119d2 100644 --- a/frontend/core-ui/src/modules/contacts/customers/customer-detail/components/CustomerDetailSelectTag.tsx +++ b/frontend/core-ui/src/modules/contacts/customers/customer-detail/components/CustomerDetailSelectTag.tsx @@ -1,7 +1,6 @@ -import { Button, Combobox, Label, Popover, toast } from 'erxes-ui'; +import { Label, toast } from 'erxes-ui'; import { SelectTags } from 'ui-modules'; -import { IconPlus } from '@tabler/icons-react'; import { ApolloError } from '@apollo/client'; export const CustomerDetailSelectTag = ({ @@ -16,38 +15,29 @@ export const CustomerDetailSelectTag = ({ - ({ update: (cache) => { - cache.modify({ - id: cache.identify({ - __typename: 'Customer', - _id: customerId, - }), - fields: { tagIds: () => newSelectedTagIds }, - }); - }, - onError: (e: ApolloError) => { - toast({ - title: 'Error', - description: e.message, - }); - }, - })} - > - - - - - - - - - + cache.modify({ + id: cache.identify({ + __typename: 'Customer', + _id: customerId, + }), + fields: { tagIds: () => newSelectedTagIds }, + }); + }, + onError: (e: ApolloError) => { + toast({ + title: 'Error', + description: e.message, + }); + }, + })} + /> ); }; diff --git a/frontend/core-ui/src/modules/contacts/customers/customer-detail/components/CustomerGeneral.tsx b/frontend/core-ui/src/modules/contacts/customers/customer-detail/components/CustomerGeneral.tsx index fa723ce6b6..2a91507df1 100644 --- a/frontend/core-ui/src/modules/contacts/customers/customer-detail/components/CustomerGeneral.tsx +++ b/frontend/core-ui/src/modules/contacts/customers/customer-detail/components/CustomerGeneral.tsx @@ -7,22 +7,27 @@ import { CustomerDetailAssignedTo } from '@/contacts/customers/customer-detail/c export const CustomerGeneral = () => { const { customerDetail, loading } = useCustomerDetail(); - const { primaryEmail, primaryPhone, tagIds, ownerId, code, score, _id } = - customerDetail || {}; if (loading) { return ; } + //To do: not found component + if (!customerDetail) { + return
Customer not found
; + } + + const { primaryEmail, primaryPhone, tagIds, ownerId, code, _id } = + customerDetail; return ( <>
- +
{ {primaryEmail} {primaryPhone} - + {/* - + /> + */}
diff --git a/frontend/core-ui/src/modules/contacts/customers/customer-detail/graphql/queries/customerDetailQueries.tsx b/frontend/core-ui/src/modules/contacts/customers/customer-detail/graphql/queries/customerDetailQueries.tsx index 7fe5c272f1..4693b9036b 100644 --- a/frontend/core-ui/src/modules/contacts/customers/customer-detail/graphql/queries/customerDetailQueries.tsx +++ b/frontend/core-ui/src/modules/contacts/customers/customer-detail/graphql/queries/customerDetailQueries.tsx @@ -25,6 +25,12 @@ export const CUSTOMER_DETAIL = gql` primaryPhone score code + companies { + _id + avatar + primaryName + } + __typename } } `; diff --git a/frontend/core-ui/src/modules/contacts/customers/customer-detail/hooks/useCustomerDetail.tsx b/frontend/core-ui/src/modules/contacts/customers/customer-detail/hooks/useCustomerDetail.tsx index 38ab6e44ae..d7e03f8b73 100644 --- a/frontend/core-ui/src/modules/contacts/customers/customer-detail/hooks/useCustomerDetail.tsx +++ b/frontend/core-ui/src/modules/contacts/customers/customer-detail/hooks/useCustomerDetail.tsx @@ -3,11 +3,16 @@ import { CUSTOMER_DETAIL } from '@/contacts/customers/customer-detail/graphql/qu import { renderingCustomerDetailAtom } from '@/contacts/states/customerDetailStates'; import { useSetAtom } from 'jotai'; import { useQueryState } from 'erxes-ui'; +import { ICustomerDetail } from 'ui-modules'; + +interface IUseCustomerDetailResponseData { + customerDetail: ICustomerDetail; +} export const useCustomerDetail = (operationVariables?: OperationVariables) => { const [_id] = useQueryState('contactId'); const setRendering = useSetAtom(renderingCustomerDetailAtom); - const { data, loading } = useQuery(CUSTOMER_DETAIL, { + const { data, loading } = useQuery(CUSTOMER_DETAIL, { variables: { _id, }, diff --git a/frontend/core-ui/src/modules/contacts/customers/graphql/mutations/editCustomers.ts b/frontend/core-ui/src/modules/contacts/customers/graphql/mutations/editCustomers.ts index 327a692652..f4f8b05165 100644 --- a/frontend/core-ui/src/modules/contacts/customers/graphql/mutations/editCustomers.ts +++ b/frontend/core-ui/src/modules/contacts/customers/graphql/mutations/editCustomers.ts @@ -20,6 +20,7 @@ export const EDIT_CUSTOMERS = gql` $description: String $links: JSON $isSubscribed: String + $companies: [String] ) { customersEdit( _id: $_id @@ -40,6 +41,7 @@ export const EDIT_CUSTOMERS = gql` description: $description links: $links isSubscribed: $isSubscribed + companies: $companies ) { _id } diff --git a/frontend/core-ui/src/modules/contacts/customers/hooks/useCustomers.tsx b/frontend/core-ui/src/modules/contacts/customers/hooks/useCustomers.tsx index f4390c1fd6..135c4ebfa2 100644 --- a/frontend/core-ui/src/modules/contacts/customers/hooks/useCustomers.tsx +++ b/frontend/core-ui/src/modules/contacts/customers/hooks/useCustomers.tsx @@ -1,6 +1,6 @@ import { QueryHookOptions, useQuery } from '@apollo/client'; import { GET_CUSTOMERS } from '@/contacts/customers/graphql/queries/getCustomers'; -import { ICustomer } from '@/contacts/types/customerType'; +import { ICustomer } from 'ui-modules'; import { useRecordTableCursor, mergeCursorData, diff --git a/frontend/libs/erxes-ui/src/modules/inputs/components/FullNameField.tsx b/frontend/libs/erxes-ui/src/modules/inputs/components/FullNameField.tsx index 57f371c97b..c600c3e47c 100644 --- a/frontend/libs/erxes-ui/src/modules/inputs/components/FullNameField.tsx +++ b/frontend/libs/erxes-ui/src/modules/inputs/components/FullNameField.tsx @@ -155,11 +155,73 @@ export const FullNameRoot = ({ ); }; +const FullNameDetail = ({ + firstName, + lastName, + onClose, + onClick, + ...props +}: FullNameProps) => { + const [firstNameState, setFirstNameState] = useState(''); + const [lastNameState, setLastNameState] = useState(''); + + useEffect(() => { + setFirstNameState(firstName); + setLastNameState(lastName); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + return ( + { + props.onOpenChange?.(open); + if (!open) { + onClose?.(firstNameState, lastNameState); + } + }} + {...props} + > + + {firstName || lastName ? ( + { + onClick?.(e); + }} + className="font-semibold text-lg" + > + {firstName} {lastName} + + ) : ( + Unnamed customer + )} + + + + ) => { + setFirstNameState(e.target.value); + }} + /> + ) => { + setLastNameState(e.target.value); + }} + /> + + + + ); +}; + const FullNameField = Object.assign(FullNameRoot, { Container: FullNameContainer, Input: FullNameInput, FirstName: FirstNameInput, LastName: LastNameInput, + Detail: FullNameDetail, }); export { FullNameField }; diff --git a/frontend/libs/ui-modules/src/modules/contacts/components/CompaniesInline.tsx b/frontend/libs/ui-modules/src/modules/contacts/components/CompaniesInline.tsx index 25c3f76934..c2d9905b00 100644 --- a/frontend/libs/ui-modules/src/modules/contacts/components/CompaniesInline.tsx +++ b/frontend/libs/ui-modules/src/modules/contacts/components/CompaniesInline.tsx @@ -5,6 +5,7 @@ import { import { Avatar, AvatarProps, + Badge, cn, Combobox, isUndefinedOrNull, @@ -158,6 +159,7 @@ const CompaniesInlineAvatar = ({ className, ...props }: AvatarProps) => { ); }; +CompaniesInlineAvatar.displayName = 'CompaniesInline.Avatar'; const CompaniesInlineTitle = () => { const { companies, loading, placeholder } = useCompaniesInlineContext(); @@ -183,6 +185,7 @@ const CompaniesInlineTitle = () => { /> ); }; +CompaniesInlineTitle.displayName = 'CompaniesInline.Title'; const CompaniesInlineRoot = ({ companyIds, @@ -197,14 +200,81 @@ const CompaniesInlineRoot = ({ placeholder={placeholder} updateCompanies={updateCompanies} > - - + + ); }; +const CompanyNameBadges = ({ + ...props +}: React.ComponentProps) => { + const { companies } = useCompaniesInlineContext(); + return ( +
+ {companies.map((company) => ( + + {company.primaryName} + + ))} +
+ ); +}; +CompanyNameBadges.displayName = 'CompaniesInline.CompanyNameBadges'; + +const CompaniesInlineWithBadges = ({ + companyIds, + companies, + placeholder, + updateCompanies, + badgeClassName, + badgeVariant, +}: Omit & { + badgeClassName?: string; + badgeVariant?: + | 'default' + | 'destructive' + | 'secondary' + | 'success' + | 'warning'; +}) => { + return ( + + + + ); +}; +CompaniesInlineWithBadges.displayName = 'CompaniesInline.WithBadges'; + +const CompaniesInlineWithoutAvatar = ({ + companyIds, + companies, + placeholder, + updateCompanies, +}: Omit) => { + return ( + + + + ); +}; + +CompaniesInlineWithoutAvatar.displayName = 'CompaniesInline.WithoutAvatar'; + export const CompaniesInline = Object.assign(CompaniesInlineRoot, { Provider: CompaniesInlineProvider, Avatar: CompaniesInlineAvatar, Title: CompaniesInlineTitle, + Badges: CompaniesInlineWithBadges, + WithoutAvatar: CompaniesInlineWithoutAvatar, }); diff --git a/frontend/libs/ui-modules/src/modules/contacts/components/SelectCompany.tsx b/frontend/libs/ui-modules/src/modules/contacts/components/SelectCompany.tsx index dcc9d2f73a..fac2f18bb5 100644 --- a/frontend/libs/ui-modules/src/modules/contacts/components/SelectCompany.tsx +++ b/frontend/libs/ui-modules/src/modules/contacts/components/SelectCompany.tsx @@ -13,6 +13,7 @@ import { RecordTableCellContent, RecordTableCellTrigger, } from 'erxes-ui'; +import { IconPlus } from '@tabler/icons-react'; interface SelectCompanyProviderProps { children: React.ReactNode; @@ -213,10 +214,68 @@ const SelectCompanyValue = () => { ); }; +// const SelectCompanyList = () => { +// const { companyIds, companies, setCompanies } = useSelectCompanyContext(); +// return ( +// +// ); +// }; + +const SelectCompanyBadgesView = () => { + const { companyIds, companies, setCompanies } = useSelectCompanyContext(); + return ( + + ); +}; + +const SelectCompanyDetail = ({ + onValueChange, + className, + mode = 'single', + ...props +}: Omit, 'children'> & { + className?: string; +}) => { + const [open, setOpen] = useState(false); + + return ( + { + if (mode === 'single') { + setOpen(false); + } + onValueChange?.(value); + }} + mode={mode} + {...props} + > + + + Add Companies + + + + + + + + + ); +}; + export const SelectCompany = Object.assign(SelectCompanyRoot, { Provider: SelectCompanyProvider, Content: SelectCompanyContent, Item: SelectCompanyCommandItem, InlineCell: SelectCompanyInlineCell, Value: SelectCompanyValue, + Badges: SelectCompanyBadgesView, + Detail: SelectCompanyDetail, }); diff --git a/frontend/libs/ui-modules/src/modules/contacts/components/index.ts b/frontend/libs/ui-modules/src/modules/contacts/components/index.ts index efd1e41fd8..921623c9dc 100644 --- a/frontend/libs/ui-modules/src/modules/contacts/components/index.ts +++ b/frontend/libs/ui-modules/src/modules/contacts/components/index.ts @@ -1,3 +1,4 @@ export * from './SelectCustomer'; export * from './SelectCompany'; export * from './CustomersInline'; +export * from './CompaniesInline'; \ No newline at end of file diff --git a/frontend/libs/ui-modules/src/modules/contacts/types/Customer.ts b/frontend/libs/ui-modules/src/modules/contacts/types/Customer.ts index 8bcdc4b41f..6d97c22a8f 100644 --- a/frontend/libs/ui-modules/src/modules/contacts/types/Customer.ts +++ b/frontend/libs/ui-modules/src/modules/contacts/types/Customer.ts @@ -1,9 +1,12 @@ import { SexCode } from 'erxes-ui'; import { CountryCode } from 'libphonenumber-js'; +import { ICompany } from './Company'; + export interface ICustomerInline { _id: string; firstName?: string; lastName?: string; + middleName?: string; primaryEmail?: string; primaryPhone?: string; avatar?: string; @@ -17,12 +20,20 @@ export interface ICustomer extends ICustomerInline { emails?: string[]; phones?: string[]; tagIds?: string[]; + ownerId?: string; + score?: number; location?: { countryCode?: CountryCode | undefined; }; sex?: SexCode; } +export interface ICustomerDetail extends ICustomer { + companies?: ICompany[]; + position?: string; + department?: string; +} + export enum CustomerType { CUSTOMER = 'customer', COMPANY = 'company', diff --git a/frontend/libs/ui-modules/src/modules/tags/components/SelectTags.tsx b/frontend/libs/ui-modules/src/modules/tags/components/SelectTags.tsx index 34da23eccf..c828d3a9dc 100644 --- a/frontend/libs/ui-modules/src/modules/tags/components/SelectTags.tsx +++ b/frontend/libs/ui-modules/src/modules/tags/components/SelectTags.tsx @@ -67,7 +67,6 @@ export const SelectTagsProvider = ({ }); } }; - return ( +
{selectedTagIds.map((tagId) => ( { if (!tag) return; if (selectedTagIds.includes(tag._id)) { + setSelectedTags(selectedTags.filter((t) => t._id !== tag._id)); + } + if (!selectedTags.includes(tag)) { setSelectedTags([...selectedTags, tag]); } }} @@ -238,7 +240,7 @@ export const TagList = ({ {...props} /> ))} - +
); }; @@ -315,7 +317,7 @@ export const SelectTagsDetail = React.forwardRef< targetIds, tagType, value, - mode, + mode = 'multiple', options, ...props }, @@ -331,13 +333,15 @@ export const SelectTagsDetail = React.forwardRef< {...{ targetIds, tagType, value, mode, options }} > - - - + + Add Tags + + + ); }, diff --git a/frontend/libs/ui-modules/src/modules/team-members/components/SelectMember.tsx b/frontend/libs/ui-modules/src/modules/team-members/components/SelectMember.tsx index 802fdd652e..598b58f8df 100644 --- a/frontend/libs/ui-modules/src/modules/team-members/components/SelectMember.tsx +++ b/frontend/libs/ui-modules/src/modules/team-members/components/SelectMember.tsx @@ -17,7 +17,7 @@ import { } from '../contexts/SelectMemberContext'; import { IUser } from '../types/TeamMembers'; -import { IconUser } from '@tabler/icons-react'; +import { IconPlus, IconUser } from '@tabler/icons-react'; import { MembersInline } from './MembersInline'; import React from 'react'; import { currentUserState } from 'ui-modules/states'; @@ -326,6 +326,48 @@ export const SelectMemberFormItem = ({ ); }; +export const SelectMemberDetail = ({ + onValueChange, + className, + size, + placeholder, + ...props +}: Omit, 'children'> & { + className?: string; + size?: 'lg'; + placeholder?: string; +}) => { + const [open, setOpen] = useState(false); + console.log(value) + return ( + { + onValueChange?.(value); + setOpen(false); + }} + {...props} + > + + + {!props.value ? ( + <> + Add Owner + + ) : ( + + )} + + + + + + + ); +}; + export const SelectMemberRoot = ({ onValueChange, className, @@ -370,4 +412,5 @@ export const SelectMember = Object.assign(SelectMemberRoot, { FilterBar: SelectMemberFilterBar, InlineCell: SelectMemberInlineCell, FormItem: SelectMemberFormItem, + Detail: SelectMemberDetail, }); From 2e5fd2a4fbfdea246918b1e1924fe35c09a16e9d Mon Sep 17 00:00:00 2001 From: Kato-101 Date: Mon, 7 Jul 2025 14:03:51 +0800 Subject: [PATCH 05/18] backup --- .../customers/components/CustomersColumns.tsx | 58 +++++++++---------- .../components/CustomerDetailSelectTag.tsx | 37 +++++++----- .../modules/tags/components/SelectTags.tsx | 35 ++++++----- 3 files changed, 70 insertions(+), 60 deletions(-) diff --git a/frontend/core-ui/src/modules/contacts/customers/components/CustomersColumns.tsx b/frontend/core-ui/src/modules/contacts/customers/components/CustomersColumns.tsx index bfba35651d..36a0f7952e 100644 --- a/frontend/core-ui/src/modules/contacts/customers/components/CustomersColumns.tsx +++ b/frontend/core-ui/src/modules/contacts/customers/components/CustomersColumns.tsx @@ -296,35 +296,35 @@ export const customersColumns: ColumnDef[] = [ }, size: 250, }, - { - id: 'tagIds', - accessorKey: 'tagIds', - header: () => , - cell: ({ cell }) => { - return ( - ({ - update: (cache) => { - cache.modify({ - id: cache.identify({ - __typename: 'Customer', - _id: cell.row.original._id, - }), - fields: { - tagIds: () => newSelectedTagIds, - }, - }); - }, - })} - /> - ); - }, - size: 360, - }, + // { + // id: 'tagIds', + // accessorKey: 'tagIds', + // header: () => , + // cell: ({ cell }) => { + // return ( + // ({ + // update: (cache) => { + // cache.modify({ + // id: cache.identify({ + // __typename: 'Customer', + // _id: cell.row.original._id, + // }), + // fields: { + // tagIds: () => newSelectedTagIds, + // }, + // }); + // }, + // })} + // /> + // ); + // }, + // size: 360, + // }, { id: 'sex', accessorKey: 'sex', diff --git a/frontend/core-ui/src/modules/contacts/customers/customer-detail/components/CustomerDetailSelectTag.tsx b/frontend/core-ui/src/modules/contacts/customers/customer-detail/components/CustomerDetailSelectTag.tsx index 42c15119d2..d1cb4a0c98 100644 --- a/frontend/core-ui/src/modules/contacts/customers/customer-detail/components/CustomerDetailSelectTag.tsx +++ b/frontend/core-ui/src/modules/contacts/customers/customer-detail/components/CustomerDetailSelectTag.tsx @@ -2,6 +2,7 @@ import { Label, toast } from 'erxes-ui'; import { SelectTags } from 'ui-modules'; import { ApolloError } from '@apollo/client'; +import { useState } from 'react'; export const CustomerDetailSelectTag = ({ tagIds, @@ -10,6 +11,7 @@ export const CustomerDetailSelectTag = ({ tagIds: string[]; customerId: string; }) => { + const [tagIdsValue, setTagIdsValue] = useState(tagIds); return (
); diff --git a/frontend/libs/ui-modules/src/modules/tags/components/SelectTags.tsx b/frontend/libs/ui-modules/src/modules/tags/components/SelectTags.tsx index c828d3a9dc..782642cfc0 100644 --- a/frontend/libs/ui-modules/src/modules/tags/components/SelectTags.tsx +++ b/frontend/libs/ui-modules/src/modules/tags/components/SelectTags.tsx @@ -37,23 +37,23 @@ export const SelectTagsProvider = ({ const [selectedTags, setSelectedTags] = useState([]); const handleSelectCallback = (tag: ITag) => { if (!tag) return; - + const isSingleMode = mode === 'single'; const multipleValue = (value as string[]) || []; const isSelected = !isSingleMode && multipleValue.includes(tag._id); - + const newSelectedTagIds = isSingleMode - ? [tag._id] - : isSelected - ? multipleValue.filter((t) => t !== tag._id) - : [...multipleValue, tag._id]; - + ? [tag._id] + : isSelected + ? multipleValue.filter((t) => t !== tag._id) + : [...multipleValue, tag._id]; + const newSelectedTags = isSingleMode - ? [tag] - : isSelected - ? selectedTags.filter((t) => t._id !== tag._id) - : [...selectedTags, tag]; - + ? [tag] + : isSelected + ? selectedTags.filter((t) => t._id !== tag._id) + : [...selectedTags, tag]; + setSelectedTags(newSelectedTags); onValueChange?.(isSingleMode ? tag._id : newSelectedTagIds); if (targetIds) { @@ -67,6 +67,7 @@ export const SelectTagsProvider = ({ }); } }; + console.log({ value, selectedTags }); return ( - + Add Tags - + - + ); }, From 5958f1afcc80348e26b36fe52184df8d499fadef Mon Sep 17 00:00:00 2001 From: Kato-101 Date: Mon, 21 Jul 2025 10:00:22 +0800 Subject: [PATCH 06/18] follow up --- .../modules/team-members/contexts/MembersInlineContext.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/libs/ui-modules/src/modules/team-members/contexts/MembersInlineContext.tsx b/frontend/libs/ui-modules/src/modules/team-members/contexts/MembersInlineContext.tsx index 8a129b7735..b060a16be3 100644 --- a/frontend/libs/ui-modules/src/modules/team-members/contexts/MembersInlineContext.tsx +++ b/frontend/libs/ui-modules/src/modules/team-members/contexts/MembersInlineContext.tsx @@ -1,5 +1,5 @@ import { createContext, useContext } from 'react'; - +import { AvatarProps } from 'erxes-ui'; import { IUser } from '../types/TeamMembers'; export interface IUsersInlineContext { @@ -7,7 +7,7 @@ export interface IUsersInlineContext { loading: boolean; memberIds?: string[]; placeholder: string; - size?: 'lg' | 'sm' | 'xl' | 'default' | 'xs'; + size?: AvatarProps['size']; updateMembers?: (members: IUser[]) => void; } From 76cd8c5a93cb1df8998070c9513ef599827f0383 Mon Sep 17 00:00:00 2001 From: batmnkh2344 Date: Tue, 22 Jul 2025 12:44:46 +0800 Subject: [PATCH 07/18] add missing fields --- .../contacts/db/definitions/customers.ts | 120 +++++++++++++++++- 1 file changed, 119 insertions(+), 1 deletion(-) diff --git a/backend/core-api/src/modules/contacts/db/definitions/customers.ts b/backend/core-api/src/modules/contacts/db/definitions/customers.ts index 31f8fe97b1..e44fc817ad 100644 --- a/backend/core-api/src/modules/contacts/db/definitions/customers.ts +++ b/backend/core-api/src/modules/contacts/db/definitions/customers.ts @@ -1,12 +1,41 @@ import { Schema } from 'mongoose'; -import { CUSTOMER_SELECT_OPTIONS } from 'erxes-api-shared/core-modules'; +import { + CUSTOMER_SELECT_OPTIONS, + customFieldSchema, +} from 'erxes-api-shared/core-modules'; import { mongooseStringRandomId, schemaWrapper } from 'erxes-api-shared/utils'; const getEnum = (fieldName: string): string[] => { return CUSTOMER_SELECT_OPTIONS[fieldName].map((option) => option.value); }; +export const visitorContactSchema = new Schema( + { + email: { type: String, label: 'Email', optional: true }, + phone: { type: String, label: 'Phone', optional: true }, + }, + { _id: false }, +); + +export const locationSchema = new Schema( + { + remoteAddress: { + type: String, + label: 'Remote address', + optional: true, + }, + country: { type: String, label: 'Country', optional: true }, + countryCode: { type: String, label: 'Country code', optional: true }, + city: { type: String, label: 'City', optional: true }, + region: { type: String, label: 'Region', optional: true }, + hostname: { type: String, label: 'Host name', optional: true }, + language: { type: String, label: 'Language', optional: true }, + userAgent: { type: String, label: 'User agent', optional: true }, + }, + { _id: false }, +); + export const customerSchema = schemaWrapper( new Schema( { @@ -122,6 +151,95 @@ export const customerSchema = schemaWrapper( label: 'Tags', }, searchText: { type: String, optional: true }, + + ownerId: { type: String, optional: true }, + position: { + type: String, + optional: true, + label: 'Position', + esType: 'keyword', + }, + department: { type: String, optional: true, label: 'Department' }, + + leadStatus: { + type: String, + enum: getEnum('LEAD_STATUS_TYPES'), + optional: true, + label: 'Lead Status', + esType: 'keyword', + selectOptions: CUSTOMER_SELECT_OPTIONS.LEAD_STATUS_TYPES, + }, + hasAuthority: { + type: String, + optional: true, + default: 'No', + label: 'Has authority', + enum: getEnum('HAS_AUTHORITY'), + selectOptions: CUSTOMER_SELECT_OPTIONS.HAS_AUTHORITY, + }, + relatedIntegrationIds: { + type: [String], + label: 'Related integrations', + esType: 'keyword', + optional: true, + }, + integrationId: { + type: String, + optional: true, + label: 'Integration', + index: true, + esType: 'keyword', + }, + + // Merged customer ids + mergedIds: { type: [String], optional: true }, + + trackedData: { + type: [customFieldSchema], + optional: true, + label: 'Tracked Data', + }, + customFieldsData: { + type: [customFieldSchema], + optional: true, + label: 'Custom fields data', + }, + + location: { + type: locationSchema, + optional: true, + label: 'Location', + }, + + // if customer is not a user then we will contact with this visitor using + // this information + visitorContactInfo: { + type: visitorContactSchema, + optional: true, + label: 'Visitor contact info', + }, + + deviceTokens: { type: [String], default: [] }, + + isOnline: { + type: Boolean, + label: 'Is online', + optional: true, + }, + lastSeenAt: { + type: Date, + label: 'Last seen at', + optional: true, + esType: 'date', + }, + sessionCount: { + type: Number, + label: 'Session count', + optional: true, + esType: 'number', + }, + visitorId: { type: String, optional: true }, + data: { type: Object, optional: true }, }, { timestamps: true, From 191b0e3d0fd382f27bc33bdc574908406719a037 Mon Sep 17 00:00:00 2001 From: Kato-101 Date: Fri, 25 Jul 2025 19:05:40 +0800 Subject: [PATCH 08/18] -ui module improvements and contact detail --- .../companies/company-edit/TextField.tsx | 2 +- .../components/CustomerMoreColumn.tsx | 32 --- .../customers/components/CustomersColumns.tsx | 7 +- .../components/CustomerDetailGeneral.tsx | 2 +- .../components/CustomerGeneral.tsx | 79 +++--- .../customer-edit/components/TextField.tsx | 29 +- .../team-member-edit/FirstNameField.tsx | 4 +- .../record/team-member-edit/TextField.tsx | 2 +- .../team-member-edit/TextFieldDetails.tsx | 2 +- .../modules/inputs/components/PhoneField.tsx | 247 +++++++++++++----- .../record-field/components/NumberField.tsx | 64 +++++ .../record-field/components/TextField.tsx | 95 +++---- .../src/modules/record-field/index.ts | 1 + .../meta-inputs/components/PhoneInput.tsx | 9 +- .../components/AccountCategoriesTable.tsx | 2 +- .../account/components/AccountsColumns.tsx | 4 +- 16 files changed, 378 insertions(+), 203 deletions(-) delete mode 100644 frontend/core-ui/src/modules/contacts/customers/components/CustomerMoreColumn.tsx create mode 100644 frontend/libs/erxes-ui/src/modules/record-field/components/NumberField.tsx diff --git a/frontend/core-ui/src/modules/contacts/companies/company-edit/TextField.tsx b/frontend/core-ui/src/modules/contacts/companies/company-edit/TextField.tsx index f2ecb1a41b..3b9172ecfb 100644 --- a/frontend/core-ui/src/modules/contacts/companies/company-edit/TextField.tsx +++ b/frontend/core-ui/src/modules/contacts/companies/company-edit/TextField.tsx @@ -21,7 +21,7 @@ export const CompanyTextField = ({ placeholder={placeholder} value={value} scope={``} - onValueChange={onSave} + onSave={onSave} /> ); }; diff --git a/frontend/core-ui/src/modules/contacts/customers/components/CustomerMoreColumn.tsx b/frontend/core-ui/src/modules/contacts/customers/components/CustomerMoreColumn.tsx deleted file mode 100644 index 33222c7721..0000000000 --- a/frontend/core-ui/src/modules/contacts/customers/components/CustomerMoreColumn.tsx +++ /dev/null @@ -1,32 +0,0 @@ -// no longer being used -import { Cell, ColumnDef } from '@tanstack/react-table'; -import { RecordTable } from 'erxes-ui'; -import { renderingCustomerDetailAtom } from '@/contacts/states/customerDetailStates'; -import { useSetAtom } from 'jotai'; -import { ICustomer } from '@/contacts/types/customerType'; -import { useQueryState } from 'erxes-ui'; - -export const CustomerMoreColumnCell = ({ - cell, -}: { - cell: Cell; -}) => { - const [, setOpen] = useQueryState('contactId'); - const setRenderingCustomerDetail = useSetAtom(renderingCustomerDetailAtom); - const { _id } = cell.row.original; - return ( - { - setOpen(_id); - setRenderingCustomerDetail(false); - }} - /> - ); -}; - -export const customerMoreColumn: ColumnDef = { - id: 'more', - cell: CustomerMoreColumnCell, - size: 33, -}; diff --git a/frontend/core-ui/src/modules/contacts/customers/components/CustomersColumns.tsx b/frontend/core-ui/src/modules/contacts/customers/components/CustomersColumns.tsx index bfba35651d..c944504bf3 100644 --- a/frontend/core-ui/src/modules/contacts/customers/components/CustomersColumns.tsx +++ b/frontend/core-ui/src/modules/contacts/customers/components/CustomersColumns.tsx @@ -18,8 +18,7 @@ import { EmailDisplay, EmailListField, FullNameField, - PhoneDisplay, - PhoneListField, + PhoneField, RecordTable, RecordTableCellContent, RecordTableCellDisplay, @@ -239,10 +238,10 @@ export const customersColumns: ColumnDef[] = [ scope={ContactsHotKeyScope.CustomersPage + '.' + _id + '.Phones'} > - + - { diff --git a/frontend/core-ui/src/modules/contacts/customers/customer-detail/components/CustomerDetailGeneral.tsx b/frontend/core-ui/src/modules/contacts/customers/customer-detail/components/CustomerDetailGeneral.tsx index a0433c3427..a432f7604c 100644 --- a/frontend/core-ui/src/modules/contacts/customers/customer-detail/components/CustomerDetailGeneral.tsx +++ b/frontend/core-ui/src/modules/contacts/customers/customer-detail/components/CustomerDetailGeneral.tsx @@ -9,7 +9,7 @@ import { useCustomerEdit } from '@/contacts/customers/hooks/useEditCustomer'; import { ContactsHotKeyScope } from '@/contacts/types/ContactsHotKeyScope'; import { SelectCompany } from 'ui-modules'; export const CustomerDetailGeneral = () => { - const { customerDetail } = useCustomerDetail(); + const { customerDetail, loading } = useCustomerDetail(); const { _id, firstName, diff --git a/frontend/core-ui/src/modules/contacts/customers/customer-detail/components/CustomerGeneral.tsx b/frontend/core-ui/src/modules/contacts/customers/customer-detail/components/CustomerGeneral.tsx index dbca6fc096..831e9b1436 100644 --- a/frontend/core-ui/src/modules/contacts/customers/customer-detail/components/CustomerGeneral.tsx +++ b/frontend/core-ui/src/modules/contacts/customers/customer-detail/components/CustomerGeneral.tsx @@ -1,52 +1,67 @@ -import { Label, Skeleton } from 'erxes-ui'; - +import { Label, PhoneInput, Skeleton, Switch, Textarea } from 'erxes-ui'; import { useCustomerDetail } from '@/contacts/customers/customer-detail/hooks/useCustomerDetail'; import { CustomerDetailSelectTag } from '@/contacts/customers/customer-detail/components/CustomerDetailSelectTag'; import { TextFieldCustomer } from '@/contacts/customers/customer-edit/components/TextField'; import { CustomerDetailAssignedTo } from '@/contacts/customers/customer-detail/components/CustomerDetailAssignedTo'; - +import { useDebounce } from 'use-debounce'; +import { useCustomerEdit } from '@/contacts/customers/hooks/useEditCustomer'; +import { useEffect, useState } from 'react'; export const CustomerGeneral = () => { const { customerDetail, loading } = useCustomerDetail(); - - if (loading) { - return ; - } - //To do: not found component + const { customerEdit } = useCustomerEdit(); if (!customerDetail) { - return
Customer not found
; + return
; } const { primaryEmail, primaryPhone, tagIds, ownerId, code, _id, score } = customerDetail; + return ( <>
-
- - - - - +
+
+ + + + + + + +
+ console.log(value)} + className="bg-transparent " + /> +
+
+ + + +
+ + - - + +