feat(task-phases): enhance phase creation with custom naming and localization updates

- Updated the phase creation logic to allow custom names, defaulting to a generated name if none is provided.
- Modified localization files for multiple languages to improve phase-related text consistency and clarity.
- Enhanced the UI components to reflect the new phase naming functionality and ensure proper integration with the task management system.
- Added dark mode styling for confirmation modals to improve user experience across themes.
This commit is contained in:
chamikaJ
2025-07-11 17:26:21 +05:30
parent 12b430a349
commit affbbbffbf
23 changed files with 464 additions and 77 deletions

View File

@@ -16,19 +16,23 @@ export default class TaskPhasesController extends WorklenzControllerBase {
if (!req.query.id) if (!req.query.id)
return res.status(400).send(new ServerResponse(false, null, "Invalid request")); return res.status(400).send(new ServerResponse(false, null, "Invalid request"));
// Use custom name if provided, otherwise use default naming pattern
const phaseName = req.body.name?.trim() ||
`Untitled Phase (${(await db.query("SELECT COUNT(*) FROM project_phases WHERE project_id = $1", [req.query.id])).rows[0].count + 1})`;
const q = ` const q = `
INSERT INTO project_phases (name, color_code, project_id, sort_index) INSERT INTO project_phases (name, color_code, project_id, sort_index)
VALUES ( VALUES (
CONCAT('Untitled Phase (', (SELECT COUNT(*) FROM project_phases WHERE project_id = $2) + 1, ')'),
$1, $1,
$2, $2,
(SELECT COUNT(*) FROM project_phases WHERE project_id = $2) + 1) $3,
(SELECT COUNT(*) FROM project_phases WHERE project_id = $3) + 1)
RETURNING id, name, color_code, sort_index; RETURNING id, name, color_code, sort_index;
`; `;
req.body.color_code = this.DEFAULT_PHASE_COLOR; req.body.color_code = this.DEFAULT_PHASE_COLOR;
const result = await db.query(q, [req.body.color_code, req.query.id]); const result = await db.query(q, [phaseName, req.body.color_code, req.query.id]);
const [data] = result.rows; const [data] = result.rows;
data.color_code = getColor(data.name) + TASK_STATUS_COLOR_ALPHA; data.color_code = getColor(data.name) + TASK_STATUS_COLOR_ALPHA;

View File

@@ -1,16 +1,20 @@
{ {
"configurePhases": "Konfiguro Fazat", "configurePhases": "Konfiguro Fazat",
"configure": "Konfiguro",
"phaseLabel": "Etiketa e Fazës", "phaseLabel": "Etiketa e Fazës",
"enterPhaseName": "Shkruani emrin e fazës", "enterPhaseName": "Shkruaj emrin e fazës",
"addOption": "Shto Opsion", "addOption": "Shto Opsion",
"phaseOptions": "Opsionet e Fazës", "phaseOptions": "Opsionet e Fazës",
"dragToReorderPhases": "Zvarrit fazat për t'i rirenditur. Çdo fazë mund të ketë një ngjyrë të ndryshme.", "optionsText": "Opsione",
"enterNewPhaseName": "Shkruani emrin e fazës së re...", "dragToReorderPhases": "Tërhiq fazat për t'i rirenditur. Çdo fazë mund të ketë një ngjyrë të ndryshme.",
"enterNewPhaseName": "Shkruaj emrin e fazës së re...",
"addPhase": "Shto Fazë", "addPhase": "Shto Fazë",
"noPhasesFound": "Nuk u gjetën faza", "noPhasesFound": "Nuk u gjetën faza",
"no": "Asnjë",
"found": "u gjet",
"deletePhase": "Fshi Fazën", "deletePhase": "Fshi Fazën",
"deletePhaseConfirm": "Jeni sigurt që doni të fshini këtë fazë? Ky veprim nuk mund të zhbëhet.", "deletePhaseConfirm": "Jeni i sigurt që doni të fshini këtë fazë? Ky veprim nuk mund të zhbëhet.",
"rename": "Riemëro", "rename": "Riemërto",
"delete": "Fshi", "delete": "Fshi",
"create": "Krijo", "create": "Krijo",
"cancel": "Anulo", "cancel": "Anulo",

View File

@@ -68,9 +68,10 @@
"clearing": "Po pastron...", "clearing": "Po pastron...",
"cancel": "Anulo", "cancel": "Anulo",
"search": "Kërko", "search": "Kërko",
"groupedBy": "I grupuar sipas", "groupedBy": "Grupuar sipas",
"manageStatuses": "Menaxho statuset", "manage": "Menaxho",
"managePhases": "Menaxho fazat", "manageStatuses": "Menaxho Statuset",
"managePhases": "Menaxho Fazat",
"dragToReorderStatuses": "Statuset janë të organizuara sipas kategorive. Tërhiq për të rirenditur brenda kategorive. Kliko 'Shto status' për të krijuar statuse të reja në çdo kategori.", "dragToReorderStatuses": "Statuset janë të organizuara sipas kategorive. Tërhiq për të rirenditur brenda kategorive. Kliko 'Shto status' për të krijuar statuse të reja në çdo kategori.",
"enterNewStatusName": "Shkruani emrin e statusit të ri...", "enterNewStatusName": "Shkruani emrin e statusit të ri...",
"addStatus": "Shto status", "addStatus": "Shto status",

View File

@@ -1,13 +1,17 @@
{ {
"configurePhases": "Phasen konfigurieren", "configurePhases": "Phasen konfigurieren",
"phaseLabel": "Phasenbezeichnung", "configure": "Konfigurieren",
"enterPhaseName": "Phasennamen eingeben", "phaseLabel": "Phasen-Label",
"enterPhaseName": "Phasenname eingeben",
"addOption": "Option hinzufügen", "addOption": "Option hinzufügen",
"phaseOptions": "Phasenoptionen", "phaseOptions": "Phasenoptionen",
"optionsText": "Optionen",
"dragToReorderPhases": "Ziehen Sie Phasen, um sie neu zu ordnen. Jede Phase kann eine andere Farbe haben.", "dragToReorderPhases": "Ziehen Sie Phasen, um sie neu zu ordnen. Jede Phase kann eine andere Farbe haben.",
"enterNewPhaseName": "Neuen Phasennamen eingeben...", "enterNewPhaseName": "Neuen Phasennamen eingeben...",
"addPhase": "Phase hinzufügen", "addPhase": "Phase hinzufügen",
"noPhasesFound": "Keine Phasen gefunden", "noPhasesFound": "Keine Phasen gefunden",
"no": "Keine",
"found": "gefunden",
"deletePhase": "Phase löschen", "deletePhase": "Phase löschen",
"deletePhaseConfirm": "Sind Sie sicher, dass Sie diese Phase löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.", "deletePhaseConfirm": "Sind Sie sicher, dass Sie diese Phase löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.",
"rename": "Umbenennen", "rename": "Umbenennen",

View File

@@ -69,6 +69,7 @@
"cancel": "Abbrechen", "cancel": "Abbrechen",
"search": "Suchen", "search": "Suchen",
"groupedBy": "Gruppiert nach", "groupedBy": "Gruppiert nach",
"manage": "Verwalten",
"manageStatuses": "Status verwalten", "manageStatuses": "Status verwalten",
"managePhases": "Phasen verwalten", "managePhases": "Phasen verwalten",
"dragToReorderStatuses": "Status sind nach Kategorien organisiert. Ziehen Sie, um innerhalb von Kategorien neu zu ordnen. Klicken Sie auf 'Status hinzufügen', um neue Status in jeder Kategorie zu erstellen.", "dragToReorderStatuses": "Status sind nach Kategorien organisiert. Ziehen Sie, um innerhalb von Kategorien neu zu ordnen. Klicken Sie auf 'Status hinzufügen', um neue Status in jeder Kategorie zu erstellen.",

View File

@@ -1,13 +1,17 @@
{ {
"configurePhases": "Configure Phases", "configurePhases": "Configure Phases",
"configure": "Configure",
"phaseLabel": "Phase Label", "phaseLabel": "Phase Label",
"enterPhaseName": "Enter phase name", "enterPhaseName": "Enter phase name",
"addOption": "Add Option", "addOption": "Add Option",
"phaseOptions": "Phase Options", "phaseOptions": "Phase Options",
"optionsText": "Options",
"dragToReorderPhases": "Drag phases to reorder them. Each phase can have a different color.", "dragToReorderPhases": "Drag phases to reorder them. Each phase can have a different color.",
"enterNewPhaseName": "Enter new phase name...", "enterNewPhaseName": "Enter new phase name...",
"addPhase": "Add Phase", "addPhase": "Add Phase",
"noPhasesFound": "No phases found", "noPhasesFound": "No phases found",
"no": "No",
"found": "found",
"deletePhase": "Delete Phase", "deletePhase": "Delete Phase",
"deletePhaseConfirm": "Are you sure you want to delete this phase? This action cannot be undone.", "deletePhaseConfirm": "Are you sure you want to delete this phase? This action cannot be undone.",
"rename": "Rename", "rename": "Rename",

View File

@@ -69,6 +69,7 @@
"cancel": "Cancel", "cancel": "Cancel",
"search": "Search", "search": "Search",
"groupedBy": "Grouped by", "groupedBy": "Grouped by",
"manage": "Manage",
"manageStatuses": "Manage Statuses", "manageStatuses": "Manage Statuses",
"managePhases": "Manage Phases", "managePhases": "Manage Phases",
"dragToReorderStatuses": "Statuses are organized by categories. Drag to reorder within categories. Click 'Add Status' to create new statuses in each category.", "dragToReorderStatuses": "Statuses are organized by categories. Drag to reorder within categories. Click 'Add Status' to create new statuses in each category.",

View File

@@ -1,13 +1,17 @@
{ {
"configurePhases": "Configurar fases", "configurePhases": "Configurar Fases",
"phaseLabel": "Etiqueta de fase", "configure": "Configurar",
"enterPhaseName": "Introducir nombre de la fase", "phaseLabel": "Etiqueta de Fase",
"addOption": "Agregar opción", "enterPhaseName": "Ingresa el nombre de la fase",
"phaseOptions": "Opciones de fase", "addOption": "Agregar Opción",
"phaseOptions": "Opciones de Fase",
"optionsText": "Opciones",
"dragToReorderPhases": "Arrastra las fases para reordenarlas. Cada fase puede tener un color diferente.", "dragToReorderPhases": "Arrastra las fases para reordenarlas. Cada fase puede tener un color diferente.",
"enterNewPhaseName": "Introducir nuevo nombre de fase...", "enterNewPhaseName": "Ingresa el nombre de la nueva fase...",
"addPhase": "Añadir Fase", "addPhase": "Agregar Fase",
"noPhasesFound": "No se encontraron fases", "noPhasesFound": "No se encontraron fases",
"no": "No",
"found": "encontrado",
"deletePhase": "Eliminar Fase", "deletePhase": "Eliminar Fase",
"deletePhaseConfirm": "¿Estás seguro de que quieres eliminar esta fase? Esta acción no se puede deshacer.", "deletePhaseConfirm": "¿Estás seguro de que quieres eliminar esta fase? Esta acción no se puede deshacer.",
"rename": "Renombrar", "rename": "Renombrar",

View File

@@ -69,8 +69,9 @@
"cancel": "Cancelar", "cancel": "Cancelar",
"search": "Buscar", "search": "Buscar",
"groupedBy": "Agrupado por", "groupedBy": "Agrupado por",
"manageStatuses": "Gestionar estados", "manage": "Gestionar",
"managePhases": "Gestionar fases", "manageStatuses": "Gestionar Estados",
"managePhases": "Gestionar Fases",
"dragToReorderStatuses": "Los estados están organizados por categorías. Arrastra para reordenar dentro de las categorías. Haz clic en 'Agregar estado' para crear nuevos estados en cada categoría.", "dragToReorderStatuses": "Los estados están organizados por categorías. Arrastra para reordenar dentro de las categorías. Haz clic en 'Agregar estado' para crear nuevos estados en cada categoría.",
"enterNewStatusName": "Ingrese el nombre del nuevo estado...", "enterNewStatusName": "Ingrese el nombre del nuevo estado...",
"addStatus": "Agregar estado", "addStatus": "Agregar estado",

View File

@@ -1,13 +1,17 @@
{ {
"configurePhases": "Configurar fases", "configurePhases": "Configurar Fases",
"phaseLabel": "Etiqueta de fase", "configure": "Configurar",
"phaseLabel": "Rótulo da Fase",
"enterPhaseName": "Digite o nome da fase", "enterPhaseName": "Digite o nome da fase",
"addOption": "Adicionar Opção", "addOption": "Adicionar Opção",
"phaseOptions": "Opções de Fase", "phaseOptions": "Opções de Fase",
"optionsText": "Opções",
"dragToReorderPhases": "Arraste as fases para reordená-las. Cada fase pode ter uma cor diferente.", "dragToReorderPhases": "Arraste as fases para reordená-las. Cada fase pode ter uma cor diferente.",
"enterNewPhaseName": "Digite o novo nome da fase...", "enterNewPhaseName": "Digite o nome da nova fase...",
"addPhase": "Adicionar Fase", "addPhase": "Adicionar Fase",
"noPhasesFound": "Nenhuma fase encontrada", "noPhasesFound": "Nenhuma fase encontrada",
"no": "Nenhuma",
"found": "encontrada",
"deletePhase": "Excluir Fase", "deletePhase": "Excluir Fase",
"deletePhaseConfirm": "Tem certeza de que deseja excluir esta fase? Esta ação não pode ser desfeita.", "deletePhaseConfirm": "Tem certeza de que deseja excluir esta fase? Esta ação não pode ser desfeita.",
"rename": "Renomear", "rename": "Renomear",

View File

@@ -69,8 +69,9 @@
"cancel": "Cancelar", "cancel": "Cancelar",
"search": "Pesquisar", "search": "Pesquisar",
"groupedBy": "Agrupado por", "groupedBy": "Agrupado por",
"manageStatuses": "Gerenciar status", "manage": "Gerenciar",
"managePhases": "Gerenciar fases", "manageStatuses": "Gerenciar Status",
"managePhases": "Gerenciar Fases",
"dragToReorderStatuses": "Os status estão organizados por categorias. Arraste para reordenar dentro das categorias. Clique em 'Adicionar status' para criar novos status em cada categoria.", "dragToReorderStatuses": "Os status estão organizados por categorias. Arraste para reordenar dentro das categorias. Clique em 'Adicionar status' para criar novos status em cada categoria.",
"enterNewStatusName": "Digite o nome do novo status...", "enterNewStatusName": "Digite o nome do novo status...",
"addStatus": "Adicionar status", "addStatus": "Adicionar status",

View File

@@ -1,15 +1,19 @@
{ {
"configurePhases": "配置阶段", "configurePhases": "配置阶段",
"configure": "配置",
"phaseLabel": "阶段标签", "phaseLabel": "阶段标签",
"enterPhaseName": "输入阶段名称", "enterPhaseName": "输入阶段名称",
"addOption": "添加选项", "addOption": "添加选项",
"phaseOptions": "阶段选项", "phaseOptions": "阶段选项",
"dragToReorderPhases": "拖拽阶段以重新排序。每个阶段可以有不同的颜色。", "optionsText": "选项",
"dragToReorderPhases": "拖拽阶段来重新排序。每个阶段可以有不同的颜色。",
"enterNewPhaseName": "输入新阶段名称...", "enterNewPhaseName": "输入新阶段名称...",
"addPhase": "添加阶段", "addPhase": "添加阶段",
"noPhasesFound": "未找到阶段", "noPhasesFound": "未找到阶段",
"no": "没有",
"found": "找到",
"deletePhase": "删除阶段", "deletePhase": "删除阶段",
"deletePhaseConfirm": "您确定要删除此阶段吗?此操作无法撤。", "deletePhaseConfirm": "您确定要删除此阶段吗?此操作无法撤。",
"rename": "重命名", "rename": "重命名",
"delete": "删除", "delete": "删除",
"create": "创建", "create": "创建",

View File

@@ -62,7 +62,8 @@
"clearing": "清除中...", "clearing": "清除中...",
"cancel": "取消", "cancel": "取消",
"search": "搜索", "search": "搜索",
"groupedBy": "分组依据", "groupedBy": "分组方式",
"manage": "管理",
"manageStatuses": "管理状态", "manageStatuses": "管理状态",
"managePhases": "管理阶段", "managePhases": "管理阶段",
"dragToReorderStatuses": "拖拽状态以重新排序。每个状态可以有不同的类别。", "dragToReorderStatuses": "拖拽状态以重新排序。每个状态可以有不同的类别。",

View File

@@ -1,12 +1,12 @@
import apiClient from '@/api/api-client';
import { API_BASE_URL } from '@/shared/constants';
import { IServerResponse } from '@/types/common.types'; import { IServerResponse } from '@/types/common.types';
import apiClient from '@api/api-client';
import { API_BASE_URL } from '@/shared/constants';
import { ITaskPhase } from '@/types/tasks/taskPhase.types'; import { ITaskPhase } from '@/types/tasks/taskPhase.types';
import { toQueryString } from '@/utils/toQueryString'; import { toQueryString } from '@/utils/toQueryString';
const rootUrl = `${API_BASE_URL}/task-phases`; const rootUrl = `${API_BASE_URL}/task-phases`;
interface UpdateSortOrderBody { export interface UpdateSortOrderBody {
from_index: number; from_index: number;
to_index: number; to_index: number;
phases: ITaskPhase[]; phases: ITaskPhase[];
@@ -14,9 +14,10 @@ interface UpdateSortOrderBody {
} }
export const phasesApiService = { export const phasesApiService = {
addPhaseOption: async (projectId: string) => { addPhaseOption: async (projectId: string, name?: string) => {
const q = toQueryString({ id: projectId, current_project_id: projectId }); const q = toQueryString({ id: projectId, current_project_id: projectId });
const response = await apiClient.post<IServerResponse<ITaskPhase>>(`${rootUrl}${q}`); const body = name ? { name } : {};
const response = await apiClient.post<IServerResponse<ITaskPhase>>(`${rootUrl}${q}`, body);
return response.data; return response.data;
}, },

View File

@@ -139,7 +139,7 @@ const EnhancedKanbanCreateSection: React.FC = () => {
} }
if (groupBy === IGroupBy.PHASE) { if (groupBy === IGroupBy.PHASE) {
try { try {
const response = await phasesApiService.addPhaseOption(projectId); const response = await phasesApiService.addPhaseOption(projectId, name);
if (response.done && response.body) { if (response.done && response.body) {
dispatch(fetchEnhancedKanbanGroups(projectId)); dispatch(fetchEnhancedKanbanGroups(projectId));
} }

View File

@@ -20,6 +20,112 @@
border-top: 1px solid #303030; border-top: 1px solid #303030;
} }
/* Dark mode confirmation modal styling */
.dark .ant-modal-confirm .ant-modal-content,
[data-theme="dark"] .ant-modal-confirm .ant-modal-content {
background-color: #1f1f1f !important;
border: 1px solid #303030 !important;
}
.dark .ant-modal-confirm .ant-modal-header,
[data-theme="dark"] .ant-modal-confirm .ant-modal-header {
background-color: #1f1f1f !important;
border-bottom: 1px solid #303030 !important;
}
.dark .ant-modal-confirm .ant-modal-body,
[data-theme="dark"] .ant-modal-confirm .ant-modal-body {
background-color: #1f1f1f !important;
color: #d9d9d9 !important;
}
.dark .ant-modal-confirm .ant-modal-footer,
[data-theme="dark"] .ant-modal-confirm .ant-modal-footer {
background-color: #1f1f1f !important;
border-top: 1px solid #303030 !important;
}
.dark .ant-modal-confirm .ant-modal-confirm-title,
[data-theme="dark"] .ant-modal-confirm .ant-modal-confirm-title {
color: #d9d9d9 !important;
}
.dark .ant-modal-confirm .ant-modal-confirm-content,
[data-theme="dark"] .ant-modal-confirm .ant-modal-confirm-content {
color: #8c8c8c !important;
}
.dark .ant-modal-confirm .ant-btn-default,
[data-theme="dark"] .ant-modal-confirm .ant-btn-default {
background-color: #141414 !important;
border-color: #303030 !important;
color: #d9d9d9 !important;
}
.dark .ant-modal-confirm .ant-btn-default:hover,
[data-theme="dark"] .ant-modal-confirm .ant-btn-default:hover {
background-color: #262626 !important;
border-color: #40a9ff !important;
color: #d9d9d9 !important;
}
.dark .ant-modal-confirm .ant-btn-primary,
[data-theme="dark"] .ant-modal-confirm .ant-btn-primary {
background-color: #1890ff !important;
border-color: #1890ff !important;
color: #ffffff !important;
}
.dark .ant-modal-confirm .ant-btn-primary:hover,
[data-theme="dark"] .ant-modal-confirm .ant-btn-primary:hover {
background-color: #40a9ff !important;
border-color: #40a9ff !important;
color: #ffffff !important;
}
.dark .ant-modal-confirm .ant-btn-dangerous,
[data-theme="dark"] .ant-modal-confirm .ant-btn-dangerous {
background-color: #ff4d4f !important;
border-color: #ff4d4f !important;
color: #ffffff !important;
}
.dark .ant-modal-confirm .ant-btn-dangerous:hover,
[data-theme="dark"] .ant-modal-confirm .ant-btn-dangerous:hover {
background-color: #ff7875 !important;
border-color: #ff7875 !important;
color: #ffffff !important;
}
/* Light mode confirmation modal styling (ensure consistency) */
.ant-modal-confirm .ant-modal-content {
background-color: #ffffff;
border: 1px solid #f0f0f0;
}
.ant-modal-confirm .ant-modal-header {
background-color: #ffffff;
border-bottom: 1px solid #f0f0f0;
}
.ant-modal-confirm .ant-modal-body {
background-color: #ffffff;
color: #262626;
}
.ant-modal-confirm .ant-modal-footer {
background-color: #ffffff;
border-top: 1px solid #f0f0f0;
}
.ant-modal-confirm .ant-modal-confirm-title {
color: #262626;
}
.ant-modal-confirm .ant-modal-confirm-content {
color: #595959;
}
.dark-modal .ant-form-item-label > label { .dark-modal .ant-form-item-label > label {
color: #d9d9d9; color: #d9d9d9;
} }

View File

@@ -18,6 +18,7 @@ import {
deletePhaseOption, deletePhaseOption,
updatePhaseColor, updatePhaseColor,
} from '@/features/projects/singleProject/phase/phases.slice'; } from '@/features/projects/singleProject/phase/phases.slice';
import { updatePhaseLabel } from '@/features/project/project.slice';
import { ITaskPhase } from '@/types/tasks/taskPhase.types'; import { ITaskPhase } from '@/types/tasks/taskPhase.types';
import { Modal as AntModal } from 'antd'; import { Modal as AntModal } from 'antd';
import { fetchTasksV3 } from '@/features/task-management/task-management.slice'; import { fetchTasksV3 } from '@/features/task-management/task-management.slice';
@@ -307,7 +308,7 @@ const ManagePhaseModal: React.FC<ManagePhaseModalProps> = ({
if (!newPhaseName.trim() || !finalProjectId) return; if (!newPhaseName.trim() || !finalProjectId) return;
try { try {
await dispatch(addPhaseOption({ projectId: finalProjectId })); await dispatch(addPhaseOption({ projectId: finalProjectId, name: newPhaseName.trim() }));
await dispatch(fetchPhasesByProjectId(finalProjectId)); await dispatch(fetchPhasesByProjectId(finalProjectId));
await refreshTasks(); await refreshTasks();
setNewPhaseName(''); setNewPhaseName('');
@@ -408,6 +409,7 @@ const ManagePhaseModal: React.FC<ManagePhaseModalProps> = ({
).unwrap(); ).unwrap();
if (res.done) { if (res.done) {
dispatch(updatePhaseLabel(phaseName));
setInitialPhaseName(phaseName); setInitialPhaseName(phaseName);
await refreshTasks(); await refreshTasks();
} }
@@ -428,7 +430,7 @@ const ManagePhaseModal: React.FC<ManagePhaseModalProps> = ({
<Title level={4} className={`m-0 font-semibold ${ <Title level={4} className={`m-0 font-semibold ${
isDarkMode ? 'text-gray-100' : 'text-gray-800' isDarkMode ? 'text-gray-100' : 'text-gray-800'
}`}> }`}>
{t('configurePhases')} {t('configure')} {phaseName || project?.phase_label || t('phasesText')}
</Title> </Title>
} }
open={open} open={open}
@@ -495,7 +497,7 @@ const ManagePhaseModal: React.FC<ManagePhaseModalProps> = ({
<Text className={`text-xs font-medium ${ <Text className={`text-xs font-medium ${
isDarkMode ? 'text-gray-300' : 'text-blue-700' isDarkMode ? 'text-gray-300' : 'text-blue-700'
}`}> }`}>
🎨 Drag phases to reorder them. Click on a phase name to rename it. Each phase can have a custom color. 🎨 Drag {(phaseName || project?.phase_label || t('phasesText')).toLowerCase()} to reorder them. Click on a {(phaseName || project?.phase_label || t('phaseText')).toLowerCase()} name to rename it. Each {(phaseName || project?.phase_label || t('phaseText')).toLowerCase()} can have a custom color.
</Text> </Text>
</div> </div>
@@ -558,7 +560,7 @@ const ManagePhaseModal: React.FC<ManagePhaseModalProps> = ({
<Text className={`text-xs font-medium ${ <Text className={`text-xs font-medium ${
isDarkMode ? 'text-gray-300' : 'text-gray-700' isDarkMode ? 'text-gray-300' : 'text-gray-700'
}`}> }`}>
{t('phaseOptions')} {phaseName || project?.phase_label || t('phasesText')} {t('optionsText')}
</Text> </Text>
<Button <Button
type="primary" type="primary"
@@ -601,7 +603,7 @@ const ManagePhaseModal: React.FC<ManagePhaseModalProps> = ({
isDarkMode ? 'text-gray-400' : 'text-gray-500' isDarkMode ? 'text-gray-400' : 'text-gray-500'
}`}> }`}>
<Text className="text-sm font-medium"> <Text className="text-sm font-medium">
{t('noPhasesFound')} {t('no')} {(phaseName || project?.phase_label || t('phasesText')).toLowerCase()} {t('found')}
</Text> </Text>
<br /> <br />
<Button <Button

View File

@@ -20,6 +20,112 @@
border-top: 1px solid #303030; border-top: 1px solid #303030;
} }
/* Dark mode confirmation modal styling */
.dark .ant-modal-confirm .ant-modal-content,
[data-theme="dark"] .ant-modal-confirm .ant-modal-content {
background-color: #1f1f1f !important;
border: 1px solid #303030 !important;
}
.dark .ant-modal-confirm .ant-modal-header,
[data-theme="dark"] .ant-modal-confirm .ant-modal-header {
background-color: #1f1f1f !important;
border-bottom: 1px solid #303030 !important;
}
.dark .ant-modal-confirm .ant-modal-body,
[data-theme="dark"] .ant-modal-confirm .ant-modal-body {
background-color: #1f1f1f !important;
color: #d9d9d9 !important;
}
.dark .ant-modal-confirm .ant-modal-footer,
[data-theme="dark"] .ant-modal-confirm .ant-modal-footer {
background-color: #1f1f1f !important;
border-top: 1px solid #303030 !important;
}
.dark .ant-modal-confirm .ant-modal-confirm-title,
[data-theme="dark"] .ant-modal-confirm .ant-modal-confirm-title {
color: #d9d9d9 !important;
}
.dark .ant-modal-confirm .ant-modal-confirm-content,
[data-theme="dark"] .ant-modal-confirm .ant-modal-confirm-content {
color: #8c8c8c !important;
}
.dark .ant-modal-confirm .ant-btn-default,
[data-theme="dark"] .ant-modal-confirm .ant-btn-default {
background-color: #141414 !important;
border-color: #303030 !important;
color: #d9d9d9 !important;
}
.dark .ant-modal-confirm .ant-btn-default:hover,
[data-theme="dark"] .ant-modal-confirm .ant-btn-default:hover {
background-color: #262626 !important;
border-color: #40a9ff !important;
color: #d9d9d9 !important;
}
.dark .ant-modal-confirm .ant-btn-primary,
[data-theme="dark"] .ant-modal-confirm .ant-btn-primary {
background-color: #1890ff !important;
border-color: #1890ff !important;
color: #ffffff !important;
}
.dark .ant-modal-confirm .ant-btn-primary:hover,
[data-theme="dark"] .ant-modal-confirm .ant-btn-primary:hover {
background-color: #40a9ff !important;
border-color: #40a9ff !important;
color: #ffffff !important;
}
.dark .ant-modal-confirm .ant-btn-dangerous,
[data-theme="dark"] .ant-modal-confirm .ant-btn-dangerous {
background-color: #ff4d4f !important;
border-color: #ff4d4f !important;
color: #ffffff !important;
}
.dark .ant-modal-confirm .ant-btn-dangerous:hover,
[data-theme="dark"] .ant-modal-confirm .ant-btn-dangerous:hover {
background-color: #ff7875 !important;
border-color: #ff7875 !important;
color: #ffffff !important;
}
/* Light mode confirmation modal styling (ensure consistency) */
.ant-modal-confirm .ant-modal-content {
background-color: #ffffff;
border: 1px solid #f0f0f0;
}
.ant-modal-confirm .ant-modal-header {
background-color: #ffffff;
border-bottom: 1px solid #f0f0f0;
}
.ant-modal-confirm .ant-modal-body {
background-color: #ffffff;
color: #262626;
}
.ant-modal-confirm .ant-modal-footer {
background-color: #ffffff;
border-top: 1px solid #f0f0f0;
}
.ant-modal-confirm .ant-modal-confirm-title {
color: #262626;
}
.ant-modal-confirm .ant-modal-confirm-content {
color: #595959;
}
.dark-modal .ant-form-item-label > label { .dark-modal .ant-form-item-label > label {
color: #d9d9d9; color: #d9d9d9;
} }

View File

@@ -369,6 +369,7 @@ const FilterDropdown: React.FC<{
dispatch?: any; dispatch?: any;
onManageStatus?: () => void; onManageStatus?: () => void;
onManagePhase?: () => void; onManagePhase?: () => void;
projectPhaseLabel?: string; // Add this prop
}> = ({ }> = ({
section, section,
onSelectionChange, onSelectionChange,
@@ -380,6 +381,7 @@ const FilterDropdown: React.FC<{
dispatch, dispatch,
onManageStatus, onManageStatus,
onManagePhase, onManagePhase,
projectPhaseLabel, // Add this prop
}) => { }) => {
const { t } = useTranslation('task-list-filters'); const { t } = useTranslation('task-list-filters');
// Add permission checks for groupBy section // Add permission checks for groupBy section
@@ -495,7 +497,7 @@ const FilterDropdown: React.FC<{
isDarkMode ? 'focus:ring-offset-gray-900' : 'focus:ring-offset-white' isDarkMode ? 'focus:ring-offset-gray-900' : 'focus:ring-offset-white'
}`} }`}
> >
{t('managePhases')} {t('manage')} {projectPhaseLabel || t('phasesText')}
</button> </button>
)} )}
{section.selectedValues[0] === 'status' && ( {section.selectedValues[0] === 'status' && (
@@ -994,6 +996,7 @@ const ImprovedTaskFilters: React.FC<ImprovedTaskFiltersProps> = ({ position, cla
const isDarkMode = useAppSelector(state => state.themeReducer?.mode === 'dark'); const isDarkMode = useAppSelector(state => state.themeReducer?.mode === 'dark');
const { projectId } = useAppSelector(state => state.projectReducer); const { projectId } = useAppSelector(state => state.projectReducer);
const { projectView } = useTabSearchParam(); const { projectView } = useTabSearchParam();
const projectPhaseLabel = useAppSelector(state => state.projectReducer.project?.phase_label);
// Theme-aware class names - memoize to prevent unnecessary re-renders // Theme-aware class names - memoize to prevent unnecessary re-renders
// Using greyish colors for both dark and light modes // Using greyish colors for both dark and light modes
@@ -1298,6 +1301,7 @@ const ImprovedTaskFilters: React.FC<ImprovedTaskFiltersProps> = ({ position, cla
dispatch={dispatch} dispatch={dispatch}
onManageStatus={() => setShowManageStatusModal(true)} onManageStatus={() => setShowManageStatusModal(true)}
onManagePhase={() => setShowManagePhaseModal(true)} onManagePhase={() => setShowManagePhaseModal(true)}
projectPhaseLabel={projectPhaseLabel}
/> />
)) ))
) : ( ) : (

View File

@@ -1,5 +1,4 @@
import React from 'react'; import React from 'react';
import { useSelectedProject } from '../../../../hooks/useSelectedProject';
import { useAppSelector } from '../../../../hooks/useAppSelector'; import { useAppSelector } from '../../../../hooks/useAppSelector';
import { Flex } from 'antd'; import { Flex } from 'antd';
import ConfigPhaseButton from './ConfigPhaseButton'; import ConfigPhaseButton from './ConfigPhaseButton';
@@ -10,19 +9,13 @@ const PhaseHeader = () => {
// localization // localization
const { t } = useTranslation('task-list-filters'); const { t } = useTranslation('task-list-filters');
// get selected project for useSelectedProject hook // get project data from redux
const selectedProject = useSelectedProject(); const { project } = useAppSelector(state => state.projectReducer);
// get phase data from redux
const phaseList = useAppSelector(state => state.phaseReducer.phaseList);
//get phases details from phases slice
const phase = phaseList.find(el => el.projectId === selectedProject?.projectId);
return ( return (
<Flex align="center" justify="space-between"> <Flex align="center" justify="space-between">
{phase?.phase || t('phasesText')} {project?.phase_label || t('phasesText')}
<ConfigPhaseButton color={colors.darkGray} /> <ConfigPhaseButton />
</Flex> </Flex>
); );
}; };

View File

@@ -16,9 +16,9 @@ const initialState: PhaseState = {
export const addPhaseOption = createAsyncThunk( export const addPhaseOption = createAsyncThunk(
'phase/addPhaseOption', 'phase/addPhaseOption',
async ({ projectId }: { projectId: string }, { rejectWithValue }) => { async ({ projectId, name }: { projectId: string; name?: string }, { rejectWithValue }) => {
try { try {
const response = await phasesApiService.addPhaseOption(projectId); const response = await phasesApiService.addPhaseOption(projectId, name);
return response; return response;
} catch (error) { } catch (error) {
return rejectWithValue(error); return rejectWithValue(error);

View File

@@ -106,13 +106,8 @@ const BoardCreateSectionCard = () => {
} }
if (groupBy === IGroupBy.PHASE && projectId) { if (groupBy === IGroupBy.PHASE && projectId) {
const body = {
name: sectionName,
project_id: projectId,
};
try { try {
const response = await phasesApiService.addPhaseOption(projectId); const response = await phasesApiService.addPhaseOption(projectId, sectionName);
if (response.done && response.body) { if (response.done && response.body) {
dispatch(fetchBoardTaskGroups(projectId)); dispatch(fetchBoardTaskGroups(projectId));
} }

View File

@@ -4,6 +4,152 @@
width: 100%; width: 100%;
} }
/* Global Confirmation Modal Styles */
/* Light mode confirmation modal styling (default) */
.ant-modal-confirm .ant-modal-content {
background-color: #ffffff;
border: 1px solid #f0f0f0;
}
.ant-modal-confirm .ant-modal-header {
background-color: #ffffff;
border-bottom: 1px solid #f0f0f0;
}
.ant-modal-confirm .ant-modal-body {
background-color: #ffffff;
color: #262626;
}
.ant-modal-confirm .ant-modal-footer {
background-color: #ffffff;
border-top: 1px solid #f0f0f0;
}
.ant-modal-confirm .ant-modal-confirm-title {
color: #262626;
}
.ant-modal-confirm .ant-modal-confirm-content {
color: #595959;
}
/* Dark mode confirmation modal styling */
.dark .ant-modal-confirm .ant-modal-content,
[data-theme="dark"] .ant-modal-confirm .ant-modal-content,
html.dark .ant-modal-confirm .ant-modal-content {
background-color: #1f1f1f !important;
border: 1px solid #303030 !important;
}
.dark .ant-modal-confirm .ant-modal-header,
[data-theme="dark"] .ant-modal-confirm .ant-modal-header,
html.dark .ant-modal-confirm .ant-modal-header {
background-color: #1f1f1f !important;
border-bottom: 1px solid #303030 !important;
}
.dark .ant-modal-confirm .ant-modal-body,
[data-theme="dark"] .ant-modal-confirm .ant-modal-body,
html.dark .ant-modal-confirm .ant-modal-body {
background-color: #1f1f1f !important;
color: #d9d9d9 !important;
}
.dark .ant-modal-confirm .ant-modal-footer,
[data-theme="dark"] .ant-modal-confirm .ant-modal-footer,
html.dark .ant-modal-confirm .ant-modal-footer {
background-color: #1f1f1f !important;
border-top: 1px solid #303030 !important;
}
.dark .ant-modal-confirm .ant-modal-confirm-title,
[data-theme="dark"] .ant-modal-confirm .ant-modal-confirm-title,
html.dark .ant-modal-confirm .ant-modal-confirm-title {
color: #d9d9d9 !important;
}
.dark .ant-modal-confirm .ant-modal-confirm-content,
[data-theme="dark"] .ant-modal-confirm .ant-modal-confirm-content,
html.dark .ant-modal-confirm .ant-modal-confirm-content {
color: #8c8c8c !important;
}
.dark .ant-modal-confirm .ant-btn-default,
[data-theme="dark"] .ant-modal-confirm .ant-btn-default,
html.dark .ant-modal-confirm .ant-btn-default {
background-color: #141414 !important;
border-color: #303030 !important;
color: #d9d9d9 !important;
}
.dark .ant-modal-confirm .ant-btn-default:hover,
[data-theme="dark"] .ant-modal-confirm .ant-btn-default:hover,
html.dark .ant-modal-confirm .ant-btn-default:hover {
background-color: #262626 !important;
border-color: #40a9ff !important;
color: #d9d9d9 !important;
}
.dark .ant-modal-confirm .ant-btn-primary,
[data-theme="dark"] .ant-modal-confirm .ant-btn-primary,
html.dark .ant-modal-confirm .ant-btn-primary {
background-color: #1890ff !important;
border-color: #1890ff !important;
color: #ffffff !important;
}
.dark .ant-modal-confirm .ant-btn-primary:hover,
[data-theme="dark"] .ant-modal-confirm .ant-btn-primary:hover,
html.dark .ant-modal-confirm .ant-btn-primary:hover {
background-color: #40a9ff !important;
border-color: #40a9ff !important;
color: #ffffff !important;
}
.dark .ant-modal-confirm .ant-btn-dangerous,
[data-theme="dark"] .ant-modal-confirm .ant-btn-dangerous,
html.dark .ant-modal-confirm .ant-btn-dangerous {
background-color: #ff4d4f !important;
border-color: #ff4d4f !important;
color: #ffffff !important;
}
.dark .ant-modal-confirm .ant-btn-dangerous:hover,
[data-theme="dark"] .ant-modal-confirm .ant-btn-dangerous:hover,
html.dark .ant-modal-confirm .ant-btn-dangerous:hover {
background-color: #ff7875 !important;
border-color: #ff7875 !important;
color: #ffffff !important;
}
/* Error modal specific styling */
.dark .ant-modal-error .ant-modal-content,
[data-theme="dark"] .ant-modal-error .ant-modal-content,
html.dark .ant-modal-error .ant-modal-content {
background-color: #1f1f1f !important;
border: 1px solid #303030 !important;
}
.dark .ant-modal-error .ant-modal-body,
[data-theme="dark"] .ant-modal-error .ant-modal-body,
html.dark .ant-modal-error .ant-modal-body {
background-color: #1f1f1f !important;
color: #d9d9d9 !important;
}
.dark .ant-modal-error .ant-modal-confirm-title,
[data-theme="dark"] .ant-modal-error .ant-modal-confirm-title,
html.dark .ant-modal-error .ant-modal-confirm-title {
color: #d9d9d9 !important;
}
.dark .ant-modal-error .ant-modal-confirm-content,
[data-theme="dark"] .ant-modal-error .ant-modal-content,
html.dark .ant-modal-error .ant-modal-confirm-content {
color: #8c8c8c !important;
}
.task-group { .task-group {
transition: all 0.2s ease; transition: all 0.2s ease;
} }