This commit is contained in:
chamikaJ
2025-04-17 18:28:54 +05:30
parent f583291d8a
commit 8825b0410a
2837 changed files with 241385 additions and 127578 deletions

View File

@@ -0,0 +1,160 @@
import { useState } from 'react';
import {
Button,
Divider,
Drawer,
Flex,
Form,
Input,
List,
Select,
Skeleton,
Typography,
} from 'antd/es';
import { useTranslation } from 'react-i18next';
import { useAppDispatch } from '@/hooks/useAppDispatch';
import { useAppSelector } from '@/hooks/useAppSelector';
import { ITaskTemplatesGetResponse } from '@/types/settings/task-templates.types';
import { IProjectTask } from '@/types/project/projectTasksViewModel.types';
import { taskTemplatesApiService } from '@/api/task-templates/task-templates.api.service';
import logger from '@/utils/errorLogger';
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';
const ImportTaskTemplate = () => {
const dispatch = useAppDispatch();
const [form] = Form.useForm();
const { t } = useTranslation('project-view/import-task-templates');
const { tab } = useTabSearchParam();
const { importTaskTemplateDrawerOpen, projectId } = useAppSelector(state => state.projectReducer);
const [templates, setTemplates] = useState<ITaskTemplatesGetResponse[]>([]);
const [tasks, setTasks] = useState<IProjectTask[]>([]);
const [loadingTemplates, setLoadingTemplates] = useState(false);
const [loadingTasks, setLoadingTasks] = useState(false);
const [importing, setImporting] = useState(false);
const handleClose = () => {
form.resetFields();
setTasks([]);
dispatch(setImportTaskTemplateDrawerOpen(false));
};
const fetchTemplates = async () => {
try {
setLoadingTemplates(true);
const res = await taskTemplatesApiService.getTemplates();
if (res.done) {
setTemplates(res.body);
}
} catch (error) {
logger.error('Error fetching templates', error);
} finally {
setLoadingTemplates(false);
}
};
const fetchTasks = async (templateId: string) => {
try {
setLoadingTasks(true);
const res = await taskTemplatesApiService.getTemplate(templateId);
if (res.done) {
setTasks(res.body?.tasks || []);
}
} catch (error) {
logger.error('Error fetching tasks', error);
} finally {
setLoadingTasks(false);
}
};
const handleTemplateSelect = (value: string) => {
if (!value) return;
fetchTasks(value);
};
const handleAfterOpenChange = (open: boolean) => {
if (open) {
fetchTemplates();
}
};
const handleImport = async () => {
if (!projectId || tasks.length === 0) return;
try {
setImporting(true);
const res = await taskTemplatesApiService.importTemplate(projectId, tasks);
if (res.done) {
if (tab === 'board') dispatch(fetchBoardTaskGroups(projectId));
if (tab === 'tasks-list') dispatch(fetchTaskGroups(projectId));
dispatch(setImportTaskTemplateDrawerOpen(false));
}
} catch (error) {
logger.error('Error importing task template', error);
} finally {
setImporting(false);
}
};
const handleRemoveTask = (index: number) => {
const newTasks = [...tasks];
newTasks.splice(index, 1);
setTasks(newTasks);
};
return (
<Drawer
title={t('importTaskTemplate')}
open={importTaskTemplateDrawerOpen}
onClose={handleClose}
width={650}
afterOpenChange={handleAfterOpenChange}
destroyOnClose
footer={
<Flex justify="end" gap={10}>
<Button onClick={handleClose}>{t('cancel')}</Button>
<Button type="primary" onClick={handleImport} loading={importing} disabled={tasks.length === 0}>
{t('import')}
</Button>
</Flex>
}
>
<Form form={form} layout="horizontal">
<Form.Item name="templateName" label={t('templateName')}>
<Select
options={templates.map(t => ({ label: t.name, value: t.id }))}
loading={loadingTemplates}
onSelect={handleTemplateSelect}
/>
</Form.Item>
<Divider />
<Typography.Text strong>
{t('selectedTasks')} ({tasks.length})
</Typography.Text>
<List
loading={loadingTasks}
dataSource={tasks}
bordered
renderItem={(task, index) => (
<List.Item
key={task.id}
actions={[
<Button type="link" onClick={() => handleRemoveTask(index)}>
{t('remove')}
</Button>,
]}
>
<Typography.Text>{task.name}</Typography.Text>
</List.Item>
)}
/>
</Form>
</Drawer>
);
};
export default ImportTaskTemplate;

View File

