From 490e032ac649c3fb5cb7edfa4be58e82137b93e5 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Wed, 1 Nov 2023 15:24:11 +0530 Subject: [PATCH] style: new avatar and avatar group components (#2584) * style: new avatar components * chore: bug fixes * chore: add pixel to size * chore: add comments to helper functions * fix: build errors --- packages/ui/package.json | 6 +- packages/ui/src/avatar/avatar-group.tsx | 85 ++++ packages/ui/src/avatar/avatar.tsx | 168 ++++++++ packages/ui/src/avatar/index.ts | 2 + .../ui/src/button/{index.tsx => index.ts} | 0 packages/ui/src/{index.tsx => index.ts} | 9 +- .../issue/change-issue-assignee.tsx | 6 +- web/components/core/filters/filters-list.tsx | 12 +- web/components/core/filters/index.ts | 1 - .../core/filters/workspace-filters-list.tsx | 347 ---------------- .../core/sidebar/sidebar-progress-stats.tsx | 15 +- .../cycles/active-cycle-details.tsx | 26 +- web/components/cycles/active-cycle-stats.tsx | 35 +- web/components/cycles/cycles-board-card.tsx | 12 +- web/components/cycles/cycles-list-item.tsx | 9 +- web/components/cycles/cycles-view.tsx | 12 +- web/components/cycles/index.ts | 2 - web/components/cycles/sidebar.tsx | 76 ++-- web/components/cycles/single-cycle-card.tsx | 389 ------------------ web/components/cycles/single-cycle-list.tsx | 369 ----------------- .../integration/github/single-user-select.tsx | 7 +- .../integration/jira/import-users.tsx | 11 +- .../filters/applied-filters/members.tsx | 8 +- .../filters/header/filters/assignee.tsx | 6 +- .../filters/header/filters/created-by.tsx | 6 +- .../issue-layouts/kanban/headers/assignee.tsx | 5 +- .../issue-layouts/kanban/properties.tsx | 23 +- .../issues/issue-layouts/list/default.tsx | 6 +- .../issue-layouts/list/headers/assignee.tsx | 11 +- .../issue-layouts/list/headers/created-by.tsx | 4 +- .../issues/issue-layouts/list/properties.tsx | 1 + .../issue-layouts/properties/assignee.tsx | 25 +- .../roots/project-layout-root.tsx | 2 - web/components/issues/select/assignee.tsx | 17 +- .../issues/sidebar-select/assignee.tsx | 18 +- web/components/modules/module-card-item.tsx | 9 +- web/components/modules/module-list-item.tsx | 9 +- web/components/modules/select/lead.tsx | 11 +- web/components/modules/select/members.tsx | 16 +- .../modules/sidebar-select/select-lead.tsx | 7 +- .../modules/sidebar-select/select-members.tsx | 20 +- web/components/project/card.tsx | 19 +- web/components/project/member-select.tsx | 15 +- web/components/project/members-select.tsx | 18 +- .../project/send-project-invitation-modal.tsx | 58 +-- web/components/ui/avatar.tsx | 148 ------- web/components/ui/index.ts | 1 - web/components/views/index.ts | 1 - web/components/views/select-filters.tsx | 258 ------------ web/components/workspace/member-select.tsx | 45 +- web/components/workspace/sidebar-dropdown.tsx | 10 +- web/constants/issue.ts | 2 +- 52 files changed, 554 insertions(+), 1824 deletions(-) create mode 100644 packages/ui/src/avatar/avatar-group.tsx create mode 100644 packages/ui/src/avatar/avatar.tsx create mode 100644 packages/ui/src/avatar/index.ts rename packages/ui/src/button/{index.tsx => index.ts} (100%) rename packages/ui/src/{index.tsx => index.ts} (90%) delete mode 100644 web/components/core/filters/workspace-filters-list.tsx delete mode 100644 web/components/cycles/single-cycle-card.tsx delete mode 100644 web/components/cycles/single-cycle-list.tsx delete mode 100644 web/components/ui/avatar.tsx delete mode 100644 web/components/views/select-filters.tsx diff --git a/packages/ui/package.json b/packages/ui/package.json index 3a89a5c71..f76bd8374 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -10,13 +10,13 @@ "dist/**" ], "scripts": { - "build": "tsup src/index.tsx --format esm,cjs --dts --external react", - "dev": "tsup src/index.tsx --format esm,cjs --watch --dts --external react", + "build": "tsup src/index.ts --format esm,cjs --dts --external react", + "dev": "tsup src/index.ts --format esm,cjs --watch --dts --external react", "lint": "eslint src/", "clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist" }, "devDependencies": { - "@types/react-color" : "^3.0.9", + "@types/react-color": "^3.0.9", "@types/node": "^20.5.2", "@types/react": "18.2.0", "@types/react-dom": "18.2.0", diff --git a/packages/ui/src/avatar/avatar-group.tsx b/packages/ui/src/avatar/avatar-group.tsx new file mode 100644 index 000000000..4abb4a93b --- /dev/null +++ b/packages/ui/src/avatar/avatar-group.tsx @@ -0,0 +1,85 @@ +import React from "react"; +// ui +import { Tooltip } from "../tooltip"; +// types +import { TAvatarSize, getSizeInfo, isAValidNumber } from "./avatar"; + +type Props = { + /** + * The children of the avatar group. + * These should ideally should be `Avatar` components + */ + children: React.ReactNode; + /** + * The maximum number of avatars to display. + * If the number of children exceeds this value, the additional avatars will be replaced by a count of the remaining avatars. + * @default 2 + */ + max?: number; + /** + * Whether to show the tooltip or not + * @default true + */ + showTooltip?: boolean; + /** + * The size of the avatars + * Possible values: "sm", "md", "base", "lg" + * @default "md" + */ + size?: TAvatarSize; +}; + +export const AvatarGroup: React.FC = (props) => { + const { children, max = 2, showTooltip = true, size = "md" } = props; + + // calculate total length of avatars inside the group + const totalAvatars = React.Children.toArray(children).length; + + // slice the children to the maximum number of avatars + const avatars = React.Children.toArray(children).slice(0, max); + + // assign the necessary props from the AvatarGroup component to the Avatar components + const avatarsWithUpdatedProps = avatars.map((avatar) => { + const updatedProps: Partial = { + showTooltip, + size, + }; + + return React.cloneElement(avatar as React.ReactElement, updatedProps); + }); + + // get size details based on the size prop + const sizeInfo = getSizeInfo(size); + + return ( +
+ {avatarsWithUpdatedProps.map((avatar, index) => ( +
+ {avatar} +
+ ))} + {max < totalAvatars && ( + +
+ +{totalAvatars - max} +
+
+ )} +
+ ); +}; diff --git a/packages/ui/src/avatar/avatar.tsx b/packages/ui/src/avatar/avatar.tsx new file mode 100644 index 000000000..2c6b5b9a3 --- /dev/null +++ b/packages/ui/src/avatar/avatar.tsx @@ -0,0 +1,168 @@ +import React from "react"; +// ui +import { Tooltip } from "../tooltip"; + +export type TAvatarSize = "sm" | "md" | "base" | "lg" | number; + +type Props = { + /** + * The name of the avatar which will be displayed on the tooltip + */ + name?: string; + /** + * The background color if the avatar image fails to load + */ + fallbackBackgroundColor?: string; + /** + * The text to display if the avatar image fails to load + */ + fallbackText?: string; + /** + * The text color if the avatar image fails to load + */ + fallbackTextColor?: string; + /** + * Whether to show the tooltip or not + * @default true + */ + showTooltip?: boolean; + /** + * The size of the avatars + * Possible values: "sm", "md", "base", "lg" + * @default "md" + */ + size?: TAvatarSize; + /** + * The shape of the avatar + * Possible values: "circle", "square" + * @default "circle" + */ + shape?: "circle" | "square"; + /** + * The source of the avatar image + */ + src?: string; +}; + +/** + * Get the size details based on the size prop + * @param size The size of the avatar + * @returns The size details + */ +export const getSizeInfo = (size: TAvatarSize) => { + switch (size) { + case "sm": + return { + avatarSize: "h-4 w-4", + fontSize: "text-xs", + spacing: "-space-x-1", + }; + case "md": + return { + avatarSize: "h-5 w-5", + fontSize: "text-xs", + spacing: "-space-x-1", + }; + case "base": + return { + avatarSize: "h-6 w-6", + fontSize: "text-sm", + spacing: "-space-x-1.5", + }; + case "lg": + return { + avatarSize: "h-7 w-7", + fontSize: "text-sm", + spacing: "-space-x-1.5", + }; + default: + return { + avatarSize: "h-5 w-5", + fontSize: "text-xs", + spacing: "-space-x-1", + }; + } +}; + +/** + * Get the border radius based on the shape prop + * @param shape The shape of the avatar + * @returns The border radius + */ +export const getBorderRadius = (shape: "circle" | "square") => { + switch (shape) { + case "circle": + return "rounded-full"; + case "square": + return "rounded-md"; + default: + return "rounded-full"; + } +}; + +/** + * Check if the value is a valid number + * @param value The value to check + * @returns Whether the value is a valid number or not + */ +export const isAValidNumber = (value: any) => { + return typeof value === "number" && !isNaN(value); +}; + +export const Avatar: React.FC = (props) => { + const { + name, + fallbackBackgroundColor, + fallbackText, + fallbackTextColor, + showTooltip = true, + size = "md", + shape = "circle", + src, + } = props; + + // get size details based on the size prop + const sizeInfo = getSizeInfo(size); + + return ( + +
+ {src ? ( + {name} + ) : ( +
+ {name ? name[0].toUpperCase() : fallbackText ?? "?"} +
+ )} +
+
+ ); +}; diff --git a/packages/ui/src/avatar/index.ts b/packages/ui/src/avatar/index.ts new file mode 100644 index 000000000..3ccfbeca0 --- /dev/null +++ b/packages/ui/src/avatar/index.ts @@ -0,0 +1,2 @@ +export * from "./avatar-group"; +export * from "./avatar"; diff --git a/packages/ui/src/button/index.tsx b/packages/ui/src/button/index.ts similarity index 100% rename from packages/ui/src/button/index.tsx rename to packages/ui/src/button/index.ts diff --git a/packages/ui/src/index.tsx b/packages/ui/src/index.ts similarity index 90% rename from packages/ui/src/index.tsx rename to packages/ui/src/index.ts index 1cd193a65..1d75c9271 100644 --- a/packages/ui/src/index.tsx +++ b/packages/ui/src/index.ts @@ -1,9 +1,10 @@ +export * from "./avatar"; +export * from "./breadcrumbs"; export * from "./button"; +export * from "./dropdowns"; export * from "./form-fields"; +export * from "./icons"; export * from "./progress"; export * from "./spinners"; -export * from "./loader"; export * from "./tooltip"; -export * from "./icons"; -export * from "./breadcrumbs"; -export * from "./dropdowns"; +export * from "./loader"; diff --git a/web/components/command-palette/issue/change-issue-assignee.tsx b/web/components/command-palette/issue/change-issue-assignee.tsx index 606acb536..9a833278c 100644 --- a/web/components/command-palette/issue/change-issue-assignee.tsx +++ b/web/components/command-palette/issue/change-issue-assignee.tsx @@ -9,7 +9,7 @@ import useProjectMembers from "hooks/use-project-members"; // constants import { ISSUE_DETAILS, PROJECT_ISSUES_ACTIVITY } from "constants/fetch-keys"; // ui -import { Avatar } from "components/ui"; +import { Avatar } from "@plane/ui"; // icons import { Check } from "lucide-react"; // types @@ -31,13 +31,13 @@ export const ChangeIssueAssignee: FC = ({ setIsPaletteOpen, issue, user } const { members } = useProjectMembers(workspaceSlug as string, projectId as string); const options = - members?.map(({ member }: any) => ({ + members?.map(({ member }) => ({ value: member.id, query: member.display_name, content: ( <>
- + {member.display_name}
{issue.assignees.includes(member.id) && ( diff --git a/web/components/core/filters/filters-list.tsx b/web/components/core/filters/filters-list.tsx index f92958504..4bc9daee6 100644 --- a/web/components/core/filters/filters-list.tsx +++ b/web/components/core/filters/filters-list.tsx @@ -1,18 +1,14 @@ import React from "react"; - -// icons -import { PriorityIcon, StateGroupIcon } from "@plane/ui"; +import { X } from "lucide-react"; // ui -import { Avatar } from "components/ui"; +import { Avatar, PriorityIcon, StateGroupIcon } from "@plane/ui"; // helpers import { replaceUnderscoreIfSnakeCase } from "helpers/string.helper"; -// helpers import { renderShortDateWithYearFormat } from "helpers/date-time.helper"; // types import { IIssueFilterOptions, IIssueLabels, IState, IUserLite, TStateGroups } from "types"; // constants import { STATE_GROUP_COLORS } from "constants/state"; -import { X } from "lucide-react"; type Props = { filters: Partial; @@ -149,7 +145,7 @@ export const FiltersList: React.FC = ({ filters, setFilters, clearAllFilt key={memberId} className="inline-flex items-center gap-x-1 rounded-full bg-custom-background-90 px-1" > - + {member?.display_name} = ({ filters, setFilters, clearAllFilt key={`${memberId}-${key}`} className="inline-flex items-center gap-x-1 rounded-full bg-custom-background-90 px-1 capitalize" > - + {member?.display_name} ; - setFilters: (updatedFilter: Partial) => void; - clearAllFilters: (...args: any) => void; - labels: IIssueLabels[] | undefined; - members: IUserLite[] | undefined; - stateGroup: string[] | undefined; - project?: IProject[] | undefined; -}; - -export const WorkspaceFiltersList: FC = (props) => { - const { filters, setFilters, clearAllFilters, labels, members, project } = props; - - if (!filters) return <>; - - const nullFilters = Object.keys(filters).filter((key) => filters[key as keyof IWorkspaceIssueFilterOptions] === null); - - return ( -
- {Object.keys(filters).map((filterKey) => { - const key = filterKey as keyof typeof filters; - - if (filters[key] === null || (filters[key]?.length ?? 0) <= 0) return null; - - return ( -
- - {key === "target_date" ? "Due Date" : replaceUnderscoreIfSnakeCase(key)}: - - {filters[key] === null || (filters[key]?.length ?? 0) <= 0 ? ( - None - ) : Array.isArray(filters[key]) ? ( -
-
- {key === "state_group" - ? filters.state_group?.map((stateGroup) => { - const group = stateGroup as TStateGroups; - - return ( -

- - - - {group} - - setFilters({ - state_group: filters.state_group?.filter((g) => g !== group), - }) - } - > - - -

- ); - }) - : key === "priority" - ? filters.priority?.map((priority: any) => ( -

- - - - {priority === "null" ? "None" : priority} - - setFilters({ - priority: filters.priority?.filter((p: any) => p !== priority), - }) - } - > - - -

- )) - : key === "assignees" - ? filters.assignees?.map((memberId: string) => { - const member = members?.find((m) => m.id === memberId); - return ( -
- - {member?.display_name} - - setFilters({ - assignees: filters.assignees?.filter((p: any) => p !== memberId), - }) - } - > - - -
- ); - }) - : key === "subscriber" - ? filters.subscriber?.map((memberId: string) => { - const member = members?.find((m) => m.id === memberId); - - return ( -
- - {member?.display_name} - - setFilters({ - assignees: filters.assignees?.filter((p: any) => p !== memberId), - }) - } - > - - -
- ); - }) - : key === "created_by" - ? filters.created_by?.map((memberId: string) => { - const member = members?.find((m) => m.id === memberId); - - return ( -
- - {member?.display_name} - - setFilters({ - created_by: filters.created_by?.filter((p: any) => p !== memberId), - }) - } - > - - -
- ); - }) - : key === "labels" - ? filters.labels?.map((labelId: string) => { - const label = labels?.find((l) => l.id === labelId); - - if (!label) return null; - const color = label.color !== "" ? label.color : "#0f172a"; - return ( -
-
- {label.name} - - setFilters({ - labels: filters.labels?.filter((l: any) => l !== labelId), - }) - } - > - - -
- ); - }) - : key === "start_date" - ? filters.start_date?.map((date: string) => { - if (filters.start_date && filters.start_date.length <= 0) return null; - - const splitDate = date.split(";"); - - return ( -
-
- - {splitDate[1]} {renderShortDateWithYearFormat(splitDate[0])} - - - setFilters({ - start_date: filters.start_date?.filter((d: any) => d !== date), - }) - } - > - - -
- ); - }) - : key === "target_date" - ? filters.target_date?.map((date: string) => { - if (filters.target_date && filters.target_date.length <= 0) return null; - - const splitDate = date.split(";"); - - return ( -
-
- - {splitDate[1]} {renderShortDateWithYearFormat(splitDate[0])} - - - setFilters({ - target_date: filters.target_date?.filter((d: any) => d !== date), - }) - } - > - - -
- ); - }) - : key === "project" - ? filters.project?.map((projectId) => { - const currentProject = project?.find((p) => p.id === projectId); - return ( -

- {currentProject?.name} - - setFilters({ - project: filters.project?.filter((p) => p !== projectId), - }) - } - > - - -

- ); - }) - : (filters[key] as any)?.join(", ")} - -
-
- ) : ( -
- {filters[key as keyof typeof filters]} - -
- )} -
- ); - })} - {Object.keys(filters).length > 0 && nullFilters.length !== Object.keys(filters).length && ( - - )} -
- ); -}; diff --git a/web/components/core/sidebar/sidebar-progress-stats.tsx b/web/components/core/sidebar/sidebar-progress-stats.tsx index 706448492..268802fec 100644 --- a/web/components/core/sidebar/sidebar-progress-stats.tsx +++ b/web/components/core/sidebar/sidebar-progress-stats.tsx @@ -10,10 +10,9 @@ import useIssuesView from "hooks/use-issues-view"; import emptyLabel from "public/empty-state/empty_label.svg"; import emptyMembers from "public/empty-state/empty_members.svg"; // components -import { StateGroupIcon } from "@plane/ui"; import { SingleProgressStats } from "components/core"; // ui -import { Avatar } from "components/ui"; +import { Avatar, StateGroupIcon } from "@plane/ui"; // types import { IModule, @@ -138,17 +137,7 @@ export const SidebarProgressStats: React.FC = ({ key={assignee.assignee_id} title={
- + {assignee.display_name}
} diff --git a/web/components/cycles/active-cycle-details.tsx b/web/components/cycles/active-cycle-details.tsx index 1ececf4dc..e93b55650 100644 --- a/web/components/cycles/active-cycle-details.tsx +++ b/web/components/cycles/active-cycle-details.tsx @@ -1,16 +1,14 @@ import { MouseEvent } from "react"; import Link from "next/link"; import { useRouter } from "next/router"; -import useSWR, { mutate } from "swr"; -// services -import { CycleService } from "services/cycle.service"; +import useSWR from "swr"; // hooks import useToast from "hooks/use-toast"; import { useMobxStore } from "lib/mobx/store-provider"; // ui -import { AssigneesList } from "components/ui/avatar"; import { SingleProgressStats } from "components/core"; import { + AvatarGroup, Loader, Tooltip, LinearProgressIndicator, @@ -19,6 +17,7 @@ import { LayersIcon, StateGroupIcon, PriorityIcon, + Avatar, } from "@plane/ui"; // components import ProgressChart from "components/core/sidebar/progress-chart"; @@ -31,9 +30,7 @@ import { AlarmClock, AlertTriangle, ArrowRight, CalendarDays, Star, Target } fro import { getDateRangeStatus, renderShortDateWithYearFormat, findHowManyDaysLeft } from "helpers/date-time.helper"; import { truncateText } from "helpers/string.helper"; // types -import { ICycle, IIssue } from "types"; -// fetch-keys -import { CURRENT_CYCLE_LIST, CYCLES_LIST, CYCLE_ISSUES_WITH_PARAMS } from "constants/fetch-keys"; +import { ICycle } from "types"; const stateGroups = [ { @@ -69,9 +66,6 @@ interface IActiveCycleDetails { } export const ActiveCycleDetails: React.FC = (props) => { - // services - const cycleService = new CycleService(); - const router = useRouter(); const { workspaceSlug, projectId } = props; @@ -306,7 +300,11 @@ export const ActiveCycleDetails: React.FC = (props) => { {cycle.assignees.length > 0 && (
- + + {cycle.assignees.map((assignee) => ( + + ))} +
)}
@@ -406,7 +404,11 @@ export const ActiveCycleDetails: React.FC = (props) => {
{issue.assignees && issue.assignees.length > 0 && Array.isArray(issue.assignees) ? (
- + + {issue.assignee_details.map((assignee: any) => ( + + ))} +
) : ( "" diff --git a/web/components/cycles/active-cycle-stats.tsx b/web/components/cycles/active-cycle-stats.tsx index e26b44803..ad54b1ae2 100644 --- a/web/components/cycles/active-cycle-stats.tsx +++ b/web/components/cycles/active-cycle-stats.tsx @@ -1,16 +1,14 @@ import React, { Fragment } from "react"; - -// headless ui import { Tab } from "@headlessui/react"; // hooks import useLocalStorage from "hooks/use-local-storage"; // components import { SingleProgressStats } from "components/core"; // ui -import { Avatar } from "components/ui"; +import { Avatar } from "@plane/ui"; // types import { ICycle } from "types"; -// types + type Props = { cycle: ICycle; }; @@ -71,10 +69,7 @@ export const ActiveCycleProgressStats: React.FC = ({ cycle }) => { {cycle.total_issues > 0 ? ( - + {cycle.distribution.assignees.map((assignee, index) => { if (assignee.assignee_id) return ( @@ -82,15 +77,8 @@ export const ActiveCycleProgressStats: React.FC = ({ cycle }) => { key={assignee.assignee_id} title={
- + + {assignee.display_name}
} @@ -105,13 +93,7 @@ export const ActiveCycleProgressStats: React.FC = ({ cycle }) => { title={
- User + User
No assignee
@@ -122,10 +104,7 @@ export const ActiveCycleProgressStats: React.FC = ({ cycle }) => { ); })}
- + {cycle.distribution.labels.map((label, index) => ( = (props) => { {cycle.assignees.length > 0 && (
- + + {cycle.assignees.map((assignee) => ( + + ))} +
)} diff --git a/web/components/cycles/cycles-list-item.tsx b/web/components/cycles/cycles-list-item.tsx index 96efceddb..3f284e455 100644 --- a/web/components/cycles/cycles-list-item.tsx +++ b/web/components/cycles/cycles-list-item.tsx @@ -8,9 +8,8 @@ import { useMobxStore } from "lib/mobx/store-provider"; import useToast from "hooks/use-toast"; // components import { CycleCreateUpdateModal, CycleDeleteModal } from "components/cycles"; -import { AssigneesList } from "components/ui"; // ui -import { CustomMenu, Tooltip, CircularProgressIndicator, CycleGroupIcon } from "@plane/ui"; +import { CustomMenu, Tooltip, CircularProgressIndicator, CycleGroupIcon, AvatarGroup, Avatar } from "@plane/ui"; // icons import { Check, Info, LinkIcon, Pencil, Star, Trash2, User2 } from "lucide-react"; // helpers @@ -207,7 +206,11 @@ export const CyclesListItem: FC = (props) => {
{cycle.assignees.length > 0 ? ( - + + {cycle.assignees.map((assignee) => ( + + ))} + ) : ( diff --git a/web/components/cycles/cycles-view.tsx b/web/components/cycles/cycles-view.tsx index f0640dec6..cacd23905 100644 --- a/web/components/cycles/cycles-view.tsx +++ b/web/components/cycles/cycles-view.tsx @@ -25,7 +25,7 @@ export const CyclesView: FC = observer((props) => { const { cycle: cycleStore } = useMobxStore(); // api call to fetch cycles list - const { isLoading } = useSWR( + useSWR( workspaceSlug && projectId && filter ? `CYCLES_LIST_${projectId}_${filter}` : null, workspaceSlug && projectId && filter ? () => cycleStore.fetchCycles(workspaceSlug, projectId, filter) : null ); @@ -36,10 +36,10 @@ export const CyclesView: FC = observer((props) => { <> {layout === "list" && ( <> - {!isLoading ? ( + {cyclesList ? ( ) : ( - + @@ -50,7 +50,7 @@ export const CyclesView: FC = observer((props) => { {layout === "board" && ( <> - {!isLoading ? ( + {cyclesList ? ( = observer((props) => { peekCycle={peekCycle} /> ) : ( - + @@ -70,7 +70,7 @@ export const CyclesView: FC = observer((props) => { {layout === "gantt" && ( <> - {!isLoading ? ( + {cyclesList ? ( ) : ( diff --git a/web/components/cycles/index.ts b/web/components/cycles/index.ts index 6bf801bbe..ea9568478 100644 --- a/web/components/cycles/index.ts +++ b/web/components/cycles/index.ts @@ -7,8 +7,6 @@ export * from "./form"; export * from "./modal"; export * from "./select"; export * from "./sidebar"; -export * from "./single-cycle-card"; -export * from "./single-cycle-list"; export * from "./transfer-issues-modal"; export * from "./transfer-issues"; export * from "./cycles-list"; diff --git a/web/components/cycles/sidebar.tsx b/web/components/cycles/sidebar.tsx index f56ddcf18..68ed29f16 100644 --- a/web/components/cycles/sidebar.tsx +++ b/web/components/cycles/sidebar.tsx @@ -15,8 +15,8 @@ import { SidebarProgressStats } from "components/core"; import ProgressChart from "components/core/sidebar/progress-chart"; import { CycleDeleteModal } from "components/cycles/delete-modal"; // ui -import { Avatar, CustomRangeDatePicker } from "components/ui"; -import { CustomMenu, Loader, LayersIcon } from "@plane/ui"; +import { CustomRangeDatePicker } from "components/ui"; +import { Avatar, CustomMenu, Loader, LayersIcon } from "@plane/ui"; // icons import { ChevronDown, LinkIcon, Trash2, UserCircle2, AlertCircle, ChevronRight, MoveRight } from "lucide-react"; // helpers @@ -137,7 +137,7 @@ export const CycleDetailsSidebar: React.FC = observer((props) => { }); if (isDateValidForExistingCycle) { - await submitChanges({ + submitChanges({ start_date: renderDateFormat(`${watch("start_date")}`), end_date: renderDateFormat(`${watch("end_date")}`), }); @@ -211,7 +211,7 @@ export const CycleDetailsSidebar: React.FC = observer((props) => { }); if (isDateValidForExistingCycle) { - await submitChanges({ + submitChanges({ start_date: renderDateFormat(`${watch("start_date")}`), end_date: renderDateFormat(`${watch("end_date")}`), }); @@ -349,41 +349,37 @@ export const CycleDetailsSidebar: React.FC = observer((props) => { )}
- {({}) => ( - <> - - {areYearsEqual ? renderShortDate(startDate, "_ _") : renderShortMonthDate(startDate, "_ _")} - + + {areYearsEqual ? renderShortDate(startDate, "_ _") : renderShortMonthDate(startDate, "_ _")} + - - - { - if (val) { - handleStartDateChange(val); - } - }} - startDate={watch("start_date") ? `${watch("start_date")}` : null} - endDate={watch("end_date") ? `${watch("end_date")}` : null} - maxDate={new Date(`${watch("end_date")}`)} - selectsStart - /> - - - - )} + + + { + if (val) { + handleStartDateChange(val); + } + }} + startDate={watch("start_date") ? `${watch("start_date")}` : null} + endDate={watch("end_date") ? `${watch("end_date")}` : null} + maxDate={new Date(`${watch("end_date")}`)} + selectsStart + /> + + @@ -441,7 +437,7 @@ export const CycleDetailsSidebar: React.FC = observer((props) => {
- + {cycleDetails.owned_by.display_name}
@@ -497,7 +493,7 @@ export const CycleDetailsSidebar: React.FC = observer((props) => {
{isStartValid && isEndValid ? ( -
+
diff --git a/web/components/cycles/single-cycle-card.tsx b/web/components/cycles/single-cycle-card.tsx deleted file mode 100644 index e3e4ff437..000000000 --- a/web/components/cycles/single-cycle-card.tsx +++ /dev/null @@ -1,389 +0,0 @@ -import React from "react"; - -import Link from "next/link"; -import { useRouter } from "next/router"; - -// headless ui -import { Disclosure, Transition } from "@headlessui/react"; -// hooks -import useToast from "hooks/use-toast"; -// components -import { SingleProgressStats } from "components/core"; -// ui -import { CustomMenu, Tooltip, LinearProgressIndicator, ContrastIcon, RunningIcon } from "@plane/ui"; -import { AssigneesList } from "components/ui/avatar"; -// icons -import { - AlarmClock, - AlertTriangle, - ArrowRight, - CalendarDays, - ChevronDown, - LinkIcon, - Pencil, - Star, - Target, - Trash2, -} from "lucide-react"; -// helpers -import { getDateRangeStatus, renderShortDateWithYearFormat, findHowManyDaysLeft } from "helpers/date-time.helper"; -import { copyTextToClipboard, truncateText } from "helpers/string.helper"; -// types -import { ICycle } from "types"; - -type TSingleStatProps = { - cycle: ICycle; - handleEditCycle: () => void; - handleDeleteCycle: () => void; - handleAddToFavorites: () => void; - handleRemoveFromFavorites: () => void; -}; - -const stateGroups = [ - { - key: "backlog_issues", - title: "Backlog", - color: "#dee2e6", - }, - { - key: "unstarted_issues", - title: "Unstarted", - color: "#26b5ce", - }, - { - key: "started_issues", - title: "Started", - color: "#f7ae59", - }, - { - key: "cancelled_issues", - title: "Cancelled", - color: "#d687ff", - }, - { - key: "completed_issues", - title: "Completed", - color: "#09a953", - }, -]; - -export const SingleCycleCard: React.FC = ({ - cycle, - handleEditCycle, - handleDeleteCycle, - handleAddToFavorites, - handleRemoveFromFavorites, -}) => { - const router = useRouter(); - const { workspaceSlug, projectId } = router.query; - - const { setToastAlert } = useToast(); - - const cycleStatus = getDateRangeStatus(cycle.start_date, cycle.end_date); - const isCompleted = cycleStatus === "completed"; - const endDate = new Date(cycle.end_date ?? ""); - const startDate = new Date(cycle.start_date ?? ""); - - const handleCopyText = () => { - const originURL = typeof window !== "undefined" && window.location.origin ? window.location.origin : ""; - - copyTextToClipboard(`${originURL}/${workspaceSlug}/projects/${projectId}/cycles/${cycle.id}`).then(() => { - setToastAlert({ - type: "success", - title: "Link Copied!", - message: "Cycle link copied to clipboard.", - }); - }); - }; - - const progressIndicatorData = stateGroups.map((group, index) => ({ - id: index, - name: group.title, - value: cycle.total_issues > 0 ? ((cycle[group.key as keyof ICycle] as number) / cycle.total_issues) * 100 : 0, - color: group.color, - })); - - const groupedIssues: any = { - backlog: cycle.backlog_issues, - unstarted: cycle.unstarted_issues, - started: cycle.started_issues, - completed: cycle.completed_issues, - cancelled: cycle.cancelled_issues, - }; - - return ( -
-
- - -
-
- - - - - -

{truncateText(cycle.name, 15)}

-
-
- - - {cycleStatus === "current" ? ( - - - {findHowManyDaysLeft(cycle.end_date ?? new Date())} Days Left - - ) : cycleStatus === "upcoming" ? ( - - - {findHowManyDaysLeft(cycle.start_date ?? new Date())} Days Left - - ) : cycleStatus === "completed" ? ( - - {cycle.total_issues - cycle.completed_issues > 0 && ( - - - - - - )}{" "} - Completed - - ) : ( - cycleStatus - )} - - {cycle.is_favorite ? ( - - ) : ( - - )} - -
-
- {cycleStatus !== "draft" && ( - <> -
- - {renderShortDateWithYearFormat(startDate)} -
- -
- - {renderShortDateWithYearFormat(endDate)} -
- - )} -
- -
-
-
-
Creator:
-
- {cycle.owned_by.avatar && cycle.owned_by.avatar !== "" ? ( - {cycle.owned_by.display_name} - ) : ( - - {cycle.owned_by.display_name.charAt(0)} - - )} - {cycle.owned_by.display_name} -
-
-
-
Members:
- {cycle.assignees.length > 0 ? ( -
- -
- ) : ( - "No members" - )} -
-
- -
- {!isCompleted && ( - - )} - - - {!isCompleted && ( - { - e.preventDefault(); - handleDeleteCycle(); - }} - > - - - Delete cycle - - - )} - { - e.preventDefault(); - handleCopyText(); - }} - > - - - Copy cycle link - - - -
-
-
-
- - -
- - {({ open }) => ( -
-
- Progress - - {Object.keys(groupedIssues).map((group, index) => ( - - - {group} -
- } - completed={groupedIssues[group]} - total={cycle.total_issues} - /> - ))} -
- } - position="bottom" - > -
- -
- - - - - -
- - -
-
-
- {stateGroups.map((group) => ( -
-
- -
{group.title}
-
-
- - {cycle[group.key as keyof ICycle] as number}{" "} - - -{" "} - {cycle.total_issues > 0 - ? `${Math.round( - ((cycle[group.key as keyof ICycle] as number) / cycle.total_issues) * 100 - )}%` - : "0%"} - - -
-
- ))} -
-
-
-
-
-
- )} - -
-
-
- ); -}; diff --git a/web/components/cycles/single-cycle-list.tsx b/web/components/cycles/single-cycle-list.tsx deleted file mode 100644 index 8e2ad9cf1..000000000 --- a/web/components/cycles/single-cycle-list.tsx +++ /dev/null @@ -1,369 +0,0 @@ -import React, { useEffect, useState } from "react"; - -import Link from "next/link"; -import { useRouter } from "next/router"; - -// hooks -import useToast from "hooks/use-toast"; -// ui -import { CustomMenu, Tooltip, LinearProgressIndicator, ContrastIcon, RunningIcon } from "@plane/ui"; -// icons -import { - AlarmClock, - AlertTriangle, - ArrowRight, - CalendarDays, - LinkIcon, - Pencil, - Star, - Target, - Trash2, -} from "lucide-react"; -// helpers -import { getDateRangeStatus, renderShortDateWithYearFormat, findHowManyDaysLeft } from "helpers/date-time.helper"; -import { copyTextToClipboard, truncateText } from "helpers/string.helper"; -// types -import { ICycle } from "types"; - -type TSingleStatProps = { - cycle: ICycle; - handleEditCycle: () => void; - handleDeleteCycle: () => void; - handleAddToFavorites: () => void; - handleRemoveFromFavorites: () => void; -}; - -const stateGroups = [ - { - key: "backlog_issues", - title: "Backlog", - color: "#dee2e6", - }, - { - key: "unstarted_issues", - title: "Unstarted", - color: "#26b5ce", - }, - { - key: "started_issues", - title: "Started", - color: "#f7ae59", - }, - { - key: "cancelled_issues", - title: "Cancelled", - color: "#d687ff", - }, - { - key: "completed_issues", - title: "Completed", - color: "#09a953", - }, -]; - -type progress = { - progress: number; -}; - -function RadialProgressBar({ progress }: progress) { - const [circumference, setCircumference] = useState(0); - - useEffect(() => { - const radius = 40; - const circumference = 2 * Math.PI * radius; - setCircumference(circumference); - }, []); - - const progressOffset = ((100 - progress) / 100) * circumference; - - return ( -
- - - - -
- ); -} - -export const SingleCycleList: React.FC = ({ - cycle, - handleEditCycle, - handleDeleteCycle, - handleAddToFavorites, - handleRemoveFromFavorites, -}) => { - const router = useRouter(); - const { workspaceSlug, projectId } = router.query; - - const { setToastAlert } = useToast(); - - const cycleStatus = getDateRangeStatus(cycle.start_date, cycle.end_date); - const isCompleted = cycleStatus === "completed"; - const endDate = new Date(cycle.end_date ?? ""); - const startDate = new Date(cycle.start_date ?? ""); - - const handleCopyText = () => { - const originURL = typeof window !== "undefined" && window.location.origin ? window.location.origin : ""; - - copyTextToClipboard(`${originURL}/${workspaceSlug}/projects/${projectId}/cycles/${cycle.id}`).then(() => { - setToastAlert({ - type: "success", - title: "Link Copied!", - message: "Cycle link copied to clipboard.", - }); - }); - }; - - const progressIndicatorData = stateGroups.map((group, index) => ({ - id: index, - name: group.title, - value: cycle.total_issues > 0 ? ((cycle[group.key as keyof ICycle] as number) / cycle.total_issues) * 100 : 0, - color: group.color, - })); - - const completedIssues = cycle.completed_issues + cycle.cancelled_issues; - - const percentage = cycle.total_issues > 0 ? (completedIssues / cycle.total_issues) * 100 : 0; - - return ( -
-
- - -
-
-
- -
- -

{truncateText(cycle.name, 60)}

-
-

{cycle.description}

-
-
-
- - {cycleStatus === "current" ? ( - - - {findHowManyDaysLeft(cycle.end_date ?? new Date())} days left - - ) : cycleStatus === "upcoming" ? ( - - - {findHowManyDaysLeft(cycle.start_date ?? new Date())} days left - - ) : cycleStatus === "completed" ? ( - - {cycle.total_issues - cycle.completed_issues > 0 && ( - - - - - - )}{" "} - Completed - - ) : ( - cycleStatus - )} - - - {cycleStatus !== "draft" && ( -
-
- - {renderShortDateWithYearFormat(startDate)} -
- -
- - {renderShortDateWithYearFormat(endDate)} -
-
- )} - -
- {cycle.owned_by.avatar && cycle.owned_by.avatar !== "" ? ( - {cycle.owned_by.display_name} - ) : ( - - {cycle.owned_by.display_name.charAt(0)} - - )} -
- - Progress - -
- } - > - - {cycleStatus === "current" ? ( - - {cycle.total_issues > 0 ? ( - <> - - {Math.floor((cycle.completed_issues / cycle.total_issues) * 100)} % - - ) : ( - No issues present - )} - - ) : cycleStatus === "upcoming" ? ( - - Yet to start - - ) : cycleStatus === "completed" ? ( - - - {Math.round(percentage)} % - - ) : ( - - - {cycleStatus} - - )} - - - {cycle.is_favorite ? ( - - ) : ( - - )} -
- - {!isCompleted && ( - { - e.preventDefault(); - handleEditCycle(); - }} - > - - - Edit Cycle - - - )} - {!isCompleted && ( - { - e.preventDefault(); - handleDeleteCycle(); - }} - > - - - Delete cycle - - - )} - { - e.preventDefault(); - handleCopyText(); - }} - > - - - Copy cycle link - - - -
-
-
-
- - -
-
- ); -}; diff --git a/web/components/integration/github/single-user-select.tsx b/web/components/integration/github/single-user-select.tsx index 40e743aa8..0bba7b85d 100644 --- a/web/components/integration/github/single-user-select.tsx +++ b/web/components/integration/github/single-user-select.tsx @@ -1,12 +1,9 @@ import { useRouter } from "next/router"; - import useSWR from "swr"; - // services import { WorkspaceService } from "services/workspace.service"; // ui -import { Avatar } from "components/ui"; -import { CustomSelect, CustomSearchSelect, Input } from "@plane/ui"; +import { Avatar, CustomSelect, CustomSearchSelect, Input } from "@plane/ui"; // types import { IGithubRepoCollaborator } from "types"; import { IUserDetails } from "./root"; @@ -52,7 +49,7 @@ export const SingleUserSelect: React.FC = ({ collaborator, index, users, query: member.member.display_name ?? "", content: (
- + {member.member.display_name}
), diff --git a/web/components/integration/jira/import-users.tsx b/web/components/integration/jira/import-users.tsx index 48f9231c2..93e0e0ec0 100644 --- a/web/components/integration/jira/import-users.tsx +++ b/web/components/integration/jira/import-users.tsx @@ -2,15 +2,14 @@ import { FC } from "react"; import { useRouter } from "next/router"; import useSWR from "swr"; import { useFormContext, useFieldArray, Controller } from "react-hook-form"; -// fetch keys -import { WORKSPACE_MEMBERS } from "constants/fetch-keys"; // services import { WorkspaceService } from "services/workspace.service"; -// components -import { Avatar } from "components/ui"; -import { CustomSelect, CustomSearchSelect, Input, ToggleSwitch } from "@plane/ui"; +// ui +import { Avatar, CustomSelect, CustomSearchSelect, Input, ToggleSwitch } from "@plane/ui"; // types import { IJiraImporterForm } from "types"; +// fetch keys +import { WORKSPACE_MEMBERS } from "constants/fetch-keys"; const workspaceService = new WorkspaceService(); @@ -39,7 +38,7 @@ export const JiraImportUsers: FC = () => { query: member.member.display_name ?? "", content: (
- + {member.member.display_name}
), diff --git a/web/components/issues/issue-layouts/filters/applied-filters/members.tsx b/web/components/issues/issue-layouts/filters/applied-filters/members.tsx index 0c6d0a6b8..cef65882c 100644 --- a/web/components/issues/issue-layouts/filters/applied-filters/members.tsx +++ b/web/components/issues/issue-layouts/filters/applied-filters/members.tsx @@ -1,9 +1,7 @@ import { observer } from "mobx-react-lite"; - -// ui -import { Avatar } from "components/ui"; -// icons import { X } from "lucide-react"; +// ui +import { Avatar } from "@plane/ui"; // types import { IUserLite } from "types"; @@ -25,7 +23,7 @@ export const AppliedMembersFilters: React.FC = observer((props) => { return (
- + {memberDetails.display_name}
)} + + {/* assignee */} + {displayProperties && displayProperties?.assignee && ( + + )}
); }); diff --git a/web/components/issues/issue-layouts/list/default.tsx b/web/components/issues/issue-layouts/list/default.tsx index 83e4a11e0..3c3f90cf8 100644 --- a/web/components/issues/issue-layouts/list/default.tsx +++ b/web/components/issues/issue-layouts/list/default.tsx @@ -39,7 +39,7 @@ const GroupByList: React.FC = observer((props) => { list.length > 0 && list.map((_list: any) => (
-
+
= observer((props) => { issues={issues} group_by={group_by} list={members} - listKey={`member.id`} + listKey={`id`} handleIssues={handleIssues} quickActions={quickActions} display_properties={display_properties} @@ -206,7 +206,7 @@ export const List: React.FC = observer((props) => { issues={issues} group_by={group_by} list={members} - listKey={`member.id`} + listKey={`id`} handleIssues={handleIssues} quickActions={quickActions} display_properties={display_properties} diff --git a/web/components/issues/issue-layouts/list/headers/assignee.tsx b/web/components/issues/issue-layouts/list/headers/assignee.tsx index 5cfdc5b06..1225f96a9 100644 --- a/web/components/issues/issue-layouts/list/headers/assignee.tsx +++ b/web/components/issues/issue-layouts/list/headers/assignee.tsx @@ -2,7 +2,8 @@ import { FC } from "react"; import { observer } from "mobx-react-lite"; // components import { HeaderGroupByCard } from "./group-by-card"; -import { Avatar } from "components/ui"; +// ui +import { Avatar } from "@plane/ui"; export interface IAssigneesHeader { column_id: string; @@ -10,7 +11,7 @@ export interface IAssigneesHeader { issues_count: number; } -export const Icon = ({ user }: any) => ; +export const Icon = ({ user }: any) => ; export const AssigneesHeader: FC = observer((props) => { const { column_id, column_value, issues_count } = props; @@ -20,11 +21,7 @@ export const AssigneesHeader: FC = observer((props) => { return ( <> {assignee && ( - } - title={assignee?.member?.display_name || ""} - count={issues_count} - /> + } title={assignee?.display_name || ""} count={issues_count} /> )} ); diff --git a/web/components/issues/issue-layouts/list/headers/created-by.tsx b/web/components/issues/issue-layouts/list/headers/created-by.tsx index 308841c38..f7f7a4c4b 100644 --- a/web/components/issues/issue-layouts/list/headers/created-by.tsx +++ b/web/components/issues/issue-layouts/list/headers/created-by.tsx @@ -19,8 +19,8 @@ export const CreatedByHeader: FC = observer((props) => { <> {createdBy && ( } - title={createdBy?.member?.display_name || ""} + icon={} + title={createdBy?.display_name || ""} count={issues_count} /> )} diff --git a/web/components/issues/issue-layouts/list/properties.tsx b/web/components/issues/issue-layouts/list/properties.tsx index 7e624f807..5fe24d399 100644 --- a/web/components/issues/issue-layouts/list/properties.tsx +++ b/web/components/issues/issue-layouts/list/properties.tsx @@ -94,6 +94,7 @@ export const KanBanProperties: FC = observer((props) => { hideDropdownArrow onChange={handleAssignee} disabled={false} + multiple /> )} diff --git a/web/components/issues/issue-layouts/properties/assignee.tsx b/web/components/issues/issue-layouts/properties/assignee.tsx index 5054cc13d..5e89021ad 100644 --- a/web/components/issues/issue-layouts/properties/assignee.tsx +++ b/web/components/issues/issue-layouts/properties/assignee.tsx @@ -1,16 +1,13 @@ import { Fragment, useState } from "react"; import { observer } from "mobx-react-lite"; import { useMobxStore } from "lib/mobx/store-provider"; - import { usePopper } from "react-popper"; -// ui -import { AssigneesList, Avatar } from "components/ui"; import { Combobox } from "@headlessui/react"; -import { Tooltip } from "@plane/ui"; import { Check, ChevronDown, Search, User2 } from "lucide-react"; +// ui +import { Avatar, AvatarGroup, Tooltip } from "@plane/ui"; // types import { Placement } from "@popperjs/core"; -import { RootStore } from "store/root"; export interface IIssuePropertyAssignee { view?: "profile" | "workspace" | "project"; @@ -41,7 +38,7 @@ export const IssuePropertyAssignee: React.FC = observer( multiple = false, } = props; - const { workspace: workspaceStore, project: projectStore }: RootStore = useMobxStore(); + const { workspace: workspaceStore, project: projectStore } = useMobxStore(); const workspaceSlug = workspaceStore?.workspaceSlug; const [query, setQuery] = useState(""); @@ -49,17 +46,17 @@ export const IssuePropertyAssignee: React.FC = observer( const [referenceElement, setReferenceElement] = useState(null); const [popperElement, setPopperElement] = useState(null); - const projectMembers = projectId && projectStore?.members?.[projectId]; + const projectMembers = projectId ? projectStore?.members?.[projectId] : undefined; const fetchProjectMembers = () => workspaceSlug && projectId && projectStore.fetchProjectMembers(workspaceSlug, projectId); - const options = (projectMembers ? projectMembers : [])?.map((member) => ({ + const options = (projectMembers ?? [])?.map((member) => ({ value: member.member.id, query: member.member.display_name, content: (
- + {member.member.display_name}
), @@ -83,7 +80,15 @@ export const IssuePropertyAssignee: React.FC = observer( >
{value && value.length > 0 && Array.isArray(value) ? ( - + + {value.map((assigneeId) => { + const member = projectMembers?.find((m) => m.member.id === assigneeId)?.member; + + if (!member) return null; + + return ; + })} + ) : ( diff --git a/web/components/issues/select/assignee.tsx b/web/components/issues/select/assignee.tsx index f26d7ecaa..4d7ec27df 100644 --- a/web/components/issues/select/assignee.tsx +++ b/web/components/issues/select/assignee.tsx @@ -1,12 +1,9 @@ import { useRouter } from "next/router"; - import useSWR from "swr"; - // services import { ProjectService } from "services/project"; // ui -import { AssigneesList, Avatar } from "components/ui"; -import { CustomSearchSelect } from "@plane/ui"; +import { Avatar, AvatarGroup, CustomSearchSelect } from "@plane/ui"; import { User2 } from "lucide-react"; // fetch-keys import { PROJECT_MEMBERS } from "constants/fetch-keys"; @@ -35,7 +32,7 @@ export const IssueAssigneeSelect: React.FC = ({ projectId, value = [], on query: member.member.display_name ?? "", content: (
- + {member.member.is_bot ? member.member.first_name : member.member.display_name}
), @@ -50,7 +47,15 @@ export const IssueAssigneeSelect: React.FC = ({ projectId, value = [], on
{value && value.length > 0 && Array.isArray(value) ? (
- + + {value.map((assigneeId) => { + const member = members?.find((m) => m.member.id === assigneeId)?.member; + + if (!member) return null; + + return ; + })} +
) : (
diff --git a/web/components/issues/sidebar-select/assignee.tsx b/web/components/issues/sidebar-select/assignee.tsx index 09b1af7dd..4fdb71fa0 100644 --- a/web/components/issues/sidebar-select/assignee.tsx +++ b/web/components/issues/sidebar-select/assignee.tsx @@ -1,14 +1,10 @@ import React from "react"; - import { useRouter } from "next/router"; - import useSWR from "swr"; - // services import { ProjectService } from "services/project"; // ui -import { CustomSearchSelect } from "@plane/ui"; -import { AssigneesList, Avatar } from "components/ui/avatar"; +import { Avatar, AvatarGroup, CustomSearchSelect } from "@plane/ui"; // fetch-keys import { PROJECT_MEMBERS } from "constants/fetch-keys"; @@ -37,7 +33,7 @@ export const SidebarAssigneeSelect: React.FC = ({ value, onChange, disabl query: member.member.display_name, content: (
- + {member.member.display_name}
), @@ -50,7 +46,15 @@ export const SidebarAssigneeSelect: React.FC = ({ value, onChange, disabl <> {value && value.length > 0 && Array.isArray(value) ? (
- + + {value.map((assigneeId) => { + const member = members?.find((m) => m.member.id === assigneeId)?.member; + + if (!member) return null; + + return ; + })} + {value.length} Assignees
) : ( diff --git a/web/components/modules/module-card-item.tsx b/web/components/modules/module-card-item.tsx index b3625df6c..036a12ccc 100644 --- a/web/components/modules/module-card-item.tsx +++ b/web/components/modules/module-card-item.tsx @@ -9,8 +9,7 @@ import useToast from "hooks/use-toast"; // components import { CreateUpdateModuleModal, DeleteModuleModal } from "components/modules"; // ui -import { AssigneesList } from "components/ui"; -import { CustomMenu, LayersIcon, Tooltip } from "@plane/ui"; +import { Avatar, AvatarGroup, CustomMenu, LayersIcon, Tooltip } from "@plane/ui"; // icons import { Info, LinkIcon, Pencil, Star, Trash2 } from "lucide-react"; // helpers @@ -164,7 +163,11 @@ export const ModuleCardItem: React.FC = observer((props) => { {module.members_detail.length > 0 && (
- + + {module.members_detail.map((member) => ( + + ))} +
)} diff --git a/web/components/modules/module-list-item.tsx b/web/components/modules/module-list-item.tsx index 745d15c9d..7eed17288 100644 --- a/web/components/modules/module-list-item.tsx +++ b/web/components/modules/module-list-item.tsx @@ -9,8 +9,7 @@ import useToast from "hooks/use-toast"; // components import { CreateUpdateModuleModal, DeleteModuleModal } from "components/modules"; // ui -import { AssigneesList } from "components/ui"; -import { CircularProgressIndicator, CustomMenu, Tooltip } from "@plane/ui"; +import { Avatar, AvatarGroup, CircularProgressIndicator, CustomMenu, Tooltip } from "@plane/ui"; // icons import { Check, Info, LinkIcon, Pencil, Star, Trash2, User2 } from "lucide-react"; // helpers @@ -174,7 +173,11 @@ export const ModuleListItem: React.FC = observer((props) => {
{module.members_detail.length > 0 ? ( - + + {module.members_detail.map((member) => ( + + ))} + ) : ( diff --git a/web/components/modules/select/lead.tsx b/web/components/modules/select/lead.tsx index 86ced41a9..9e12e2017 100644 --- a/web/components/modules/select/lead.tsx +++ b/web/components/modules/select/lead.tsx @@ -4,8 +4,7 @@ import useSWR from "swr"; // services import { ProjectService } from "services/project"; // ui -import { Avatar } from "components/ui"; -import { CustomSearchSelect } from "@plane/ui"; +import { Avatar, CustomSearchSelect } from "@plane/ui"; // icons import { UserCircle } from "lucide-react"; // fetch-keys @@ -34,7 +33,7 @@ export const ModuleLeadSelect: React.FC = ({ value, onChange }) => { query: member.member.display_name, content: (
- + {member.member.display_name}
), @@ -48,7 +47,11 @@ export const ModuleLeadSelect: React.FC = ({ value, onChange }) => { value={value} label={
- {selectedOption ? : } + {selectedOption ? ( + + ) : ( + + )} {selectedOption ? selectedOption?.display_name : Lead}
} diff --git a/web/components/modules/select/members.tsx b/web/components/modules/select/members.tsx index 36aaa869b..94c74f05b 100644 --- a/web/components/modules/select/members.tsx +++ b/web/components/modules/select/members.tsx @@ -4,9 +4,7 @@ import useSWR from "swr"; // services import { ProjectService } from "services/project"; // ui -import { AssigneesList, Avatar } from "components/ui"; -// icons -import { CustomSearchSelect, UserGroupIcon } from "@plane/ui"; +import { Avatar, AvatarGroup, CustomSearchSelect, UserGroupIcon } from "@plane/ui"; // fetch-keys import { PROJECT_MEMBERS } from "constants/fetch-keys"; @@ -32,7 +30,7 @@ export const ModuleMembersSelect: React.FC = ({ value, onChange }) => { query: member.member.display_name, content: (
- + {member.member.display_name}
), @@ -45,7 +43,15 @@ export const ModuleMembersSelect: React.FC = ({ value, onChange }) => {
{value && value.length > 0 && Array.isArray(value) ? (
- + + {value.map((assigneeId) => { + const member = members?.find((m) => m.member.id === assigneeId)?.member; + + if (!member) return null; + + return ; + })} + {value.length} Assignees
) : ( diff --git a/web/components/modules/sidebar-select/select-lead.tsx b/web/components/modules/sidebar-select/select-lead.tsx index 271b1dfc9..5d7e5f419 100644 --- a/web/components/modules/sidebar-select/select-lead.tsx +++ b/web/components/modules/sidebar-select/select-lead.tsx @@ -4,8 +4,7 @@ import useSWR from "swr"; // services import { ProjectService } from "services/project"; // ui -import { Avatar } from "components/ui"; -import { CustomSearchSelect } from "@plane/ui"; +import { Avatar, CustomSearchSelect } from "@plane/ui"; // icons import { ChevronDown, UserCircle2 } from "lucide-react"; // fetch-keys @@ -36,7 +35,7 @@ export const SidebarLeadSelect: FC = (props) => { query: member.member.display_name, content: (
- + {member.member.display_name}
), @@ -58,7 +57,7 @@ export const SidebarLeadSelect: FC = (props) => { customButton={ selectedOption ? (
- + {selectedOption?.display_name}
) : ( diff --git a/web/components/modules/sidebar-select/select-members.tsx b/web/components/modules/sidebar-select/select-members.tsx index 35fbcd7a1..98a57692c 100644 --- a/web/components/modules/sidebar-select/select-members.tsx +++ b/web/components/modules/sidebar-select/select-members.tsx @@ -1,14 +1,10 @@ import React from "react"; - import { useRouter } from "next/router"; - import useSWR from "swr"; - // services import { ProjectService } from "services/project"; // ui -import { AssigneesList, Avatar } from "components/ui"; -import { CustomSearchSelect, UserGroupIcon } from "@plane/ui"; +import { Avatar, AvatarGroup, CustomSearchSelect, UserGroupIcon } from "@plane/ui"; // icons import { ChevronDown } from "lucide-react"; // fetch-keys @@ -38,7 +34,7 @@ export const SidebarMembersSelect: React.FC = ({ value, onChange }) => { query: member.member.display_name, content: (
- + {member.member.display_name}
), @@ -57,8 +53,16 @@ export const SidebarMembersSelect: React.FC = ({ value, onChange }) => { customButtonClassName="rounded-sm" customButton={ value && value.length > 0 && Array.isArray(value) ? ( -
- +
+ + {value.map((assigneeId) => { + const member = members?.find((m) => m.member.id === assigneeId)?.member; + + if (!member) return null; + + return ; + })} +
) : (
diff --git a/web/components/project/card.tsx b/web/components/project/card.tsx index 35f260d97..550a83383 100644 --- a/web/components/project/card.tsx +++ b/web/components/project/card.tsx @@ -8,16 +8,15 @@ import { RootStore } from "store/root"; import { LinkIcon, Lock, Pencil, Star } from "lucide-react"; // hooks import useToast from "hooks/use-toast"; +// components +import { DeleteProjectModal, JoinProjectModal } from "components/project"; // ui -import { Button, Tooltip } from "@plane/ui"; +import { Avatar, AvatarGroup, Button, Tooltip } from "@plane/ui"; // helpers import { copyTextToClipboard } from "helpers/string.helper"; import { renderEmoji } from "helpers/emoji.helper"; // types import type { IProject } from "types"; -// components -import { DeleteProjectModal, JoinProjectModal } from "components/project"; -import { AssigneesList } from "components/ui"; export type ProjectCardProps = { project: IProject; @@ -177,7 +176,17 @@ export const ProjectCard: React.FC = observer((props) => { > {projectMembersIds.length > 0 ? (
- + + {projectMembersIds.map((memberId) => { + const member = project.members?.find((m) => m.id === memberId); + + if (!member) return null; + + return ( + + ); + })} +
) : ( No Member Yet diff --git a/web/components/project/member-select.tsx b/web/components/project/member-select.tsx index 8885544fc..b7d011236 100644 --- a/web/components/project/member-select.tsx +++ b/web/components/project/member-select.tsx @@ -1,15 +1,12 @@ import React from "react"; import { useRouter } from "next/router"; -import useSWR from "swr"; - -// store import { observer } from "mobx-react-lite"; +import useSWR from "swr"; +import { Ban } from "lucide-react"; +// mobx store import { useMobxStore } from "lib/mobx/store-provider"; // ui -import { Avatar } from "components/ui"; -import { CustomSearchSelect } from "@plane/ui"; -// icon -import { Ban } from "lucide-react"; +import { Avatar, CustomSearchSelect } from "@plane/ui"; type Props = { value: any; @@ -41,7 +38,7 @@ export const MemberSelect: React.FC = observer((props) => { query: member.member.display_name, content: (
- + {member.member.display_name}
), @@ -54,7 +51,7 @@ export const MemberSelect: React.FC = observer((props) => { value={value} label={
- {selectedOption && } + {selectedOption && } {selectedOption ? ( selectedOption?.display_name ) : ( diff --git a/web/components/project/members-select.tsx b/web/components/project/members-select.tsx index 4d8819883..b08e2ce00 100644 --- a/web/components/project/members-select.tsx +++ b/web/components/project/members-select.tsx @@ -4,7 +4,9 @@ import { Placement } from "@popperjs/core"; import { Combobox } from "@headlessui/react"; import { Check, ChevronDown, Search, User2 } from "lucide-react"; // components -import { AssigneesList, Avatar, Tooltip } from "components/ui"; +import { Tooltip } from "components/ui"; +// ui +import { Avatar, AvatarGroup } from "@plane/ui"; // types import { IUserLite } from "types"; @@ -63,7 +65,7 @@ export const MembersSelect: React.FC = ({ query: member.display_name, content: (
- + {member.display_name}
), @@ -87,7 +89,15 @@ export const MembersSelect: React.FC = ({ >
{value && value.length > 0 && Array.isArray(value) ? ( - + + {value.map((assigneeId) => { + const member = members?.find((m) => m.id === assigneeId); + + if (!member) return null; + + return ; + })} + ) : ( diff --git a/web/components/project/send-project-invitation-modal.tsx b/web/components/project/send-project-invitation-modal.tsx index 5252f50cb..52512687a 100644 --- a/web/components/project/send-project-invitation-modal.tsx +++ b/web/components/project/send-project-invitation-modal.tsx @@ -3,11 +3,9 @@ import { useRouter } from "next/router"; import useSWR from "swr"; import { useForm, Controller, useFieldArray } from "react-hook-form"; import { Dialog, Transition } from "@headlessui/react"; -// ui -import { Button, CustomSelect, CustomSearchSelect } from "@plane/ui"; -import { Avatar } from "components/ui"; -//icons import { ChevronDown, Plus, X } from "lucide-react"; +// ui +import { Avatar, Button, CustomSelect, CustomSearchSelect } from "@plane/ui"; // services import { ProjectService } from "services/project"; import { WorkspaceService } from "services/workspace.service"; @@ -139,7 +137,7 @@ export const SendProjectInvitationModal: React.FC = (props) => { query: person.member.display_name, content: (
- + {person.member.display_name} ({person.member.first_name + " " + person.member.last_name})
), @@ -189,29 +187,33 @@ export const SendProjectInvitationModal: React.FC = (props) => { control={control} name={`members.${index}.member_id`} rules={{ required: "Please select a member" }} - render={({ field: { value, onChange } }) => ( - - {value && value !== "" ? ( -
- p.member.id === value)?.member} /> - {people?.find((p) => p.member.id === value)?.member.display_name} -
- ) : ( -
Select co-worker
- )} -