mirror of
https://github.com/mudita/mudita-center.git
synced 2025-12-23 14:20:40 -05:00
[CP-3303] duplicated contacts menu (#2654)
This commit is contained in:
@@ -45,7 +45,7 @@ describe("E2E mock sample - overview view", () => {
|
||||
|
||||
const contactsCounter = ContactsKompaktPage.contactsCounter
|
||||
await expect(contactsCounter).toBeDisplayed()
|
||||
await expect(contactsCounter).toHaveText("Contacts (17)")
|
||||
await expect(contactsCounter).toHaveText("All Contacts (17)")
|
||||
await browser.pause(500)
|
||||
})
|
||||
|
||||
@@ -108,6 +108,6 @@ describe("E2E mock sample - overview view", () => {
|
||||
//verify if counter is updated after deleting (number should be deducted by 1)
|
||||
const contactsCounter = ContactsKompaktPage.contactsCounter
|
||||
await expect(contactsCounter).toBeDisplayed()
|
||||
await expect(contactsCounter).toHaveText("Contacts (16)")
|
||||
await expect(contactsCounter).toHaveText("All Contacts (16)")
|
||||
})
|
||||
})
|
||||
|
||||
@@ -45,7 +45,7 @@ describe("E2E mock sample - overview view", () => {
|
||||
|
||||
const contactsCounter = ContactsKompaktPage.contactsCounter
|
||||
await expect(contactsCounter).toBeDisplayed()
|
||||
await expect(contactsCounter).toHaveText("Contacts (17)")
|
||||
await expect(contactsCounter).toHaveText("All Contacts (17)")
|
||||
})
|
||||
|
||||
it("Select first contact's checkbox", async () => {
|
||||
@@ -114,6 +114,6 @@ describe("E2E mock sample - overview view", () => {
|
||||
//verify if counter is updated after deleting (number should be deducted by 1)
|
||||
const contactsCounter = ContactsKompaktPage.contactsCounter
|
||||
await expect(contactsCounter).toBeDisplayed()
|
||||
await expect(contactsCounter).toHaveText("Contacts (16)")
|
||||
await expect(contactsCounter).toHaveText("All Contacts (16)")
|
||||
})
|
||||
})
|
||||
|
||||
@@ -45,7 +45,7 @@ describe("E2E mock sample - overview view", () => {
|
||||
|
||||
const contactsCounter = ContactsKompaktPage.contactsCounter
|
||||
await expect(contactsCounter).toBeDisplayed()
|
||||
await expect(contactsCounter).toHaveText("Contacts (17)")
|
||||
await expect(contactsCounter).toHaveText("All Contacts (17)")
|
||||
})
|
||||
|
||||
it("Select sixth contact to open contact details", async () => {
|
||||
|
||||
@@ -44,7 +44,7 @@ describe("E2E mock sample - overview view", () => {
|
||||
|
||||
const contactsCounter = ContactsKompaktPage.contactsCounter
|
||||
await expect(contactsCounter).toBeDisplayed()
|
||||
await expect(contactsCounter).toHaveText("Contacts (17)")
|
||||
await expect(contactsCounter).toHaveText("All Contacts (17)")
|
||||
await browser.pause(500)
|
||||
})
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ export async function getApiFeaturesAndEntityTypes(
|
||||
const genericFeatures = [
|
||||
"mc-overview",
|
||||
"mc-contacts",
|
||||
"mc-contacts-duplicates",
|
||||
"mc-data-migration",
|
||||
"mc-file-manager-internal",
|
||||
].sort()
|
||||
|
||||
@@ -80,6 +80,20 @@ describe("API configuration", () => {
|
||||
expect(mcContactsMenuItem?.feature).toBe("mc-contacts")
|
||||
expect(mcContactsMenuItem?.icon).toBe("contacts-book")
|
||||
|
||||
const mcContactsDuplicatesMenuItem = mcContactsMenuItem?.submenu?.find(
|
||||
(item) => item.feature === "mc-contacts-duplicates"
|
||||
)
|
||||
if (mcContactsDuplicatesMenuItem !== undefined) {
|
||||
expect(mcContactsMenuItem?.inheritHeaderName).toBeTruthy()
|
||||
expect(mcContactsDuplicatesMenuItem).toBeDefined()
|
||||
expect(mcContactsDuplicatesMenuItem?.displayName).toBe(
|
||||
"Manage Duplicates"
|
||||
)
|
||||
expect(mcContactsDuplicatesMenuItem?.feature).toBe(
|
||||
"mc-contacts-duplicates"
|
||||
)
|
||||
}
|
||||
|
||||
expect(mcDataMigrationMenuItem?.displayName).toBe("Data Migration")
|
||||
expect(mcDataMigrationMenuItem?.feature).toBe("mc-data-migration")
|
||||
expect(mcDataMigrationMenuItem?.icon).toBe("data-migration")
|
||||
|
||||
@@ -25,6 +25,10 @@ import Icon, {
|
||||
import { IconType } from "Core/__deprecated__/renderer/components/core/icon/icon-type"
|
||||
import { intl } from "Core/__deprecated__/renderer/utils/intl"
|
||||
import { selectActiveDeviceMenuElements } from "generic-view/store"
|
||||
import {
|
||||
MenuElement,
|
||||
MenuElementItem,
|
||||
} from "Core/__deprecated__/renderer/constants/menu-elements"
|
||||
|
||||
const messages = defineMessages({
|
||||
backButtonLabel: { id: "module.generic.viewBackButton" },
|
||||
@@ -74,39 +78,26 @@ const Header: FunctionComponent<HeaderProps> = ({
|
||||
}
|
||||
const previousViewName = location?.state?.previousViewName
|
||||
|
||||
const genericMenu = useSelector(
|
||||
// (state: ReduxRootState) => state.genericViews.menu
|
||||
selectActiveDeviceMenuElements
|
||||
)
|
||||
const genericMenu = useSelector(selectActiveDeviceMenuElements)
|
||||
const [currentLocation, setCurrentLocation] = useState<
|
||||
{ id: string } | string
|
||||
>()
|
||||
const [renderHeaderButton, setRenderHeaderButton] = useState(false)
|
||||
useEffect(() => {
|
||||
const pathname = location.pathname
|
||||
const currentMenuElementName = Object.keys(views).find(
|
||||
(key) => views[key as keyof typeof views].url === pathname
|
||||
)
|
||||
const menuElementNameWithHeaderButton = Object.keys(views).find(
|
||||
(key) => views[key as keyof typeof views].renderHeaderButton
|
||||
)
|
||||
if (currentMenuElementName) {
|
||||
const currentMenuElement =
|
||||
views[currentMenuElementName as keyof typeof views]
|
||||
setCurrentLocation(currentMenuElement.label)
|
||||
setRenderHeaderButton(
|
||||
menuElementNameWithHeaderButton === currentMenuElementName
|
||||
)
|
||||
} else if (!previousViewName) {
|
||||
const currentGenericMenuElement = genericMenu
|
||||
?.flatMap((element) => element.items)
|
||||
.find((item) => item?.button.url === pathname)
|
||||
if (currentGenericMenuElement) {
|
||||
setCurrentLocation(currentGenericMenuElement.button.label)
|
||||
setRenderHeaderButton(false)
|
||||
}
|
||||
const label = resolveHeaderLabel(pathname, genericMenu ?? [])
|
||||
if (label !== undefined) {
|
||||
setCurrentLocation(label)
|
||||
}
|
||||
const renderButton =
|
||||
Object.keys(views).find(
|
||||
(key) =>
|
||||
views[key as keyof typeof views].url === pathname &&
|
||||
views[key as keyof typeof views].renderHeaderButton
|
||||
) !== undefined
|
||||
setRenderHeaderButton(renderButton)
|
||||
}, [genericMenu, location, previousViewName])
|
||||
|
||||
return (
|
||||
<HeaderWrapper>
|
||||
{previousViewName ? (
|
||||
@@ -137,4 +128,42 @@ const Header: FunctionComponent<HeaderProps> = ({
|
||||
)
|
||||
}
|
||||
|
||||
const findLabelInMenuItems = (
|
||||
items: MenuElementItem[],
|
||||
pathname: string
|
||||
): string | undefined => {
|
||||
for (const item of items) {
|
||||
if (item?.items && item.button.inheritHeaderName) {
|
||||
const found = findLabelInMenuItems(item.items, pathname)
|
||||
if (found) return found
|
||||
}
|
||||
if (item?.button?.url === pathname) {
|
||||
const label = item.button.label
|
||||
return typeof label === "string" ? label : label?.id
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
const resolveHeaderLabel = (
|
||||
pathname: string,
|
||||
genericMenu: MenuElement[]
|
||||
): string | undefined => {
|
||||
const viewKey = Object.keys(views).find(
|
||||
(key) => views[key as keyof typeof views].url === pathname
|
||||
)
|
||||
|
||||
if (viewKey) {
|
||||
const label = views[viewKey as keyof typeof views].label
|
||||
if (typeof label === "string") return label
|
||||
if (label?.id) return intl.formatMessage({ id: label.id })
|
||||
return undefined
|
||||
}
|
||||
|
||||
const allItems = genericMenu.flatMap((element) =>
|
||||
Array.isArray(element.items) ? element.items : []
|
||||
)
|
||||
return allItems.length ? findLabelInMenuItems(allItems, pathname) : undefined
|
||||
}
|
||||
|
||||
export default Header
|
||||
|
||||
@@ -52,6 +52,7 @@ export type Views = {
|
||||
}
|
||||
| string
|
||||
url: string
|
||||
inheritHeaderName?: boolean
|
||||
renderHeaderButton?: boolean
|
||||
}
|
||||
}
|
||||
@@ -113,5 +114,5 @@ export const views: Views = {
|
||||
[View.Quotations]: {
|
||||
label: messages.quotations,
|
||||
url: URL_MAIN.quotations,
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@@ -9,12 +9,14 @@ import { z } from "zod"
|
||||
const SubmenuItemConfigValidator = z.object({
|
||||
feature: z.string(),
|
||||
displayName: z.string().optional(),
|
||||
inheritHeaderName: z.boolean().optional(),
|
||||
})
|
||||
|
||||
const MenuItemConfigValidator = z.object({
|
||||
feature: z.string(),
|
||||
displayName: z.string().optional(),
|
||||
icon: z.nativeEnum(IconType).optional(),
|
||||
inheritHeaderName: z.boolean().optional(),
|
||||
submenu: z.array(SubmenuItemConfigValidator).optional(),
|
||||
})
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
featureConfigurationFileManagerInternal,
|
||||
featureConfigurationFileManagerExternal,
|
||||
featureConfigurationOverview,
|
||||
featureConfigurationContactsDuplicates,
|
||||
} from "./feature-configuration-responses"
|
||||
|
||||
//import from "Core/device" breaks usage in e2e
|
||||
@@ -69,6 +70,7 @@ export const DEFAULT_RESPONSES: MocksArrayResponsesMap = {
|
||||
features: [
|
||||
"mc-overview",
|
||||
"mc-contacts",
|
||||
"mc-contacts-duplicates",
|
||||
"mc-file-manager-internal",
|
||||
"mc-file-manager-external",
|
||||
],
|
||||
@@ -107,6 +109,17 @@ export const DEFAULT_RESPONSES: MocksArrayResponsesMap = {
|
||||
feature: "mc-contacts",
|
||||
displayName: "Contacts",
|
||||
icon: "contacts-book",
|
||||
inheritHeaderName: true,
|
||||
submenu: [
|
||||
{
|
||||
feature: "mc-contacts",
|
||||
displayName: "All Contacts",
|
||||
},
|
||||
{
|
||||
feature: "mc-contacts-duplicates",
|
||||
displayName: "Manage Duplicates",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
feature: "mc-file-manager-internal",
|
||||
@@ -148,6 +161,16 @@ export const DEFAULT_RESPONSES: MocksArrayResponsesMap = {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
status: ResponseStatus.Ok,
|
||||
body: featureConfigurationContactsDuplicates,
|
||||
match: {
|
||||
expected: {
|
||||
feature: "mc-contacts-duplicates",
|
||||
lang: "en-US",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
status: ResponseStatus.Ok,
|
||||
body: featureConfigurationOverview,
|
||||
|
||||
@@ -124,7 +124,18 @@ export const featureConfigurationContacts = {
|
||||
entityTypes: ["contacts"],
|
||||
},
|
||||
// @ts-ignore
|
||||
screenTitle: "Contacts",
|
||||
screenTitle: "All Contacts",
|
||||
},
|
||||
}
|
||||
|
||||
export const featureConfigurationContactsDuplicates = {
|
||||
main: {
|
||||
component: "mc-contacts-duplicates-view",
|
||||
config: {
|
||||
entityTypes: ["contacts"],
|
||||
},
|
||||
// @ts-ignore
|
||||
screenTitle: "Manage Duplicates",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Copyright (c) Mudita sp. z o.o. All rights reserved.
|
||||
* For licensing, see https://github.com/mudita/mudita-center/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import { View } from "generic-view/utils"
|
||||
|
||||
// @ts-ignore
|
||||
export const contactsDuplicatesView: View = {
|
||||
main: {
|
||||
component: "mc-contacts-duplicates-view",
|
||||
config: {
|
||||
entityTypes: ["contacts"],
|
||||
},
|
||||
// @ts-ignore
|
||||
screenTitle: "Manage Duplicates",
|
||||
},
|
||||
}
|
||||
@@ -13,6 +13,6 @@ export const contactsView: View = {
|
||||
entityTypes: ["contacts"],
|
||||
},
|
||||
// @ts-ignore
|
||||
screenTitle: "Contacts",
|
||||
screenTitle: "All Contacts",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -3,12 +3,14 @@
|
||||
* For licensing, see https://github.com/mudita/mudita-center/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import { contactsDuplicatesView } from "./contacts-duplicates-view"
|
||||
import { contactsView } from "./contacts-view"
|
||||
import { fileManagerView } from "./file-manager-view"
|
||||
import { mcDataMigrationView } from "./mc-data-migration-view"
|
||||
|
||||
export default {
|
||||
contacts: contactsView,
|
||||
contactsDuplicates: contactsDuplicatesView,
|
||||
fileManager: fileManagerView,
|
||||
["mc-data-migration"]: mcDataMigrationView,
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ import { backupRestore } from "./lib/backup-restore"
|
||||
import { backupRestoreAvailable } from "./lib/backup-restore-available"
|
||||
import { mcImportContactsButton } from "./lib/mc-import-contacts-button"
|
||||
import { mcContactsView } from "./lib/mc-contacts-view"
|
||||
import { mcContactsDuplicatesView } from "./lib/mc-contacts-duplicates-view"
|
||||
import { mcDataMigration } from "./lib/mc-data-migration"
|
||||
import { mcFileManagerView } from "./lib/mc-file-manager"
|
||||
import { incomingFeatureInfo } from "./lib/incoming-feature-info"
|
||||
@@ -128,6 +129,7 @@ export * from "./lib/backup-restore"
|
||||
export * from "./lib/import-contacts"
|
||||
export * from "./lib/mc-import-contacts-button"
|
||||
export * from "./lib/mc-contacts-view"
|
||||
export * from "./lib/mc-contacts-duplicates-view"
|
||||
export * from "./lib/modal-visibility-controller"
|
||||
export * from "./lib/mc-data-migration"
|
||||
export * from "./lib/mc-file-manager"
|
||||
@@ -203,6 +205,7 @@ export default {
|
||||
[importContacts.key]: importContacts,
|
||||
[mcImportContactsButton.key]: mcImportContactsButton,
|
||||
[mcContactsView.key]: mcContactsView,
|
||||
[mcContactsDuplicatesView.key]: mcContactsDuplicatesView,
|
||||
[mcDataMigration.key]: mcDataMigration,
|
||||
[mcFileManagerView.key]: mcFileManagerView,
|
||||
[incomingFeatureInfo.key]: incomingFeatureInfo,
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Copyright (c) Mudita sp. z o.o. All rights reserved.
|
||||
* For licensing, see https://github.com/mudita/mudita-center/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import { z } from "zod"
|
||||
|
||||
const dataValidator = z.undefined()
|
||||
|
||||
const configValidator = z.object({
|
||||
entityTypes: z.array(z.string()).min(1),
|
||||
})
|
||||
|
||||
export type McContactsDuplicatesView = z.infer<typeof configValidator>
|
||||
|
||||
export const mcContactsDuplicatesView = {
|
||||
key: "mc-contacts-duplicates-view",
|
||||
dataValidator,
|
||||
configValidator,
|
||||
} as const
|
||||
@@ -6,6 +6,7 @@
|
||||
import {
|
||||
Feature,
|
||||
mcContactsView,
|
||||
mcContactsDuplicatesView,
|
||||
mcFileManagerView,
|
||||
mcImportContactsButton,
|
||||
} from "generic-view/models"
|
||||
@@ -14,12 +15,16 @@ import { generateMcFileManagerView } from "./mc-file-manager/mc-file-manager-vie
|
||||
import { generateFileManagerData } from "./mc-file-manager/mc-file-manager-data"
|
||||
import { generateMcImportContactsButton } from "./mc-import-contacts-button"
|
||||
import { View } from "generic-view/utils"
|
||||
import { generateMcContactsDuplicatesView } from "./mc-contacts-view/mc-contacts-duplicates-view"
|
||||
|
||||
export * from "./mc-import-contacts-button"
|
||||
export * from "./mc-contacts-view/mc-contacts-view"
|
||||
export * from "./mc-contacts-view/mc-contacts-duplicates-view"
|
||||
export * from "./mc-file-manager/mc-file-manager-view"
|
||||
|
||||
export const generatedViews = {
|
||||
[mcContactsView.key]: generateMcContactsView,
|
||||
[mcContactsDuplicatesView.key]: generateMcContactsDuplicatesView,
|
||||
[mcFileManagerView.key]: generateMcFileManagerView,
|
||||
[mcImportContactsButton.key]: generateMcImportContactsButton,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* Copyright (c) Mudita sp. z o.o. All rights reserved.
|
||||
* For licensing, see https://github.com/mudita/mudita-center/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import { ComponentGenerator, IconType } from "generic-view/utils"
|
||||
import { McContactsDuplicatesView } from "generic-view/models"
|
||||
|
||||
export const generateMcContactsDuplicatesView: ComponentGenerator<
|
||||
McContactsDuplicatesView
|
||||
> = (key, config) => {
|
||||
return {
|
||||
[key]: {
|
||||
component: "block-plain",
|
||||
config: {
|
||||
backgroundColor: "white",
|
||||
},
|
||||
layout: {
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
gridLayout: {
|
||||
rows: ["auto", "1fr"],
|
||||
columns: ["1fr"],
|
||||
},
|
||||
},
|
||||
childrenKeys: ["emptyListWrapper"],
|
||||
},
|
||||
emptyListWrapper: {
|
||||
component: "conditional-renderer",
|
||||
dataProvider: {
|
||||
source: "entities-metadata",
|
||||
entitiesType: "contacts",
|
||||
fields: [
|
||||
{
|
||||
modifier: "length",
|
||||
providerField: "totalDuplicates",
|
||||
componentField: "data.render",
|
||||
condition: "eq",
|
||||
value: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
childrenKeys: ["fullScreenWrapper"],
|
||||
},
|
||||
fullScreenWrapper: {
|
||||
component: "block-plain",
|
||||
childrenKeys: [
|
||||
"emptyStateIcon",
|
||||
"emptyStateText",
|
||||
"importContactsButton",
|
||||
],
|
||||
layout: {
|
||||
flexLayout: {
|
||||
direction: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
rowGap: "24px",
|
||||
},
|
||||
gridPlacement: {
|
||||
row: 1,
|
||||
column: 1,
|
||||
width: 1,
|
||||
height: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
emptyStateIcon: {
|
||||
component: "modal.titleIcon",
|
||||
config: {
|
||||
type: IconType.ContactsBook,
|
||||
},
|
||||
},
|
||||
emptyStateText: {
|
||||
component: "block-plain",
|
||||
childrenKeys: ["emptyStateTitle", "emptyStateDetailText"],
|
||||
layout: {
|
||||
flexLayout: {
|
||||
direction: "column",
|
||||
alignItems: "center",
|
||||
rowGap: "8px",
|
||||
},
|
||||
},
|
||||
},
|
||||
emptyStateTitle: {
|
||||
component: "typography.h3",
|
||||
config: {
|
||||
text: "We couldn't find any duplicates",
|
||||
},
|
||||
},
|
||||
emptyStateDetailText: {
|
||||
component: "typography.p1",
|
||||
config: {
|
||||
text: "If we detect any new duplicates, we'll list them here.",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -53,6 +53,7 @@ const processMenuItem = (item: MenuItemConfig): MenuElementItem => ({
|
||||
icon: getIcon(item.feature, item.icon),
|
||||
button: {
|
||||
label: item.displayName as string,
|
||||
inheritHeaderName: item.inheritHeaderName as boolean,
|
||||
url: `/generic/${item.feature}`,
|
||||
},
|
||||
items: item.submenu?.map((subitem) => processMenuItem(subitem)),
|
||||
|
||||
Reference in New Issue
Block a user