Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 89d9ad7557 | |||
| 3eae768d5a | |||
| b847dbca69 | |||
| 44fd61c039 |
@@ -43,6 +43,10 @@ export const getSubMenuNameFromPath = (pathname: string): string => {
|
|||||||
return "customers";
|
return "customers";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (pathname.startsWith("/workspace") || pathname.startsWith("/project") || pathname.startsWith("/taskmanager")) {
|
||||||
|
return "other";
|
||||||
|
}
|
||||||
|
|
||||||
const segment = pathname.split("/")[1] ?? "";
|
const segment = pathname.split("/")[1] ?? "";
|
||||||
return pathSegmentToSubMenu[segment] ?? "";
|
return pathSegmentToSubMenu[segment] ?? "";
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1023,8 +1023,10 @@
|
|||||||
"add_column": "اضافه کردن",
|
"add_column": "اضافه کردن",
|
||||||
"column_name": "نام ستون",
|
"column_name": "نام ستون",
|
||||||
"delete_column": "حذف ستون",
|
"delete_column": "حذف ستون",
|
||||||
|
"edit_column": "ویرایش نام",
|
||||||
"delete_column_confirm": "آیا از حذف این ستون اطمینان دارید؟",
|
"delete_column_confirm": "آیا از حذف این ستون اطمینان دارید؟",
|
||||||
"column_deleted": "ستون با موفقیت حذف شد",
|
"column_deleted": "ستون با موفقیت حذف شد",
|
||||||
|
"column_updated": "ستون با موفقیت ویرایش شد",
|
||||||
"add_new_task": "اضافه کردن تسک",
|
"add_new_task": "اضافه کردن تسک",
|
||||||
"add_task": "اضافه کردن",
|
"add_task": "اضافه کردن",
|
||||||
"task_title": "عنوان تسک",
|
"task_title": "عنوان تسک",
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { ErrorType } from "../../../helpers/types";
|
|||||||
import { useCreateTask, useGetTasksByTaskPhase } from "../task/hooks/useTaskData";
|
import { useCreateTask, useGetTasksByTaskPhase } from "../task/hooks/useTaskData";
|
||||||
import type { TaskItemType } from "../task/types/TaskTypes";
|
import type { TaskItemType } from "../task/types/TaskTypes";
|
||||||
import { mapTaskItemToTask } from "../task/utils/mapTaskItemToTask";
|
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 type { Column as ColumnType, Task as TaskType } from "../types";
|
||||||
import ColumnMenu from "./ColumnMenu";
|
import ColumnMenu from "./ColumnMenu";
|
||||||
import Task from "./Task";
|
import Task from "./Task";
|
||||||
@@ -22,6 +22,7 @@ type Props = {
|
|||||||
onColumnDrop: (overColumnId: string) => void;
|
onColumnDrop: (overColumnId: string) => void;
|
||||||
onTaskClick?: (task: TaskType) => void;
|
onTaskClick?: (task: TaskType) => void;
|
||||||
onColumnDeleted?: (columnId: string) => void;
|
onColumnDeleted?: (columnId: string) => void;
|
||||||
|
onColumnTitleUpdated?: (columnId: string, title: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Column: FC<Props> = ({
|
const Column: FC<Props> = ({
|
||||||
@@ -33,9 +34,11 @@ const Column: FC<Props> = ({
|
|||||||
onColumnDrop,
|
onColumnDrop,
|
||||||
onTaskClick,
|
onTaskClick,
|
||||||
onColumnDeleted,
|
onColumnDeleted,
|
||||||
|
onColumnTitleUpdated,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation("global");
|
const { t } = useTranslation("global");
|
||||||
const deleteTaskPhase = useDeleteTaskPhase();
|
const deleteTaskPhase = useDeleteTaskPhase();
|
||||||
|
const updateTaskPhase = useUpdateTaskPhase();
|
||||||
const createTask = useCreateTask();
|
const createTask = useCreateTask();
|
||||||
const {
|
const {
|
||||||
data: tasksData,
|
data: tasksData,
|
||||||
@@ -55,10 +58,13 @@ const Column: FC<Props> = ({
|
|||||||
|
|
||||||
const [isAdding, setIsAdding] = useState(false);
|
const [isAdding, setIsAdding] = useState(false);
|
||||||
const [title, setTitle] = useState("");
|
const [title, setTitle] = useState("");
|
||||||
|
const [isEditingTitle, setIsEditingTitle] = useState(false);
|
||||||
|
const [editTitle, setEditTitle] = useState(column.title);
|
||||||
const [isDeleteConfirmOpen, setIsDeleteConfirmOpen] = useState(false);
|
const [isDeleteConfirmOpen, setIsDeleteConfirmOpen] = useState(false);
|
||||||
const [isColumnDragOver, setIsColumnDragOver] = useState(false);
|
const [isColumnDragOver, setIsColumnDragOver] = useState(false);
|
||||||
const columnRef = useRef<HTMLDivElement>(null);
|
const columnRef = useRef<HTMLDivElement>(null);
|
||||||
const dragImageRef = useRef<HTMLElement | null>(null);
|
const dragImageRef = useRef<HTMLElement | null>(null);
|
||||||
|
const isCancellingTitleEdit = useRef(false);
|
||||||
const isDraggingColumn = draggedColumnId === column.id;
|
const isDraggingColumn = draggedColumnId === column.id;
|
||||||
|
|
||||||
const handleTasksScroll = (event: React.UIEvent<HTMLDivElement>) => {
|
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 (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={columnRef}
|
ref={columnRef}
|
||||||
@@ -178,15 +228,38 @@ const Column: FC<Props> = ({
|
|||||||
onDrop={handleColumnDrop}
|
onDrop={handleColumnDrop}
|
||||||
>
|
>
|
||||||
<div className="flex justify-between items-center shrink-0 gap-2">
|
<div className="flex justify-between items-center shrink-0 gap-2">
|
||||||
<div
|
{isEditingTitle ? (
|
||||||
draggable
|
<input
|
||||||
onDragStart={handleColumnDragStart}
|
type="text"
|
||||||
onDragEnd={handleColumnDragEnd}
|
value={editTitle}
|
||||||
className="flex-1 min-w-0 font-bold text-sm truncate cursor-grab active:cursor-grabbing"
|
onChange={(e) => setEditTitle(e.target.value)}
|
||||||
>
|
onBlur={handleSaveTitle}
|
||||||
{column.title}
|
onKeyDown={(e) => {
|
||||||
</div>
|
if (e.key === "Enter") {
|
||||||
<ColumnMenu onDelete={() => setIsDeleteConfirmOpen(true)} />
|
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>
|
</div>
|
||||||
|
|
||||||
<ModalConfrim
|
<ModalConfrim
|
||||||
|
|||||||
@@ -4,33 +4,51 @@ import { FC } from "react";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
onEdit: () => void;
|
||||||
onDelete: () => void;
|
onDelete: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ColumnMenu: FC<Props> = ({ onDelete }) => {
|
const ColumnMenu: FC<Props> = ({ onEdit, onDelete }) => {
|
||||||
const { t } = useTranslation("global");
|
const { t } = useTranslation("global");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover className="relative">
|
<Popover className="relative">
|
||||||
<PopoverButton
|
{({ close }) => (
|
||||||
className="flex items-center justify-center size-8 rounded-lg hover:bg-black/5 transition-colors outline-none shrink-0"
|
<>
|
||||||
onClick={(e) => e.stopPropagation()}
|
<PopoverButton
|
||||||
>
|
className="flex items-center justify-center size-8 rounded-lg hover:bg-black/5 transition-colors outline-none shrink-0"
|
||||||
<More size={20} color="black" />
|
onClick={(e) => e.stopPropagation()}
|
||||||
</PopoverButton>
|
>
|
||||||
|
<More size={20} color="black" />
|
||||||
|
</PopoverButton>
|
||||||
|
|
||||||
<PopoverPanel
|
<PopoverPanel
|
||||||
anchor="bottom start"
|
anchor="bottom start"
|
||||||
className="z-20 mt-1 min-w-[120px] rounded-xl bg-white shadow-md border border-border py-1 text-sm"
|
className="z-20 mt-1 min-w-[120px] rounded-xl bg-white shadow-md border border-border py-1 text-sm"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onDelete}
|
onClick={() => {
|
||||||
className="w-full px-4 py-2.5 text-right text-red-500 hover:bg-red-50 transition-colors"
|
close();
|
||||||
>
|
onEdit();
|
||||||
{t("taskmanager.delete_column")}
|
}}
|
||||||
</button>
|
className="w-full px-4 py-2.5 text-right hover:bg-black/5 transition-colors"
|
||||||
</PopoverPanel>
|
>
|
||||||
|
{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>
|
</Popover>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { InfoCircle, Setting2, UserAdd } from "iconsax-react";
|
import { ArrowRight, Setting2 } from "iconsax-react";
|
||||||
import type { FC } from "react";
|
import type { FC } from "react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import Button from "../../../components/Button";
|
|
||||||
|
|
||||||
type HeaderWorkspaceProps = {
|
type HeaderWorkspaceProps = {
|
||||||
projectName: string;
|
projectName: string;
|
||||||
@@ -11,27 +10,25 @@ const HeaderWorkspace: FC<HeaderWorkspaceProps> = ({ projectName }) => {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex justify-between items-center bg-primary h-14 sm:h-16 xl:h-[102px] px-3 sm:px-6 xl:px-8 shrink-0 gap-3">
|
<div className="flex justify-between items-center bg-primary h-14 sm:h-16 xl:h-[80px] px-3 sm:px-6 xl:px-8 shrink-0 gap-3">
|
||||||
<div className="flex gap-2 sm:gap-4 items-center min-w-0">
|
<div className="flex gap-2 sm:gap-4 items-center min-w-0">
|
||||||
<div className="text-white text-base sm:text-lg xl:text-xl truncate">{projectName}</div>
|
<div className="text-white text-base sm:text-lg xl:text-xl truncate">{projectName}</div>
|
||||||
<InfoCircle
|
{/* <InfoCircle
|
||||||
size={18}
|
size={18}
|
||||||
color="white"
|
color="white"
|
||||||
/>
|
/> */}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex gap-2 sm:gap-4 items-center shrink-0">
|
<div className="flex gap-2 sm:gap-4 items-center shrink-0">
|
||||||
<Button
|
<div
|
||||||
onClick={() => navigate(-1)}
|
onClick={() => navigate(-1)}
|
||||||
label="برگشت"
|
className="flex gap-1 items-center px-2 sm:px-3 rounded-lg bg-white h-8"
|
||||||
/>
|
>
|
||||||
|
<ArrowRight
|
||||||
<div className="flex gap-1 items-center px-2 sm:px-3 rounded-lg bg-white h-8">
|
|
||||||
<UserAdd
|
|
||||||
size={18}
|
size={18}
|
||||||
color="black"
|
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>
|
</div>
|
||||||
|
|
||||||
<Setting2
|
<Setting2
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { CSSProperties, FC } from "react";
|
import { FC } from "react";
|
||||||
import { Link, useNavigate } from "react-router-dom";
|
import { Link, useNavigate } from "react-router-dom";
|
||||||
import { Pages } from "../../../../config/Pages";
|
import { Pages } from "../../../../config/Pages";
|
||||||
import type { ProjectItem } from "../types/ProjectTypes";
|
import type { ProjectItem } from "../types/ProjectTypes";
|
||||||
|
import { getProjectBackgroundStyle } from "../utils/getProjectBackgroundStyle";
|
||||||
import ProjectCardMenu from "./ProjectCardMenu";
|
import ProjectCardMenu from "./ProjectCardMenu";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -9,30 +10,6 @@ type Props = {
|
|||||||
onDelete: (id: string) => void;
|
onDelete: (id: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const hexToRgba = (hex: string, alpha: number) => {
|
|
||||||
const normalized = hex.replace("#", "");
|
|
||||||
const r = parseInt(normalized.slice(0, 2), 16);
|
|
||||||
const g = parseInt(normalized.slice(2, 4), 16);
|
|
||||||
const b = parseInt(normalized.slice(4, 6), 16);
|
|
||||||
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getCardBackgroundStyle = (project: ProjectItem): CSSProperties => {
|
|
||||||
if (project.backgroundPicture) {
|
|
||||||
return {
|
|
||||||
backgroundImage: `url(${project.backgroundPicture})`,
|
|
||||||
backgroundSize: "cover",
|
|
||||||
backgroundPosition: "center",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const color = project.backgroundColor ?? "#A8E6CF";
|
|
||||||
|
|
||||||
return {
|
|
||||||
background: `linear-gradient(135deg, ${color}, ${hexToRgba(color, 0.5)})`,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const ProjectCard: FC<Props> = ({ project, onDelete }) => {
|
const ProjectCard: FC<Props> = ({ project, onDelete }) => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
@@ -43,7 +20,7 @@ const ProjectCard: FC<Props> = ({ project, onDelete }) => {
|
|||||||
return (
|
return (
|
||||||
<div className="bg-white rounded-[20px] overflow-hidden shadow-sm border border-border/40 p-3">
|
<div className="bg-white rounded-[20px] overflow-hidden shadow-sm border border-border/40 p-3">
|
||||||
<Link to={Pages.taskmanager.workspace + project.id}>
|
<Link to={Pages.taskmanager.workspace + project.id}>
|
||||||
<div className="h-[84px] w-full rounded-[16px]" style={getCardBackgroundStyle(project)} />
|
<div className="h-[84px] w-full rounded-[16px]" style={getProjectBackgroundStyle(project)} />
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<div className="flex items-center justify-between mt-3.5">
|
<div className="flex items-center justify-between mt-3.5">
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Add, Setting2 } from "iconsax-react";
|
import { Add, ArrowRight } from "iconsax-react";
|
||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link } from "react-router-dom";
|
import { Link, useNavigate } from "react-router-dom";
|
||||||
import Button from "../../../../components/Button";
|
import Button from "../../../../components/Button";
|
||||||
import { Pages } from "../../../../config/Pages";
|
import { Pages } from "../../../../config/Pages";
|
||||||
|
|
||||||
@@ -13,23 +13,33 @@ type Props = {
|
|||||||
|
|
||||||
const ProjectListHeader: FC<Props> = ({ title, workspaceId, onSettingsClick }) => {
|
const ProjectListHeader: FC<Props> = ({ title, workspaceId, onSettingsClick }) => {
|
||||||
const { t } = useTranslation("global");
|
const { t } = useTranslation("global");
|
||||||
const createProjectLink = workspaceId
|
const createProjectLink = workspaceId ? `${Pages.taskmanager.createProject}?workspaceId=${workspaceId}` : Pages.taskmanager.createProject;
|
||||||
? `${Pages.taskmanager.createProject}?workspaceId=${workspaceId}`
|
const navigate = useNavigate();
|
||||||
: Pages.taskmanager.createProject;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex w-full justify-between items-center">
|
<div className="flex w-full justify-between items-center">
|
||||||
<h1>{title}</h1>
|
<h1>{title}</h1>
|
||||||
|
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Button onClick={onSettingsClick} className="flex items-center gap-2 bg-[#C3C7DD] text-black whitespace-nowrap px-4">
|
<Button
|
||||||
<Setting2 size={18} color="black" />
|
onClick={() => navigate(-1)}
|
||||||
<span>{t("sidebar.setting")}</span>
|
className="flex items-center gap-2 bg-[#C3C7DD] text-black whitespace-nowrap px-4"
|
||||||
|
>
|
||||||
|
<ArrowRight
|
||||||
|
size={18}
|
||||||
|
color="black"
|
||||||
|
/>
|
||||||
|
<span>برگشت</span>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Link to={createProjectLink}>
|
<Link to={createProjectLink}>
|
||||||
<Button onClick={onSettingsClick} className="flex items-center gap-2 whitespace-nowrap px-4">
|
<Button
|
||||||
<Add size={18} color="white" />
|
onClick={onSettingsClick}
|
||||||
|
className="flex items-center gap-2 whitespace-nowrap px-4"
|
||||||
|
>
|
||||||
|
<Add
|
||||||
|
size={18}
|
||||||
|
color="white"
|
||||||
|
/>
|
||||||
<span>{t("taskmanager.new_project")}</span>
|
<span>{t("taskmanager.new_project")}</span>
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import type { CSSProperties } from "react";
|
||||||
|
|
||||||
|
type ProjectBackground = {
|
||||||
|
backgroundColor?: string;
|
||||||
|
backgroundPicture?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const hexToRgba = (hex: string, alpha: number) => {
|
||||||
|
const normalized = hex.replace("#", "");
|
||||||
|
const r = parseInt(normalized.slice(0, 2), 16);
|
||||||
|
const g = parseInt(normalized.slice(2, 4), 16);
|
||||||
|
const b = parseInt(normalized.slice(4, 6), 16);
|
||||||
|
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getProjectBackgroundStyle = (project: ProjectBackground): CSSProperties => {
|
||||||
|
if (project.backgroundPicture) {
|
||||||
|
return {
|
||||||
|
backgroundImage: `url(${project.backgroundPicture})`,
|
||||||
|
backgroundSize: "cover",
|
||||||
|
backgroundPosition: "center",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const color = project.backgroundColor ?? "#A8E6CF";
|
||||||
|
|
||||||
|
return {
|
||||||
|
background: `linear-gradient(135deg, ${color}, ${hexToRgba(color, 0.5)})`,
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -6,7 +6,8 @@ export type CreateTaskPhaseType = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type UpdateTaskPhaseType = {
|
export type UpdateTaskPhaseType = {
|
||||||
order: number;
|
order?: number;
|
||||||
|
name?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TaskPhaseItemType = {
|
export type TaskPhaseItemType = {
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ import AddNewColumn from "./components/AddNewColumn";
|
|||||||
import Column from "./components/Column";
|
import Column from "./components/Column";
|
||||||
import HeaderWorkspace from "./components/HeaderWorkspace";
|
import HeaderWorkspace from "./components/HeaderWorkspace";
|
||||||
import TaskDetailModal from "./components/task-detail/TaskDetailModal";
|
import TaskDetailModal from "./components/task-detail/TaskDetailModal";
|
||||||
import { useGetProject } from "./project/hooks/useProjectData";
|
import { useGetProject, useGetProjectDetail } from "./project/hooks/useProjectData";
|
||||||
|
import type { ProjectDetailType } from "./project/types/ProjectTypes";
|
||||||
|
import { getProjectBackgroundStyle } from "./project/utils/getProjectBackgroundStyle";
|
||||||
import { useUpdateTaskPhase } from "./task-phase/hooks/useTaskPhaseData";
|
import { useUpdateTaskPhase } from "./task-phase/hooks/useTaskPhaseData";
|
||||||
import type { TaskPhaseItemType } from "./task-phase/types/TaskPhaseTypes";
|
import type { TaskPhaseItemType } from "./task-phase/types/TaskPhaseTypes";
|
||||||
import { useChangeTaskPhase, useChangeTaskPriority } from "./task/hooks/useTaskData";
|
import { useChangeTaskPhase, useChangeTaskPriority } from "./task/hooks/useTaskData";
|
||||||
@@ -25,9 +27,12 @@ const Workspace: FC = () => {
|
|||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const { slug: projectId = "" } = useParams<{ slug: string }>();
|
const { slug: projectId = "" } = useParams<{ slug: string }>();
|
||||||
const { data: project, isPending } = useGetProject(projectId);
|
const { data: project, isPending } = useGetProject(projectId);
|
||||||
const changeTaskPhase = useChangeTaskPhase();
|
const { data: projectDetailData } = useGetProjectDetail(projectId);
|
||||||
const changeTaskPriority = useChangeTaskPriority();
|
const projectDetail: ProjectDetailType | undefined =
|
||||||
const updateTaskPhase = useUpdateTaskPhase();
|
projectDetailData?.data?.project ?? projectDetailData?.data;
|
||||||
|
const { mutate: changeTaskPhase } = useChangeTaskPhase();
|
||||||
|
const { mutate: changeTaskPriority } = useChangeTaskPriority();
|
||||||
|
const { mutate: updateTaskPhase } = useUpdateTaskPhase();
|
||||||
const [columns, setColumns] = useState<ColumnType[]>([]);
|
const [columns, setColumns] = useState<ColumnType[]>([]);
|
||||||
const [draggedTask, setDraggedTask] = useState<Task | null>(null);
|
const [draggedTask, setDraggedTask] = useState<Task | null>(null);
|
||||||
const [draggedColumnId, setDraggedColumnId] = useState<string | null>(null);
|
const [draggedColumnId, setDraggedColumnId] = useState<string | null>(null);
|
||||||
@@ -75,7 +80,7 @@ const Workspace: FC = () => {
|
|||||||
updateTaskPhaseItems(current, (items) => reorderTaskItems(items, activeTask.id, index)),
|
updateTaskPhaseItems(current, (items) => reorderTaskItems(items, activeTask.id, index)),
|
||||||
);
|
);
|
||||||
|
|
||||||
changeTaskPriority.mutate(
|
changeTaskPriority(
|
||||||
{ srcId: activeTask.id, destId: destTaskId, taskPhaseId: columnId, projectId },
|
{ srcId: activeTask.id, destId: destTaskId, taskPhaseId: columnId, projectId },
|
||||||
{
|
{
|
||||||
onError: (error: ErrorType) => {
|
onError: (error: ErrorType) => {
|
||||||
@@ -122,12 +127,12 @@ const Workspace: FC = () => {
|
|||||||
setSelectedTask({ ...activeTask, columnId });
|
setSelectedTask({ ...activeTask, columnId });
|
||||||
}
|
}
|
||||||
|
|
||||||
changeTaskPhase.mutate(
|
changeTaskPhase(
|
||||||
{ id: activeTask.id, params: { taskPhaseId: columnId }, projectId },
|
{ id: activeTask.id, params: { taskPhaseId: columnId }, projectId },
|
||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
if (destTaskId) {
|
if (destTaskId) {
|
||||||
changeTaskPriority.mutate(
|
changeTaskPriority(
|
||||||
{ srcId: activeTask.id, destId: destTaskId, taskPhaseId: columnId, projectId },
|
{ srcId: activeTask.id, destId: destTaskId, taskPhaseId: columnId, projectId },
|
||||||
{
|
{
|
||||||
onError: (error: ErrorType) => {
|
onError: (error: ErrorType) => {
|
||||||
@@ -202,7 +207,7 @@ const Workspace: FC = () => {
|
|||||||
|
|
||||||
if (!movedColumn?.order) return;
|
if (!movedColumn?.order) return;
|
||||||
|
|
||||||
updateTaskPhase.mutate(
|
updateTaskPhase(
|
||||||
{ id: draggedColumnId, params: { order: movedColumn.order }, projectId },
|
{ id: draggedColumnId, params: { order: movedColumn.order }, projectId },
|
||||||
{
|
{
|
||||||
onError: (error: ErrorType) => {
|
onError: (error: ErrorType) => {
|
||||||
@@ -227,12 +232,22 @@ const Workspace: FC = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleColumnTitleUpdated = (columnId: string, title: string) => {
|
||||||
|
setColumns((prev) => prev.map((column) => (column.id === columnId ? { ...column, title } : column)));
|
||||||
|
};
|
||||||
|
|
||||||
if (isPending) {
|
if (isPending) {
|
||||||
return <PageLoading />;
|
return <PageLoading />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-[#01347A] h-full min-h-0 rounded-none xl:rounded-[32px] overflow-hidden flex flex-col">
|
<div
|
||||||
|
className="h-full min-h-0 rounded-none xl:rounded-[32px] overflow-hidden flex flex-col"
|
||||||
|
style={getProjectBackgroundStyle({
|
||||||
|
backgroundColor: projectDetail?.backgroundColor,
|
||||||
|
backgroundPicture: projectDetail?.backgroundPicture,
|
||||||
|
})}
|
||||||
|
>
|
||||||
<HeaderWorkspace projectName={project?.data?.name ?? ""} />
|
<HeaderWorkspace projectName={project?.data?.name ?? ""} />
|
||||||
<div className="flex-1 min-h-0 flex flex-col px-3 sm:px-4 xl:px-8 py-4 xl:py-6">
|
<div className="flex-1 min-h-0 flex flex-col px-3 sm:px-4 xl:px-8 py-4 xl:py-6">
|
||||||
<DragDropContext onDragStart={handleTaskDragStart} onDragEnd={handleTaskDragEnd}>
|
<DragDropContext onDragStart={handleTaskDragStart} onDragEnd={handleTaskDragEnd}>
|
||||||
@@ -249,6 +264,7 @@ const Workspace: FC = () => {
|
|||||||
onColumnDrop={handleColumnDrop}
|
onColumnDrop={handleColumnDrop}
|
||||||
onTaskClick={setSelectedTask}
|
onTaskClick={setSelectedTask}
|
||||||
onColumnDeleted={handleColumnDeleted}
|
onColumnDeleted={handleColumnDeleted}
|
||||||
|
onColumnTitleUpdated={handleColumnTitleUpdated}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
|
|||||||
@@ -73,12 +73,7 @@ const WorkspaceList: FC = () => {
|
|||||||
className="size-4 rounded-full shrink-0"
|
className="size-4 rounded-full shrink-0"
|
||||||
style={{ backgroundColor: item.color }}
|
style={{ backgroundColor: item.color }}
|
||||||
/>
|
/>
|
||||||
<Link
|
{item.name}
|
||||||
to={Pages.taskmanager.workspace + item.id}
|
|
||||||
className="text-[#0047FF] hover:opacity-70 transition-opacity"
|
|
||||||
>
|
|
||||||
{item.name}
|
|
||||||
</Link>
|
|
||||||
</div>
|
</div>
|
||||||
</Td>
|
</Td>
|
||||||
<Td text={item.description ?? ""} />
|
<Td text={item.description ?? ""} />
|
||||||
|
|||||||
@@ -12,6 +12,15 @@ const OtherSubMenu: FC = () => {
|
|||||||
|
|
||||||
const isActive = (name: string) => location.pathname.includes(name);
|
const isActive = (name: string) => location.pathname.includes(name);
|
||||||
|
|
||||||
|
const isTaskManagerActive =
|
||||||
|
location.pathname.startsWith(Pages.taskmanager.workspace) ||
|
||||||
|
location.pathname === Pages.taskmanager.workspaceList ||
|
||||||
|
location.pathname.startsWith(Pages.taskmanager.updateWorkspace) ||
|
||||||
|
location.pathname === Pages.taskmanager.createWorkspace ||
|
||||||
|
location.pathname === Pages.taskmanager.createProject ||
|
||||||
|
location.pathname.startsWith(Pages.taskmanager.updateProject) ||
|
||||||
|
location.pathname.startsWith(Pages.taskmanager.projectList);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="py-12">
|
<div className="py-12">
|
||||||
<div className="flex gap-3 items-center border-b pb-10 px-6">
|
<div className="flex gap-3 items-center border-b pb-10 px-6">
|
||||||
@@ -70,6 +79,11 @@ const OtherSubMenu: FC = () => {
|
|||||||
link={Pages.messages.list}
|
link={Pages.messages.list}
|
||||||
activeName="contacts_us"
|
activeName="contacts_us"
|
||||||
/>
|
/>
|
||||||
|
<SubMenuItem
|
||||||
|
title={t("sidebar.taskmanager")}
|
||||||
|
isActive={isTaskManagerActive}
|
||||||
|
link={Pages.taskmanager.workspaceList}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user