diff --git a/worklenz-frontend/src/components/task-templates/import-task-template.tsx b/worklenz-frontend/src/components/task-templates/import-task-template.tsx index 3dfcf188..4090d56c 100644 --- a/worklenz-frontend/src/components/task-templates/import-task-template.tsx +++ b/worklenz-frontend/src/components/task-templates/import-task-template.tsx @@ -23,12 +23,15 @@ import { fetchBoardTaskGroups } from '@/features/board/board-slice'; import { setImportTaskTemplateDrawerOpen } from '@/features/project/project.slice'; import useTabSearchParam from '@/hooks/useTabSearchParam'; import { fetchTaskGroups } from '@/features/tasks/tasks.slice'; +import { evt_project_import_tasks } from '@/shared/worklenz-analytics-events'; +import { useMixpanelTracking } from '@/hooks/useMixpanelTracking'; const ImportTaskTemplate = () => { const dispatch = useAppDispatch(); const [form] = Form.useForm(); const { t } = useTranslation('project-view/import-task-templates'); const { tab } = useTabSearchParam(); + const { trackMixpanelEvent } = useMixpanelTracking(); const { importTaskTemplateDrawerOpen, projectId } = useAppSelector(state => state.projectReducer); const [templates, setTemplates] = useState([]); @@ -86,6 +89,7 @@ const ImportTaskTemplate = () => { if (!projectId || tasks.length === 0) return; try { + trackMixpanelEvent(evt_project_import_tasks); setImporting(true); const res = await taskTemplatesApiService.importTemplate(projectId, tasks); if (res.done) { @@ -117,7 +121,12 @@ const ImportTaskTemplate = () => { footer={ - diff --git a/worklenz-frontend/src/components/task-templates/task-template-drawer.tsx b/worklenz-frontend/src/components/task-templates/task-template-drawer.tsx index eab34400..3211497c 100644 --- a/worklenz-frontend/src/components/task-templates/task-template-drawer.tsx +++ b/worklenz-frontend/src/components/task-templates/task-template-drawer.tsx @@ -8,6 +8,8 @@ import logger from '@/utils/errorLogger'; import { ITaskTemplateGetResponse } from '@/types/settings/task-templates.types'; import { useAppSelector } from '@/hooks/useAppSelector'; import { setSelectedTasks } from '@/features/project/project.slice'; +import { evt_project_task_create } from '@/shared/worklenz-analytics-events'; +import { useMixpanelTracking } from '@/hooks/useMixpanelTracking'; interface TaskTemplateDrawerProps { showDrawer: boolean; @@ -21,6 +23,7 @@ const TaskTemplateDrawer = ({ onClose, }: TaskTemplateDrawerProps) => { const dispatch = useAppDispatch(); + const {trackMixpanelEvent} = useMixpanelTracking(); const { t } = useTranslation('task-template-drawer'); const [form] = Form.useForm(); const [templateData, setTemplateData] = useState({}); @@ -75,6 +78,8 @@ const TaskTemplateDrawer = ({ const values = form.getFieldsValue(); if (!values.name || !templateData.tasks) return; try { + trackMixpanelEvent(evt_project_task_create); + setCreatingTemplate(true); const res = await taskTemplatesApiService.createTemplate({ name: values.name || '', diff --git a/worklenz-frontend/src/pages/projects/project-view-1/roadmap/roadmap-table/roadmap-table.tsx b/worklenz-frontend/src/pages/projects/project-view-1/roadmap/roadmap-table/roadmap-table.tsx index ca3c6e77..49c77062 100644 --- a/worklenz-frontend/src/pages/projects/project-view-1/roadmap/roadmap-table/roadmap-table.tsx +++ b/worklenz-frontend/src/pages/projects/project-view-1/roadmap/roadmap-table/roadmap-table.tsx @@ -6,6 +6,8 @@ import { useAppDispatch } from '@/hooks/useAppDispatch'; import { NewTaskType, updateTaskDate } from '@features/roadmap/roadmap-slice'; import { colors } from '@/styles/colors'; import RoadmapTaskCell from './roadmap-task-cell'; +import { evt_roadmap_drag_change_date } from '@/shared/worklenz-analytics-events'; +import { useMixpanelTracking } from '@/hooks/useMixpanelTracking'; const RoadmapTable = () => { // Get task list and expanded tasks from roadmap slice @@ -15,11 +17,18 @@ const RoadmapTable = () => { const themeMode = useAppSelector(state => state.themeReducer.mode); const dispatch = useAppDispatch(); + const { trackMixpanelEvent } = useMixpanelTracking(); // function to handle date changes const handleDateChange = (taskId: string, dateType: 'start' | 'end', date: Dayjs) => { const updatedDate = date.toDate(); + trackMixpanelEvent(evt_roadmap_drag_change_date, { + task_id: taskId, + date_type: dateType, + new_date: updatedDate.toISOString(), + }); + dispatch( updateTaskDate({ taskId, diff --git a/worklenz-frontend/src/pages/projects/projectView/members/project-view-members.tsx b/worklenz-frontend/src/pages/projects/projectView/members/project-view-members.tsx index 8b1b862f..94d961b3 100644 --- a/worklenz-frontend/src/pages/projects/projectView/members/project-view-members.tsx +++ b/worklenz-frontend/src/pages/projects/projectView/members/project-view-members.tsx @@ -37,7 +37,7 @@ import logger from '@/utils/errorLogger'; // Components import EmptyListPlaceholder from '../../../../components/EmptyListPlaceholder'; import { useAppSelector } from '@/hooks/useAppSelector'; -import { evt_project_members_visit } from '@/shared/worklenz-analytics-events'; +import { evt_project_members_visit, evt_people_delete } from '@/shared/worklenz-analytics-events'; import { useMixpanelTracking } from '@/hooks/useMixpanelTracking'; interface PaginationType { @@ -60,7 +60,7 @@ const ProjectViewMembers = () => { const { trackMixpanelEvent } = useMixpanelTracking(); const { refreshTimestamp } = useAppSelector(state => state.projectReducer); - + // State const [isLoading, setIsLoading] = useState(false); const [members, setMembers] = useState(); @@ -104,6 +104,11 @@ const ProjectViewMembers = () => { try { const res = await projectMembersApiService.deleteProjectMember(memberId, projectId); if (res.done) { + trackMixpanelEvent(evt_people_delete, { + project_id: projectId, + member_id: memberId, + }); + void getProjectMembers(); } } catch (error) { @@ -138,7 +143,14 @@ const ProjectViewMembers = () => { // Effects useEffect(() => { void getProjectMembers(); - }, [refreshTimestamp, projectId, pagination.current, pagination.pageSize, pagination.field, pagination.order]); + }, [ + refreshTimestamp, + projectId, + pagination.current, + pagination.pageSize, + pagination.field, + pagination.order, + ]); useEffect(() => { trackMixpanelEvent(evt_project_members_visit); @@ -151,9 +163,13 @@ const ProjectViewMembers = () => { title: t('nameColumn'), dataIndex: 'name', sorter: true, - sortOrder: pagination.order === 'ascend' && pagination.field === 'name' ? 'ascend' : - pagination.order === 'descend' && pagination.field === 'name' ? 'descend' : null, - render: (_,record: IProjectMemberViewModel) => ( + sortOrder: + pagination.order === 'ascend' && pagination.field === 'name' + ? 'ascend' + : pagination.order === 'descend' && pagination.field === 'name' + ? 'descend' + : null, + render: (_, record: IProjectMemberViewModel) => ( {record.name?.charAt(0)} @@ -167,8 +183,12 @@ const ProjectViewMembers = () => { title: t('jobTitleColumn'), dataIndex: 'job_title', sorter: true, - sortOrder: pagination.order === 'ascend' && pagination.field === 'job_title' ? 'ascend' : - pagination.order === 'descend' && pagination.field === 'job_title' ? 'descend' : null, + sortOrder: + pagination.order === 'ascend' && pagination.field === 'job_title' + ? 'ascend' + : pagination.order === 'descend' && pagination.field === 'job_title' + ? 'descend' + : null, render: (_, record: IProjectMemberViewModel) => ( {record?.job_title || '-'} @@ -180,8 +200,12 @@ const ProjectViewMembers = () => { title: t('emailColumn'), dataIndex: 'email', sorter: true, - sortOrder: pagination.order === 'ascend' && pagination.field === 'email' ? 'ascend' : - pagination.order === 'descend' && pagination.field === 'email' ? 'descend' : null, + sortOrder: + pagination.order === 'ascend' && pagination.field === 'email' + ? 'ascend' + : pagination.order === 'descend' && pagination.field === 'email' + ? 'descend' + : null, render: (_, record: IProjectMemberViewModel) => ( {record.email} ), @@ -210,8 +234,12 @@ const ProjectViewMembers = () => { title: t('accessColumn'), dataIndex: 'access', sorter: true, - sortOrder: pagination.order === 'ascend' && pagination.field === 'access' ? 'ascend' : - pagination.order === 'descend' && pagination.field === 'access' ? 'descend' : null, + sortOrder: + pagination.order === 'ascend' && pagination.field === 'access' + ? 'ascend' + : pagination.order === 'descend' && pagination.field === 'access' + ? 'descend' + : null, render: (_, record: IProjectMemberViewModel) => ( {record.access} ), diff --git a/worklenz-frontend/src/pages/projects/projectView/project-view-header.tsx b/worklenz-frontend/src/pages/projects/projectView/project-view-header.tsx index 81ff318d..215c5cfa 100644 --- a/worklenz-frontend/src/pages/projects/projectView/project-view-header.tsx +++ b/worklenz-frontend/src/pages/projects/projectView/project-view-header.tsx @@ -22,8 +22,17 @@ import { useAppSelector } from '@/hooks/useAppSelector'; import { SocketEvents } from '@/shared/socket-events'; import { useAuthService } from '@/hooks/useAuth'; import { useSocket } from '@/socket/socketContext'; -import { setProject, setImportTaskTemplateDrawerOpen, setRefreshTimestamp } from '@features/project/project.slice'; -import { addTask, fetchTaskGroups, fetchTaskListColumns, IGroupBy } from '@features/tasks/tasks.slice'; +import { + setProject, + setImportTaskTemplateDrawerOpen, + setRefreshTimestamp, +} from '@features/project/project.slice'; +import { + addTask, + fetchTaskGroups, + fetchTaskListColumns, + IGroupBy, +} from '@features/tasks/tasks.slice'; import ProjectStatusIcon from '@/components/common/project-status-icon/project-status-icon'; import { formatDate } from '@/utils/timeUtils'; import { toggleSaveAsTemplateDrawer } from '@/features/projects/projectsSlice'; @@ -49,6 +58,14 @@ import useTabSearchParam from '@/hooks/useTabSearchParam'; import { addTaskCardToTheTop, fetchBoardTaskGroups } from '@/features/board/board-slice'; import { fetchPhasesByProjectId } from '@/features/projects/singleProject/phase/phases.slice'; +import { + evt_project_task_create, + evt_project_refresh_click, + evt_project_settings_click, + evt_project_import_tasks_click, +} from '@/shared/worklenz-analytics-events'; +import { useMixpanelTracking } from '@/hooks/useMixpanelTracking'; + const ProjectViewHeader = () => { const navigate = useNavigate(); const { t } = useTranslation('project-view/project-view-header'); @@ -56,24 +73,29 @@ const ProjectViewHeader = () => { const currentSession = useAuthService().getCurrentSession(); const isOwnerOrAdmin = useAuthService().isOwnerOrAdmin(); const isProjectManager = useIsProjectManager(); + const { trackMixpanelEvent } = useMixpanelTracking(); const { tab } = useTabSearchParam(); const { socket } = useSocket(); - const { - project: selectedProject, - projectId, - } = useAppSelector(state => state.projectReducer); + const { project: selectedProject, projectId } = useAppSelector(state => state.projectReducer); const { loadingGroups, groupBy } = useAppSelector(state => state.taskReducer); const [creatingTask, setCreatingTask] = useState(false); const handleRefresh = () => { if (!projectId) return; + + trackMixpanelEvent(evt_project_refresh_click, { + project_id: projectId, + tab: tab, + project_name: selectedProject?.name, + }); + switch (tab) { case 'tasks-list': dispatch(fetchTaskListColumns(projectId)); - dispatch(fetchPhasesByProjectId(projectId)) + dispatch(fetchPhasesByProjectId(projectId)); dispatch(fetchTaskGroups(projectId)); break; case 'board': @@ -113,6 +135,11 @@ const ProjectViewHeader = () => { const handleSettingsClick = () => { if (selectedProject?.id) { + trackMixpanelEvent(evt_project_settings_click, { + project_id: selectedProject.id, + project_name: selectedProject.name, + }); + dispatch(setProjectId(selectedProject.id)); dispatch(fetchProjectData(selectedProject.id)); dispatch(toggleProjectDrawer()); @@ -123,6 +150,14 @@ const ProjectViewHeader = () => { try { setCreatingTask(true); + trackMixpanelEvent(evt_project_task_create, { + project_id: selectedProject?.id, + project_name: selectedProject?.name, + reporter_id: currentSession?.id, + team_id: currentSession?.team_id, + creation_method: 'quick_create', + }); + const body: ITaskCreateRequest = { name: DEFAULT_TASK_NAME, project_id: selectedProject?.id, @@ -155,6 +190,11 @@ const ProjectViewHeader = () => { }; const handleImportTaskTemplate = () => { + trackMixpanelEvent(evt_project_import_tasks_click, { + project_id: selectedProject?.id, + project_name: selectedProject?.name, + }); + dispatch(setImportTaskTemplateDrawerOpen(true)); }; @@ -221,7 +261,7 @@ const ProjectViewHeader = () => { /> - {(isOwnerOrAdmin) && ( + {isOwnerOrAdmin && (