diff --git a/packages/i18n/src/locales/en/translations-extended.json b/packages/i18n/src/locales/en/translations-extended.json index 9f7b1cbbce..710e3d54f0 100644 --- a/packages/i18n/src/locales/en/translations-extended.json +++ b/packages/i18n/src/locales/en/translations-extended.json @@ -912,6 +912,31 @@ }, "customers": { "label": "{count, plural, one {Customer} other {Customers}}", + "properties": { + "default": { + "title": "Default Properties", + "customer_name": "Customer Name", + "description": "Description", + "email": "Email", + "website_url": "Website URL", + "employees": "Employees", + "domain": "Domain", + "stage": "Stage", + "contract_status": "Contract Status", + "revenue": "Revenue" + }, + "custom": { + "title": "Custom Properties", + "info": "Customize your customer properties here. Choose from different property types to aggregate your customers data." + }, + "empty_state": { + "title": "Add custom properties", + "description": "New properties you add for the customer will show here" + }, + "add": { + "primary_button": "Add new property" + } + }, "settings": { "unauthorized": "You are not authorized to access this page.", "description": "Track and manage customer relationships in your workflow.", diff --git a/packages/i18n/src/locales/es/translations-extended.json b/packages/i18n/src/locales/es/translations-extended.json index 2c03848476..382b4dc369 100644 --- a/packages/i18n/src/locales/es/translations-extended.json +++ b/packages/i18n/src/locales/es/translations-extended.json @@ -919,6 +919,31 @@ }, "customers": { "label": "{count, plural, one {Cliente} other {Clientes}}", + "properties": { + "default": { + "title": "Propiedades predeterminadas", + "customer_name": "Nombre del cliente", + "description": "Descripción", + "email": "Correo electrónico", + "website_url": "URL del sitio web", + "employees": "Empleados", + "domain": "Dominio", + "stage": "Etapa", + "contract_status": "Estado del contrato", + "revenue": "Ingresos" + }, + "custom": { + "title": "Propiedades personalizadas", + "info": "Personaliza las propiedades de tus clientes aquí. Elige entre diferentes tipos de propiedades para agregar los datos de tus clientes." + }, + "empty_state": { + "title": "Agregar propiedades personalizadas", + "description": "Las nuevas propiedades que agregues para el cliente se mostrarán aquí" + }, + "add": { + "primary_button": "Agregar nueva propiedad" + } + }, "settings": { "unauthorized": "No está autorizado para acceder a esta página.", "description": "Realice seguimiento y gestione las relaciones con los clientes en su flujo de trabajo.", diff --git a/packages/i18n/src/locales/fr/translations-extended.json b/packages/i18n/src/locales/fr/translations-extended.json index 6122e1ff6a..1379a3f05d 100644 --- a/packages/i18n/src/locales/fr/translations-extended.json +++ b/packages/i18n/src/locales/fr/translations-extended.json @@ -919,6 +919,31 @@ }, "customers": { "label": "{count, plural, one {Client} other {Clients}}", + "properties": { + "default": { + "title": "Propriétés par défaut", + "customer_name": "Nom du client", + "description": "Description", + "email": "Email", + "website_url": "URL du site web", + "employees": "Employés", + "domain": "Domaine", + "stage": "Étape", + "contract_status": "Statut du contrat", + "revenue": "Revenus" + }, + "custom": { + "title": "Propriétés personnalisées", + "info": "Personnalisez vos propriétés client ici. Choisissez parmi différents types de propriétés pour agréger les données de vos clients." + }, + "empty_state": { + "title": "Ajouter des propriétés personnalisées", + "description": "Les nouvelles propriétés que vous ajoutez pour le client apparaîtront ici" + }, + "add": { + "primary_button": "Ajouter une nouvelle propriété" + } + }, "settings": { "unauthorized": "Vous n'êtes pas autorisé à accéder à cette page.", "description": "Suivez et gérez les relations clients dans votre flux de travail.", diff --git a/packages/i18n/src/locales/ja/translations-extended.json b/packages/i18n/src/locales/ja/translations-extended.json index e6aef17bec..824e021291 100644 --- a/packages/i18n/src/locales/ja/translations-extended.json +++ b/packages/i18n/src/locales/ja/translations-extended.json @@ -919,6 +919,31 @@ }, "customers": { "label": "{count, plural, one {顧客} other {顧客}}", + "properties": { + "default": { + "title": "デフォルトプロパティ", + "customer_name": "顧客名", + "description": "説明", + "email": "メール", + "website_url": "ウェブサイトURL", + "employees": "従業員数", + "domain": "ドメイン", + "stage": "ステージ", + "contract_status": "契約状況", + "revenue": "収益" + }, + "custom": { + "title": "カスタムプロパティ", + "info": "ここで顧客プロパティをカスタマイズできます。異なるプロパティタイプから選択して、顧客データを集計します。" + }, + "empty_state": { + "title": "カスタムプロパティを追加", + "description": "顧客に追加した新しいプロパティがここに表示されます" + }, + "add": { + "primary_button": "新しいプロパティを追加" + } + }, "settings": { "unauthorized": "このページにアクセスする権限がありません。", "description": "ワークフローで顧客関係を追跡および管理します。", diff --git a/packages/i18n/src/locales/zh-CN/translations-extended.json b/packages/i18n/src/locales/zh-CN/translations-extended.json index ace565a324..d24d68659d 100644 --- a/packages/i18n/src/locales/zh-CN/translations-extended.json +++ b/packages/i18n/src/locales/zh-CN/translations-extended.json @@ -919,6 +919,31 @@ }, "customers": { "label": "{count, plural, one {客户} other {客户}}", + "properties": { + "default": { + "title": "默认属性", + "customer_name": "客户名称", + "description": "描述", + "email": "电子邮件", + "website_url": "网站链接", + "employees": "员工数", + "domain": "域名", + "stage": "阶段", + "contract_status": "合同状态", + "revenue": "收入" + }, + "custom": { + "title": "自定义属性", + "info": "在此处自定义您的客户属性。从不同的属性类型中选择以汇总您的客户数据。" + }, + "empty_state": { + "title": "添加自定义属性", + "description": "您为客户添加的新属性将显示在此处" + }, + "add": { + "primary_button": "添加新属性" + } + }, "settings": { "unauthorized": "您无权访问此页面。", "description": "在工作流程中跟踪和管理客户关系。", diff --git a/packages/ui/src/icons/buildings-icon.tsx b/packages/ui/src/icons/buildings-icon.tsx new file mode 100644 index 0000000000..6a3a34d70d --- /dev/null +++ b/packages/ui/src/icons/buildings-icon.tsx @@ -0,0 +1,14 @@ +import * as React from "react"; + +import { ISvgIcons } from "./type"; + +export const BuildingsIcon: React.FC = ({ className = "text-current", ...rest }) => ( + + + +); diff --git a/packages/ui/src/icons/index.ts b/packages/ui/src/icons/index.ts index 15e0fa6cb0..6b9595a1ac 100644 --- a/packages/ui/src/icons/index.ts +++ b/packages/ui/src/icons/index.ts @@ -63,3 +63,4 @@ export * from "./multiple-sticky"; export * from "./sticky-note-icon"; export * from "./bar-icon"; export * from "./tree-map-icon"; +export * from "./buildings-icon"; diff --git a/web/app/[workspaceSlug]/(projects)/settings/customers/page.tsx b/web/app/[workspaceSlug]/(projects)/settings/customers/page.tsx index 0e9efef681..e4f9a98196 100644 --- a/web/app/[workspaceSlug]/(projects)/settings/customers/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/settings/customers/page.tsx @@ -91,8 +91,8 @@ const CustomerSettingsPage = observer(() => { <> } workspaceSlug={workspaceSlug?.toString()}> -
-
+
+

{t("customers.label", { count: 2 })}

{t("customers.settings.description")} diff --git a/web/ee/components/customers/settings/index.ts b/web/ee/components/customers/settings/index.ts index 0ec07f8c18..72d0524d47 100644 --- a/web/ee/components/customers/settings/index.ts +++ b/web/ee/components/customers/settings/index.ts @@ -1,3 +1,4 @@ export * from "./root"; export * from "./disabled"; export * from "./loader"; +export * from "./properties"; diff --git a/web/ee/components/customers/settings/properties/custom-properties.tsx b/web/ee/components/customers/settings/properties/custom-properties.tsx new file mode 100644 index 0000000000..c2b447a8cd --- /dev/null +++ b/web/ee/components/customers/settings/properties/custom-properties.tsx @@ -0,0 +1,117 @@ +import React, { FC, useRef, useState } from "react"; +import { v4 } from "uuid"; +import { ChevronRight, Plus } from "lucide-react"; +import { EIssuePropertyType } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; +import { TCreationListModes, TIssueProperty } from "@plane/types"; +import { Button, Collapsible } from "@plane/ui"; +import { cn } from "@/helpers/common.helper"; +import { CustomerPropertiesEmptyState } from "./empty-state"; +import { IssuePropertyList } from "@/plane-web/components/issue-types"; + +export type TCustomerPropertyCreateList = Partial> & { + key: string; +}; + +export const defaultCustomProperty: Partial> = { + id: undefined, + display_name: "", + property_type: undefined, + relation_type: undefined, + is_multi: false, + is_active: false, + is_required: false, +}; + +export const CustomerCustomPropertiesRoot: FC = () => { + // states + const [isOpen, setIsOpen] = useState(true); + const [customerPropertyCreateList, setCustomerPropertyCreateList] = useState([]); + // hooks + const { t } = useTranslation(); + + // derived + const isAnyPropertiesAvailable = customerPropertyCreateList.length > 0; + + // refs + const containerRef = useRef(null); + const lastElementRef = useRef(null); + + // handlers + const handleCustomerPropertiesCreate = (mode: TCreationListModes, value: TCustomerPropertyCreateList) => { + switch (mode) { + case "add": + setCustomerPropertyCreateList((prevValue) => { + prevValue = prevValue ? [...prevValue] : []; + return [...prevValue, value]; + }); + break; + case "remove": + if (value) { + setCustomerPropertyCreateList((prevValue) => { + prevValue = prevValue ? [...prevValue] : []; + return prevValue.filter((item) => item.key !== value.key); + }); + } + break; + default: + break; + } + }; + return ( +
+ setIsOpen(!isOpen)} + title={ +
+
+ +
+
+

{t("customers.properties.custom.title")}

+

{t("customers.properties.custom.info")}

+
+
+ } + buttonClassName={cn("flex w-full py-3 gap-2 items-center justify-between")} + > +
+ {isAnyPropertiesAvailable ? ( + <> + +
+ +
+ + ) : ( + + )} +
+
+
+ ); +}; diff --git a/web/ee/components/customers/settings/properties/default-properties.tsx b/web/ee/components/customers/settings/properties/default-properties.tsx new file mode 100644 index 0000000000..720109abea --- /dev/null +++ b/web/ee/components/customers/settings/properties/default-properties.tsx @@ -0,0 +1,86 @@ +import React, { useState } from "react"; +import { AlignLeft, ChevronRight, Hash, CircleChevronDown } from "lucide-react"; +import { useTranslation } from "@plane/i18n"; +import { Collapsible } from "@plane/ui"; +import { cn } from "@/helpers/common.helper"; + +const DEFAULT_PROPERTIES_LIST = [ + { + i18n_title: "customers.properties.default.customer_name", + icon: AlignLeft, + }, + { + i18n_title: "customers.properties.default.description", + icon: AlignLeft, + }, + { + i18n_title: "customers.properties.default.email", + icon: AlignLeft, + }, + { + i18n_title: "customers.properties.default.website_url", + icon: AlignLeft, + }, + { + i18n_title: "customers.properties.default.employees", + icon: Hash, + }, + { + i18n_title: "customers.properties.default.domain", + icon: AlignLeft, + }, + { + i18n_title: "customers.properties.default.stage", + icon: CircleChevronDown, + }, + { + i18n_title: "customers.properties.default.contract_status", + icon: CircleChevronDown, + }, + { + i18n_title: "customers.properties.default.revenue", + icon: Hash, + }, +]; + +export const CustomerDefaultProperties = () => { + // states + const [isOpen, setIsOpen] = useState(true); + // hooks + const { t } = useTranslation(); + return ( +
+ setIsOpen(!isOpen)} + title={ +
+
+ +
+
+

{t("customers.properties.default.title")}

+
+
+ } + buttonClassName={cn("flex w-full py-3 gap-2 items-center justify-between")} + > +
+ {DEFAULT_PROPERTIES_LIST.map((property) => ( +
+ +

{t(property.i18n_title)}

+
+ ))} +
+
+
+ ); +}; diff --git a/web/ee/components/customers/settings/properties/empty-state.tsx b/web/ee/components/customers/settings/properties/empty-state.tsx new file mode 100644 index 0000000000..b371b932f1 --- /dev/null +++ b/web/ee/components/customers/settings/properties/empty-state.tsx @@ -0,0 +1,39 @@ +import React, { FC } from "react"; +import { useTranslation } from "@plane/i18n"; +import { BuildingsIcon, Button } from "@plane/ui"; +import { Plus } from "lucide-react"; +import { TCreationListModes } from "@plane/types"; +import { TIssuePropertyCreateList } from "@/plane-web/components/issue-types"; +import { defaultCustomProperty } from "@/plane-web/components/customers/settings"; +import { v4 } from "uuid"; + +type TCustomerPropertiesEmptyStateProps = { + handleCustomerPropertiesCreate: (mode: TCreationListModes, value: TIssuePropertyCreateList) => void; +}; + +export const CustomerPropertiesEmptyState: FC = (props) => { + const { handleCustomerPropertiesCreate } = props; + const { t } = useTranslation(); + return ( +
+
+
+ +
+
{t("customers.properties.empty_state.title")}
+
{t("customers.properties.empty_state.description")}
+ +
+
+ ); +}; diff --git a/web/ee/components/customers/settings/properties/index.tsx b/web/ee/components/customers/settings/properties/index.tsx new file mode 100644 index 0000000000..2aa0dfd8ac --- /dev/null +++ b/web/ee/components/customers/settings/properties/index.tsx @@ -0,0 +1,3 @@ +export * from "./root"; +export * from "./default-properties"; +export * from "./custom-properties"; diff --git a/web/ee/components/customers/settings/properties/root.tsx b/web/ee/components/customers/settings/properties/root.tsx new file mode 100644 index 0000000000..b0983b3be0 --- /dev/null +++ b/web/ee/components/customers/settings/properties/root.tsx @@ -0,0 +1,16 @@ +import React, { FC } from "react"; +import { CustomerDefaultProperties, CustomerCustomPropertiesRoot } from "@/plane-web/components/customers/settings"; +type TCustomPropertiesRootProps = { + workspaceSlug: string; +}; + +export const CustomerPropertiesRoot: FC = (props) => { + const { workspaceSlug } = props; + + return ( + <> + + + + ); +}; diff --git a/web/ee/components/customers/settings/root.tsx b/web/ee/components/customers/settings/root.tsx index c850b4e91d..37fe76ff35 100644 --- a/web/ee/components/customers/settings/root.tsx +++ b/web/ee/components/customers/settings/root.tsx @@ -1,5 +1,9 @@ import { FC } from "react"; -import { CustomerSettingsDisabled, CustomerPropertiesLoader } from "@/plane-web/components/customers/settings"; +import { + CustomerSettingsDisabled, + CustomerPropertiesLoader, + CustomerPropertiesRoot, +} from "@/plane-web/components/customers/settings"; import { useWorkspaceFeatures } from "@/plane-web/hooks/store"; type TCustomerSettingsRoot = { workspaceSlug: string; @@ -9,16 +13,16 @@ type TCustomerSettingsRoot = { }; export const CustomerSettingsRoot: FC = (props) => { - const { toggleCustomersFeature, isCustomersFeatureEnabled } = props; + const { toggleCustomersFeature, isCustomersFeatureEnabled, workspaceSlug } = props; const { loader } = useWorkspaceFeatures(); return ( -
+
{isCustomersFeatureEnabled ? ( loader ? ( ) : ( - <> + ) ) : (