diff --git a/src/components/Input.tsx b/src/components/Input.tsx index e8a9f24..c97efc5 100644 --- a/src/components/Input.tsx +++ b/src/components/Input.tsx @@ -1,115 +1,105 @@ -import { FC, InputHTMLAttributes, useEffect, useState } from 'react' -import { clx } from '../helpers/utils'; -import EyeIcon from '../assets/images/eye.svg' -import { SearchNormal } from 'iconsax-react'; -import Error from './Error'; +import { SearchNormal } from "iconsax-react"; +import { FC, InputHTMLAttributes, useEffect, useState } from "react"; +import EyeIcon from "../assets/images/eye.svg"; +import { clx } from "../helpers/utils"; +import Error from "./Error"; type Variant = "floating_outlined" | "primary" | "search"; type Props = { - label?: string; - className?: string; - variant?: Variant; - error_text?: string; - onEnter?: () => void; - unit?: string; - seprator?: boolean; - isNotRequired?: boolean; - onChangeSearchFinal?: (value: string) => void; -} & InputHTMLAttributes + label?: string; + className?: string; + variant?: Variant; + error_text?: string; + onEnter?: () => void; + unit?: string; + seprator?: boolean; + isNotRequired?: boolean; + onChangeSearchFinal?: (value: string) => void; + labelClassName?: string; +} & InputHTMLAttributes; const formatNumber = (value: string | number): string => { - if (!value) return ''; - const inputValue = String(value).replace(/,/g, ''); - return inputValue.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ','); + if (!value) return ""; + const inputValue = String(value).replace(/,/g, ""); + return inputValue.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); }; const Input: FC = (props: Props) => { + const [formattedValue, setFormattedValue] = useState(props.value ? formatNumber(props.value as string) : ""); - const [formattedValue, setFormattedValue] = useState( - props.value ? formatNumber(props.value as string) : '' - ); + const [showPassword, setShowPassword] = useState(false); + const [search, setSearch] = useState(""); - const [showPassword, setShowPassword] = useState(false) - const [search, setSearch] = useState('') + const inputClass = clx( + "w-full h-10 text-black block px-4 text-xs rounded-xl border border-border", + props.readOnly && "bg-gray-100 border-0 text-description", + props.variant === "search" && "bg-[#EEF0F7] border-0 ps-10", + props.className, + ); - const inputClass = clx( - 'w-full h-10 text-black block px-4 text-xs rounded-xl border border-border', - props.readOnly && 'bg-gray-100 border-0 text-description', - props.variant === 'search' && 'bg-[#EEF0F7] border-0 ps-10', - props.className - ); + const handleInputChange = (event: React.ChangeEvent) => { + if (props.seprator) { + const inputValue = event.target.value.replace(/,/g, ""); // حذف کاماها + const formatted = formatNumber(inputValue); - const handleInputChange = (event: React.ChangeEvent) => { - if (props.seprator) { - const inputValue = event.target.value.replace(/,/g, ''); // حذف کاماها - const formatted = formatNumber(inputValue); + // به‌روزرسانی مقدار قالب‌بندی‌شده + setFormattedValue(formatted); - // به‌روزرسانی مقدار قالب‌بندی‌شده - setFormattedValue(formatted); + // ارسال مقدار خام به `onChange` والد + props.onChange?.({ + ...event, + target: { + ...event.target, + value: inputValue, + }, + }); + } else { + props.onChange?.(event); + } + }; - // ارسال مقدار خام به `onChange` والد - props.onChange?.({ - ...event, - target: { - ...event.target, - value: inputValue, - }, - }); - } else { - props.onChange?.(event); - } - }; + useEffect(() => { + if (props.value) { + setFormattedValue(formatNumber(props.value as string)); + } else { + setFormattedValue(""); + } + }, [props.value]); - useEffect(() => { - if (props.value) { - setFormattedValue(formatNumber(props.value as string)); - } else { - setFormattedValue(''); - } - }, [props.value]) + useEffect(() => { + if (props.variant === "search" && props.onChangeSearchFinal) { + const timeout = setTimeout(() => { + props.onChangeSearchFinal?.(search); + }, 1000); + return () => clearTimeout(timeout); + } + }, [search, props]); - useEffect(() => { - if (props.variant === 'search' && props.onChangeSearchFinal) { - const timeout = setTimeout(() => { - props.onChangeSearchFinal?.(search) - }, 1000) - return () => clearTimeout(timeout) - } - }, [search, props]) + return ( +
+ +
+ { + setSearch(e.target.value); + handleInputChange(e); + }} + value={props.seprator ? formattedValue : props.value} + type={props.type === "password" && showPassword ? "text" : props.type === "password" ? "password" : undefined} + className={inputClass} + /> - return ( -
- + {props.type === "password" && setShowPassword((oldValue) => !oldValue)} src={EyeIcon} className="w-5 absolute top-0 bottom-0 cursor-pointer my-auto left-3" />} -
- { - setSearch(e.target.value) - handleInputChange(e) - }} value={props.seprator ? formattedValue : props.value} type={props.type === 'password' && showPassword ? 'text' : props.type === 'password' ? 'password' : undefined} className={inputClass} /> + {props.variant === "search" && } - { - props.type === 'password' && - setShowPassword((oldValue) => !oldValue)} src={EyeIcon} className='w-5 absolute top-0 bottom-0 cursor-pointer my-auto left-3' /> - } + {props.error_text && } +
+
+ ); +}; - { - props.variant === 'search' && - - } - - { - props.error_text && - - } -
-
- ) -} - -export default Input \ No newline at end of file +export default Input; diff --git a/src/langs/fa.json b/src/langs/fa.json index d5e7577..7da556d 100644 --- a/src/langs/fa.json +++ b/src/langs/fa.json @@ -1000,7 +1000,8 @@ "description_placeholder": "توضیحات خود را اضافه کنید", "new_label": "برچسب جدید", "label_title": "عنوان", - "choose_custom_color": "انتخاب رنگ دلخواه" + "choose_custom_color": "انتخاب رنگ دلخواه", + "members": "اعضا" } } } diff --git a/src/pages/taskmanager/components/task-detail/TaskDetailActionBar.tsx b/src/pages/taskmanager/components/task-detail/TaskDetailActionBar.tsx index 6adf460..e4b5df5 100644 --- a/src/pages/taskmanager/components/task-detail/TaskDetailActionBar.tsx +++ b/src/pages/taskmanager/components/task-detail/TaskDetailActionBar.tsx @@ -21,9 +21,10 @@ type Props = { activeTab: TaskDetailTab | null; onTabChange: (tab: TaskDetailTab) => void; labelsPopover?: ReactNode; + usersPopover?: ReactNode; }; -const TaskDetailActionBar: FC = ({ activeTab, onTabChange, labelsPopover }) => { +const TaskDetailActionBar: FC = ({ activeTab, onTabChange, labelsPopover, usersPopover }) => { const { t } = useTranslation("global"); return ( @@ -42,11 +43,11 @@ const TaskDetailActionBar: FC = ({ activeTab, onTabChange, labelsPopover ); - if (id === "labels") { + if (id === "labels" || id === "users") { return (
{button} - {labelsPopover} + {id === "labels" ? labelsPopover : usersPopover}
); } diff --git a/src/pages/taskmanager/components/task-detail/TaskDetailMetadataPreview.tsx b/src/pages/taskmanager/components/task-detail/TaskDetailMetadataPreview.tsx index 4e60392..474b08e 100644 --- a/src/pages/taskmanager/components/task-detail/TaskDetailMetadataPreview.tsx +++ b/src/pages/taskmanager/components/task-detail/TaskDetailMetadataPreview.tsx @@ -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 = ({ selectedLabels }) => { +const TaskDetailMetadataPreview: FC = ({ selectedLabels, selectedUsers }) => { const { t } = useTranslation("global"); return ( @@ -37,10 +39,12 @@ const TaskDetailMetadataPreview: FC = ({ selectedLabels }) => {
{t("taskmanager.users")}
-
-
- -
+
+ {selectedUsers.map((user) => ( +
+ {user.name} +
+ ))}
); }; diff --git a/src/pages/taskmanager/components/task-detail/users/TaskDetailUsersList.tsx b/src/pages/taskmanager/components/task-detail/users/TaskDetailUsersList.tsx new file mode 100644 index 0000000..8a4ee16 --- /dev/null +++ b/src/pages/taskmanager/components/task-detail/users/TaskDetailUsersList.tsx @@ -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; + onToggle: (id: string) => void; +}; + +const TaskDetailUsersList: FC = ({ 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 ( +
+
+

{t("taskmanager.users")}

+
+ + setSearch(e.target.value)} className="h-8 bg-white/63 border-white" labelClassName="text-xs" /> + +
+
{t("taskmanager.task_detail.members")}
+ +
+ {filteredUsers.map((user) => { + return ( +
+
+
{user.name}
+
+ ); + })} +
+
+
+ ); +}; + +export default TaskDetailUsersList; diff --git a/src/pages/taskmanager/components/task-detail/users/TaskDetailUsersPopover.tsx b/src/pages/taskmanager/components/task-detail/users/TaskDetailUsersPopover.tsx new file mode 100644 index 0000000..84c8b2e --- /dev/null +++ b/src/pages/taskmanager/components/task-detail/users/TaskDetailUsersPopover.tsx @@ -0,0 +1,19 @@ +import { type FC } from "react"; +import TaskDetailUsersList from "./TaskDetailUsersList"; +import type { TaskUser } from "./types"; + +type Props = { + users: TaskUser[]; + selectedIds: Set; + onToggle: (id: string) => void; +}; + +const TaskDetailUsersPopover: FC = ({ users, selectedIds, onToggle }) => { + return ( +
+ +
+ ); +}; + +export default TaskDetailUsersPopover; diff --git a/src/pages/taskmanager/components/task-detail/users/constants.ts b/src/pages/taskmanager/components/task-detail/users/constants.ts new file mode 100644 index 0000000..c2b1253 --- /dev/null +++ b/src/pages/taskmanager/components/task-detail/users/constants.ts @@ -0,0 +1,6 @@ +import type { TaskUser } from "./types"; + +export const DEFAULT_USERS: TaskUser[] = [ + { id: "1", name: "فروغ براتی" }, + { id: "2", name: "پارسا مهرزاد" }, +]; diff --git a/src/pages/taskmanager/components/task-detail/users/types.ts b/src/pages/taskmanager/components/task-detail/users/types.ts new file mode 100644 index 0000000..507fda6 --- /dev/null +++ b/src/pages/taskmanager/components/task-detail/users/types.ts @@ -0,0 +1,5 @@ +export type TaskUser = { + id: string; + name: string; + avatar?: string; +};