updated
This commit is contained in:
@@ -23,12 +23,15 @@ import { fetchBoardTaskGroups } from '@/features/board/board-slice';
|
|||||||
import { setImportTaskTemplateDrawerOpen } from '@/features/project/project.slice';
|
import { setImportTaskTemplateDrawerOpen } from '@/features/project/project.slice';
|
||||||
import useTabSearchParam from '@/hooks/useTabSearchParam';
|
import useTabSearchParam from '@/hooks/useTabSearchParam';
|
||||||
import { fetchTaskGroups } from '@/features/tasks/tasks.slice';
|
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 ImportTaskTemplate = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const { t } = useTranslation('project-view/import-task-templates');
|
const { t } = useTranslation('project-view/import-task-templates');
|
||||||
const { tab } = useTabSearchParam();
|
const { tab } = useTabSearchParam();
|
||||||
|
const { trackMixpanelEvent } = useMixpanelTracking();
|
||||||
|
|
||||||
const { importTaskTemplateDrawerOpen, projectId } = useAppSelector(state => state.projectReducer);
|
const { importTaskTemplateDrawerOpen, projectId } = useAppSelector(state => state.projectReducer);
|
||||||
const [templates, setTemplates] = useState<ITaskTemplatesGetResponse[]>([]);
|
const [templates, setTemplates] = useState<ITaskTemplatesGetResponse[]>([]);
|
||||||
@@ -86,6 +89,7 @@ const ImportTaskTemplate = () => {
|
|||||||
if (!projectId || tasks.length === 0) return;
|
if (!projectId || tasks.length === 0) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
trackMixpanelEvent(evt_project_import_tasks);
|
||||||
setImporting(true);
|
setImporting(true);
|
||||||
const res = await taskTemplatesApiService.importTemplate(projectId, tasks);
|
const res = await taskTemplatesApiService.importTemplate(projectId, tasks);
|
||||||
if (res.done) {
|
if (res.done) {
|
||||||
@@ -117,7 +121,12 @@ const ImportTaskTemplate = () => {
|
|||||||
footer={
|
footer={
|
||||||
<Flex justify="end" gap={10}>
|
<Flex justify="end" gap={10}>
|
||||||
<Button onClick={handleClose}>{t('cancel')}</Button>
|
<Button onClick={handleClose}>{t('cancel')}</Button>
|
||||||
<Button type="primary" onClick={handleImport} loading={importing} disabled={tasks.length === 0}>
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={handleImport}
|
||||||
|
loading={importing}
|
||||||
|
disabled={tasks.length === 0}
|
||||||
|
>
|
||||||
{t('import')}
|
{t('import')}
|
||||||
</Button>
|
</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import logger from '@/utils/errorLogger';
|
|||||||
import { ITaskTemplateGetResponse } from '@/types/settings/task-templates.types';
|
import { ITaskTemplateGetResponse } from '@/types/settings/task-templates.types';
|
||||||
import { useAppSelector } from '@/hooks/useAppSelector';
|
import { useAppSelector } from '@/hooks/useAppSelector';
|
||||||
import { setSelectedTasks } from '@/features/project/project.slice';
|
import { setSelectedTasks } from '@/features/project/project.slice';
|
||||||
|
import { evt_project_task_create } from '@/shared/worklenz-analytics-events';
|
||||||
|
import { useMixpanelTracking } from '@/hooks/useMixpanelTracking';
|
||||||
|
|
||||||
interface TaskTemplateDrawerProps {
|
interface TaskTemplateDrawerProps {
|
||||||
showDrawer: boolean;
|
showDrawer: boolean;
|
||||||
@@ -21,6 +23,7 @@ const TaskTemplateDrawer = ({
|
|||||||
onClose,
|
onClose,
|
||||||
}: TaskTemplateDrawerProps) => {
|
}: TaskTemplateDrawerProps) => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
const {trackMixpanelEvent} = useMixpanelTracking();
|
||||||
const { t } = useTranslation('task-template-drawer');
|
const { t } = useTranslation('task-template-drawer');
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const [templateData, setTemplateData] = useState<ITaskTemplateGetResponse>({});
|
const [templateData, setTemplateData] = useState<ITaskTemplateGetResponse>({});
|
||||||
@@ -75,6 +78,8 @@ const TaskTemplateDrawer = ({
|
|||||||
const values = form.getFieldsValue();
|
const values = form.getFieldsValue();
|
||||||
if (!values.name || !templateData.tasks) return;
|
if (!values.name || !templateData.tasks) return;
|
||||||
try {
|
try {
|
||||||
|
trackMixpanelEvent(evt_project_task_create);
|
||||||
|
|
||||||
setCreatingTemplate(true);
|
setCreatingTemplate(true);
|
||||||
const res = await taskTemplatesApiService.createTemplate({
|
const res = await taskTemplatesApiService.createTemplate({
|
||||||
name: values.name || '',
|
name: values.name || '',
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import { useAppDispatch } from '@/hooks/useAppDispatch';
|
|||||||
import { NewTaskType, updateTaskDate } from '@features/roadmap/roadmap-slice';
|
import { NewTaskType, updateTaskDate } from '@features/roadmap/roadmap-slice';
|
||||||
import { colors } from '@/styles/colors';
|
import { colors } from '@/styles/colors';
|
||||||
import RoadmapTaskCell from './roadmap-task-cell';
|
import RoadmapTaskCell from './roadmap-task-cell';
|
||||||
|
import { evt_roadmap_drag_change_date } from '@/shared/worklenz-analytics-events';
|
||||||
|
import { useMixpanelTracking } from '@/hooks/useMixpanelTracking';
|
||||||
|
|
||||||
const RoadmapTable = () => {
|
const RoadmapTable = () => {
|
||||||
// Get task list and expanded tasks from roadmap slice
|
// Get task list and expanded tasks from roadmap slice
|
||||||
@@ -15,11 +17,18 @@ const RoadmapTable = () => {
|
|||||||
const themeMode = useAppSelector(state => state.themeReducer.mode);
|
const themeMode = useAppSelector(state => state.themeReducer.mode);
|
||||||
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
const { trackMixpanelEvent } = useMixpanelTracking();
|
||||||
|
|
||||||
// function to handle date changes
|
// function to handle date changes
|
||||||
const handleDateChange = (taskId: string, dateType: 'start' | 'end', date: Dayjs) => {
|
const handleDateChange = (taskId: string, dateType: 'start' | 'end', date: Dayjs) => {
|
||||||
const updatedDate = date.toDate();
|
const updatedDate = date.toDate();
|
||||||
|
|
||||||
|
trackMixpanelEvent(evt_roadmap_drag_change_date, {
|
||||||
|
task_id: taskId,
|
||||||
|
date_type: dateType,
|
||||||
|
new_date: updatedDate.toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
dispatch(
|
dispatch(
|
||||||
updateTaskDate({
|
updateTaskDate({
|
||||||
taskId,
|
taskId,
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ import logger from '@/utils/errorLogger';
|
|||||||
// Components
|
// Components
|
||||||
import EmptyListPlaceholder from '../../../../components/EmptyListPlaceholder';
|
import EmptyListPlaceholder from '../../../../components/EmptyListPlaceholder';
|
||||||
import { useAppSelector } from '@/hooks/useAppSelector';
|
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';
|
import { useMixpanelTracking } from '@/hooks/useMixpanelTracking';
|
||||||
|
|
||||||
interface PaginationType {
|
interface PaginationType {
|
||||||
@@ -60,7 +60,7 @@ const ProjectViewMembers = () => {
|
|||||||
const { trackMixpanelEvent } = useMixpanelTracking();
|
const { trackMixpanelEvent } = useMixpanelTracking();
|
||||||
|
|
||||||
const { refreshTimestamp } = useAppSelector(state => state.projectReducer);
|
const { refreshTimestamp } = useAppSelector(state => state.projectReducer);
|
||||||
|
|
||||||
// State
|
// State
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [members, setMembers] = useState<IProjectMembersViewModel>();
|
const [members, setMembers] = useState<IProjectMembersViewModel>();
|
||||||
@@ -104,6 +104,11 @@ const ProjectViewMembers = () => {
|
|||||||
try {
|
try {
|
||||||
const res = await projectMembersApiService.deleteProjectMember(memberId, projectId);
|
const res = await projectMembersApiService.deleteProjectMember(memberId, projectId);
|
||||||
if (res.done) {
|
if (res.done) {
|
||||||
|
trackMixpanelEvent(evt_people_delete, {
|
||||||
|
project_id: projectId,
|
||||||
|
member_id: memberId,
|
||||||
|
});
|
||||||
|
|
||||||
void getProjectMembers();
|
void getProjectMembers();
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -138,7 +143,14 @@ const ProjectViewMembers = () => {
|
|||||||
// Effects
|
// Effects
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
void getProjectMembers();
|
void getProjectMembers();
|
||||||
}, [refreshTimestamp, projectId, pagination.current, pagination.pageSize, pagination.field, pagination.order]);
|
}, [
|
||||||
|
refreshTimestamp,
|
||||||
|
projectId,
|
||||||
|
pagination.current,
|
||||||
|
pagination.pageSize,
|
||||||
|
pagination.field,
|
||||||
|
pagination.order,
|
||||||
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
trackMixpanelEvent(evt_project_members_visit);
|
trackMixpanelEvent(evt_project_members_visit);
|
||||||
@@ -151,9 +163,13 @@ const ProjectViewMembers = () => {
|
|||||||
title: t('nameColumn'),
|
title: t('nameColumn'),
|
||||||
dataIndex: 'name',
|
dataIndex: 'name',
|
||||||
sorter: true,
|
sorter: true,
|
||||||
sortOrder: pagination.order === 'ascend' && pagination.field === 'name' ? 'ascend' :
|
sortOrder:
|
||||||
pagination.order === 'descend' && pagination.field === 'name' ? 'descend' : null,
|
pagination.order === 'ascend' && pagination.field === 'name'
|
||||||
render: (_,record: IProjectMemberViewModel) => (
|
? 'ascend'
|
||||||
|
: pagination.order === 'descend' && pagination.field === 'name'
|
||||||
|
? 'descend'
|
||||||
|
: null,
|
||||||
|
render: (_, record: IProjectMemberViewModel) => (
|
||||||
<Flex gap={8} align="center">
|
<Flex gap={8} align="center">
|
||||||
<Avatar size={28} src={record.avatar_url}>
|
<Avatar size={28} src={record.avatar_url}>
|
||||||
{record.name?.charAt(0)}
|
{record.name?.charAt(0)}
|
||||||
@@ -167,8 +183,12 @@ const ProjectViewMembers = () => {
|
|||||||
title: t('jobTitleColumn'),
|
title: t('jobTitleColumn'),
|
||||||
dataIndex: 'job_title',
|
dataIndex: 'job_title',
|
||||||
sorter: true,
|
sorter: true,
|
||||||
sortOrder: pagination.order === 'ascend' && pagination.field === 'job_title' ? 'ascend' :
|
sortOrder:
|
||||||
pagination.order === 'descend' && pagination.field === 'job_title' ? 'descend' : null,
|
pagination.order === 'ascend' && pagination.field === 'job_title'
|
||||||
|
? 'ascend'
|
||||||
|
: pagination.order === 'descend' && pagination.field === 'job_title'
|
||||||
|
? 'descend'
|
||||||
|
: null,
|
||||||
render: (_, record: IProjectMemberViewModel) => (
|
render: (_, record: IProjectMemberViewModel) => (
|
||||||
<Typography.Text style={{ marginInlineStart: 12 }}>
|
<Typography.Text style={{ marginInlineStart: 12 }}>
|
||||||
{record?.job_title || '-'}
|
{record?.job_title || '-'}
|
||||||
@@ -180,8 +200,12 @@ const ProjectViewMembers = () => {
|
|||||||
title: t('emailColumn'),
|
title: t('emailColumn'),
|
||||||
dataIndex: 'email',
|
dataIndex: 'email',
|
||||||
sorter: true,
|
sorter: true,
|
||||||
sortOrder: pagination.order === 'ascend' && pagination.field === 'email' ? 'ascend' :
|
sortOrder:
|
||||||
pagination.order === 'descend' && pagination.field === 'email' ? 'descend' : null,
|
pagination.order === 'ascend' && pagination.field === 'email'
|
||||||
|
? 'ascend'
|
||||||
|
: pagination.order === 'descend' && pagination.field === 'email'
|
||||||
|
? 'descend'
|
||||||
|
: null,
|
||||||
render: (_, record: IProjectMemberViewModel) => (
|
render: (_, record: IProjectMemberViewModel) => (
|
||||||
<Typography.Text>{record.email}</Typography.Text>
|
<Typography.Text>{record.email}</Typography.Text>
|
||||||
),
|
),
|
||||||
@@ -210,8 +234,12 @@ const ProjectViewMembers = () => {
|
|||||||
title: t('accessColumn'),
|
title: t('accessColumn'),
|
||||||
dataIndex: 'access',
|
dataIndex: 'access',
|
||||||
sorter: true,
|
sorter: true,
|
||||||
sortOrder: pagination.order === 'ascend' && pagination.field === 'access' ? 'ascend' :
|
sortOrder:
|
||||||
pagination.order === 'descend' && pagination.field === 'access' ? 'descend' : null,
|
pagination.order === 'ascend' && pagination.field === 'access'
|
||||||
|
? 'ascend'
|
||||||
|
: pagination.order === 'descend' && pagination.field === 'access'
|
||||||
|
? 'descend'
|
||||||
|
: null,
|
||||||
render: (_, record: IProjectMemberViewModel) => (
|
render: (_, record: IProjectMemberViewModel) => (
|
||||||
<Typography.Text style={{ textTransform: 'capitalize' }}>{record.access}</Typography.Text>
|
<Typography.Text style={{ textTransform: 'capitalize' }}>{record.access}</Typography.Text>
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -22,8 +22,17 @@ import { useAppSelector } from '@/hooks/useAppSelector';
|
|||||||
import { SocketEvents } from '@/shared/socket-events';
|
import { SocketEvents } from '@/shared/socket-events';
|
||||||
import { useAuthService } from '@/hooks/useAuth';
|
import { useAuthService } from '@/hooks/useAuth';
|
||||||
import { useSocket } from '@/socket/socketContext';
|
import { useSocket } from '@/socket/socketContext';
|
||||||
import { setProject, setImportTaskTemplateDrawerOpen, setRefreshTimestamp } from '@features/project/project.slice';
|
import {
|
||||||
import { addTask, fetchTaskGroups, fetchTaskListColumns, IGroupBy } from '@features/tasks/tasks.slice';
|
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 ProjectStatusIcon from '@/components/common/project-status-icon/project-status-icon';
|
||||||
import { formatDate } from '@/utils/timeUtils';
|
import { formatDate } from '@/utils/timeUtils';
|
||||||
import { toggleSaveAsTemplateDrawer } from '@/features/projects/projectsSlice';
|
import { toggleSaveAsTemplateDrawer } from '@/features/projects/projectsSlice';
|
||||||
@@ -49,6 +58,14 @@ import useTabSearchParam from '@/hooks/useTabSearchParam';
|
|||||||
import { addTaskCardToTheTop, fetchBoardTaskGroups } from '@/features/board/board-slice';
|
import { addTaskCardToTheTop, fetchBoardTaskGroups } from '@/features/board/board-slice';
|
||||||
import { fetchPhasesByProjectId } from '@/features/projects/singleProject/phase/phases.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 ProjectViewHeader = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { t } = useTranslation('project-view/project-view-header');
|
const { t } = useTranslation('project-view/project-view-header');
|
||||||
@@ -56,24 +73,29 @@ const ProjectViewHeader = () => {
|
|||||||
const currentSession = useAuthService().getCurrentSession();
|
const currentSession = useAuthService().getCurrentSession();
|
||||||
const isOwnerOrAdmin = useAuthService().isOwnerOrAdmin();
|
const isOwnerOrAdmin = useAuthService().isOwnerOrAdmin();
|
||||||
const isProjectManager = useIsProjectManager();
|
const isProjectManager = useIsProjectManager();
|
||||||
|
const { trackMixpanelEvent } = useMixpanelTracking();
|
||||||
const { tab } = useTabSearchParam();
|
const { tab } = useTabSearchParam();
|
||||||
|
|
||||||
const { socket } = useSocket();
|
const { socket } = useSocket();
|
||||||
|
|
||||||
const {
|
const { project: selectedProject, projectId } = useAppSelector(state => state.projectReducer);
|
||||||
project: selectedProject,
|
|
||||||
projectId,
|
|
||||||
} = useAppSelector(state => state.projectReducer);
|
|
||||||
const { loadingGroups, groupBy } = useAppSelector(state => state.taskReducer);
|
const { loadingGroups, groupBy } = useAppSelector(state => state.taskReducer);
|
||||||
|
|
||||||
const [creatingTask, setCreatingTask] = useState(false);
|
const [creatingTask, setCreatingTask] = useState(false);
|
||||||
|
|
||||||
const handleRefresh = () => {
|
const handleRefresh = () => {
|
||||||
if (!projectId) return;
|
if (!projectId) return;
|
||||||
|
|
||||||
|
trackMixpanelEvent(evt_project_refresh_click, {
|
||||||
|
project_id: projectId,
|
||||||
|
tab: tab,
|
||||||
|
project_name: selectedProject?.name,
|
||||||
|
});
|
||||||
|
|
||||||
switch (tab) {
|
switch (tab) {
|
||||||
case 'tasks-list':
|
case 'tasks-list':
|
||||||
dispatch(fetchTaskListColumns(projectId));
|
dispatch(fetchTaskListColumns(projectId));
|
||||||
dispatch(fetchPhasesByProjectId(projectId))
|
dispatch(fetchPhasesByProjectId(projectId));
|
||||||
dispatch(fetchTaskGroups(projectId));
|
dispatch(fetchTaskGroups(projectId));
|
||||||
break;
|
break;
|
||||||
case 'board':
|
case 'board':
|
||||||
@@ -113,6 +135,11 @@ const ProjectViewHeader = () => {
|
|||||||
|
|
||||||
const handleSettingsClick = () => {
|
const handleSettingsClick = () => {
|
||||||
if (selectedProject?.id) {
|
if (selectedProject?.id) {
|
||||||
|
trackMixpanelEvent(evt_project_settings_click, {
|
||||||
|
project_id: selectedProject.id,
|
||||||
|
project_name: selectedProject.name,
|
||||||
|
});
|
||||||
|
|
||||||
dispatch(setProjectId(selectedProject.id));
|
dispatch(setProjectId(selectedProject.id));
|
||||||
dispatch(fetchProjectData(selectedProject.id));
|
dispatch(fetchProjectData(selectedProject.id));
|
||||||
dispatch(toggleProjectDrawer());
|
dispatch(toggleProjectDrawer());
|
||||||
@@ -123,6 +150,14 @@ const ProjectViewHeader = () => {
|
|||||||
try {
|
try {
|
||||||
setCreatingTask(true);
|
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 = {
|
const body: ITaskCreateRequest = {
|
||||||
name: DEFAULT_TASK_NAME,
|
name: DEFAULT_TASK_NAME,
|
||||||
project_id: selectedProject?.id,
|
project_id: selectedProject?.id,
|
||||||
@@ -155,6 +190,11 @@ const ProjectViewHeader = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleImportTaskTemplate = () => {
|
const handleImportTaskTemplate = () => {
|
||||||
|
trackMixpanelEvent(evt_project_import_tasks_click, {
|
||||||
|
project_id: selectedProject?.id,
|
||||||
|
project_name: selectedProject?.name,
|
||||||
|
});
|
||||||
|
|
||||||
dispatch(setImportTaskTemplateDrawerOpen(true));
|
dispatch(setImportTaskTemplateDrawerOpen(true));
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -221,7 +261,7 @@ const ProjectViewHeader = () => {
|
|||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
{(isOwnerOrAdmin) && (
|
{isOwnerOrAdmin && (
|
||||||
<Tooltip title="Save as template">
|
<Tooltip title="Save as template">
|
||||||
<Button
|
<Button
|
||||||
shape="circle"
|
shape="circle"
|
||||||
@@ -298,10 +338,9 @@ const ProjectViewHeader = () => {
|
|||||||
style={{ paddingInline: 0, marginBlockEnd: 12 }}
|
style={{ paddingInline: 0, marginBlockEnd: 12 }}
|
||||||
extra={renderHeaderActions()}
|
extra={renderHeaderActions()}
|
||||||
/>
|
/>
|
||||||
{createPortal(<ProjectDrawer onClose={() => { }} />, document.body, 'project-drawer')}
|
{createPortal(<ProjectDrawer onClose={() => {}} />, document.body, 'project-drawer')}
|
||||||
{createPortal(<ImportTaskTemplate />, document.body, 'import-task-template')}
|
{createPortal(<ImportTaskTemplate />, document.body, 'import-task-template')}
|
||||||
{createPortal(<SaveProjectAsTemplate />, document.body, 'save-project-as-template')}
|
{createPortal(<SaveProjectAsTemplate />, document.body, 'save-project-as-template')}
|
||||||
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,11 +12,14 @@ import { fetchStatusesCategories } from '@/features/taskAttributes/taskStatusSli
|
|||||||
import { fetchPhasesByProjectId } from '@/features/projects/singleProject/phase/phases.slice';
|
import { fetchPhasesByProjectId } from '@/features/projects/singleProject/phase/phases.slice';
|
||||||
import { Empty } from 'antd';
|
import { Empty } from 'antd';
|
||||||
import useTabSearchParam from '@/hooks/useTabSearchParam';
|
import useTabSearchParam from '@/hooks/useTabSearchParam';
|
||||||
|
import { evt_project_task_list_visit } from '@/shared/worklenz-analytics-events';
|
||||||
|
import { useMixpanelTracking } from '@/hooks/useMixpanelTracking';
|
||||||
|
|
||||||
const ProjectViewTaskList = () => {
|
const ProjectViewTaskList = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { projectView } = useTabSearchParam();
|
const { projectView } = useTabSearchParam();
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
|
const { trackMixpanelEvent } = useMixpanelTracking();
|
||||||
|
|
||||||
const { projectId } = useAppSelector(state => state.projectReducer);
|
const { projectId } = useAppSelector(state => state.projectReducer);
|
||||||
const { taskGroups, loadingGroups, groupBy, archived, fields, search } = useAppSelector(
|
const { taskGroups, loadingGroups, groupBy, archived, fields, search } = useAppSelector(
|
||||||
@@ -38,6 +41,8 @@ const ProjectViewTaskList = () => {
|
|||||||
}, [projectView, searchParams, setSearchParams]);
|
}, [projectView, searchParams, setSearchParams]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
trackMixpanelEvent(evt_project_task_list_visit);
|
||||||
|
|
||||||
if (projectId && groupBy) {
|
if (projectId && groupBy) {
|
||||||
if (!loadingColumns) dispatch(fetchTaskListColumns(projectId));
|
if (!loadingColumns) dispatch(fetchTaskListColumns(projectId));
|
||||||
if (!loadingPhases) dispatch(fetchPhasesByProjectId(projectId));
|
if (!loadingPhases) dispatch(fetchPhasesByProjectId(projectId));
|
||||||
@@ -54,10 +59,10 @@ const ProjectViewTaskList = () => {
|
|||||||
<Flex vertical gap={16} style={{ overflowX: 'hidden' }}>
|
<Flex vertical gap={16} style={{ overflowX: 'hidden' }}>
|
||||||
<TaskListFilters position="list" />
|
<TaskListFilters position="list" />
|
||||||
|
|
||||||
{(taskGroups.length === 0 && !loadingGroups) ? (
|
{taskGroups.length === 0 && !loadingGroups ? (
|
||||||
<Empty description="No tasks group found" />
|
<Empty description="No tasks group found" />
|
||||||
) : (
|
) : (
|
||||||
<Skeleton active loading={loadingGroups} className='mt-4 p-4'>
|
<Skeleton active loading={loadingGroups} className="mt-4 p-4">
|
||||||
<TaskGroupWrapper taskGroups={taskGroups} groupBy={groupBy} />
|
<TaskGroupWrapper taskGroups={taskGroups} groupBy={groupBy} />
|
||||||
</Skeleton>
|
</Skeleton>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Button, DatePicker, DatePickerProps, Flex, Select, Space } from 'antd';
|
import { Button, DatePicker, DatePickerProps, Flex, Select, Space } from 'antd';
|
||||||
import React, { useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
import { SettingOutlined } from '@ant-design/icons';
|
import { SettingOutlined } from '@ant-design/icons';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { setDate, setType, toggleSettingsDrawer } from '@/features/schedule/scheduleSlice';
|
import { setDate, setType, toggleSettingsDrawer } from '@/features/schedule/scheduleSlice';
|
||||||
@@ -11,6 +11,8 @@ import ScheduleDrawer from '@/features/schedule/ScheduleDrawer';
|
|||||||
import GranttChart from '@/components/schedule/grant-chart/grantt-chart';
|
import GranttChart from '@/components/schedule/grant-chart/grantt-chart';
|
||||||
import { useAppSelector } from '@/hooks/useAppSelector';
|
import { useAppSelector } from '@/hooks/useAppSelector';
|
||||||
import { PickerType } from '@/types/schedule/schedule-v2.types';
|
import { PickerType } from '@/types/schedule/schedule-v2.types';
|
||||||
|
import { evt_schedule_page_visit } from '@/shared/worklenz-analytics-events';
|
||||||
|
import { useMixpanelTracking } from '@/hooks/useMixpanelTracking';
|
||||||
|
|
||||||
const { Option } = Select;
|
const { Option } = Select;
|
||||||
|
|
||||||
@@ -31,6 +33,7 @@ const Schedule: React.FC = () => {
|
|||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const granttChartRef = useRef<any>(null);
|
const granttChartRef = useRef<any>(null);
|
||||||
const { date, type } = useAppSelector(state => state.scheduleReducer);
|
const { date, type } = useAppSelector(state => state.scheduleReducer);
|
||||||
|
const { trackMixpanelEvent } = useMixpanelTracking();
|
||||||
|
|
||||||
useDocumentTitle('Schedule');
|
useDocumentTitle('Schedule');
|
||||||
|
|
||||||
@@ -53,6 +56,10 @@ const Schedule: React.FC = () => {
|
|||||||
console.log('Today:', today);
|
console.log('Today:', today);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
trackMixpanelEvent(evt_schedule_page_visit);
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ marginBlockStart: 65, minHeight: '90vh' }}>
|
<div style={{ marginBlockStart: 65, minHeight: '90vh' }}>
|
||||||
<Flex align="center" justify="space-between">
|
<Flex align="center" justify="space-between">
|
||||||
|
|||||||
@@ -20,6 +20,11 @@ import { categoriesApiService } from '@/api/settings/categories/categories.api.s
|
|||||||
import { IProjectCategory, IProjectCategoryViewModel } from '@/types/project/projectCategory.types';
|
import { IProjectCategory, IProjectCategoryViewModel } from '@/types/project/projectCategory.types';
|
||||||
import { useDocumentTitle } from '@/hooks/useDoumentTItle';
|
import { useDocumentTitle } from '@/hooks/useDoumentTItle';
|
||||||
import { useAppDispatch } from '@/hooks/useAppDispatch';
|
import { useAppDispatch } from '@/hooks/useAppDispatch';
|
||||||
|
import {
|
||||||
|
evt_settings_categories_visit,
|
||||||
|
evt_settings_category_delete,
|
||||||
|
} from '@/shared/worklenz-analytics-events';
|
||||||
|
import { useMixpanelTracking } from '@/hooks/useMixpanelTracking';
|
||||||
|
|
||||||
const CategoriesSettings = () => {
|
const CategoriesSettings = () => {
|
||||||
// localization
|
// localization
|
||||||
@@ -28,6 +33,7 @@ const CategoriesSettings = () => {
|
|||||||
useDocumentTitle('Manage Categories');
|
useDocumentTitle('Manage Categories');
|
||||||
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
const { trackMixpanelEvent } = useMixpanelTracking();
|
||||||
// get currently hover row
|
// get currently hover row
|
||||||
const [hoverRow, setHoverRow] = useState<string | null>(null);
|
const [hoverRow, setHoverRow] = useState<string | null>(null);
|
||||||
const [categories, setCategories] = useState<IProjectCategoryViewModel[]>([]);
|
const [categories, setCategories] = useState<IProjectCategoryViewModel[]>([]);
|
||||||
@@ -56,6 +62,10 @@ const CategoriesSettings = () => {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
trackMixpanelEvent(evt_settings_categories_visit);
|
||||||
|
}, [trackMixpanelEvent]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getCategories();
|
getCategories();
|
||||||
}, [getCategories]);
|
}, [getCategories]);
|
||||||
@@ -70,7 +80,9 @@ const CategoriesSettings = () => {
|
|||||||
{
|
{
|
||||||
key: 'associatedTask',
|
key: 'associatedTask',
|
||||||
title: t('associatedTaskColumn'),
|
title: t('associatedTaskColumn'),
|
||||||
render: (record: IProjectCategoryViewModel) => <Typography.Text>{record.usage}</Typography.Text>,
|
render: (record: IProjectCategoryViewModel) => (
|
||||||
|
<Typography.Text>{record.usage}</Typography.Text>
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'actionBtns',
|
key: 'actionBtns',
|
||||||
@@ -82,7 +94,10 @@ const CategoriesSettings = () => {
|
|||||||
icon={<ExclamationCircleFilled style={{ color: colors.vibrantOrange }} />}
|
icon={<ExclamationCircleFilled style={{ color: colors.vibrantOrange }} />}
|
||||||
okText={t('deleteConfirmationOk')}
|
okText={t('deleteConfirmationOk')}
|
||||||
cancelText={t('deleteConfirmationCancel')}
|
cancelText={t('deleteConfirmationCancel')}
|
||||||
onConfirm={() => record.id && dispatch(deleteCategory(record.id))}
|
onConfirm={() => {
|
||||||
|
trackMixpanelEvent(evt_settings_category_delete, { categoryId: record.id });
|
||||||
|
record.id && dispatch(deleteCategory(record.id));
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Tooltip title="Delete">
|
<Tooltip title="Delete">
|
||||||
<Button shape="default" icon={<DeleteOutlined />} size="small" />
|
<Button shape="default" icon={<DeleteOutlined />} size="small" />
|
||||||
|
|||||||
@@ -31,11 +31,14 @@ import { DEFAULT_PAGE_SIZE } from '@/shared/constants';
|
|||||||
import ClientDrawer from './client-drawer';
|
import ClientDrawer from './client-drawer';
|
||||||
import { useDocumentTitle } from '@/hooks/useDoumentTItle';
|
import { useDocumentTitle } from '@/hooks/useDoumentTItle';
|
||||||
import logger from '@/utils/errorLogger';
|
import logger from '@/utils/errorLogger';
|
||||||
|
import { evt_settings_clients_visit, evt_settings_clients_create } from '@/shared/worklenz-analytics-events';
|
||||||
|
import { useMixpanelTracking } from '@/hooks/useMixpanelTracking';
|
||||||
|
|
||||||
const ClientsSettings: React.FC = () => {
|
const ClientsSettings: React.FC = () => {
|
||||||
const { t } = useTranslation('settings/clients');
|
const { t } = useTranslation('settings/clients');
|
||||||
const { clients } = useAppSelector(state => state.clientReducer);
|
const { clients } = useAppSelector(state => state.clientReducer);
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
const { trackMixpanelEvent } = useMixpanelTracking();
|
||||||
|
|
||||||
useDocumentTitle('Manage Clients');
|
useDocumentTitle('Manage Clients');
|
||||||
|
|
||||||
@@ -62,6 +65,10 @@ const ClientsSettings: React.FC = () => {
|
|||||||
};
|
};
|
||||||
}, [pagination, searchQuery, dispatch]);
|
}, [pagination, searchQuery, dispatch]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
trackMixpanelEvent(evt_settings_clients_visit);
|
||||||
|
}, [trackMixpanelEvent]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getClients();
|
getClients();
|
||||||
}, [searchQuery]);
|
}, [searchQuery]);
|
||||||
|
|||||||
@@ -27,6 +27,11 @@ import { useEffect, useMemo, useState } from 'react';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import JobTitleDrawer from './job-titles-drawer';
|
import JobTitleDrawer from './job-titles-drawer';
|
||||||
import logger from '@/utils/errorLogger';
|
import logger from '@/utils/errorLogger';
|
||||||
|
import {
|
||||||
|
evt_settings_job_titles_visit,
|
||||||
|
evt_settings_job_titles_create,
|
||||||
|
} from '@/shared/worklenz-analytics-events';
|
||||||
|
import { useMixpanelTracking } from '@/hooks/useMixpanelTracking';
|
||||||
|
|
||||||
interface PaginationType {
|
interface PaginationType {
|
||||||
current: number;
|
current: number;
|
||||||
@@ -42,6 +47,7 @@ const JobTitlesSettings = () => {
|
|||||||
const { t } = useTranslation('settings/job-titles');
|
const { t } = useTranslation('settings/job-titles');
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
useDocumentTitle('Manage Job Titles');
|
useDocumentTitle('Manage Job Titles');
|
||||||
|
const { trackMixpanelEvent } = useMixpanelTracking();
|
||||||
|
|
||||||
const [selectedJobId, setSelectedJobId] = useState<string | null>(null);
|
const [selectedJobId, setSelectedJobId] = useState<string | null>(null);
|
||||||
const [showDrawer, setShowDrawer] = useState(false);
|
const [showDrawer, setShowDrawer] = useState(false);
|
||||||
@@ -73,6 +79,10 @@ const JobTitlesSettings = () => {
|
|||||||
};
|
};
|
||||||
}, [pagination.current, pagination.pageSize, pagination.field, pagination.order, searchQuery]);
|
}, [pagination.current, pagination.pageSize, pagination.field, pagination.order, searchQuery]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
trackMixpanelEvent(evt_settings_job_titles_visit);
|
||||||
|
}, [trackMixpanelEvent]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getJobTitles();
|
getJobTitles();
|
||||||
}, [getJobTitles]);
|
}, [getJobTitles]);
|
||||||
@@ -83,6 +93,8 @@ const JobTitlesSettings = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleCreateClick = () => {
|
const handleCreateClick = () => {
|
||||||
|
trackMixpanelEvent(evt_settings_job_titles_create);
|
||||||
|
|
||||||
setSelectedJobId(null);
|
setSelectedJobId(null);
|
||||||
setShowDrawer(true);
|
setShowDrawer(true);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -19,6 +19,11 @@ import { labelsApiService } from '@/api/taskAttributes/labels/labels.api.service
|
|||||||
import CustomColorLabel from '@components/task-list-common/labelsSelector/custom-color-label';
|
import CustomColorLabel from '@components/task-list-common/labelsSelector/custom-color-label';
|
||||||
import { useDocumentTitle } from '@/hooks/useDoumentTItle';
|
import { useDocumentTitle } from '@/hooks/useDoumentTItle';
|
||||||
import logger from '@/utils/errorLogger';
|
import logger from '@/utils/errorLogger';
|
||||||
|
import {
|
||||||
|
evt_settings_labels_visit,
|
||||||
|
evt_settings_labels_delete,
|
||||||
|
} from '@/shared/worklenz-analytics-events';
|
||||||
|
import { useMixpanelTracking } from '@/hooks/useMixpanelTracking';
|
||||||
|
|
||||||
const LabelsSettings = () => {
|
const LabelsSettings = () => {
|
||||||
const { t } = useTranslation('settings/labels');
|
const { t } = useTranslation('settings/labels');
|
||||||
@@ -27,6 +32,7 @@ const LabelsSettings = () => {
|
|||||||
const [searchQuery, setSearchQuery] = useState('');
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
const [labels, setLabels] = useState<ITaskLabel[]>([]);
|
const [labels, setLabels] = useState<ITaskLabel[]>([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
const { trackMixpanelEvent } = useMixpanelTracking();
|
||||||
|
|
||||||
const filteredData = useMemo(
|
const filteredData = useMemo(
|
||||||
() =>
|
() =>
|
||||||
@@ -49,12 +55,18 @@ const LabelsSettings = () => {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
trackMixpanelEvent(evt_settings_labels_visit);
|
||||||
|
}, [trackMixpanelEvent]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getLabels();
|
getLabels();
|
||||||
}, [getLabels]);
|
}, [getLabels]);
|
||||||
|
|
||||||
const deleteLabel = async (id: string) => {
|
const deleteLabel = async (id: string) => {
|
||||||
try {
|
try {
|
||||||
|
trackMixpanelEvent(evt_settings_labels_delete, { labelId: id });
|
||||||
|
|
||||||
const response = await labelsApiService.deleteById(id);
|
const response = await labelsApiService.deleteById(id);
|
||||||
if (response.done) {
|
if (response.done) {
|
||||||
getLabels();
|
getLabels();
|
||||||
|
|||||||
@@ -6,17 +6,24 @@ import { useDocumentTitle } from '@/hooks/useDoumentTItle';
|
|||||||
import { INotificationSettings } from '@/types/settings/notifications.types';
|
import { INotificationSettings } from '@/types/settings/notifications.types';
|
||||||
import { profileSettingsApiService } from '@/api/settings/profile/profile-settings.api.service';
|
import { profileSettingsApiService } from '@/api/settings/profile/profile-settings.api.service';
|
||||||
import logger from '@/utils/errorLogger';
|
import logger from '@/utils/errorLogger';
|
||||||
|
import { evt_settings_notifications_visit } from '@/shared/worklenz-analytics-events';
|
||||||
|
import { useMixpanelTracking } from '@/hooks/useMixpanelTracking';
|
||||||
|
|
||||||
const NotificationsSettings = () => {
|
const NotificationsSettings = () => {
|
||||||
const { t } = useTranslation('settings/notifications');
|
const { t } = useTranslation('settings/notifications');
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const themeMode = useAppSelector(state => state.themeReducer.mode);
|
const themeMode = useAppSelector(state => state.themeReducer.mode);
|
||||||
|
const { trackMixpanelEvent } = useMixpanelTracking();
|
||||||
|
|
||||||
const [notificationsSettings, setNotificationsSettings] = useState<INotificationSettings>({});
|
const [notificationsSettings, setNotificationsSettings] = useState<INotificationSettings>({});
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
useDocumentTitle(t('title'));
|
useDocumentTitle(t('title'));
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
trackMixpanelEvent(evt_settings_notifications_visit);
|
||||||
|
}, [trackMixpanelEvent]);
|
||||||
|
|
||||||
const fetchNotificationsSettings = async () => {
|
const fetchNotificationsSettings = async () => {
|
||||||
try {
|
try {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
|||||||
@@ -11,10 +11,16 @@ import { ITaskTemplatesGetResponse } from '@/types/settings/task-templates.types
|
|||||||
import logger from '@/utils/errorLogger';
|
import logger from '@/utils/errorLogger';
|
||||||
import { taskTemplatesApiService } from '@/api/task-templates/task-templates.api.service';
|
import { taskTemplatesApiService } from '@/api/task-templates/task-templates.api.service';
|
||||||
import { calculateTimeGap } from '@/utils/calculate-time-gap';
|
import { calculateTimeGap } from '@/utils/calculate-time-gap';
|
||||||
|
import {
|
||||||
|
evt_settings_task_templates_visit,
|
||||||
|
evt_settings_task_templates_delete,
|
||||||
|
} from '@/shared/worklenz-analytics-events';
|
||||||
|
import { useMixpanelTracking } from '@/hooks/useMixpanelTracking';
|
||||||
|
|
||||||
const TaskTemplatesSettings = () => {
|
const TaskTemplatesSettings = () => {
|
||||||
const { t } = useTranslation('settings/task-templates');
|
const { t } = useTranslation('settings/task-templates');
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
const { trackMixpanelEvent } = useMixpanelTracking();
|
||||||
const themeMode = useAppSelector(state => state.themeReducer.mode);
|
const themeMode = useAppSelector(state => state.themeReducer.mode);
|
||||||
const [taskTemplates, setTaskTemplates] = useState<ITaskTemplatesGetResponse[]>([]);
|
const [taskTemplates, setTaskTemplates] = useState<ITaskTemplatesGetResponse[]>([]);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
@@ -34,12 +40,18 @@ const TaskTemplatesSettings = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
trackMixpanelEvent(evt_settings_task_templates_visit);
|
||||||
|
}, [trackMixpanelEvent]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchTaskTemplates();
|
fetchTaskTemplates();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleDeleteTemplate = async (id: string) => {
|
const handleDeleteTemplate = async (id: string) => {
|
||||||
try {
|
try {
|
||||||
|
trackMixpanelEvent(evt_settings_task_templates_delete, { templateId: id });
|
||||||
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
await taskTemplatesApiService.deleteTemplate(id);
|
await taskTemplatesApiService.deleteTemplate(id);
|
||||||
await fetchTaskTemplates();
|
await fetchTaskTemplates();
|
||||||
@@ -111,10 +123,10 @@ const TaskTemplatesSettings = () => {
|
|||||||
<Table
|
<Table
|
||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
size="small"
|
size="small"
|
||||||
pagination={{
|
pagination={{
|
||||||
size: 'small',
|
size: 'small',
|
||||||
showSizeChanger: true,
|
showSizeChanger: true,
|
||||||
showTotal: (total) => t('totalItems', { total })
|
showTotal: total => t('totalItems', { total }),
|
||||||
}}
|
}}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
dataSource={taskTemplates}
|
dataSource={taskTemplates}
|
||||||
|
|||||||
@@ -37,11 +37,15 @@ import { DEFAULT_PAGE_SIZE, PAGE_SIZE_OPTIONS } from '@/shared/constants';
|
|||||||
import { teamMembersApiService } from '@/api/team-members/teamMembers.api.service';
|
import { teamMembersApiService } from '@/api/team-members/teamMembers.api.service';
|
||||||
import { colors } from '@/styles/colors';
|
import { colors } from '@/styles/colors';
|
||||||
|
|
||||||
|
import { evt_people_refresh_click, evt_people_delete, evt_settings_teams_visit } from '@/shared/worklenz-analytics-events';
|
||||||
|
import { useMixpanelTracking } from '@/hooks/useMixpanelTracking';
|
||||||
|
|
||||||
const TeamMembersSettings = () => {
|
const TeamMembersSettings = () => {
|
||||||
const { t } = useTranslation('settings/team-members');
|
const { t } = useTranslation('settings/team-members');
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { socket } = useSocket();
|
const { socket } = useSocket();
|
||||||
const refreshTeamMembers = useAppSelector(state => state.memberReducer.refreshTeamMembers); // Listen to refresh flag
|
const refreshTeamMembers = useAppSelector(state => state.memberReducer.refreshTeamMembers); // Listen to refresh flag
|
||||||
|
const { trackMixpanelEvent } = useMixpanelTracking();
|
||||||
|
|
||||||
const [model, setModel] = useState<ITeamMembersViewModel>({ total: 0, data: [] });
|
const [model, setModel] = useState<ITeamMembersViewModel>({ total: 0, data: [] });
|
||||||
const [searchQuery, setSearchQuery] = useState<string>('');
|
const [searchQuery, setSearchQuery] = useState<string>('');
|
||||||
@@ -96,6 +100,11 @@ const TeamMembersSettings = () => {
|
|||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
const res = await teamMembersApiService.delete(record.id);
|
const res = await teamMembersApiService.delete(record.id);
|
||||||
if (res.done) {
|
if (res.done) {
|
||||||
|
trackMixpanelEvent(evt_people_delete, {
|
||||||
|
member_id: record.id,
|
||||||
|
member_name: record.name,
|
||||||
|
});
|
||||||
|
|
||||||
await getTeamMembers();
|
await getTeamMembers();
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
@@ -114,6 +123,9 @@ const TeamMembersSettings = () => {
|
|||||||
|
|
||||||
const handleRefresh = useCallback(() => {
|
const handleRefresh = useCallback(() => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
|
||||||
|
trackMixpanelEvent(evt_people_refresh_click);
|
||||||
|
|
||||||
getTeamMembers().finally(() => setIsLoading(false));
|
getTeamMembers().finally(() => setIsLoading(false));
|
||||||
}, [getTeamMembers]);
|
}, [getTeamMembers]);
|
||||||
|
|
||||||
@@ -152,6 +164,8 @@ const TeamMembersSettings = () => {
|
|||||||
}, [refreshTeamMembers, handleRefresh]);
|
}, [refreshTeamMembers, handleRefresh]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
trackMixpanelEvent(evt_settings_teams_visit);
|
||||||
|
|
||||||
getTeamMembers();
|
getTeamMembers();
|
||||||
}, [getTeamMembers]);
|
}, [getTeamMembers]);
|
||||||
|
|
||||||
@@ -340,14 +354,11 @@ const TeamMembersSettings = () => {
|
|||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
{createPortal(
|
{createPortal(
|
||||||
<UpdateMemberDrawer
|
<UpdateMemberDrawer selectedMemberId={selectedMemberId} onRoleUpdate={handleRoleUpdate} />,
|
||||||
selectedMemberId={selectedMemberId}
|
|
||||||
onRoleUpdate={handleRoleUpdate}
|
|
||||||
/>,
|
|
||||||
document.body
|
document.body
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default TeamMembersSettings;
|
export default TeamMembersSettings;
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ import { useAppDispatch } from '@/hooks/useAppDispatch';
|
|||||||
import { useDocumentTitle } from '@/hooks/useDoumentTItle';
|
import { useDocumentTitle } from '@/hooks/useDoumentTItle';
|
||||||
import { ITeamGetResponse } from '@/types/teams/team.type';
|
import { ITeamGetResponse } from '@/types/teams/team.type';
|
||||||
|
|
||||||
|
import { evt_settings_teams_visit } from '@/shared/worklenz-analytics-events';
|
||||||
|
import { useMixpanelTracking } from '@/hooks/useMixpanelTracking';
|
||||||
|
|
||||||
const TeamsSettings = () => {
|
const TeamsSettings = () => {
|
||||||
useDocumentTitle('Teams');
|
useDocumentTitle('Teams');
|
||||||
|
|
||||||
@@ -18,8 +21,11 @@ const TeamsSettings = () => {
|
|||||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
const { teamsList } = useAppSelector(state => state.teamReducer);
|
const { teamsList } = useAppSelector(state => state.teamReducer);
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
const { trackMixpanelEvent } = useMixpanelTracking();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
trackMixpanelEvent(evt_settings_teams_visit);
|
||||||
|
|
||||||
dispatch(fetchTeams());
|
dispatch(fetchTeams());
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user