feat(localization): update translation keys for phase management
- Revised translation keys in Albanian, German, English, Spanish, Portuguese, and Chinese for improved clarity and consistency in phase management. - Adjusted phrases related to phase naming, options, and actions to enhance user experience across multiple languages. - Added new keys for creating and canceling phases to align with recent UI updates in the ManagePhaseModal component.
This commit is contained in:
@@ -1,18 +1,19 @@
|
|||||||
{
|
{
|
||||||
"configurePhases": "Konfiguro Fazat",
|
"configurePhases": "Konfiguro Fazat",
|
||||||
"phaseLabel": "Etiketa e Fazës",
|
"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",
|
"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.",
|
"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...",
|
"enterNewPhaseName": "Shkruani emrin e fazës së re...",
|
||||||
"addPhase": "Shto Fazë",
|
"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",
|
"deletePhase": "Fshi Fazën",
|
||||||
"deletePhaseConfirm": "Jeni të sigurt që doni të fshini këtë fazë? Ky veprim nuk mund të zhbëhet.",
|
"deletePhaseConfirm": "Jeni të sigurt që doni të fshini këtë fazë? Ky veprim nuk mund të zhbëhet.",
|
||||||
"rename": "Riemëro",
|
"rename": "Riemëro",
|
||||||
"delete": "Fshi",
|
"delete": "Fshi",
|
||||||
"enterPhaseName": "Shkruani emrin e fazës",
|
"create": "Krijo",
|
||||||
|
"cancel": "Anulo",
|
||||||
"selectColor": "Zgjidh ngjyrën",
|
"selectColor": "Zgjidh ngjyrën",
|
||||||
"managePhases": "Menaxho Fazat",
|
"managePhases": "Menaxho Fazat",
|
||||||
"close": "Mbyll"
|
"close": "Mbyll"
|
||||||
|
|||||||
@@ -1,18 +1,19 @@
|
|||||||
{
|
{
|
||||||
"configurePhases": "Phasen konfigurieren",
|
"configurePhases": "Phasen konfigurieren",
|
||||||
"phaseLabel": "Phasenbezeichnung",
|
"phaseLabel": "Phasenbezeichnung",
|
||||||
"enterPhaseName": "Namen für Phasenbezeichnung eingeben",
|
"enterPhaseName": "Phasennamen eingeben",
|
||||||
"addOption": "Option hinzufügen",
|
"addOption": "Option hinzufügen",
|
||||||
"phaseOptions": "Phasenoptionen:",
|
"phaseOptions": "Phasenoptionen",
|
||||||
"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. Erstellen Sie Ihre erste Phase oben.",
|
"noPhasesFound": "Keine Phasen 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",
|
||||||
"delete": "Löschen",
|
"delete": "Löschen",
|
||||||
"enterPhaseName": "Phasennamen eingeben",
|
"create": "Erstellen",
|
||||||
|
"cancel": "Abbrechen",
|
||||||
"selectColor": "Farbe auswählen",
|
"selectColor": "Farbe auswählen",
|
||||||
"managePhases": "Phasen verwalten",
|
"managePhases": "Phasen verwalten",
|
||||||
"close": "Schließen"
|
"close": "Schließen"
|
||||||
|
|||||||
@@ -1,18 +1,19 @@
|
|||||||
{
|
{
|
||||||
"configurePhases": "Configure Phases",
|
"configurePhases": "Configure Phases",
|
||||||
"phaseLabel": "Phase Label",
|
"phaseLabel": "Phase Label",
|
||||||
"enterPhaseName": "Enter a name for phase label",
|
"enterPhaseName": "Enter phase name",
|
||||||
"addOption": "Add Option",
|
"addOption": "Add Option",
|
||||||
"phaseOptions": "Phase Options:",
|
"phaseOptions": "Phase 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. Create your first phase above.",
|
"noPhasesFound": "No phases 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",
|
||||||
"delete": "Delete",
|
"delete": "Delete",
|
||||||
"enterPhaseName": "Enter phase name",
|
"create": "Create",
|
||||||
|
"cancel": "Cancel",
|
||||||
"selectColor": "Select color",
|
"selectColor": "Select color",
|
||||||
"managePhases": "Manage Phases",
|
"managePhases": "Manage Phases",
|
||||||
"close": "Close"
|
"close": "Close"
|
||||||
|
|||||||
@@ -1,18 +1,19 @@
|
|||||||
{
|
{
|
||||||
"configurePhases": "Configurar fases",
|
"configurePhases": "Configurar fases",
|
||||||
"phaseLabel": "Etiqueta de fase",
|
"phaseLabel": "Etiqueta de fase",
|
||||||
"enterPhaseName": "Ingrese un nombre para la etiqueta de fase",
|
"enterPhaseName": "Introducir nombre de la fase",
|
||||||
"addOption": "Agregar opción",
|
"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.",
|
"dragToReorderPhases": "Arrastra las fases para reordenarlas. Cada fase puede tener un color diferente.",
|
||||||
"enterNewPhaseName": "Introducir nuevo nombre de fase...",
|
"enterNewPhaseName": "Introducir nuevo nombre de fase...",
|
||||||
"addPhase": "Añadir Fase",
|
"addPhase": "Añadir Fase",
|
||||||
"noPhasesFound": "No se encontraron fases. Crea tu primera fase arriba.",
|
"noPhasesFound": "No se encontraron fases",
|
||||||
"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",
|
||||||
"delete": "Eliminar",
|
"delete": "Eliminar",
|
||||||
"enterPhaseName": "Introducir nombre de la fase",
|
"create": "Crear",
|
||||||
|
"cancel": "Cancelar",
|
||||||
"selectColor": "Seleccionar color",
|
"selectColor": "Seleccionar color",
|
||||||
"managePhases": "Gestionar Fases",
|
"managePhases": "Gestionar Fases",
|
||||||
"close": "Cerrar"
|
"close": "Cerrar"
|
||||||
|
|||||||
@@ -1,18 +1,19 @@
|
|||||||
{
|
{
|
||||||
"configurePhases": "Configurar fases",
|
"configurePhases": "Configurar fases",
|
||||||
"phaseLabel": "Etiqueta de fase",
|
"phaseLabel": "Etiqueta de fase",
|
||||||
"enterPhaseName": "Digite um nome para o rótulo 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",
|
||||||
"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 novo nome da fase...",
|
||||||
"addPhase": "Adicionar Fase",
|
"addPhase": "Adicionar Fase",
|
||||||
"noPhasesFound": "Nenhuma fase encontrada. Crie sua primeira fase acima.",
|
"noPhasesFound": "Nenhuma fase 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",
|
||||||
"delete": "Excluir",
|
"delete": "Excluir",
|
||||||
"enterPhaseName": "Digite o nome da fase",
|
"create": "Criar",
|
||||||
|
"cancel": "Cancelar",
|
||||||
"selectColor": "Selecionar cor",
|
"selectColor": "Selecionar cor",
|
||||||
"managePhases": "Gerenciar Fases",
|
"managePhases": "Gerenciar Fases",
|
||||||
"close": "Fechar"
|
"close": "Fechar"
|
||||||
|
|||||||
@@ -1,18 +1,19 @@
|
|||||||
{
|
{
|
||||||
"configurePhases": "配置阶段",
|
"configurePhases": "配置阶段",
|
||||||
"phaseLabel": "阶段标签",
|
"phaseLabel": "阶段标签",
|
||||||
"enterPhaseName": "输入阶段标签名称",
|
"enterPhaseName": "输入阶段名称",
|
||||||
"addOption": "添加选项",
|
"addOption": "添加选项",
|
||||||
"phaseOptions": "阶段选项:",
|
"phaseOptions": "阶段选项",
|
||||||
"dragToReorderPhases": "拖拽阶段以重新排序。每个阶段可以有不同的颜色。",
|
"dragToReorderPhases": "拖拽阶段以重新排序。每个阶段可以有不同的颜色。",
|
||||||
"enterNewPhaseName": "输入新阶段名称...",
|
"enterNewPhaseName": "输入新阶段名称...",
|
||||||
"addPhase": "添加阶段",
|
"addPhase": "添加阶段",
|
||||||
"noPhasesFound": "未找到阶段。请在上面创建您的第一个阶段。",
|
"noPhasesFound": "未找到阶段",
|
||||||
"deletePhase": "删除阶段",
|
"deletePhase": "删除阶段",
|
||||||
"deletePhaseConfirm": "您确定要删除此阶段吗?此操作无法撤销。",
|
"deletePhaseConfirm": "您确定要删除此阶段吗?此操作无法撤销。",
|
||||||
"rename": "重命名",
|
"rename": "重命名",
|
||||||
"delete": "删除",
|
"delete": "删除",
|
||||||
"enterPhaseName": "输入阶段名称",
|
"create": "创建",
|
||||||
|
"cancel": "取消",
|
||||||
"selectColor": "选择颜色",
|
"selectColor": "选择颜色",
|
||||||
"managePhases": "管理阶段",
|
"managePhases": "管理阶段",
|
||||||
"close": "关闭"
|
"close": "关闭"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { useState, useCallback, useRef, useEffect } from 'react';
|
import React, { useState, useCallback, useRef, useEffect } from 'react';
|
||||||
import { Modal, Form, Input, Button, Space, Divider, Typography, Flex, ColorPicker } from 'antd';
|
import { Modal, Form, Input, Button, Space, Divider, Typography, Flex, ColorPicker, Tooltip } from 'antd';
|
||||||
import { PlusOutlined, HolderOutlined } from '@ant-design/icons';
|
import { PlusOutlined, HolderOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { DndContext, DragEndEvent, PointerSensor, useSensor, useSensors } from '@dnd-kit/core';
|
import { DndContext, DragEndEvent, PointerSensor, useSensor, useSensors } from '@dnd-kit/core';
|
||||||
import { SortableContext, useSortable, verticalListSortingStrategy } from '@dnd-kit/sortable';
|
import { SortableContext, useSortable, verticalListSortingStrategy } from '@dnd-kit/sortable';
|
||||||
@@ -41,7 +41,7 @@ interface PhaseItemProps {
|
|||||||
isDarkMode: boolean;
|
isDarkMode: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sortable Phase Item Component
|
// Sortable Phase Item Component (compact with hover actions)
|
||||||
const SortablePhaseItem: React.FC<PhaseItemProps & { id: string }> = ({
|
const SortablePhaseItem: React.FC<PhaseItemProps & { id: string }> = ({
|
||||||
id,
|
id,
|
||||||
phase,
|
phase,
|
||||||
@@ -50,9 +50,11 @@ const SortablePhaseItem: React.FC<PhaseItemProps & { id: string }> = ({
|
|||||||
onColorChange,
|
onColorChange,
|
||||||
isDarkMode,
|
isDarkMode,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { t } = useTranslation('phases-drawer');
|
||||||
const [isEditing, setIsEditing] = useState(false);
|
const [isEditing, setIsEditing] = useState(false);
|
||||||
const [editName, setEditName] = useState(phase.name || '');
|
const [editName, setEditName] = useState(phase.name || '');
|
||||||
const [color, setColor] = useState(phase.color_code || PhaseColorCodes[0]);
|
const [color, setColor] = useState(phase.color_code || PhaseColorCodes[0]);
|
||||||
|
const [isHovered, setIsHovered] = useState(false);
|
||||||
const inputRef = useRef<any>(null);
|
const inputRef = useRef<any>(null);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -90,6 +92,10 @@ const SortablePhaseItem: React.FC<PhaseItemProps & { id: string }> = ({
|
|||||||
}
|
}
|
||||||
}, [handleSave, handleCancel]);
|
}, [handleSave, handleCancel]);
|
||||||
|
|
||||||
|
const handleClick = useCallback(() => {
|
||||||
|
setIsEditing(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const handleColorChangeComplete = useCallback(() => {
|
const handleColorChangeComplete = useCallback(() => {
|
||||||
if (color !== phase.color_code) {
|
if (color !== phase.color_code) {
|
||||||
onColorChange(id, color);
|
onColorChange(id, color);
|
||||||
@@ -111,82 +117,30 @@ const SortablePhaseItem: React.FC<PhaseItemProps & { id: string }> = ({
|
|||||||
<div
|
<div
|
||||||
ref={setNodeRef}
|
ref={setNodeRef}
|
||||||
style={style}
|
style={style}
|
||||||
className={`p-3 rounded-md border transition-all duration-200 ${
|
className={`group relative py-1.5 px-2 rounded border transition-all duration-200 ${
|
||||||
isDarkMode
|
isDarkMode
|
||||||
? 'bg-gray-800 border-gray-700 hover:bg-gray-750'
|
? 'bg-gray-700 border-gray-600 hover:bg-gray-600 hover:border-gray-500'
|
||||||
: 'bg-white border-gray-200 hover:bg-gray-50 shadow-sm'
|
: 'bg-white border-gray-200 hover:bg-gray-50 hover:border-gray-300'
|
||||||
}`}
|
} ${isDragging ? 'shadow-lg opacity-50 rotate-2 scale-105' : 'shadow-sm hover:shadow-md'}`}
|
||||||
|
onMouseEnter={() => setIsHovered(true)}
|
||||||
|
onMouseLeave={() => setIsHovered(false)}
|
||||||
>
|
>
|
||||||
{/* Header Row - Drag Handle, Phase Name, Actions */}
|
<div className="flex items-center gap-2">
|
||||||
<div className="flex items-center gap-2 mb-2">
|
|
||||||
{/* Drag Handle */}
|
{/* Drag Handle */}
|
||||||
<div
|
<div
|
||||||
{...attributes}
|
{...attributes}
|
||||||
{...listeners}
|
{...listeners}
|
||||||
className={`cursor-grab active:cursor-grabbing p-1 rounded transition-colors ${
|
className={`flex-shrink-0 cursor-grab active:cursor-grabbing p-1 rounded transition-all duration-200 ${
|
||||||
isDarkMode
|
isDarkMode
|
||||||
? 'text-gray-400 hover:text-gray-300 hover:bg-gray-700'
|
? 'text-gray-400 hover:text-gray-300 hover:bg-gray-600'
|
||||||
: 'text-gray-500 hover:text-gray-600 hover:bg-gray-100'
|
: 'text-gray-400 hover:text-gray-600 hover:bg-gray-100'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<HolderOutlined />
|
<HolderOutlined className="text-sm" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Phase Name */}
|
{/* Phase Color */}
|
||||||
<div className="flex-1">
|
<div className="flex-shrink-0 flex items-center gap-1">
|
||||||
{isEditing ? (
|
|
||||||
<Input
|
|
||||||
ref={inputRef}
|
|
||||||
value={editName}
|
|
||||||
onChange={(e) => setEditName(e.target.value)}
|
|
||||||
onBlur={handleSave}
|
|
||||||
onKeyDown={handleKeyDown}
|
|
||||||
className="font-medium"
|
|
||||||
placeholder="Enter phase name"
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Text
|
|
||||||
className={`text-sm font-medium cursor-pointer transition-colors ${
|
|
||||||
isDarkMode ? 'text-gray-100 hover:text-white' : 'text-gray-900 hover:text-gray-700'
|
|
||||||
}`}
|
|
||||||
onClick={() => setIsEditing(true)}
|
|
||||||
>
|
|
||||||
{phase.name}
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Actions */}
|
|
||||||
<Space size={4}>
|
|
||||||
<Button
|
|
||||||
type="text"
|
|
||||||
size="small"
|
|
||||||
onClick={() => setIsEditing(true)}
|
|
||||||
className={`text-xs ${isDarkMode ? 'text-gray-400 hover:text-gray-300' : 'text-gray-500 hover:text-gray-600'} hover:bg-transparent`}
|
|
||||||
>
|
|
||||||
Rename
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
type="text"
|
|
||||||
size="small"
|
|
||||||
danger
|
|
||||||
onClick={() => onDelete(id)}
|
|
||||||
className="text-xs text-red-500 hover:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/20"
|
|
||||||
>
|
|
||||||
Delete
|
|
||||||
</Button>
|
|
||||||
</Space>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Color Row */}
|
|
||||||
<div className="flex items-center gap-1.5">
|
|
||||||
<Text
|
|
||||||
className={`text-xs font-medium ${
|
|
||||||
isDarkMode ? 'text-gray-400' : 'text-gray-600'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
Color:
|
|
||||||
</Text>
|
|
||||||
<ColorPicker
|
<ColorPicker
|
||||||
value={color}
|
value={color}
|
||||||
onChange={(value) => setColor(value.toHexString())}
|
onChange={(value) => setColor(value.toHexString())}
|
||||||
@@ -195,13 +149,75 @@ const SortablePhaseItem: React.FC<PhaseItemProps & { id: string }> = ({
|
|||||||
className="phase-color-picker"
|
className="phase-color-picker"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
className="w-3 h-3 rounded-full border ml-1 transition-all duration-200"
|
className="w-2.5 h-2.5 rounded border shadow-sm"
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: color,
|
backgroundColor: color,
|
||||||
borderColor: isDarkMode ? '#374151' : '#e5e7eb',
|
borderColor: isDarkMode ? '#6b7280' : '#d1d5db',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Phase Name */}
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
|
{isEditing ? (
|
||||||
|
<Input
|
||||||
|
ref={inputRef}
|
||||||
|
value={editName}
|
||||||
|
onChange={(e) => setEditName(e.target.value)}
|
||||||
|
onBlur={handleSave}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
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')}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Text
|
||||||
|
className={`text-xs font-medium cursor-pointer transition-colors select-none ${
|
||||||
|
isDarkMode ? 'text-gray-200 hover:text-gray-100' : 'text-gray-800 hover:text-gray-900'
|
||||||
|
}`}
|
||||||
|
onClick={handleClick}
|
||||||
|
title={t('rename')}
|
||||||
|
>
|
||||||
|
{phase.name}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Hover Actions */}
|
||||||
|
<div className={`flex items-center gap-1 transition-all duration-200 ${
|
||||||
|
isHovered || isEditing ? 'opacity-100' : 'opacity-0'
|
||||||
|
}`}>
|
||||||
|
<Tooltip title={t('rename')}>
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
size="small"
|
||||||
|
icon={<EditOutlined />}
|
||||||
|
onClick={() => setIsEditing(true)}
|
||||||
|
className={`h-6 w-6 flex items-center justify-center transition-all duration-200 ${
|
||||||
|
isDarkMode
|
||||||
|
? 'text-gray-400 hover:text-gray-300 hover:bg-gray-600'
|
||||||
|
: 'text-gray-500 hover:text-gray-700 hover:bg-gray-100'
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title={t('delete')}>
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
size="small"
|
||||||
|
icon={<DeleteOutlined />}
|
||||||
|
onClick={() => onDelete(id)}
|
||||||
|
className={`h-6 w-6 flex items-center justify-center transition-all duration-200 ${
|
||||||
|
isDarkMode
|
||||||
|
? 'text-red-400 hover:text-red-300 hover:bg-red-800'
|
||||||
|
: 'text-red-500 hover:text-red-600 hover:bg-red-50'
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -224,6 +240,8 @@ const ManagePhaseModal: React.FC<ManagePhaseModalProps> = ({
|
|||||||
const [initialPhaseName, setInitialPhaseName] = useState<string>(project?.phase_label || '');
|
const [initialPhaseName, setInitialPhaseName] = useState<string>(project?.phase_label || '');
|
||||||
const [sorting, setSorting] = useState(false);
|
const [sorting, setSorting] = useState(false);
|
||||||
const [isSaving, setIsSaving] = useState(false);
|
const [isSaving, setIsSaving] = useState(false);
|
||||||
|
const [newPhaseName, setNewPhaseName] = useState('');
|
||||||
|
const [showAddForm, setShowAddForm] = useState(false);
|
||||||
|
|
||||||
const finalProjectId = projectId || currentProjectId;
|
const finalProjectId = projectId || currentProjectId;
|
||||||
|
|
||||||
@@ -285,17 +303,28 @@ const ManagePhaseModal: React.FC<ManagePhaseModalProps> = ({
|
|||||||
}
|
}
|
||||||
}, [finalProjectId, phaseList, dispatch, refreshTasks]);
|
}, [finalProjectId, phaseList, dispatch, refreshTasks]);
|
||||||
|
|
||||||
const handleAddPhase = useCallback(async () => {
|
const handleCreatePhase = useCallback(async () => {
|
||||||
if (!finalProjectId) return;
|
if (!newPhaseName.trim() || !finalProjectId) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await dispatch(addPhaseOption({ projectId: finalProjectId }));
|
await dispatch(addPhaseOption({ projectId: finalProjectId }));
|
||||||
await dispatch(fetchPhasesByProjectId(finalProjectId));
|
await dispatch(fetchPhasesByProjectId(finalProjectId));
|
||||||
await refreshTasks();
|
await refreshTasks();
|
||||||
|
setNewPhaseName('');
|
||||||
|
setShowAddForm(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error adding phase:', 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) => {
|
const handleRenamePhase = useCallback(async (id: string, name: string) => {
|
||||||
if (!finalProjectId) return;
|
if (!finalProjectId) return;
|
||||||
@@ -326,8 +355,11 @@ const ManagePhaseModal: React.FC<ManagePhaseModalProps> = ({
|
|||||||
if (!finalProjectId) return;
|
if (!finalProjectId) return;
|
||||||
|
|
||||||
AntModal.confirm({
|
AntModal.confirm({
|
||||||
title: 'Delete Phase',
|
title: t('deletePhase'),
|
||||||
content: 'Are you sure you want to delete this phase? This action cannot be undone.',
|
content: t('deletePhaseConfirm'),
|
||||||
|
okText: t('delete'),
|
||||||
|
cancelText: t('cancel'),
|
||||||
|
okButtonProps: { danger: true },
|
||||||
onOk: async () => {
|
onOk: async () => {
|
||||||
try {
|
try {
|
||||||
const response = await dispatch(
|
const response = await dispatch(
|
||||||
@@ -343,7 +375,7 @@ const ManagePhaseModal: React.FC<ManagePhaseModalProps> = ({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}, [finalProjectId, dispatch, refreshTasks]);
|
}, [finalProjectId, dispatch, refreshTasks, t]);
|
||||||
|
|
||||||
const handleColorChange = useCallback(async (id: string, color: string) => {
|
const handleColorChange = useCallback(async (id: string, color: string) => {
|
||||||
if (!finalProjectId) return;
|
if (!finalProjectId) return;
|
||||||
@@ -393,39 +425,52 @@ const ManagePhaseModal: React.FC<ManagePhaseModalProps> = ({
|
|||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={
|
title={
|
||||||
<Title level={4} className={isDarkMode ? 'text-gray-200' : 'text-gray-800'}>
|
<Title level={4} className={`m-0 font-semibold ${
|
||||||
|
isDarkMode ? 'text-gray-100' : 'text-gray-800'
|
||||||
|
}`}>
|
||||||
{t('configurePhases')}
|
{t('configurePhases')}
|
||||||
</Title>
|
</Title>
|
||||||
}
|
}
|
||||||
open={open}
|
open={open}
|
||||||
onCancel={handleClose}
|
onCancel={handleClose}
|
||||||
width={600}
|
width={720}
|
||||||
style={{ top: 20 }}
|
style={{ top: 20 }}
|
||||||
bodyStyle={{
|
styles={{
|
||||||
|
body: {
|
||||||
maxHeight: 'calc(100vh - 200px)',
|
maxHeight: 'calc(100vh - 200px)',
|
||||||
overflowY: 'auto',
|
overflowY: 'auto',
|
||||||
padding: '24px',
|
padding: '16px',
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
footer={
|
footer={
|
||||||
<Flex justify="flex-end">
|
<div className={`flex justify-end pt-3 ${
|
||||||
<Button onClick={handleClose}>
|
isDarkMode ? 'border-gray-700' : 'border-gray-200'
|
||||||
Close
|
}`}>
|
||||||
|
<Button
|
||||||
|
onClick={handleClose}
|
||||||
|
className={`font-medium ${
|
||||||
|
isDarkMode
|
||||||
|
? 'text-gray-300 hover:text-gray-200 border-gray-600'
|
||||||
|
: 'text-gray-600 hover:text-gray-800 border-gray-300'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{t('close')}
|
||||||
</Button>
|
</Button>
|
||||||
</Flex>
|
</div>
|
||||||
}
|
}
|
||||||
className={isDarkMode ? 'dark-modal' : ''}
|
className={`${isDarkMode ? 'dark-modal' : ''} phase-manage-modal`}
|
||||||
loading={loadingPhases || sorting}
|
loading={loadingPhases || sorting}
|
||||||
>
|
>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{/* Phase Label Configuration */}
|
{/* Phase Label Configuration */}
|
||||||
<div className={`p-3 rounded-md border ${
|
<div className={`p-3 rounded border transition-all duration-200 ${
|
||||||
isDarkMode
|
isDarkMode
|
||||||
? 'bg-gray-800/30 border-gray-700'
|
? 'bg-gray-800 border-gray-700 text-gray-300'
|
||||||
: 'bg-blue-50/50 border-blue-200'
|
: 'bg-blue-50 border-blue-200 text-blue-700'
|
||||||
}`}>
|
}`}>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Text className={`text-xs font-medium ${
|
<Text className={`text-xs font-medium ${
|
||||||
isDarkMode ? 'text-gray-300' : 'text-gray-700'
|
isDarkMode ? 'text-gray-300' : 'text-blue-700'
|
||||||
}`}>
|
}`}>
|
||||||
{t('phaseLabel')}
|
{t('phaseLabel')}
|
||||||
</Text>
|
</Text>
|
||||||
@@ -441,47 +486,93 @@ const ManagePhaseModal: React.FC<ManagePhaseModalProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Divider className={isDarkMode ? 'border-gray-700' : 'border-gray-300'} />
|
{/* Info Banner */}
|
||||||
|
<div className={`p-3 rounded border transition-all duration-200 ${
|
||||||
{/* Phase Options */}
|
isDarkMode
|
||||||
<div className="space-y-4">
|
? 'bg-gray-800 border-gray-700 text-gray-300'
|
||||||
<div className={`p-2.5 rounded-md ${
|
: 'bg-blue-50 border-blue-200 text-blue-700'
|
||||||
isDarkMode ? 'bg-gray-800/50' : 'bg-purple-50'
|
|
||||||
}`}>
|
}`}>
|
||||||
<Text
|
<Text className={`text-xs font-medium ${
|
||||||
type="secondary"
|
isDarkMode ? 'text-gray-300' : 'text-blue-700'
|
||||||
className={`text-xs ${
|
}`}>
|
||||||
isDarkMode ? 'text-gray-400' : 'text-purple-600'
|
🎨 Drag phases to reorder them. Click on a phase name to rename it. Each phase can have a custom color.
|
||||||
}`}
|
|
||||||
>
|
|
||||||
🎨 Drag phases to reorder them. Each phase can have a custom color.
|
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Add New Phase */}
|
{/* Add New Phase Form */}
|
||||||
<div className={`p-3 rounded-md border-2 border-dashed transition-colors ${
|
{showAddForm && (
|
||||||
|
<div className={`p-2 rounded border-2 border-dashed transition-all duration-200 ${
|
||||||
|
isDarkMode
|
||||||
|
? 'border-gray-600 bg-gray-700 hover:border-gray-500'
|
||||||
|
: 'border-gray-300 bg-white hover:border-gray-400'
|
||||||
|
} shadow-sm`}>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Input
|
||||||
|
placeholder={t('enterNewPhaseName')}
|
||||||
|
value={newPhaseName}
|
||||||
|
onChange={(e) => 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
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={handleCreatePhase}
|
||||||
|
disabled={!newPhaseName.trim()}
|
||||||
|
size="small"
|
||||||
|
className="text-xs"
|
||||||
|
>
|
||||||
|
{t('create')}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setNewPhaseName('');
|
||||||
|
setShowAddForm(false);
|
||||||
|
}}
|
||||||
|
size="small"
|
||||||
|
className={`text-xs ${
|
||||||
|
isDarkMode
|
||||||
|
? 'text-gray-300 hover:text-gray-200 border-gray-600'
|
||||||
|
: 'text-gray-600 hover:text-gray-800 border-gray-300'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{t('cancel')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Add Phase Button */}
|
||||||
|
{!showAddForm && (
|
||||||
|
<div className={`p-3 rounded border-2 border-dashed transition-colors ${
|
||||||
isDarkMode
|
isDarkMode
|
||||||
? 'border-gray-600 bg-gray-800/50 hover:border-gray-500'
|
? 'border-gray-600 bg-gray-800/50 hover:border-gray-500'
|
||||||
: 'border-gray-300 bg-gray-50/50 hover:border-gray-400'
|
: 'border-gray-300 bg-gray-50/50 hover:border-gray-400'
|
||||||
}`}>
|
}`}>
|
||||||
<div className="flex gap-2">
|
<div className="flex items-center justify-between">
|
||||||
<Text className={`text-xs font-medium flex-shrink-0 self-center ${
|
<Text className={`text-xs font-medium ${
|
||||||
isDarkMode ? 'text-gray-300' : 'text-gray-700'
|
isDarkMode ? 'text-gray-300' : 'text-gray-700'
|
||||||
}`}>
|
}`}>
|
||||||
{t('phaseOptions')}:
|
{t('phaseOptions')}
|
||||||
</Text>
|
</Text>
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
icon={<PlusOutlined />}
|
icon={<PlusOutlined />}
|
||||||
onClick={handleAddPhase}
|
onClick={() => setShowAddForm(true)}
|
||||||
disabled={loadingPhases}
|
disabled={loadingPhases}
|
||||||
size="small"
|
size="small"
|
||||||
className="ml-auto"
|
className="text-xs"
|
||||||
>
|
>
|
||||||
{t('addOption')}
|
{t('addOption')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Phase List with Drag & Drop */}
|
{/* Phase List with Drag & Drop */}
|
||||||
<DndContext sensors={sensors} onDragEnd={handleDragEnd}>
|
<DndContext sensors={sensors} onDragEnd={handleDragEnd}>
|
||||||
@@ -489,7 +580,7 @@ const ManagePhaseModal: React.FC<ManagePhaseModalProps> = ({
|
|||||||
items={phaseList.map(phase => phase.id)}
|
items={phaseList.map(phase => phase.id)}
|
||||||
strategy={verticalListSortingStrategy}
|
strategy={verticalListSortingStrategy}
|
||||||
>
|
>
|
||||||
<div className="space-y-2">
|
<div className="space-y-1">
|
||||||
{phaseList.map((phase) => (
|
{phaseList.map((phase) => (
|
||||||
<SortablePhaseItem
|
<SortablePhaseItem
|
||||||
key={phase.id}
|
key={phase.id}
|
||||||
@@ -506,12 +597,28 @@ const ManagePhaseModal: React.FC<ManagePhaseModalProps> = ({
|
|||||||
</DndContext>
|
</DndContext>
|
||||||
|
|
||||||
{phaseList.length === 0 && (
|
{phaseList.length === 0 && (
|
||||||
<div className={`text-center py-8 ${isDarkMode ? 'text-gray-400' : 'text-gray-500'}`}>
|
<div className={`text-center py-8 transition-colors ${
|
||||||
<Text>No phases found. Add your first phase above.</Text>
|
isDarkMode ? 'text-gray-400' : 'text-gray-500'
|
||||||
|
}`}>
|
||||||
|
<Text className="text-sm font-medium">
|
||||||
|
{t('noPhasesFound')}
|
||||||
|
</Text>
|
||||||
|
<br />
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
size="small"
|
||||||
|
onClick={() => setShowAddForm(true)}
|
||||||
|
className={`text-xs mt-1 font-medium ${
|
||||||
|
isDarkMode
|
||||||
|
? 'text-blue-400 hover:text-blue-300'
|
||||||
|
: 'text-blue-600 hover:text-blue-700'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{t('addOption')}
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user