update column name
This commit is contained in:
@@ -1023,8 +1023,10 @@
|
||||
"add_column": "اضافه کردن",
|
||||
"column_name": "نام ستون",
|
||||
"delete_column": "حذف ستون",
|
||||
"edit_column": "ویرایش نام",
|
||||
"delete_column_confirm": "آیا از حذف این ستون اطمینان دارید؟",
|
||||
"column_deleted": "ستون با موفقیت حذف شد",
|
||||
"column_updated": "ستون با موفقیت ویرایش شد",
|
||||
"add_new_task": "اضافه کردن تسک",
|
||||
"add_task": "اضافه کردن",
|
||||
"task_title": "عنوان تسک",
|
||||
|
||||
@@ -8,7 +8,7 @@ import { ErrorType } from "../../../helpers/types";
|
||||
import { useCreateTask, useGetTasksByTaskPhase } from "../task/hooks/useTaskData";
|
||||
import type { TaskItemType } from "../task/types/TaskTypes";
|
||||
import { mapTaskItemToTask } from "../task/utils/mapTaskItemToTask";
|
||||
import { useDeleteTaskPhase } from "../task-phase/hooks/useTaskPhaseData";
|
||||
import { useDeleteTaskPhase, useUpdateTaskPhase } from "../task-phase/hooks/useTaskPhaseData";
|
||||
import type { Column as ColumnType, Task as TaskType } from "../types";
|
||||
import ColumnMenu from "./ColumnMenu";
|
||||
import Task from "./Task";
|
||||
@@ -22,6 +22,7 @@ type Props = {
|
||||
onColumnDrop: (overColumnId: string) => void;
|
||||
onTaskClick?: (task: TaskType) => void;
|
||||
onColumnDeleted?: (columnId: string) => void;
|
||||
onColumnTitleUpdated?: (columnId: string, title: string) => void;
|
||||
};
|
||||
|
||||
const Column: FC<Props> = ({
|
||||
@@ -33,9 +34,11 @@ const Column: FC<Props> = ({
|
||||
onColumnDrop,
|
||||
onTaskClick,
|
||||
onColumnDeleted,
|
||||
onColumnTitleUpdated,
|
||||
}) => {
|
||||
const { t } = useTranslation("global");
|
||||
const deleteTaskPhase = useDeleteTaskPhase();
|
||||
const updateTaskPhase = useUpdateTaskPhase();
|
||||
const createTask = useCreateTask();
|
||||
const {
|
||||
data: tasksData,
|
||||
@@ -55,10 +58,13 @@ const Column: FC<Props> = ({
|
||||
|
||||
const [isAdding, setIsAdding] = useState(false);
|
||||
const [title, setTitle] = useState("");
|
||||
const [isEditingTitle, setIsEditingTitle] = useState(false);
|
||||
const [editTitle, setEditTitle] = useState(column.title);
|
||||
const [isDeleteConfirmOpen, setIsDeleteConfirmOpen] = useState(false);
|
||||
const [isColumnDragOver, setIsColumnDragOver] = useState(false);
|
||||
const columnRef = useRef<HTMLDivElement>(null);
|
||||
const dragImageRef = useRef<HTMLElement | null>(null);
|
||||
const isCancellingTitleEdit = useRef(false);
|
||||
const isDraggingColumn = draggedColumnId === column.id;
|
||||
|
||||
const handleTasksScroll = (event: React.UIEvent<HTMLDivElement>) => {
|
||||
@@ -167,6 +173,50 @@ const Column: FC<Props> = ({
|
||||
);
|
||||
};
|
||||
|
||||
const startEditingTitle = () => {
|
||||
isCancellingTitleEdit.current = false;
|
||||
setEditTitle(column.title);
|
||||
setIsEditingTitle(true);
|
||||
};
|
||||
|
||||
const cancelEditingTitle = () => {
|
||||
isCancellingTitleEdit.current = true;
|
||||
setEditTitle(column.title);
|
||||
setIsEditingTitle(false);
|
||||
};
|
||||
|
||||
const handleSaveTitle = () => {
|
||||
if (isCancellingTitleEdit.current) {
|
||||
isCancellingTitleEdit.current = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const trimmedTitle = editTitle.trim();
|
||||
if (!trimmedTitle || trimmedTitle === column.title) {
|
||||
setIsEditingTitle(false);
|
||||
setEditTitle(column.title);
|
||||
return;
|
||||
}
|
||||
|
||||
const previousTitle = column.title;
|
||||
onColumnTitleUpdated?.(column.id, trimmedTitle);
|
||||
setIsEditingTitle(false);
|
||||
|
||||
updateTaskPhase.mutate(
|
||||
{ id: column.id, params: { name: trimmedTitle }, projectId },
|
||||
{
|
||||
onSuccess: () => {
|
||||
toast.success(t("taskmanager.column_updated"));
|
||||
},
|
||||
onError: (error: ErrorType) => {
|
||||
onColumnTitleUpdated?.(column.id, previousTitle);
|
||||
setEditTitle(previousTitle);
|
||||
toast.error(error.response?.data?.error.message[0]);
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={columnRef}
|
||||
@@ -178,15 +228,38 @@ const Column: FC<Props> = ({
|
||||
onDrop={handleColumnDrop}
|
||||
>
|
||||
<div className="flex justify-between items-center shrink-0 gap-2">
|
||||
<div
|
||||
draggable
|
||||
onDragStart={handleColumnDragStart}
|
||||
onDragEnd={handleColumnDragEnd}
|
||||
className="flex-1 min-w-0 font-bold text-sm truncate cursor-grab active:cursor-grabbing"
|
||||
>
|
||||
{column.title}
|
||||
</div>
|
||||
<ColumnMenu onDelete={() => setIsDeleteConfirmOpen(true)} />
|
||||
{isEditingTitle ? (
|
||||
<input
|
||||
type="text"
|
||||
value={editTitle}
|
||||
onChange={(e) => setEditTitle(e.target.value)}
|
||||
onBlur={handleSaveTitle}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
handleSaveTitle();
|
||||
}
|
||||
if (e.key === "Escape") {
|
||||
e.preventDefault();
|
||||
cancelEditingTitle();
|
||||
}
|
||||
}}
|
||||
disabled={updateTaskPhase.isPending}
|
||||
className="flex-1 min-w-0 font-bold text-sm bg-white rounded-lg px-2 py-1 outline-none"
|
||||
autoFocus
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
draggable
|
||||
onDragStart={handleColumnDragStart}
|
||||
onDragEnd={handleColumnDragEnd}
|
||||
onDoubleClick={startEditingTitle}
|
||||
className="flex-1 min-w-0 font-bold text-sm truncate cursor-grab active:cursor-grabbing"
|
||||
>
|
||||
{column.title}
|
||||
</div>
|
||||
)}
|
||||
<ColumnMenu onEdit={startEditingTitle} onDelete={() => setIsDeleteConfirmOpen(true)} />
|
||||
</div>
|
||||
|
||||
<ModalConfrim
|
||||
|
||||
@@ -4,33 +4,51 @@ import { FC } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
type Props = {
|
||||
onEdit: () => void;
|
||||
onDelete: () => void;
|
||||
};
|
||||
|
||||
const ColumnMenu: FC<Props> = ({ onDelete }) => {
|
||||
const ColumnMenu: FC<Props> = ({ onEdit, onDelete }) => {
|
||||
const { t } = useTranslation("global");
|
||||
|
||||
return (
|
||||
<Popover className="relative">
|
||||
<PopoverButton
|
||||
className="flex items-center justify-center size-8 rounded-lg hover:bg-black/5 transition-colors outline-none shrink-0"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<More size={20} color="black" />
|
||||
</PopoverButton>
|
||||
{({ close }) => (
|
||||
<>
|
||||
<PopoverButton
|
||||
className="flex items-center justify-center size-8 rounded-lg hover:bg-black/5 transition-colors outline-none shrink-0"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<More size={20} color="black" />
|
||||
</PopoverButton>
|
||||
|
||||
<PopoverPanel
|
||||
anchor="bottom start"
|
||||
className="z-20 mt-1 min-w-[120px] rounded-xl bg-white shadow-md border border-border py-1 text-sm"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onDelete}
|
||||
className="w-full px-4 py-2.5 text-right text-red-500 hover:bg-red-50 transition-colors"
|
||||
>
|
||||
{t("taskmanager.delete_column")}
|
||||
</button>
|
||||
</PopoverPanel>
|
||||
<PopoverPanel
|
||||
anchor="bottom start"
|
||||
className="z-20 mt-1 min-w-[120px] rounded-xl bg-white shadow-md border border-border py-1 text-sm"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
close();
|
||||
onEdit();
|
||||
}}
|
||||
className="w-full px-4 py-2.5 text-right hover:bg-black/5 transition-colors"
|
||||
>
|
||||
{t("taskmanager.edit_column")}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
close();
|
||||
onDelete();
|
||||
}}
|
||||
className="w-full px-4 py-2.5 text-right text-red-500 hover:bg-red-50 transition-colors"
|
||||
>
|
||||
{t("taskmanager.delete_column")}
|
||||
</button>
|
||||
</PopoverPanel>
|
||||
</>
|
||||
)}
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { InfoCircle, Setting2, UserAdd } from "iconsax-react";
|
||||
import { ArrowRight, InfoCircle, Setting2 } from "iconsax-react";
|
||||
import type { FC } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import Button from "../../../components/Button";
|
||||
|
||||
type HeaderWorkspaceProps = {
|
||||
projectName: string;
|
||||
@@ -21,17 +20,15 @@ const HeaderWorkspace: FC<HeaderWorkspaceProps> = ({ projectName }) => {
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2 sm:gap-4 items-center shrink-0">
|
||||
<Button
|
||||
<div
|
||||
onClick={() => navigate(-1)}
|
||||
label="برگشت"
|
||||
/>
|
||||
|
||||
<div className="flex gap-1 items-center px-2 sm:px-3 rounded-lg bg-white h-8">
|
||||
<UserAdd
|
||||
className="flex gap-1 items-center px-2 sm:px-3 rounded-lg bg-white h-8"
|
||||
>
|
||||
<ArrowRight
|
||||
size={18}
|
||||
color="black"
|
||||
/>
|
||||
<div className="text-black text-xs hidden sm:block whitespace-nowrap">اشتراک گذاری</div>
|
||||
<div className="text-black text-xs hidden sm:block whitespace-nowrap">برگشت</div>
|
||||
</div>
|
||||
|
||||
<Setting2
|
||||
|
||||
@@ -6,7 +6,8 @@ export type CreateTaskPhaseType = {
|
||||
};
|
||||
|
||||
export type UpdateTaskPhaseType = {
|
||||
order: number;
|
||||
order?: number;
|
||||
name?: string;
|
||||
};
|
||||
|
||||
export type TaskPhaseItemType = {
|
||||
|
||||
@@ -227,6 +227,10 @@ const Workspace: FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleColumnTitleUpdated = (columnId: string, title: string) => {
|
||||
setColumns((prev) => prev.map((column) => (column.id === columnId ? { ...column, title } : column)));
|
||||
};
|
||||
|
||||
if (isPending) {
|
||||
return <PageLoading />;
|
||||
}
|
||||
@@ -249,6 +253,7 @@ const Workspace: FC = () => {
|
||||
onColumnDrop={handleColumnDrop}
|
||||
onTaskClick={setSelectedTask}
|
||||
onColumnDeleted={handleColumnDeleted}
|
||||
onColumnTitleUpdated={handleColumnTitleUpdated}
|
||||
/>
|
||||
))}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user