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,136 @@
import { Button, ColorPicker, ConfigProvider, Flex, Input } from 'antd';
import { CloseCircleOutlined, HolderOutlined } from '@ant-design/icons';
import { nanoid } from '@reduxjs/toolkit';
import { useAppDispatch } from '@/hooks/useAppDispatch';
import { deletePhaseOption, fetchPhasesByProjectId, updatePhaseColor, updatePhaseName } from './phases.slice';
import { PhaseColorCodes } from '@/shared/constants';
import { ITaskPhase } from '@/types/tasks/taskPhase.types';
import { TFunction } from 'i18next';
import logger from '@/utils/errorLogger';
import { useState, useEffect, useCallback } from 'react';
import { useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import { fetchBoardTaskGroups } from '@/features/board/board-slice';
import useTabSearchParam from '@/hooks/useTabSearchParam';
import { fetchTaskGroups } from '@/features/tasks/tasks.slice';
interface PhaseOptionItemProps {
option: ITaskPhase | null;
projectId: string | null;
t: TFunction;
}
const PhaseOptionItem = ({ option, projectId, t }: PhaseOptionItemProps) => {
const [color, setColor] = useState(option?.color_code || PhaseColorCodes[0]);
const [phaseName, setPhaseName] = useState(option?.name || '');
const dispatch = useAppDispatch();
const { projectView } = useTabSearchParam();
const { attributes, listeners, setNodeRef, transform, transition } = useSortable({
id: option?.id || 'temp-id'
});
const style = {
transform: CSS.Transform.toString(transform),
transition,
};
useEffect(() => {
if (option) {
setPhaseName(option.name);
setColor(option.color_code);
}
}, [option]);
const refreshTasks = useCallback(() => {
if (!projectId) return;
const fetchAction = projectView === 'list' ? fetchTaskGroups : fetchBoardTaskGroups;
dispatch(fetchAction(projectId));
}, [projectId, projectView, dispatch]);
const handlePhaseNameChange = async (e: React.FocusEvent<HTMLInputElement>) => {
if (!projectId || !option || phaseName.trim() === option.name.trim()) return;
try {
const updatedPhase = { ...option, name: phaseName.trim() };
const response = await dispatch(updatePhaseName({
phaseId: option.id,
phase: updatedPhase,
projectId
})).unwrap();
if (response.done) {
dispatch(fetchPhasesByProjectId(projectId));
refreshTasks();
}
} catch (error) {
logger.error('Error updating phase name', error);
setPhaseName(option.name);
}
};
const handleDeletePhaseOption = async () => {
if (!option?.id || !projectId) return;
try {
const response = await dispatch(
deletePhaseOption({ phaseOptionId: option.id, projectId })
).unwrap();
if (response.done) {
dispatch(fetchPhasesByProjectId(projectId));
refreshTasks();
}
} catch (error) {
logger.error('Error deleting phase option', error);
}
};
const handleColorChange = async () => {
if (!projectId || !option) return;
try {
const updatedPhase = { ...option, color_code: color };
const response = await dispatch(updatePhaseColor({ projectId, body: updatedPhase })).unwrap();
if (response.done) {
dispatch(fetchPhasesByProjectId(projectId));
refreshTasks();
}
} catch (error) {
logger.error('Error changing phase color', error);
}
};
return (
<ConfigProvider wave={{ disabled: true }}>
<div ref={setNodeRef} style={style} {...attributes}>
<Flex key={option?.id || nanoid()} align="center" gap={8}>
<div {...listeners} style={{ cursor: 'grab' }}>
<HolderOutlined />
</div>
<Input
type="text"
value={phaseName}
onChange={(e) => setPhaseName(e.target.value)}
onBlur={handlePhaseNameChange}
onPressEnter={(e) => e.currentTarget.blur()}
placeholder={t('enterPhaseName')}
/>
<ColorPicker
onChange={(value) => setColor(value.toHexString())}
onChangeComplete={handleColorChange}
value={color}
/>
<Button
className="borderless-icon-btn"
icon={<CloseCircleOutlined />}
onClick={handleDeletePhaseOption}
/>
</Flex>
</div>
</ConfigProvider>
);
};
export default PhaseOptionItem;