diff --git a/worklenz-frontend/public/locales/alb/phases-drawer.json b/worklenz-frontend/public/locales/alb/phases-drawer.json index cccda7d2..23816727 100644 --- a/worklenz-frontend/public/locales/alb/phases-drawer.json +++ b/worklenz-frontend/public/locales/alb/phases-drawer.json @@ -1,18 +1,19 @@ { "configurePhases": "Konfiguro Fazat", "phaseLabel": "Etiketa e Fazës", - "enterPhaseName": "Vendosni një emër për etiketën e fazës", + "enterPhaseName": "Shkruani emrin e fazës", "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.", "enterNewPhaseName": "Shkruani emrin e fazës së re...", "addPhase": "Shto Fazë", - "noPhasesFound": "Nuk u gjetën faza. Krijoni fazën tuaj të parë më sipër.", + "noPhasesFound": "Nuk u gjetën faza", "deletePhase": "Fshi Fazën", "deletePhaseConfirm": "Jeni të sigurt që doni të fshini këtë fazë? Ky veprim nuk mund të zhbëhet.", "rename": "Riemëro", "delete": "Fshi", - "enterPhaseName": "Shkruani emrin e fazës", + "create": "Krijo", + "cancel": "Anulo", "selectColor": "Zgjidh ngjyrën", "managePhases": "Menaxho Fazat", "close": "Mbyll" diff --git a/worklenz-frontend/public/locales/de/phases-drawer.json b/worklenz-frontend/public/locales/de/phases-drawer.json index c9e41e09..4a143c7f 100644 --- a/worklenz-frontend/public/locales/de/phases-drawer.json +++ b/worklenz-frontend/public/locales/de/phases-drawer.json @@ -1,18 +1,19 @@ { "configurePhases": "Phasen konfigurieren", "phaseLabel": "Phasenbezeichnung", - "enterPhaseName": "Namen für Phasenbezeichnung eingeben", + "enterPhaseName": "Phasennamen eingeben", "addOption": "Option hinzufügen", - "phaseOptions": "Phasenoptionen:", + "phaseOptions": "Phasenoptionen", "dragToReorderPhases": "Ziehen Sie Phasen, um sie neu zu ordnen. Jede Phase kann eine andere Farbe haben.", "enterNewPhaseName": "Neuen Phasennamen eingeben...", "addPhase": "Phase hinzufügen", - "noPhasesFound": "Keine Phasen gefunden. Erstellen Sie Ihre erste Phase oben.", + "noPhasesFound": "Keine Phasen gefunden", "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.", "rename": "Umbenennen", "delete": "Löschen", - "enterPhaseName": "Phasennamen eingeben", + "create": "Erstellen", + "cancel": "Abbrechen", "selectColor": "Farbe auswählen", "managePhases": "Phasen verwalten", "close": "Schließen" diff --git a/worklenz-frontend/public/locales/en/phases-drawer.json b/worklenz-frontend/public/locales/en/phases-drawer.json index 10ad78a4..7791a08b 100644 --- a/worklenz-frontend/public/locales/en/phases-drawer.json +++ b/worklenz-frontend/public/locales/en/phases-drawer.json @@ -1,18 +1,19 @@ { "configurePhases": "Configure Phases", "phaseLabel": "Phase Label", - "enterPhaseName": "Enter a name for phase label", + "enterPhaseName": "Enter phase name", "addOption": "Add Option", - "phaseOptions": "Phase Options:", + "phaseOptions": "Phase Options", "dragToReorderPhases": "Drag phases to reorder them. Each phase can have a different color.", "enterNewPhaseName": "Enter new phase name...", "addPhase": "Add Phase", - "noPhasesFound": "No phases found. Create your first phase above.", + "noPhasesFound": "No phases found", "deletePhase": "Delete Phase", "deletePhaseConfirm": "Are you sure you want to delete this phase? This action cannot be undone.", "rename": "Rename", "delete": "Delete", - "enterPhaseName": "Enter phase name", + "create": "Create", + "cancel": "Cancel", "selectColor": "Select color", "managePhases": "Manage Phases", "close": "Close" diff --git a/worklenz-frontend/public/locales/es/phases-drawer.json b/worklenz-frontend/public/locales/es/phases-drawer.json index e961b068..abb6ee81 100644 --- a/worklenz-frontend/public/locales/es/phases-drawer.json +++ b/worklenz-frontend/public/locales/es/phases-drawer.json @@ -1,18 +1,19 @@ { "configurePhases": "Configurar fases", "phaseLabel": "Etiqueta de fase", - "enterPhaseName": "Ingrese un nombre para la etiqueta de fase", + "enterPhaseName": "Introducir nombre de la fase", "addOption": "Agregar opción", - "phaseOptions": "Opciones de fase:", + "phaseOptions": "Opciones de fase", "dragToReorderPhases": "Arrastra las fases para reordenarlas. Cada fase puede tener un color diferente.", "enterNewPhaseName": "Introducir nuevo nombre de fase...", "addPhase": "Añadir Fase", - "noPhasesFound": "No se encontraron fases. Crea tu primera fase arriba.", + "noPhasesFound": "No se encontraron fases", "deletePhase": "Eliminar Fase", "deletePhaseConfirm": "¿Estás seguro de que quieres eliminar esta fase? Esta acción no se puede deshacer.", "rename": "Renombrar", "delete": "Eliminar", - "enterPhaseName": "Introducir nombre de la fase", + "create": "Crear", + "cancel": "Cancelar", "selectColor": "Seleccionar color", "managePhases": "Gestionar Fases", "close": "Cerrar" diff --git a/worklenz-frontend/public/locales/pt/phases-drawer.json b/worklenz-frontend/public/locales/pt/phases-drawer.json index 080b13df..b0ca7c51 100644 --- a/worklenz-frontend/public/locales/pt/phases-drawer.json +++ b/worklenz-frontend/public/locales/pt/phases-drawer.json @@ -1,18 +1,19 @@ { "configurePhases": "Configurar fases", "phaseLabel": "Etiqueta de fase", - "enterPhaseName": "Digite um nome para o rótulo da fase", + "enterPhaseName": "Digite o nome da fase", "addOption": "Adicionar Opção", - "phaseOptions": "Opções de Fase:", + "phaseOptions": "Opções de Fase", "dragToReorderPhases": "Arraste as fases para reordená-las. Cada fase pode ter uma cor diferente.", "enterNewPhaseName": "Digite o novo nome da fase...", "addPhase": "Adicionar Fase", - "noPhasesFound": "Nenhuma fase encontrada. Crie sua primeira fase acima.", + "noPhasesFound": "Nenhuma fase encontrada", "deletePhase": "Excluir Fase", "deletePhaseConfirm": "Tem certeza de que deseja excluir esta fase? Esta ação não pode ser desfeita.", "rename": "Renomear", "delete": "Excluir", - "enterPhaseName": "Digite o nome da fase", + "create": "Criar", + "cancel": "Cancelar", "selectColor": "Selecionar cor", "managePhases": "Gerenciar Fases", "close": "Fechar" diff --git a/worklenz-frontend/public/locales/zh/phases-drawer.json b/worklenz-frontend/public/locales/zh/phases-drawer.json index 24d21b38..8f55e527 100644 --- a/worklenz-frontend/public/locales/zh/phases-drawer.json +++ b/worklenz-frontend/public/locales/zh/phases-drawer.json @@ -1,18 +1,19 @@ { "configurePhases": "配置阶段", "phaseLabel": "阶段标签", - "enterPhaseName": "输入阶段标签名称", + "enterPhaseName": "输入阶段名称", "addOption": "添加选项", - "phaseOptions": "阶段选项:", + "phaseOptions": "阶段选项", "dragToReorderPhases": "拖拽阶段以重新排序。每个阶段可以有不同的颜色。", "enterNewPhaseName": "输入新阶段名称...", "addPhase": "添加阶段", - "noPhasesFound": "未找到阶段。请在上面创建您的第一个阶段。", + "noPhasesFound": "未找到阶段", "deletePhase": "删除阶段", "deletePhaseConfirm": "您确定要删除此阶段吗?此操作无法撤销。", "rename": "重命名", "delete": "删除", - "enterPhaseName": "输入阶段名称", + "create": "创建", + "cancel": "取消", "selectColor": "选择颜色", "managePhases": "管理阶段", "close": "关闭" diff --git a/worklenz-frontend/src/components/task-management/ManagePhaseModal.tsx b/worklenz-frontend/src/components/task-management/ManagePhaseModal.tsx index 0e10d437..81a44378 100644 --- a/worklenz-frontend/src/components/task-management/ManagePhaseModal.tsx +++ b/worklenz-frontend/src/components/task-management/ManagePhaseModal.tsx @@ -1,6 +1,6 @@ import React, { useState, useCallback, useRef, useEffect } from 'react'; -import { Modal, Form, Input, Button, Space, Divider, Typography, Flex, ColorPicker } from 'antd'; -import { PlusOutlined, HolderOutlined } from '@ant-design/icons'; +import { Modal, Form, Input, Button, Space, Divider, Typography, Flex, ColorPicker, Tooltip } from 'antd'; +import { PlusOutlined, HolderOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons'; import { useTranslation } from 'react-i18next'; import { DndContext, DragEndEvent, PointerSensor, useSensor, useSensors } from '@dnd-kit/core'; import { SortableContext, useSortable, verticalListSortingStrategy } from '@dnd-kit/sortable'; @@ -41,7 +41,7 @@ interface PhaseItemProps { isDarkMode: boolean; } -// Sortable Phase Item Component +// Sortable Phase Item Component (compact with hover actions) const SortablePhaseItem: React.FC = ({ id, phase, @@ -50,9 +50,11 @@ const SortablePhaseItem: React.FC = ({ onColorChange, isDarkMode, }) => { + const { t } = useTranslation('phases-drawer'); const [isEditing, setIsEditing] = useState(false); const [editName, setEditName] = useState(phase.name || ''); const [color, setColor] = useState(phase.color_code || PhaseColorCodes[0]); + const [isHovered, setIsHovered] = useState(false); const inputRef = useRef(null); const { @@ -90,6 +92,10 @@ const SortablePhaseItem: React.FC = ({ } }, [handleSave, handleCancel]); + const handleClick = useCallback(() => { + setIsEditing(true); + }, []); + const handleColorChangeComplete = useCallback(() => { if (color !== phase.color_code) { onColorChange(id, color); @@ -107,33 +113,52 @@ const SortablePhaseItem: React.FC = ({ setColor(phase.color_code || PhaseColorCodes[0]); }, [phase.color_code]); - return ( + return (
setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} > - {/* Header Row - Drag Handle, Phase Name, Actions */} -
+
{/* Drag Handle */}
- + +
+ + {/* Phase Color */} +
+ setColor(value.toHexString())} + onChangeComplete={handleColorChangeComplete} + size="small" + className="phase-color-picker" + /> +
{/* Phase Name */} -
+
{isEditing ? ( = ({ onChange={(e) => setEditName(e.target.value)} onBlur={handleSave} onKeyDown={handleKeyDown} - className="font-medium" - placeholder="Enter phase name" + className={`font-medium text-xs border-0 px-1 py-1 shadow-none ${ + isDarkMode + ? 'bg-transparent text-gray-200 placeholder-gray-400' + : 'bg-transparent text-gray-900 placeholder-gray-500' + }`} + placeholder={t('enterPhaseName')} /> ) : ( setIsEditing(true)} + onClick={handleClick} + title={t('rename')} > {phase.name} )}
- {/* Actions */} - - - - -
- - {/* Color Row */} -
- - Color: - - setColor(value.toHexString())} - onChangeComplete={handleColorChangeComplete} - size="small" - className="phase-color-picker" - /> -
+ {/* Hover Actions */} +
+ +
); @@ -224,6 +240,8 @@ const ManagePhaseModal: React.FC = ({ const [initialPhaseName, setInitialPhaseName] = useState(project?.phase_label || ''); const [sorting, setSorting] = useState(false); const [isSaving, setIsSaving] = useState(false); + const [newPhaseName, setNewPhaseName] = useState(''); + const [showAddForm, setShowAddForm] = useState(false); const finalProjectId = projectId || currentProjectId; @@ -285,17 +303,28 @@ const ManagePhaseModal: React.FC = ({ } }, [finalProjectId, phaseList, dispatch, refreshTasks]); - const handleAddPhase = useCallback(async () => { - if (!finalProjectId) return; + const handleCreatePhase = useCallback(async () => { + if (!newPhaseName.trim() || !finalProjectId) return; try { await dispatch(addPhaseOption({ projectId: finalProjectId })); await dispatch(fetchPhasesByProjectId(finalProjectId)); await refreshTasks(); + setNewPhaseName(''); + setShowAddForm(false); } catch (error) { console.error('Error adding phase:', error); } - }, [finalProjectId, dispatch, refreshTasks]); + }, [finalProjectId, dispatch, refreshTasks, newPhaseName]); + + const handleKeyDown = useCallback((e: React.KeyboardEvent) => { + if (e.key === 'Enter') { + handleCreatePhase(); + } else if (e.key === 'Escape') { + setNewPhaseName(''); + setShowAddForm(false); + } + }, [handleCreatePhase]); const handleRenamePhase = useCallback(async (id: string, name: string) => { if (!finalProjectId) return; @@ -326,8 +355,11 @@ const ManagePhaseModal: React.FC = ({ if (!finalProjectId) return; AntModal.confirm({ - title: 'Delete Phase', - content: 'Are you sure you want to delete this phase? This action cannot be undone.', + title: t('deletePhase'), + content: t('deletePhaseConfirm'), + okText: t('delete'), + cancelText: t('cancel'), + okButtonProps: { danger: true }, onOk: async () => { try { const response = await dispatch( @@ -343,7 +375,7 @@ const ManagePhaseModal: React.FC = ({ } }, }); - }, [finalProjectId, dispatch, refreshTasks]); + }, [finalProjectId, dispatch, refreshTasks, t]); const handleColorChange = useCallback(async (id: string, color: string) => { if (!finalProjectId) return; @@ -393,39 +425,52 @@ const ManagePhaseModal: React.FC = ({ return ( + {t('configurePhases')} } open={open} onCancel={handleClose} - width={600} + width={720} style={{ top: 20 }} - bodyStyle={{ - maxHeight: 'calc(100vh - 200px)', - overflowY: 'auto', - padding: '24px', + styles={{ + body: { + maxHeight: 'calc(100vh - 200px)', + overflowY: 'auto', + padding: '16px', + }, }} footer={ - - - +
} - className={isDarkMode ? 'dark-modal' : ''} + className={`${isDarkMode ? 'dark-modal' : ''} phase-manage-modal`} loading={loadingPhases || sorting} >
{/* Phase Label Configuration */} -
{t('phaseLabel')} @@ -441,76 +486,138 @@ const ManagePhaseModal: React.FC = ({
- - - {/* Phase Options */} -
-
+ - - 🎨 Drag phases to reorder them. Each phase can have a custom color. - -
+ 🎨 Drag phases to reorder them. Click on a phase name to rename it. Each phase can have a custom color. + +
- {/* Add New Phase */} -
+
+ setNewPhaseName(e.target.value)} + onKeyDown={handleKeyDown} + className={`flex-1 ${ + isDarkMode + ? 'bg-gray-600 border-gray-500 text-gray-100 placeholder-gray-400' + : 'bg-white border-gray-300 text-gray-900 placeholder-gray-500' + }`} + size="small" + autoFocus + /> + + +
+
+ )} + + {/* Add Phase Button */} + {!showAddForm && ( +
-
- + - {t('phaseOptions')}: + {t('phaseOptions')}
+ )} - {/* Phase List with Drag & Drop */} - - phase.id)} - strategy={verticalListSortingStrategy} - > -
- {phaseList.map((phase) => ( - - ))} -
-
-
- - {phaseList.length === 0 && ( -
- No phases found. Add your first phase above. + {/* Phase List with Drag & Drop */} + + phase.id)} + strategy={verticalListSortingStrategy} + > +
+ {phaseList.map((phase) => ( + + ))}
- )} -
+ + + + {phaseList.length === 0 && ( +
+ + {t('noPhasesFound')} + +
+ +
+ )}
);