setting project in workspacce a project
deploy to danak / build_and_deploy (push) Has been cancelled
deploy to danak / build_and_deploy (push) Has been cancelled
This commit is contained in:
@@ -1013,6 +1013,7 @@
|
|||||||
"project_start_date": "تاریخ شروع پروژه",
|
"project_start_date": "تاریخ شروع پروژه",
|
||||||
"employer_name": "نام کارفرما",
|
"employer_name": "نام کارفرما",
|
||||||
"project_status": "وضعیت فضای کار",
|
"project_status": "وضعیت فضای کار",
|
||||||
|
"project_settings": "تنظیمات پروژه",
|
||||||
"background": "پس زمینه",
|
"background": "پس زمینه",
|
||||||
"background_image": "تصویر پس زمینه",
|
"background_image": "تصویر پس زمینه",
|
||||||
"upload_background": "تصویر مورد نظر را دراپ کنید",
|
"upload_background": "تصویر مورد نظر را دراپ کنید",
|
||||||
|
|||||||
@@ -4,9 +4,10 @@ import { useNavigate } from "react-router-dom";
|
|||||||
|
|
||||||
type HeaderWorkspaceProps = {
|
type HeaderWorkspaceProps = {
|
||||||
projectName: string;
|
projectName: string;
|
||||||
|
onSettingsClick?: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const HeaderWorkspace: FC<HeaderWorkspaceProps> = ({ projectName }) => {
|
const HeaderWorkspace: FC<HeaderWorkspaceProps> = ({ projectName, onSettingsClick }) => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -34,7 +35,8 @@ const HeaderWorkspace: FC<HeaderWorkspaceProps> = ({ projectName }) => {
|
|||||||
<Setting2
|
<Setting2
|
||||||
size={18}
|
size={18}
|
||||||
color="white"
|
color="white"
|
||||||
className="min-w-[18px]"
|
className="min-w-[18px] cursor-pointer"
|
||||||
|
onClick={onSettingsClick}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ type Props = {
|
|||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
onWorkspaceIdChange: (id: string) => void;
|
onWorkspaceIdChange: (id: string) => void;
|
||||||
workspaceError?: string;
|
workspaceError?: string;
|
||||||
|
className?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const CreateProjectSidebar: FC<Props> = ({
|
const CreateProjectSidebar: FC<Props> = ({
|
||||||
@@ -63,6 +64,7 @@ const CreateProjectSidebar: FC<Props> = ({
|
|||||||
workspaceId,
|
workspaceId,
|
||||||
onWorkspaceIdChange,
|
onWorkspaceIdChange,
|
||||||
workspaceError,
|
workspaceError,
|
||||||
|
className,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation("global");
|
const { t } = useTranslation("global");
|
||||||
const getUsers = useGetUsers("");
|
const getUsers = useGetUsers("");
|
||||||
@@ -118,7 +120,12 @@ const CreateProjectSidebar: FC<Props> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-white mt-10 w-sidebar text-xs hidden 2xl:block h-fit px-5 py-7 rounded-3xl">
|
<div
|
||||||
|
className={
|
||||||
|
className ??
|
||||||
|
"bg-white mt-10 w-sidebar text-xs hidden 2xl:block h-fit px-5 py-7 rounded-3xl"
|
||||||
|
}
|
||||||
|
>
|
||||||
<div className="text-sm flex items-center justify-between">
|
<div className="text-sm flex items-center justify-between">
|
||||||
<div>{t("taskmanager.project_status")}</div>
|
<div>{t("taskmanager.project_status")}</div>
|
||||||
<div className="flex gap-2 text-xs items-center text-description">
|
<div className="flex gap-2 text-xs items-center text-description">
|
||||||
|
|||||||
@@ -0,0 +1,192 @@
|
|||||||
|
import { CloseCircle, TickCircle } from "iconsax-react";
|
||||||
|
import { type FC, useEffect, useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
import Button from "../../../../components/Button";
|
||||||
|
import { ErrorType } from "../../../../helpers/types";
|
||||||
|
import { clx } from "../../../../helpers/utils";
|
||||||
|
import { useSingleUpload } from "../../../service/hooks/useServiceData";
|
||||||
|
import { useUpdateProject } from "../hooks/useProjectData";
|
||||||
|
import type { ProjectDetailType } from "../types/ProjectTypes";
|
||||||
|
import CreateProjectSidebar, {
|
||||||
|
findMatchingBackgroundPreset,
|
||||||
|
type SelectedUser,
|
||||||
|
} from "./CreateProjectSidebar";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
projectId: string;
|
||||||
|
project?: ProjectDetailType;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ProjectSettingsPanel: FC<Props> = ({ open, onClose, projectId, project }) => {
|
||||||
|
const { t } = useTranslation("global");
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const updateProject = useUpdateProject();
|
||||||
|
const singleUpload = useSingleUpload();
|
||||||
|
|
||||||
|
const [isActive, setIsActive] = useState(true);
|
||||||
|
const [selectedColor, setSelectedColor] = useState("#A8E6CF");
|
||||||
|
const [selectedUsers, setSelectedUsers] = useState<SelectedUser[]>([]);
|
||||||
|
const [selectedBackground, setSelectedBackground] = useState<string | null>(null);
|
||||||
|
const [backgroundImage, setBackgroundImage] = useState<File | null>(null);
|
||||||
|
const [workspaceId, setWorkspaceId] = useState("");
|
||||||
|
const [existingBackgroundPicture, setExistingBackgroundPicture] = useState("");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!open) {
|
||||||
|
document.body.style.overflow = "auto";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.body.style.overflow = "hidden";
|
||||||
|
return () => {
|
||||||
|
document.body.style.overflow = "auto";
|
||||||
|
};
|
||||||
|
}, [open]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!open || !project) return;
|
||||||
|
|
||||||
|
const backgroundColor = project.backgroundColor ?? "#A8E6CF";
|
||||||
|
setIsActive(project.isActive);
|
||||||
|
setSelectedBackground(findMatchingBackgroundPreset(backgroundColor));
|
||||||
|
setSelectedColor(backgroundColor);
|
||||||
|
setWorkspaceId(project.workspaceId);
|
||||||
|
setExistingBackgroundPicture(project.backgroundPicture ?? "");
|
||||||
|
setBackgroundImage(null);
|
||||||
|
|
||||||
|
if (project.users?.length) {
|
||||||
|
setSelectedUsers(
|
||||||
|
project.users.map((user) => ({
|
||||||
|
id: user.id,
|
||||||
|
name: `${user.firstName} ${user.lastName}`,
|
||||||
|
profilePic: user.profilePic,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
} else if (project.userIds?.length) {
|
||||||
|
setSelectedUsers(
|
||||||
|
project.userIds.map((userId) => ({
|
||||||
|
id: userId,
|
||||||
|
name: userId,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
setSelectedUsers([]);
|
||||||
|
}
|
||||||
|
}, [open, project]);
|
||||||
|
|
||||||
|
const handleSave = async () => {
|
||||||
|
if (!project) return;
|
||||||
|
|
||||||
|
if (!workspaceId) {
|
||||||
|
toast.error(t("taskmanager.select_workspace"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let backgroundPicture = existingBackgroundPicture;
|
||||||
|
|
||||||
|
if (backgroundImage) {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("file", backgroundImage);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const uploadResult = await singleUpload.mutateAsync(formData);
|
||||||
|
backgroundPicture = uploadResult?.data?.url ?? "";
|
||||||
|
} catch (error) {
|
||||||
|
const uploadError = error as ErrorType;
|
||||||
|
toast.error(uploadError.response?.data?.error.message[0]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateProject.mutate(
|
||||||
|
{
|
||||||
|
id: projectId,
|
||||||
|
params: {
|
||||||
|
name: project.name,
|
||||||
|
ownerName: project.ownerName,
|
||||||
|
description: project.description ?? "",
|
||||||
|
startDate: project.startDate,
|
||||||
|
backgroundPicture,
|
||||||
|
backgroundColor: selectedColor,
|
||||||
|
isActive,
|
||||||
|
workspaceId,
|
||||||
|
userIds: selectedUsers.map((user) => user.id),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success(t("success"));
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["project", projectId] });
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["project-detail", projectId] });
|
||||||
|
onClose();
|
||||||
|
},
|
||||||
|
onError: (error: ErrorType) => {
|
||||||
|
toast.error(error.response?.data?.error.message[0]);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!open) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
className="fixed inset-0 z-[70] bg-black/40"
|
||||||
|
onClick={onClose}
|
||||||
|
aria-hidden
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={clx(
|
||||||
|
"fixed top-0 bottom-0 left-0 z-[80] w-full max-w-[320px] bg-white shadow-xl flex flex-col transition-transform duration-300 ease-in-out",
|
||||||
|
open ? "translate-x-0" : "-translate-x-full",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-between px-5 py-4 border-b border-border shrink-0">
|
||||||
|
<div className="text-sm font-bold">{t("taskmanager.project_settings")}</div>
|
||||||
|
<button type="button" onClick={onClose} className="p-1">
|
||||||
|
<CloseCircle size={22} color="#8C90A3" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex-1 overflow-y-auto px-5 py-6">
|
||||||
|
<CreateProjectSidebar
|
||||||
|
className="w-full text-xs block h-fit p-0 rounded-none"
|
||||||
|
isActive={isActive}
|
||||||
|
onIsActiveChange={setIsActive}
|
||||||
|
selectedColor={selectedColor}
|
||||||
|
onColorChange={setSelectedColor}
|
||||||
|
selectedUsers={selectedUsers}
|
||||||
|
onSelectedUsersChange={setSelectedUsers}
|
||||||
|
selectedBackground={selectedBackground}
|
||||||
|
onBackgroundChange={setSelectedBackground}
|
||||||
|
backgroundImage={backgroundImage}
|
||||||
|
onBackgroundImageChange={setBackgroundImage}
|
||||||
|
workspaceId={workspaceId}
|
||||||
|
onWorkspaceIdChange={setWorkspaceId}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="px-5 py-4 border-t border-border shrink-0">
|
||||||
|
<Button
|
||||||
|
className="w-full"
|
||||||
|
isLoading={updateProject.isPending || singleUpload.isPending}
|
||||||
|
onClick={handleSave}
|
||||||
|
>
|
||||||
|
<div className="flex gap-2 justify-center">
|
||||||
|
<TickCircle className="size-5" color="#fff" />
|
||||||
|
<div>{t("submit")}</div>
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProjectSettingsPanel;
|
||||||
@@ -10,6 +10,7 @@ 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, useGetProjectDetail } from "./project/hooks/useProjectData";
|
import { useGetProject, useGetProjectDetail } from "./project/hooks/useProjectData";
|
||||||
|
import ProjectSettingsPanel from "./project/components/ProjectSettingsPanel";
|
||||||
import type { ProjectDetailType } from "./project/types/ProjectTypes";
|
import type { ProjectDetailType } from "./project/types/ProjectTypes";
|
||||||
import { getProjectBackgroundStyle } from "./project/utils/getProjectBackgroundStyle";
|
import { getProjectBackgroundStyle } from "./project/utils/getProjectBackgroundStyle";
|
||||||
import { useUpdateTaskPhase } from "./task-phase/hooks/useTaskPhaseData";
|
import { useUpdateTaskPhase } from "./task-phase/hooks/useTaskPhaseData";
|
||||||
@@ -37,6 +38,7 @@ const Workspace: FC = () => {
|
|||||||
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);
|
||||||
const [selectedTask, setSelectedTask] = useState<Task | null>(null);
|
const [selectedTask, setSelectedTask] = useState<Task | null>(null);
|
||||||
|
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!project?.data?.taskPhases) return;
|
if (!project?.data?.taskPhases) return;
|
||||||
@@ -248,7 +250,10 @@ const Workspace: FC = () => {
|
|||||||
backgroundPicture: projectDetail?.backgroundPicture,
|
backgroundPicture: projectDetail?.backgroundPicture,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<HeaderWorkspace projectName={project?.data?.name ?? ""} />
|
<HeaderWorkspace
|
||||||
|
projectName={project?.data?.name ?? ""}
|
||||||
|
onSettingsClick={() => setIsSettingsOpen(true)}
|
||||||
|
/>
|
||||||
<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}>
|
||||||
<div className="flex-1 min-h-0 overflow-x-auto overflow-y-hidden overscroll-x-contain">
|
<div className="flex-1 min-h-0 overflow-x-auto overflow-y-hidden overscroll-x-contain">
|
||||||
@@ -286,6 +291,13 @@ const Workspace: FC = () => {
|
|||||||
onClose={() => setSelectedTask(null)}
|
onClose={() => setSelectedTask(null)}
|
||||||
onTaskPhaseChanged={handleTaskPhaseChanged}
|
onTaskPhaseChanged={handleTaskPhaseChanged}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<ProjectSettingsPanel
|
||||||
|
open={isSettingsOpen}
|
||||||
|
onClose={() => setIsSettingsOpen(false)}
|
||||||
|
projectId={projectId}
|
||||||
|
project={projectDetail}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user