diff --git a/apps/web/modules/ee/contacts/segments/components/edit-segment-modal.tsx b/apps/web/modules/ee/contacts/segments/components/edit-segment-modal.tsx index 472cca0d4b..3eff432ad6 100644 --- a/apps/web/modules/ee/contacts/segments/components/edit-segment-modal.tsx +++ b/apps/web/modules/ee/contacts/segments/components/edit-segment-modal.tsx @@ -4,7 +4,7 @@ import { UsersIcon } from "lucide-react"; import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { TContactAttributeKey } from "@formbricks/types/contact-attribute-key"; -import { TSegment, TSegmentWithSurveyNames } from "@formbricks/types/segment"; +import { TSegment, TSegmentWithSurveyRefs } from "@formbricks/types/segment"; import { SegmentSettings } from "@/modules/ee/contacts/segments/components/segment-settings"; import { Dialog, @@ -15,23 +15,63 @@ import { DialogTitle, } from "@/modules/ui/components/dialog"; import { SegmentActivityTab } from "./segment-activity-tab"; +import { TSegmentActivitySummary } from "./segment-activity-utils"; interface EditSegmentModalProps { environmentId: string; open: boolean; setOpen: (open: boolean) => void; - currentSegment: TSegmentWithSurveyNames; + currentSegment: TSegmentWithSurveyRefs; + activitySummary: TSegmentActivitySummary; segments: TSegment[]; contactAttributeKeys: TContactAttributeKey[]; isContactsEnabled: boolean; isReadOnly: boolean; } +const SegmentSettingsTab = ({ + activitySummary, + contactAttributeKeys, + currentSegment, + environmentId, + isContactsEnabled, + isReadOnly, + segments, + setOpen, +}: Pick< + EditSegmentModalProps, + | "activitySummary" + | "contactAttributeKeys" + | "currentSegment" + | "environmentId" + | "isContactsEnabled" + | "isReadOnly" + | "segments" + | "setOpen" +>) => { + if (!isContactsEnabled) { + return null; + } + + return ( + + ); +}; + export const EditSegmentModal = ({ environmentId, open, setOpen, currentSegment, + activitySummary, contactAttributeKeys, segments, isContactsEnabled, @@ -40,31 +80,25 @@ export const EditSegmentModal = ({ const { t } = useTranslation(); const [activeTab, setActiveTab] = useState(0); - const SettingsTab = () => { - if (isContactsEnabled) { - return ( - - ); - } - - return null; - }; - const tabs = [ { title: t("common.activity"), - children: , + children: , }, { title: t("common.settings"), - children: , + children: ( + + ), }, ]; diff --git a/apps/web/modules/ee/contacts/segments/components/segment-activity-tab.tsx b/apps/web/modules/ee/contacts/segments/components/segment-activity-tab.tsx index b0f37a9e4a..9fe3bc42c0 100644 --- a/apps/web/modules/ee/contacts/segments/components/segment-activity-tab.tsx +++ b/apps/web/modules/ee/contacts/segments/components/segment-activity-tab.tsx @@ -1,19 +1,20 @@ "use client"; import { useTranslation } from "react-i18next"; -import { TSegmentWithSurveyNames } from "@formbricks/types/segment"; +import { TSegmentWithSurveyRefs } from "@formbricks/types/segment"; import { convertDateTimeStringShort } from "@/lib/time"; import { IdBadge } from "@/modules/ui/components/id-badge"; import { Label } from "@/modules/ui/components/label"; +import { TSegmentActivitySummary } from "./segment-activity-utils"; interface SegmentActivityTabProps { - currentSegment: TSegmentWithSurveyNames; + currentSegment: TSegmentWithSurveyRefs; + activitySummary: TSegmentActivitySummary; } -export const SegmentActivityTab = ({ currentSegment }: SegmentActivityTabProps) => { +export const SegmentActivityTab = ({ currentSegment, activitySummary }: SegmentActivityTabProps) => { const { t } = useTranslation(); - - const { activeSurveys, inactiveSurveys } = currentSegment; + const { activeSurveys, inactiveSurveys } = activitySummary; return (
@@ -22,20 +23,20 @@ export const SegmentActivityTab = ({ currentSegment }: SegmentActivityTabProps) {!activeSurveys?.length &&

-

} - {activeSurveys?.map((survey, index) => ( -

- {survey} -

+ {activeSurveys?.map((surveyName) => ( +
+

{surveyName}

+
))}
{!inactiveSurveys?.length &&

-

} - {inactiveSurveys?.map((survey, index) => ( -

- {survey} -

+ {inactiveSurveys?.map((surveyName) => ( +
+

{surveyName}

+
))}
diff --git a/apps/web/modules/ee/contacts/segments/components/segment-activity-utils.ts b/apps/web/modules/ee/contacts/segments/components/segment-activity-utils.ts new file mode 100644 index 0000000000..9dc10d8356 --- /dev/null +++ b/apps/web/modules/ee/contacts/segments/components/segment-activity-utils.ts @@ -0,0 +1,99 @@ +import { TBaseFilters, TSegmentWithSurveyRefs } from "@formbricks/types/segment"; +import { TSurvey } from "@formbricks/types/surveys/types"; + +type TSurveySummary = Pick; +type TReferencingSegmentSurveyGroup = { + segmentId: string; + segmentTitle: string; + surveys: TSurveySummary[]; +}; + +export type TSegmentActivitySummary = { + activeSurveys: string[]; + inactiveSurveys: string[]; +}; + +export const doesSegmentReferenceSegment = (filters: TBaseFilters, targetSegmentId: string): boolean => { + for (const filter of filters) { + const { resource } = filter; + + if (Array.isArray(resource)) { + if (doesSegmentReferenceSegment(resource, targetSegmentId)) { + return true; + } + continue; + } + + if (resource.root.type === "segment" && resource.root.segmentId === targetSegmentId) { + return true; + } + } + + return false; +}; + +export const getReferencingSegments = ( + segments: TSegmentWithSurveyRefs[], + targetSegmentId: string +): TSegmentWithSurveyRefs[] => + segments.filter( + (segment) => + segment.id !== targetSegmentId && doesSegmentReferenceSegment(segment.filters, targetSegmentId) + ); + +export const buildSegmentActivitySummary = ( + directSurveys: TSurveySummary[], + indirectSurveyGroups: TReferencingSegmentSurveyGroup[] +): TSegmentActivitySummary => { + const surveyMap = new Map(); + + for (const survey of directSurveys) { + surveyMap.set(survey.id, survey); + } + + for (const segment of indirectSurveyGroups) { + for (const survey of segment.surveys) { + if (!surveyMap.has(survey.id)) { + surveyMap.set(survey.id, survey); + } + } + } + + const surveys = Array.from(surveyMap.values()); + + return { + activeSurveys: surveys.filter((survey) => survey.status === "inProgress").map((survey) => survey.name), + inactiveSurveys: surveys + .filter((survey) => survey.status === "draft" || survey.status === "paused") + .map((survey) => survey.name), + }; +}; + +export const buildSegmentActivitySummaryFromSegments = ( + currentSegment: TSegmentWithSurveyRefs, + segments: TSegmentWithSurveyRefs[] +): TSegmentActivitySummary => { + const activeSurveyMap = new Map(currentSegment.activeSurveys.map((s) => [s.id, s.name])); + const inactiveSurveyMap = new Map(currentSegment.inactiveSurveys.map((s) => [s.id, s.name])); + const allDirectIds = new Set([...activeSurveyMap.keys(), ...inactiveSurveyMap.keys()]); + + const referencingSegments = getReferencingSegments(segments, currentSegment.id); + for (const segment of referencingSegments) { + for (const survey of segment.activeSurveys) { + if (!allDirectIds.has(survey.id) && !activeSurveyMap.has(survey.id)) { + activeSurveyMap.set(survey.id, survey.name); + } + } + + for (const survey of segment.inactiveSurveys) { + if (!allDirectIds.has(survey.id) && !inactiveSurveyMap.has(survey.id)) { + inactiveSurveyMap.set(survey.id, survey.name); + } + } + } + + return { + activeSurveys: Array.from(activeSurveyMap.values()), + inactiveSurveys: Array.from(inactiveSurveyMap.values()), + }; +}; diff --git a/apps/web/modules/ee/contacts/segments/components/segment-settings.tsx b/apps/web/modules/ee/contacts/segments/components/segment-settings.tsx index 664e2c0fb9..cf55daa0e4 100644 --- a/apps/web/modules/ee/contacts/segments/components/segment-settings.tsx +++ b/apps/web/modules/ee/contacts/segments/components/segment-settings.tsx @@ -6,7 +6,7 @@ import { type Dispatch, type SetStateAction, useMemo, useState } from "react"; import toast from "react-hot-toast"; import { useTranslation } from "react-i18next"; import { TContactAttributeKey } from "@formbricks/types/contact-attribute-key"; -import type { TBaseFilter, TSegment, TSegmentWithSurveyNames } from "@formbricks/types/segment"; +import type { TBaseFilter, TSegment, TSegmentWithSurveyRefs } from "@formbricks/types/segment"; import { ZSegmentFilters } from "@formbricks/types/segment"; import { cn } from "@/lib/cn"; import { structuredClone } from "@/lib/pollyfills/structuredClone"; @@ -16,18 +16,21 @@ import { Button } from "@/modules/ui/components/button"; import { ConfirmDeleteSegmentModal } from "@/modules/ui/components/confirm-delete-segment-modal"; import { Input } from "@/modules/ui/components/input"; import { AddFilterModal } from "./add-filter-modal"; +import { TSegmentActivitySummary } from "./segment-activity-utils"; import { SegmentEditor } from "./segment-editor"; interface TSegmentSettingsTabProps { + activitySummary: TSegmentActivitySummary; environmentId: string; setOpen: (open: boolean) => void; - initialSegment: TSegmentWithSurveyNames; + initialSegment: TSegmentWithSurveyRefs; segments: TSegment[]; contactAttributeKeys: TContactAttributeKey[]; isReadOnly: boolean; } export function SegmentSettings({ + activitySummary, environmentId, initialSegment, setOpen, @@ -38,7 +41,7 @@ export function SegmentSettings({ const router = useRouter(); const { t } = useTranslation(); const [addFilterModalOpen, setAddFilterModalOpen] = useState(false); - const [segment, setSegment] = useState(initialSegment); + const [segment, setSegment] = useState(initialSegment); const [isUpdatingSegment, setIsUpdatingSegment] = useState(false); const [isDeletingSegment, setIsDeletingSegment] = useState(false); @@ -257,9 +260,9 @@ export function SegmentSettings({ {isDeleteSegmentModalOpen ? ( ) : null} diff --git a/apps/web/modules/ee/contacts/segments/components/segment-table-columns.tsx b/apps/web/modules/ee/contacts/segments/components/segment-table-columns.tsx index 685d6f48c7..895b06458e 100644 --- a/apps/web/modules/ee/contacts/segments/components/segment-table-columns.tsx +++ b/apps/web/modules/ee/contacts/segments/components/segment-table-columns.tsx @@ -4,10 +4,10 @@ import { ColumnDef } from "@tanstack/react-table"; import { format, formatDistanceToNow } from "date-fns"; import { TFunction } from "i18next"; import { UsersIcon } from "lucide-react"; -import { TSegmentWithSurveyNames } from "@formbricks/types/segment"; +import { TSegmentWithSurveyRefs } from "@formbricks/types/segment"; -export const generateSegmentTableColumns = (t: TFunction): ColumnDef[] => { - const titleColumn: ColumnDef = { +export const generateSegmentTableColumns = (t: TFunction): ColumnDef[] => { + const titleColumn: ColumnDef = { id: "title", accessorKey: "title", header: t("common.title"), @@ -28,7 +28,7 @@ export const generateSegmentTableColumns = (t: TFunction): ColumnDef = { + const updatedAtColumn: ColumnDef = { id: "updatedAt", accessorKey: "updatedAt", header: t("common.updated_at"), @@ -41,7 +41,7 @@ export const generateSegmentTableColumns = (t: TFunction): ColumnDef = { + const createdAtColumn: ColumnDef = { id: "createdAt", accessorKey: "createdAt", header: t("common.created_at"), diff --git a/apps/web/modules/ee/contacts/segments/components/segment-table-data-row-container.tsx b/apps/web/modules/ee/contacts/segments/components/segment-table-data-row-container.tsx deleted file mode 100644 index 8e33b7089e..0000000000 --- a/apps/web/modules/ee/contacts/segments/components/segment-table-data-row-container.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { TContactAttributeKey } from "@formbricks/types/contact-attribute-key"; -import { TSegment } from "@formbricks/types/segment"; -import { getSurveysBySegmentId } from "@/lib/survey/service"; -import { SegmentTableDataRow } from "./segment-table-data-row"; - -type TSegmentTableDataRowProps = { - currentSegment: TSegment; - segments: TSegment[]; - contactAttributeKeys: TContactAttributeKey[]; - isContactsEnabled: boolean; - isReadOnly: boolean; -}; - -export const SegmentTableDataRowContainer = async ({ - currentSegment, - segments, - contactAttributeKeys, - isContactsEnabled, - isReadOnly, -}: TSegmentTableDataRowProps) => { - const surveys = await getSurveysBySegmentId(currentSegment.id); - - const activeSurveys = surveys?.length - ? surveys.filter((survey) => survey.status === "inProgress").map((survey) => survey.name) - : []; - - const inactiveSurveys = surveys?.length - ? surveys.filter((survey) => ["draft", "paused"].includes(survey.status)).map((survey) => survey.name) - : []; - - const filteredSegments = segments.filter((segment) => segment.id !== currentSegment.id); - - return ( - - ); -}; diff --git a/apps/web/modules/ee/contacts/segments/components/segment-table-data-row.tsx b/apps/web/modules/ee/contacts/segments/components/segment-table-data-row.tsx index 45943498aa..bd624a45d2 100644 --- a/apps/web/modules/ee/contacts/segments/components/segment-table-data-row.tsx +++ b/apps/web/modules/ee/contacts/segments/components/segment-table-data-row.tsx @@ -4,11 +4,13 @@ import { format, formatDistanceToNow } from "date-fns"; import { UsersIcon } from "lucide-react"; import { useState } from "react"; import { TContactAttributeKey } from "@formbricks/types/contact-attribute-key"; -import { TSegment, TSegmentWithSurveyNames } from "@formbricks/types/segment"; +import { TSegment, TSegmentWithSurveyRefs } from "@formbricks/types/segment"; import { EditSegmentModal } from "./edit-segment-modal"; +import { TSegmentActivitySummary } from "./segment-activity-utils"; type TSegmentTableDataRowProps = { - currentSegment: TSegmentWithSurveyNames; + currentSegment: TSegmentWithSurveyRefs; + activitySummary: TSegmentActivitySummary; segments: TSegment[]; contactAttributeKeys: TContactAttributeKey[]; isContactsEnabled: boolean; @@ -17,6 +19,7 @@ type TSegmentTableDataRowProps = { export const SegmentTableDataRow = ({ currentSegment, + activitySummary, contactAttributeKeys, segments, isContactsEnabled, @@ -62,6 +65,7 @@ export const SegmentTableDataRow = ({ open={isEditSegmentModalOpen} setOpen={setIsEditSegmentModalOpen} currentSegment={currentSegment} + activitySummary={activitySummary} contactAttributeKeys={contactAttributeKeys} segments={segments} isContactsEnabled={isContactsEnabled} diff --git a/apps/web/modules/ee/contacts/segments/components/segment-table.tsx b/apps/web/modules/ee/contacts/segments/components/segment-table.tsx index 16b2fed651..2d862c9536 100644 --- a/apps/web/modules/ee/contacts/segments/components/segment-table.tsx +++ b/apps/web/modules/ee/contacts/segments/components/segment-table.tsx @@ -4,13 +4,15 @@ import { Header, getCoreRowModel, useReactTable } from "@tanstack/react-table"; import { useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { TContactAttributeKey } from "@formbricks/types/contact-attribute-key"; -import { TSegmentWithSurveyNames } from "@formbricks/types/segment"; +import { TSegmentWithSurveyRefs } from "@formbricks/types/segment"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/modules/ui/components/table"; import { EditSegmentModal } from "./edit-segment-modal"; +import { buildSegmentActivitySummaryFromSegments } from "./segment-activity-utils"; import { generateSegmentTableColumns } from "./segment-table-columns"; interface SegmentTableUpdatedProps { - segments: TSegmentWithSurveyNames[]; + segments: TSegmentWithSurveyRefs[]; + allSegments: TSegmentWithSurveyRefs[]; contactAttributeKeys: TContactAttributeKey[]; isContactsEnabled: boolean; isReadOnly: boolean; @@ -18,16 +20,17 @@ interface SegmentTableUpdatedProps { export function SegmentTable({ segments, + allSegments, contactAttributeKeys, isContactsEnabled, isReadOnly, -}: SegmentTableUpdatedProps) { +}: Readonly) { const { t } = useTranslation(); - const [editingSegment, setEditingSegment] = useState(null); + const [editingSegment, setEditingSegment] = useState(null); const columns = useMemo(() => { return generateSegmentTableColumns(t); - }, []); + }, [t]); const table = useReactTable({ data: segments, @@ -35,7 +38,7 @@ export function SegmentTable({ getCoreRowModel: getCoreRowModel(), }); - const getHeader = (header: Header) => { + const getHeader = (header: Header) => { if (header.isPlaceholder) { return null; } @@ -136,6 +139,7 @@ export function SegmentTable({ open={!!editingSegment} setOpen={(open) => !open && setEditingSegment(null)} currentSegment={editingSegment} + activitySummary={buildSegmentActivitySummaryFromSegments(editingSegment, allSegments)} contactAttributeKeys={contactAttributeKeys} segments={segments} isContactsEnabled={isContactsEnabled} diff --git a/apps/web/modules/ee/contacts/segments/lib/filter/prisma-query.test.ts b/apps/web/modules/ee/contacts/segments/lib/filter/prisma-query.test.ts index 6c0140f7df..0b0762d729 100644 --- a/apps/web/modules/ee/contacts/segments/lib/filter/prisma-query.test.ts +++ b/apps/web/modules/ee/contacts/segments/lib/filter/prisma-query.test.ts @@ -1,6 +1,6 @@ import { Prisma } from "@prisma/client"; import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; -import { TBaseFilters, TSegmentWithSurveyNames } from "@formbricks/types/segment"; +import { TBaseFilters, TSegmentWithSurveyRefs } from "@formbricks/types/segment"; import { getSegment } from "../segments"; import { segmentFilterToPrismaQuery } from "./prisma-query"; @@ -270,7 +270,7 @@ describe("segmentFilterToPrismaQuery", () => { ]; // Mock the getSegment function to return a segment with filters - const mockSegment: TSegmentWithSurveyNames = { + const mockSegment: TSegmentWithSurveyRefs = { id: nestedSegmentId, filters: nestedFilters, environmentId: mockEnvironmentId, @@ -336,7 +336,7 @@ describe("segmentFilterToPrismaQuery", () => { // Mock getSegment to return null for the non-existent segment vi.mocked(getSegment).mockResolvedValueOnce(mockSegment); - vi.mocked(getSegment).mockResolvedValueOnce(null as unknown as TSegmentWithSurveyNames); + vi.mocked(getSegment).mockResolvedValueOnce(null as unknown as TSegmentWithSurveyRefs); const result = await segmentFilterToPrismaQuery(mockSegmentId, filters, mockEnvironmentId); @@ -426,7 +426,7 @@ describe("segmentFilterToPrismaQuery", () => { ]; // Mock the getSegment function to return a segment with filters - const mockSegment: TSegmentWithSurveyNames = { + const mockSegment: TSegmentWithSurveyRefs = { id: nestedSegmentId, filters: nestedFilters, environmentId: mockEnvironmentId, @@ -490,7 +490,7 @@ describe("segmentFilterToPrismaQuery", () => { test("handle circular references in segment filters", async () => { // Mock getSegment to simulate a circular reference - const circularSegment: TSegmentWithSurveyNames = { + const circularSegment: TSegmentWithSurveyRefs = { id: mockSegmentId, // Same ID creates the circular reference filters: [ { @@ -550,7 +550,7 @@ describe("segmentFilterToPrismaQuery", () => { test("handle missing segments in segment filters", async () => { const nestedSegmentId = "segment-missing-123"; - vi.mocked(getSegment).mockResolvedValue(null as unknown as TSegmentWithSurveyNames); + vi.mocked(getSegment).mockResolvedValue(null as unknown as TSegmentWithSurveyRefs); const filters: TBaseFilters = [ { @@ -599,7 +599,7 @@ describe("segmentFilterToPrismaQuery", () => { ]; // Mock the nested segment - const mockNestedSegment: TSegmentWithSurveyNames = { + const mockNestedSegment: TSegmentWithSurveyRefs = { id: nestedSegmentId, filters: nestedFilters, environmentId: mockEnvironmentId, @@ -890,7 +890,7 @@ describe("segmentFilterToPrismaQuery", () => { ]; // Set up the mocks - const mockCircularSegment: TSegmentWithSurveyNames = { + const mockCircularSegment: TSegmentWithSurveyRefs = { id: circularSegmentId, filters: circularFilters, environmentId: mockEnvironmentId, @@ -904,7 +904,7 @@ describe("segmentFilterToPrismaQuery", () => { inactiveSurveys: [], }; - const mockSecondSegment: TSegmentWithSurveyNames = { + const mockSecondSegment: TSegmentWithSurveyRefs = { id: secondSegmentId, filters: secondFilters, environmentId: mockEnvironmentId, @@ -922,7 +922,7 @@ describe("segmentFilterToPrismaQuery", () => { vi.mocked(getSegment) .mockResolvedValueOnce(mockCircularSegment) // First call for circularSegmentId .mockResolvedValueOnce(mockSecondSegment) // Third call for secondSegmentId - .mockResolvedValueOnce(null as unknown as TSegmentWithSurveyNames); // Fourth call for non-existent-segment + .mockResolvedValueOnce(null as unknown as TSegmentWithSurveyRefs); // Fourth call for non-existent-segment // Complex filters with mixed error conditions const filters: TBaseFilters = [ diff --git a/apps/web/modules/ee/contacts/segments/lib/helper.test.ts b/apps/web/modules/ee/contacts/segments/lib/helper.test.ts index 03c149803b..481b8a4f63 100644 --- a/apps/web/modules/ee/contacts/segments/lib/helper.test.ts +++ b/apps/web/modules/ee/contacts/segments/lib/helper.test.ts @@ -1,6 +1,6 @@ import { beforeEach, describe, expect, test, vi } from "vitest"; import { InvalidInputError } from "@formbricks/types/errors"; -import { TBaseFilters, TSegmentWithSurveyNames } from "@formbricks/types/segment"; +import { TBaseFilters, TSegmentWithSurveyRefs } from "@formbricks/types/segment"; import { checkForRecursiveSegmentFilter } from "@/modules/ee/contacts/segments/lib/helper"; import { getSegment } from "@/modules/ee/contacts/segments/lib/segments"; @@ -77,7 +77,7 @@ describe("checkForRecursiveSegmentFilter", () => { ], }; - vi.mocked(getSegment).mockResolvedValue(referencedSegment as unknown as TSegmentWithSurveyNames); + vi.mocked(getSegment).mockResolvedValue(referencedSegment as unknown as TSegmentWithSurveyRefs); // Act & Assert // The function should complete without throwing an error diff --git a/apps/web/modules/ee/contacts/segments/lib/segments.test.ts b/apps/web/modules/ee/contacts/segments/lib/segments.test.ts index 24f4803fd2..50b5e4f303 100644 --- a/apps/web/modules/ee/contacts/segments/lib/segments.test.ts +++ b/apps/web/modules/ee/contacts/segments/lib/segments.test.ts @@ -8,7 +8,7 @@ import { TEvaluateSegmentUserData, TSegmentCreateInput, TSegmentUpdateInput, - TSegmentWithSurveyNames, + TSegmentWithSurveyRefs, } from "@formbricks/types/segment"; import { getSurvey } from "@/lib/survey/service"; import { validateInputs } from "@/lib/utils/validate"; @@ -79,10 +79,10 @@ const mockSegmentPrisma = { surveys: [{ id: surveyId, name: "Test Survey", status: "inProgress" }], }; -const mockSegment: TSegmentWithSurveyNames = { +const mockSegment: TSegmentWithSurveyRefs = { ...mockSegmentPrisma, surveys: [surveyId], - activeSurveys: ["Test Survey"], + activeSurveys: [{ id: surveyId, name: "Test Survey" }], inactiveSurveys: [], }; @@ -287,7 +287,7 @@ describe("Segment Service Tests", () => { ...mockSegment, id: clonedSegmentId, title: "Copy of Test Segment (1)", - activeSurveys: ["Test Survey"], + activeSurveys: [{ id: surveyId, name: "Test Survey" }], inactiveSurveys: [], }; @@ -327,7 +327,7 @@ describe("Segment Service Tests", () => { const clonedSegment2 = { ...clonedSegment, title: "Copy of Test Segment (2)", - activeSurveys: ["Test Survey"], + activeSurveys: [{ id: surveyId, name: "Test Survey" }], inactiveSurveys: [], }; @@ -415,7 +415,7 @@ describe("Segment Service Tests", () => { title: surveyId, isPrivate: true, filters: [], - activeSurveys: ["Test Survey"], + activeSurveys: [{ id: surveyId, name: "Test Survey" }], inactiveSurveys: [], }; @@ -487,7 +487,7 @@ describe("Segment Service Tests", () => { const updatedSegment = { ...mockSegment, title: "Updated Segment", - activeSurveys: ["Test Survey"], + activeSurveys: [{ id: surveyId, name: "Test Survey" }], inactiveSurveys: [], }; const updateData: TSegmentUpdateInput = { title: "Updated Segment" }; @@ -531,7 +531,7 @@ describe("Segment Service Tests", () => { ...updatedSegment, surveys: [newSurveyId], activeSurveys: [], - inactiveSurveys: ["New Survey"], + inactiveSurveys: [{ id: newSurveyId, name: "New Survey" }], }; vi.mocked(prisma.segment.update).mockResolvedValue(updatedSegmentPrismaWithSurvey); diff --git a/apps/web/modules/ee/contacts/segments/lib/segments.ts b/apps/web/modules/ee/contacts/segments/lib/segments.ts index d129661285..26607d4aff 100644 --- a/apps/web/modules/ee/contacts/segments/lib/segments.ts +++ b/apps/web/modules/ee/contacts/segments/lib/segments.ts @@ -25,7 +25,7 @@ import { TSegmentPersonFilter, TSegmentSegmentFilter, TSegmentUpdateInput, - TSegmentWithSurveyNames, + TSegmentWithSurveyRefs, ZRelativeDateValue, ZSegmentCreateInput, ZSegmentFilters, @@ -66,14 +66,14 @@ export const selectSegment = { }, } satisfies Prisma.SegmentSelect; -export const transformPrismaSegment = (segment: PrismaSegment): TSegmentWithSurveyNames => { +export const transformPrismaSegment = (segment: PrismaSegment): TSegmentWithSurveyRefs => { const activeSurveys = segment.surveys .filter((survey) => survey.status === "inProgress") - .map((survey) => survey.name); + .map((survey) => ({ id: survey.id, name: survey.name })); const inactiveSurveys = segment.surveys .filter((survey) => survey.status !== "inProgress") - .map((survey) => survey.name); + .map((survey) => ({ id: survey.id, name: survey.name })); return { ...segment, @@ -83,7 +83,7 @@ export const transformPrismaSegment = (segment: PrismaSegment): TSegmentWithSurv }; }; -export const getSegment = reactCache(async (segmentId: string): Promise => { +export const getSegment = reactCache(async (segmentId: string): Promise => { validateInputs([segmentId, ZId]); try { const segment = await prisma.segment.findUnique({ @@ -107,7 +107,7 @@ export const getSegment = reactCache(async (segmentId: string): Promise => { +export const getSegments = reactCache(async (environmentId: string): Promise => { validateInputs([environmentId, ZId]); try { const segments = await prisma.segment.findMany({ diff --git a/apps/web/modules/ee/contacts/segments/page.tsx b/apps/web/modules/ee/contacts/segments/page.tsx index 2aa6b41cef..6cb9e1be71 100644 --- a/apps/web/modules/ee/contacts/segments/page.tsx +++ b/apps/web/modules/ee/contacts/segments/page.tsx @@ -47,6 +47,7 @@ export const SegmentsPage = async ({ upgradePromptTitle={t("environments.segments.unlock_segments_title")} upgradePromptDescription={t("environments.segments.unlock_segments_description")}> >; - segment: TSegmentWithSurveyNames; onDelete: () => Promise; } export const ConfirmDeleteSegmentModal = ({ + activitySummary, onDelete, open, - segment, setOpen, }: ConfirmDeleteSegmentModalProps) => { const { t } = useTranslation(); @@ -32,9 +32,9 @@ export const ConfirmDeleteSegmentModal = ({ await onDelete(); }; - const segmentHasSurveys = useMemo(() => { - return segment.activeSurveys.length > 0 || segment.inactiveSurveys.length > 0; - }, [segment.activeSurveys.length, segment.inactiveSurveys.length]); + const allSurveys = useMemo(() => { + return [...activitySummary.activeSurveys, ...activitySummary.inactiveSurveys]; + }, [activitySummary.activeSurveys, activitySummary.inactiveSurveys]); return ( @@ -46,16 +46,13 @@ export const ConfirmDeleteSegmentModal = ({ - {segmentHasSurveys && ( + {allSurveys.length > 0 && (

{t("environments.segments.cannot_delete_segment_used_in_surveys")}

    - {segment.activeSurveys.map((survey) => ( -
  1. {survey}
  2. - ))} - {segment.inactiveSurveys.map((survey) => ( -
  3. {survey}
  4. + {allSurveys.map((surveyName) => ( +
  5. {surveyName}
  6. ))}
@@ -69,7 +66,7 @@ export const ConfirmDeleteSegmentModal = ({ - diff --git a/packages/types/segment.ts b/packages/types/segment.ts index 8900a77f34..89704726bd 100644 --- a/packages/types/segment.ts +++ b/packages/types/segment.ts @@ -357,9 +357,13 @@ export const ZSegmentCreateInput = z.object({ export type TSegmentCreateInput = z.infer; export type TSegment = z.infer; -export type TSegmentWithSurveyNames = TSegment & { - activeSurveys: string[]; - inactiveSurveys: string[]; +export interface TSegmentSurveyReference { + id: string; + name: string; +} +export type TSegmentWithSurveyRefs = TSegment & { + activeSurveys: TSegmentSurveyReference[]; + inactiveSurveys: TSegmentSurveyReference[]; }; export const ZSegmentUpdateInput = z