manage users
This commit is contained in:
@@ -21,9 +21,10 @@ type Props = {
|
||||
activeTab: TaskDetailTab | null;
|
||||
onTabChange: (tab: TaskDetailTab) => void;
|
||||
labelsPopover?: ReactNode;
|
||||
usersPopover?: ReactNode;
|
||||
};
|
||||
|
||||
const TaskDetailActionBar: FC<Props> = ({ activeTab, onTabChange, labelsPopover }) => {
|
||||
const TaskDetailActionBar: FC<Props> = ({ activeTab, onTabChange, labelsPopover, usersPopover }) => {
|
||||
const { t } = useTranslation("global");
|
||||
|
||||
return (
|
||||
@@ -42,11 +43,11 @@ const TaskDetailActionBar: FC<Props> = ({ activeTab, onTabChange, labelsPopover
|
||||
</button>
|
||||
);
|
||||
|
||||
if (id === "labels") {
|
||||
if (id === "labels" || id === "users") {
|
||||
return (
|
||||
<div key={id} className="relative">
|
||||
{button}
|
||||
{labelsPopover}
|
||||
{id === "labels" ? labelsPopover : usersPopover}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,12 +3,14 @@ import { type FC } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import AvatarImage from "../../../../assets/images/avatar_image.png";
|
||||
import type { TaskLabel } from "./labels/types";
|
||||
import type { TaskUser } from "./users/types";
|
||||
|
||||
type Props = {
|
||||
selectedLabels: TaskLabel[];
|
||||
selectedUsers: TaskUser[];
|
||||
};
|
||||
|
||||
const TaskDetailMetadataPreview: FC<Props> = ({ selectedLabels }) => {
|
||||
const TaskDetailMetadataPreview: FC<Props> = ({ selectedLabels, selectedUsers }) => {
|
||||
const { t } = useTranslation("global");
|
||||
|
||||
return (
|
||||
@@ -37,10 +39,12 @@ const TaskDetailMetadataPreview: FC<Props> = ({ selectedLabels }) => {
|
||||
|
||||
<div>
|
||||
<div className="text-xs font-medium mb-2">{t("taskmanager.users")}</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="size-8 rounded-full bg-[#D0D0D0] overflow-hidden">
|
||||
<img src={AvatarImage} alt="" className="size-full object-cover" />
|
||||
</div>
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
{selectedUsers.map((user) => (
|
||||
<div key={user.id} className="size-8 rounded-full bg-[#D0D0D0] overflow-hidden">
|
||||
<img src={user.avatar ?? AvatarImage} alt={user.name} className="size-full object-cover" />
|
||||
</div>
|
||||
))}
|
||||
<button
|
||||
type="button"
|
||||
className="size-7 rounded-full border border-[#D0D0D0] bg-white/60 flex items-center justify-center cursor-pointer"
|
||||
|
||||
@@ -5,6 +5,8 @@ import type { LabelsView, TaskLabel } from "./labels/types";
|
||||
import TaskDetailActionBar from "./TaskDetailActionBar";
|
||||
import TaskDetailMetadataPreview from "./TaskDetailMetadataPreview";
|
||||
import type { TaskDetailTab } from "./types";
|
||||
import { DEFAULT_USERS } from "./users/constants";
|
||||
import TaskDetailUsersPopover from "./users/TaskDetailUsersPopover";
|
||||
|
||||
type Props = {
|
||||
activeTab: TaskDetailTab | null;
|
||||
@@ -14,10 +16,13 @@ type Props = {
|
||||
const TaskDetailToolbar: FC<Props> = ({ activeTab, onTabChange }) => {
|
||||
const [labelsView, setLabelsView] = useState<LabelsView>("list");
|
||||
const [labels, setLabels] = useState<TaskLabel[]>(DEFAULT_LABELS);
|
||||
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set(["1"]));
|
||||
const [selectedLabelIds, setSelectedLabelIds] = useState<Set<string>>(new Set(["1"]));
|
||||
const [selectedUserIds, setSelectedUserIds] = useState<Set<string>>(new Set(["1"]));
|
||||
|
||||
const selectedLabels = labels.filter((label) => selectedIds.has(label.id));
|
||||
const selectedLabels = labels.filter((label) => selectedLabelIds.has(label.id));
|
||||
const selectedUsers = DEFAULT_USERS.filter((user) => selectedUserIds.has(user.id));
|
||||
const isLabelsOpen = activeTab === "labels";
|
||||
const isUsersOpen = activeTab === "users";
|
||||
|
||||
const handleTabChange = (tab: TaskDetailTab) => {
|
||||
if (tab === "labels" && activeTab !== "labels") {
|
||||
@@ -26,8 +31,20 @@ const TaskDetailToolbar: FC<Props> = ({ activeTab, onTabChange }) => {
|
||||
onTabChange(tab);
|
||||
};
|
||||
|
||||
const handleToggle = (id: string) => {
|
||||
setSelectedIds((prev) => {
|
||||
const handleLabelToggle = (id: string) => {
|
||||
setSelectedLabelIds((prev) => {
|
||||
const next = new Set(prev);
|
||||
if (next.has(id)) {
|
||||
next.delete(id);
|
||||
} else {
|
||||
next.add(id);
|
||||
}
|
||||
return next;
|
||||
});
|
||||
};
|
||||
|
||||
const handleUserToggle = (id: string) => {
|
||||
setSelectedUserIds((prev) => {
|
||||
const next = new Set(prev);
|
||||
if (next.has(id)) {
|
||||
next.delete(id);
|
||||
@@ -45,7 +62,7 @@ const TaskDetailToolbar: FC<Props> = ({ activeTab, onTabChange }) => {
|
||||
color,
|
||||
};
|
||||
setLabels((prev) => [...prev, newLabel]);
|
||||
setSelectedIds((prev) => new Set([...prev, newLabel.id]));
|
||||
setSelectedLabelIds((prev) => new Set([...prev, newLabel.id]));
|
||||
setLabelsView("list");
|
||||
};
|
||||
|
||||
@@ -55,11 +72,25 @@ const TaskDetailToolbar: FC<Props> = ({ activeTab, onTabChange }) => {
|
||||
activeTab={activeTab}
|
||||
onTabChange={handleTabChange}
|
||||
labelsPopover={
|
||||
isLabelsOpen ? <TaskDetailLabelsPopover view={labelsView} labels={labels} selectedIds={selectedIds} onViewChange={setLabelsView} onToggle={handleToggle} onCreate={handleCreate} /> : null
|
||||
isLabelsOpen ? (
|
||||
<TaskDetailLabelsPopover
|
||||
view={labelsView}
|
||||
labels={labels}
|
||||
selectedIds={selectedLabelIds}
|
||||
onViewChange={setLabelsView}
|
||||
onToggle={handleLabelToggle}
|
||||
onCreate={handleCreate}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
usersPopover={
|
||||
isUsersOpen ? (
|
||||
<TaskDetailUsersPopover users={DEFAULT_USERS} selectedIds={selectedUserIds} onToggle={handleUserToggle} />
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
|
||||
<TaskDetailMetadataPreview selectedLabels={selectedLabels} />
|
||||
<TaskDetailMetadataPreview selectedLabels={selectedLabels} selectedUsers={selectedUsers} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import { type FC, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Input from "../../../../../components/Input";
|
||||
import type { TaskUser } from "./types";
|
||||
|
||||
type Props = {
|
||||
users: TaskUser[];
|
||||
selectedIds: Set<string>;
|
||||
onToggle: (id: string) => void;
|
||||
};
|
||||
|
||||
const TaskDetailUsersList: FC<Props> = ({ users }) => {
|
||||
const { t } = useTranslation("global");
|
||||
const [search, setSearch] = useState("");
|
||||
|
||||
const filteredUsers = useMemo(() => {
|
||||
const query = search.trim().toLowerCase();
|
||||
if (!query) return users;
|
||||
return users.filter((user) => user.name.toLowerCase().includes(query));
|
||||
}, [users, search]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-3">
|
||||
<div>
|
||||
<h3 className="text-xs font-bold text-center">{t("taskmanager.users")}</h3>
|
||||
</div>
|
||||
|
||||
<Input label={t("search")} value={search} onChange={(e) => setSearch(e.target.value)} className="h-8 bg-white/63 border-white" labelClassName="text-xs" />
|
||||
|
||||
<div>
|
||||
<div className="text-xs font-medium mb-2">{t("taskmanager.task_detail.members")}</div>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
{filteredUsers.map((user) => {
|
||||
return (
|
||||
<div key={user.id} className="flex gap-1.5 items-center">
|
||||
<div className="size-6 rounded-full bg-gray-400"></div>
|
||||
<div className="text-[10px]">{user.name}</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TaskDetailUsersList;
|
||||
@@ -0,0 +1,19 @@
|
||||
import { type FC } from "react";
|
||||
import TaskDetailUsersList from "./TaskDetailUsersList";
|
||||
import type { TaskUser } from "./types";
|
||||
|
||||
type Props = {
|
||||
users: TaskUser[];
|
||||
selectedIds: Set<string>;
|
||||
onToggle: (id: string) => void;
|
||||
};
|
||||
|
||||
const TaskDetailUsersPopover: FC<Props> = ({ users, selectedIds, onToggle }) => {
|
||||
return (
|
||||
<div className="absolute top-full right-0 mt-2 z-30 w-[300px] bg-white/90 backdrop-blur-md rounded-2xl p-4 shadow-lg border border-white/80">
|
||||
<TaskDetailUsersList users={users} selectedIds={selectedIds} onToggle={onToggle} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TaskDetailUsersPopover;
|
||||
@@ -0,0 +1,6 @@
|
||||
import type { TaskUser } from "./types";
|
||||
|
||||
export const DEFAULT_USERS: TaskUser[] = [
|
||||
{ id: "1", name: "فروغ براتی" },
|
||||
{ id: "2", name: "پارسا مهرزاد" },
|
||||
];
|
||||
@@ -0,0 +1,5 @@
|
||||
export type TaskUser = {
|
||||
id: string;
|
||||
name: string;
|
||||
avatar?: string;
|
||||
};
|
||||
Reference in New Issue
Block a user