@@ -0,0 +1,179 @@
import { useState } from 'react';
import { Button, Drawer, Form, Input, List, Typography } from 'antd';
import { useTranslation } from 'react-i18next';
import { useAppDispatch } from '@/hooks/useAppDispatch';
import { taskTemplatesApiService } from '@/api/task-templates/task-templates.api.service';
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';
interface TaskTemplateDrawerProps {
showDrawer: boolean;
selectedTemplateId: string | null;
onClose: () => void;
}
const TaskTemplateDrawer = ({
showDrawer = false,
selectedTemplateId,
onClose,
}: TaskTemplateDrawerProps) => {
const dispatch = useAppDispatch();
const { t } = useTranslation('task-template-drawer');
const [form] = Form.useForm();
const [templateData, setTemplateData] = useState<ITaskTemplateGetResponse>({});
const [isLoading, setIsLoading] = useState(false);
const [creatingTemplate, setCreatingTemplate] = useState(false);
const [updatingTemplate, setUpdatingTemplate] = useState(false);
const { selectedTasks } = useAppSelector(state => state.bulkActionReducer);
const onCloseDrawer = () => {
form.resetFields();
setTemplateData({});
onClose();
};
const fetchTemplateData = async () => {
if (!selectedTemplateId) return;
try {
setIsLoading(true);
const res = await taskTemplatesApiService.getTemplate(selectedTemplateId);
if (res.done) {
setTemplateData(res.body);
form.setFieldsValue({
name: res.body.name,
});
}
} catch (error) {
logger.error('Failed to fetch template data:', error);
} finally {
setIsLoading(false);
}
};
const afterOpenChange = (open: boolean) => {
if (selectedTemplateId) {
fetchTemplateData();
return;
}
setTemplateData({ tasks: selectedTasks });
};
const handleRemoveTask = (index: number) => {
const updatedTasks = [...(templateData.tasks || [])];
updatedTasks.splice(index, 1);
setTemplateData({
...templateData,
tasks: updatedTasks,
});
};
const createTemplate = async () => {
const values = form.getFieldsValue();
if (!values.name || !templateData.tasks) return;
try {
setCreatingTemplate(true);
const res = await taskTemplatesApiService.createTemplate({
name: values.name || '',
tasks: templateData.tasks || [],
});
if (res.done) {
onCloseDrawer();
dispatch(setSelectedTasks([]));
}
} catch (error) {
logger.error('Failed to create template:', error);
} finally {
setCreatingTemplate(false);
}
};
const updateTemplate = async () => {
if (!selectedTemplateId || !templateData.name || !templateData.tasks) return;
const values = form.getFieldsValue();
try {
setUpdatingTemplate(true);
const res = await taskTemplatesApiService.updateTemplate(selectedTemplateId, {
name: values.name || '',
tasks: templateData.tasks || [],
});
if (res.done) {
onCloseDrawer();
dispatch(setSelectedTasks([]));
}
} catch (error) {
logger.error('Failed to update template:', error);
} finally {
setUpdatingTemplate(false);
}
};
const handleSaveTemplate = () => {
if (!selectedTemplateId) {
createTemplate();
} else {
updateTemplate();
}
};
return (
<Drawer
width={650}
title={selectedTemplateId ? t('editTaskTemplate') : t('createTaskTemplate')}
open={showDrawer}
onClose={onCloseDrawer}
afterOpenChange={afterOpenChange}
destroyOnClose={true}
footer={
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', justifyContent: 'right' }}>
<Button onClick={onCloseDrawer}>{t('cancelButton')}</Button>
<Button type="primary" onClick={handleSaveTemplate} loading={creatingTemplate || updatingTemplate}>
{t('saveButton')}
</Button>
</div>
}
>
<Form form={form} initialValues={{ name: templateData?.name }}>
<Form.Item
name="name"
label={t('templateNameText')}
rules={[{ required: true, message: t('templateNameRequired') }]}
>
<Input type="text" />
</Form.Item>
<Typography.Text style={{ fontWeight: 700 }}>
{t('selectedTasks')} ({templateData?.tasks?.length || 0})
</Typography.Text>
<div style={{ marginTop: '1.5rem' }}>
<List
loading={isLoading}
bordered
dataSource={templateData?.tasks}
renderItem={(item, index) => (
<List.Item>
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
width: '100%',
}}
>
<span>{item.name}</span>
<Button type="link" onClick={() => handleRemoveTask(index)}>
{t('removeTask')}
</Button>
</div>
</List.Item>
)}
/>
</div>
</Form>
</Drawer>
);
};
export default TaskTemplateDrawer;