diff --git a/apps/app/components/icons/assignment-clipboard-icon.tsx b/apps/app/components/icons/assignment-clipboard-icon.tsx new file mode 100644 index 000000000..8b9af676e --- /dev/null +++ b/apps/app/components/icons/assignment-clipboard-icon.tsx @@ -0,0 +1,20 @@ +import React from "react"; + +import type { Props } from "./types"; + +export const AssignmentClipboardIcon: React.FC = ({ + width = "24", + height = "24", + className, +}) => ( + + + +); diff --git a/apps/app/components/icons/contrast-icon.tsx b/apps/app/components/icons/contrast-icon.tsx new file mode 100644 index 000000000..5dfa47e4d --- /dev/null +++ b/apps/app/components/icons/contrast-icon.tsx @@ -0,0 +1,25 @@ +import React from "react"; + +import type { Props } from "./types"; + +export const ContrastIcon: React.FC = ({ + width = "24", + height = "24", + color = "#858E96", + className, +}) => ( + + + + +); diff --git a/apps/app/components/icons/grid-view-icons.tsx b/apps/app/components/icons/grid-view-icons.tsx new file mode 100644 index 000000000..f5e81daf8 --- /dev/null +++ b/apps/app/components/icons/grid-view-icons.tsx @@ -0,0 +1,24 @@ +import React from "react"; + +import type { Props } from "./types"; + +export const GridViewIcon: React.FC = ({ + width = "24", + height = "24", + color = "#858E96", + className, +}) => ( + + + +); diff --git a/apps/app/components/icons/heartbeat-icon.tsx b/apps/app/components/icons/heartbeat-icon.tsx index 8c70fbb87..ff950ca0b 100644 --- a/apps/app/components/icons/heartbeat-icon.tsx +++ b/apps/app/components/icons/heartbeat-icon.tsx @@ -2,21 +2,26 @@ import React from "react"; import type { Props } from "./types"; -export const HeartbeatIcon: React.FC = ({ width = "24", height = "24", className }) => ( - - - - ); +export const HeartbeatIcon: React.FC = ({ + width = "24", + height = "24", + color = "#858E96", + className, +}) => ( + + + +); diff --git a/apps/app/components/icons/index.ts b/apps/app/components/icons/index.ts index e07e22c29..6ec881448 100644 --- a/apps/app/components/icons/index.ts +++ b/apps/app/components/icons/index.ts @@ -36,3 +36,8 @@ export * from "./upcoming-cycle-icon"; export * from "./user-group-icon"; export * from "./user-icon-circle"; export * from "./user-icon"; +export * from "./grid-view-icons"; +export * from "./assignment-clipboard-icon"; +export * from "./tick-mark-icon"; +export * from "./contrast-icon"; +export * from "./people-group-icon"; diff --git a/apps/app/components/icons/layer-diagonal-icon.tsx b/apps/app/components/icons/layer-diagonal-icon.tsx index a3eb7a815..aff7bc66c 100644 --- a/apps/app/components/icons/layer-diagonal-icon.tsx +++ b/apps/app/components/icons/layer-diagonal-icon.tsx @@ -6,19 +6,21 @@ export const LayerDiagonalIcon: React.FC = ({ width = "24", height = "24", className, - color = "black", + color = "#858E96", }) => ( - - - - ); + + + +); diff --git a/apps/app/components/icons/people-group-icon.tsx b/apps/app/components/icons/people-group-icon.tsx new file mode 100644 index 000000000..813511208 --- /dev/null +++ b/apps/app/components/icons/people-group-icon.tsx @@ -0,0 +1,24 @@ +import React from "react"; + +import type { Props } from "./types"; + +export const PeopleGroupIcon: React.FC = ({ + width = "24", + height = "16", + color = "#858E96", + className, +}) => ( + + + +); diff --git a/apps/app/components/icons/setting-icon.tsx b/apps/app/components/icons/setting-icon.tsx index 1c572f977..1451be9b9 100644 --- a/apps/app/components/icons/setting-icon.tsx +++ b/apps/app/components/icons/setting-icon.tsx @@ -2,18 +2,23 @@ import React from "react"; import type { Props } from "./types"; -export const SettingIcon: React.FC = ({ width = "24", height = "24", className }) => ( - - - - ); +export const SettingIcon: React.FC = ({ + width = "24", + height = "24", + color = "black", + className, +}) => ( + + + +); diff --git a/apps/app/components/icons/tick-mark-icon.tsx b/apps/app/components/icons/tick-mark-icon.tsx new file mode 100644 index 000000000..3d7c1618c --- /dev/null +++ b/apps/app/components/icons/tick-mark-icon.tsx @@ -0,0 +1,24 @@ +import React from "react"; + +import type { Props } from "./types"; + +export const TickMarkIcon: React.FC = ({ + width = "24", + height = "24", + color = "#858E96", + className, +}) => ( + + + +); diff --git a/apps/app/components/issues/form.tsx b/apps/app/components/issues/form.tsx index 11675bac8..d8dad77a2 100644 --- a/apps/app/components/issues/form.tsx +++ b/apps/app/components/issues/form.tsx @@ -14,8 +14,8 @@ import { IssuePrioritySelect, IssueProjectSelect, IssueStateSelect, + IssueDateSelect, } from "components/issues/select"; -import { CycleSelect as IssueCycleSelect } from "components/cycles/select"; import { CreateStateModal } from "components/states"; import { CreateUpdateCycleModal } from "components/cycles"; import { CreateLabelModal } from "components/labels"; @@ -266,16 +266,16 @@ export const IssueForm: FC = ({ /> ( - + )} /> ( - + )} /> = ({ control={control} name="target_date" render={({ field: { value, onChange } }) => ( - + )} /> - ( - - )} - /> = ({ > {({ open }: any) => ( <> - -
- {value && Array.isArray(value) ? : null} -
+ + `flex items-center text-xs cursor-pointer border rounded-md shadow-sm duration-200 + ${ + open + ? "outline-none border-theme bg-theme/5 ring-1 ring-theme " + : "hover:bg-theme/5 " + }` + } + > + {value && value.length > 0 && Array.isArray(value) ? ( + + + {value.length} Assignees + + ) : ( + + + Assignee + + )} - setQuery(event.target.value)} - placeholder="Search" - displayValue={(assigned: any) => assigned?.name} - /> -
+
+ + setQuery(event.target.value)} + placeholder="Search for a person..." + displayValue={(assigned: any) => assigned?.name} + /> +
+
{filteredOptions ? ( filteredOptions.length > 0 ? ( filteredOptions.map((option) => ( - `${active ? "bg-indigo-50" : ""} ${ - selected ? "bg-indigo-50 font-medium" : "" - } flex cursor-pointer select-none items-center gap-2 truncate px-2 py-1 text-gray-900` + className={({ active }) => + `${ + active ? "bg-gray-200" : "" + } group flex min-w-[14rem] cursor-pointer select-none items-center gap-2 truncate rounded px-1 py-1.5 text-gray-500` } value={option.value} > - {people && ( - <> - p.member.id === option.value)?.member} - /> - {option.display} - + {({ selected, active }) => ( +
+
+ p.member.id === option.value)?.member} + /> + {option.display} +
+
+ +
+
)}
)) diff --git a/apps/app/components/issues/select/date.tsx b/apps/app/components/issues/select/date.tsx new file mode 100644 index 000000000..0760b0214 --- /dev/null +++ b/apps/app/components/issues/select/date.tsx @@ -0,0 +1,70 @@ +import React from "react"; + +import { Popover, Transition } from "@headlessui/react"; +import { CalendarDaysIcon, XMarkIcon } from "@heroicons/react/24/outline"; +// react-datepicker +import DatePicker from "react-datepicker"; +// import "react-datepicker/dist/react-datepicker.css"; +import { renderDateFormat } from "helpers/date-time.helper"; + +type Props = { + value: string | null; + onChange: (val: string | null) => void; +}; + +export const IssueDateSelect: React.FC = ({ value, onChange }) => ( + + {({ open }) => ( + <> + + `flex items-center text-xs cursor-pointer border rounded-md shadow-sm duration-200 + ${ + open + ? "outline-none border-theme bg-theme/5 ring-1 ring-theme " + : "hover:bg-theme/5 " + }` + } + > + + {value ? ( + <> + {value} + + + ) : ( + <> + + Due Date + + )} + + + + + + { + if (!val) onChange(""); + else onChange(renderDateFormat(val)); + }} + dateFormat="dd-MM-yyyy" + inline + /> + + + + )} + +); diff --git a/apps/app/components/issues/select/index.ts b/apps/app/components/issues/select/index.ts index 4338b3162..a21a1cbbb 100644 --- a/apps/app/components/issues/select/index.ts +++ b/apps/app/components/issues/select/index.ts @@ -4,3 +4,4 @@ export * from "./parent"; export * from "./priority"; export * from "./project"; export * from "./state"; +export * from "./date"; diff --git a/apps/app/components/issues/select/label.tsx b/apps/app/components/issues/select/label.tsx index 2b810b30a..d762b2bc0 100644 --- a/apps/app/components/issues/select/label.tsx +++ b/apps/app/components/issues/select/label.tsx @@ -7,13 +7,20 @@ import useSWR from "swr"; // headless ui import { Combobox, Transition } from "@headlessui/react"; // icons -import { PlusIcon, RectangleGroupIcon, TagIcon } from "@heroicons/react/24/outline"; +import { + CheckIcon, + MagnifyingGlassIcon, + PlusIcon, + RectangleGroupIcon, + TagIcon, +} from "@heroicons/react/24/outline"; // services import issuesServices from "services/issues.service"; // types import type { IIssueLabels } from "types"; // fetch-keys import { PROJECT_ISSUE_LABELS } from "constants/fetch-keys"; +import { IssueLabelsList } from "components/ui"; type Props = { setIsOpen: React.Dispatch>; @@ -52,36 +59,57 @@ export const IssueLabelSelect: React.FC = ({ setIsOpen, value, onChange, > {({ open }: any) => ( <> - Labels + `flex items-center text-xs cursor-pointer border rounded-md shadow-sm duration-200 + ${ + open + ? "outline-none border-theme bg-theme/5 ring-1 ring-theme " + : "hover:bg-theme/5 " + }` + } > - - - {Array.isArray(value) - ? value.map((v) => issueLabels?.find((l) => l.id === v)?.name).join(", ") || - "Labels" - : issueLabels?.find((l) => l.id === value)?.name || "Labels"} - + {value && value.length > 0 ? ( + + issueLabels?.find((l) => l.id === v)?.color) ?? []} + length={3} + showLength + /> + {value.length} Labels + + ) : ( + + + Label + + )} - setQuery(event.target.value)} - placeholder="Search" - displayValue={(assigned: any) => assigned?.name} - /> -
+
+ + setQuery(event.target.value)} + placeholder="Search for label..." + displayValue={(assigned: any) => assigned?.name} + /> +
+
{issueLabels && filteredOptions ? ( filteredOptions.length > 0 ? ( filteredOptions.map((label) => { @@ -92,21 +120,36 @@ export const IssueLabelSelect: React.FC = ({ setIsOpen, value, onChange, return ( - `${active ? "bg-indigo-50" : ""} ${ - selected ? "bg-indigo-50 font-medium" : "" - } flex cursor-pointer select-none items-center gap-2 truncate p-2 text-gray-900` + className={({ active }) => + `${ + active ? "bg-gray-200" : "" + } group flex min-w-[14rem] cursor-pointer select-none items-center gap-2 truncate rounded px-1 py-1.5 text-gray-600` } value={label.id} > - - {label.name} + {({ selected }) => ( +
+
+ + {label.name} +
+
+ +
+
+ )}
); } else @@ -119,20 +162,33 @@ export const IssueLabelSelect: React.FC = ({ setIsOpen, value, onChange, {children.map((child) => ( - `${active ? "bg-indigo-50" : ""} ${ - selected ? "bg-indigo-50 font-medium" : "" - } flex cursor-pointer select-none items-center gap-2 truncate p-2 text-gray-900` + className={({ active }) => + `${ + active ? "bg-gray-200" : "" + } group flex min-w-[14rem] cursor-pointer select-none items-center gap-2 truncate rounded px-1 py-1.5 text-gray-600` } value={child.id} > - - {child.name} + {({ selected }) => ( +
+
+ + {child.name} +
+
+ +
+
+ )}
))}
@@ -147,11 +203,13 @@ export const IssueLabelSelect: React.FC = ({ setIsOpen, value, onChange, )}
diff --git a/apps/app/components/issues/select/priority.tsx b/apps/app/components/issues/select/priority.tsx index 1347e2765..4a3c5fdba 100644 --- a/apps/app/components/issues/select/priority.tsx +++ b/apps/app/components/issues/select/priority.tsx @@ -6,6 +6,7 @@ import { Listbox, Transition } from "@headlessui/react"; import { getPriorityIcon } from "components/icons/priority-icon"; // constants import { PRIORITIES } from "constants/project"; +import { CheckIcon } from "@heroicons/react/24/outline"; type Props = { value: string | null; @@ -16,34 +17,62 @@ export const IssuePrioritySelect: React.FC = ({ value, onChange }) => ( {({ open }) => ( <> - - {getPriorityIcon(value)} -
{value ?? "Priority"}
+ + `flex items-center text-xs cursor-pointer border rounded-md shadow-sm duration-200 + ${ + open ? "outline-none border-theme bg-theme/5 ring-1 ring-theme " : "hover:bg-theme/5" + }` + } + > + + + {getPriorityIcon(value, `${value ? "text-xs" : "text-xs text-gray-500"}`)} + + + {value ?? "Priority"} + + - -
+ +
{PRIORITIES.map((priority) => ( - `${selected ? "bg-indigo-50 font-medium" : ""} ${ - active ? "bg-indigo-50" : "" - } relative cursor-pointer select-none p-2 text-gray-900` + className={({ active }) => + `${ + active ? "bg-gray-200" : "" + } group flex min-w-[14rem] cursor-pointer select-none items-center gap-2 truncate rounded px-1 py-1.5 text-gray-600` } value={priority} > - - {getPriorityIcon(priority)} - {priority ?? "None"} - + {({ selected, active }) => ( +
+
+ {getPriorityIcon(priority)} + {priority ?? "None"} +
+
+ +
+
+ )}
))}
diff --git a/apps/app/components/issues/select/state.tsx b/apps/app/components/issues/select/state.tsx index 269b0af73..41f498ec0 100644 --- a/apps/app/components/issues/select/state.tsx +++ b/apps/app/components/issues/select/state.tsx @@ -7,13 +7,19 @@ import useSWR from "swr"; // services import stateService from "services/state.service"; // headless ui -import { Squares2X2Icon, PlusIcon } from "@heroicons/react/24/outline"; +import { + Squares2X2Icon, + PlusIcon, + MagnifyingGlassIcon, + CheckIcon, +} from "@heroicons/react/24/outline"; // icons import { Combobox, Transition } from "@headlessui/react"; // helpers import { getStatesList } from "helpers/state.helper"; // fetch keys import { STATE_LIST } from "constants/fetch-keys"; +import { getStateGroupIcon } from "components/icons"; type Props = { setIsOpen: React.Dispatch>; @@ -41,6 +47,7 @@ export const IssueStateSelect: React.FC = ({ setIsOpen, value, onChange, value: state.id, display: state.name, color: state.color, + group: state.group, })); const filteredOptions = @@ -48,6 +55,7 @@ export const IssueStateSelect: React.FC = ({ setIsOpen, value, onChange, ? options : options?.filter((option) => option.display.toLowerCase().includes(query.toLowerCase())); + const currentOption = options?.find((option) => option.value === value); return ( = ({ setIsOpen, value, onChange, > {({ open }: any) => ( <> - State + `flex items-center text-xs cursor-pointer border rounded-md shadow-sm duration-200 + ${ + open ? "outline-none border-theme bg-theme/5 ring-1 ring-theme " : "hover:bg-theme/5" + }` + } > - - - {value && value !== "" ? ( - option.value === value)?.color, - }} - /> - ) : null} - {options?.find((option) => option.value === value)?.display || "State"} - + {value && value !== "" ? ( + + {currentOption && currentOption.group + ? getStateGroupIcon(currentOption.group, "16", "16", currentOption.color) + : ""} + {currentOption?.display} + + ) : ( + + + {currentOption?.display || "State"} + + )} - setQuery(event.target.value)} - placeholder="Search" - displayValue={(assigned: any) => assigned?.name} - /> -
+
+ + setQuery(event.target.value)} + placeholder="Search States" + displayValue={(assigned: any) => assigned?.name} + /> +
+
{filteredOptions ? ( filteredOptions.length > 0 ? ( filteredOptions.map((option) => ( - `${active ? "bg-indigo-50" : ""} ${ - selected ? "bg-indigo-50 font-medium" : "" - } flex cursor-pointer select-none items-center gap-2 truncate p-2 text-gray-900` + className={({ active }) => + `${ + active ? "bg-gray-200" : "" + } group flex min-w-[14rem] cursor-pointer select-none items-center gap-2 truncate rounded px-1 py-1.5 text-gray-600` } value={option.value} > - {states && ( - <> - - {option.display} - - )} + {({ selected, active }) => + states && ( +
+
+ {getStateGroupIcon(option.group, "16", "16", option.color)} + {option.display} +
+
+ +
+
+ ) + }
)) ) : ( @@ -125,11 +149,13 @@ export const IssueStateSelect: React.FC = ({ setIsOpen, value, onChange, )}
diff --git a/apps/app/components/project/sidebar-list.tsx b/apps/app/components/project/sidebar-list.tsx index 5f22b78f7..300b141c9 100644 --- a/apps/app/components/project/sidebar-list.tsx +++ b/apps/app/components/project/sidebar-list.tsx @@ -5,14 +5,8 @@ import { Disclosure, Transition } from "@headlessui/react"; import useSWR from "swr"; // icons -import { - ChevronDownIcon, - PlusIcon, - Cog6ToothIcon, - RectangleStackIcon, - RectangleGroupIcon, -} from "@heroicons/react/24/outline"; -import { CyclesIcon } from "components/icons"; +import { ContrastIcon, LayerDiagonalIcon, PeopleGroupIcon } from "components/icons"; +import { ChevronDownIcon, PlusIcon, Cog6ToothIcon } from "@heroicons/react/24/outline"; // hooks import useToast from "hooks/use-toast"; import useTheme from "hooks/use-theme"; @@ -31,17 +25,17 @@ const navigation = (workspaceSlug: string, projectId: string) => [ { name: "Issues", href: `/${workspaceSlug}/projects/${projectId}/issues`, - icon: RectangleStackIcon, + icon: LayerDiagonalIcon, }, { name: "Cycles", href: `/${workspaceSlug}/projects/${projectId}/cycles`, - icon: CyclesIcon, + icon: ContrastIcon, }, { name: "Modules", href: `/${workspaceSlug}/projects/${projectId}/modules`, - icon: RectangleGroupIcon, + icon: PeopleGroupIcon, }, { name: "Settings", @@ -81,11 +75,8 @@ export const ProjectSidebarList: FC = () => { return ( <> -
+
+ {!sidebarCollapse &&
Projects
} {projects ? ( <> {projects.length > 0 ? ( @@ -93,12 +84,13 @@ export const ProjectSidebarList: FC = () => { {({ open }) => ( <> -
- + +
{project.icon ? ( {String.fromCodePoint(parseInt(project.icon))} @@ -110,26 +102,30 @@ export const ProjectSidebarList: FC = () => { )} {!sidebarCollapse && ( - - - {project?.name} - - - - +

+ {project?.name} +

+ )} +
+ +
+ {!sidebarCollapse && ( + + handleCopyText(project.id)}> + Copy project link + + + )} + {!sidebarCollapse && ( + + )} - - {!sidebarCollapse && ( - - handleCopyText(project.id)}> - Copy project link - - - )} -
+
+ + { } flex flex-col gap-y-1`} > {navigation(workspaceSlug as string, project?.id).map((item) => { - const hi = "hi"; - if (item.name === "Cycles" && !project.cycle_view) return; if (item.name === "Modules" && !project.module_view) return; @@ -154,18 +148,20 @@ export const ProjectSidebarList: FC = () => { - diff --git a/apps/app/components/ui/avatar.tsx b/apps/app/components/ui/avatar.tsx index 4726ca835..2b1c7b2cc 100644 --- a/apps/app/components/ui/avatar.tsx +++ b/apps/app/components/ui/avatar.tsx @@ -18,7 +18,7 @@ type AvatarProps = { }; export const Avatar: React.FC = ({ user, index }) => ( -
+
{user && user.avatar && user.avatar !== "" ? (
| (Partial | undefined)[] | Partial[]; userIds?: string[]; length?: number; + showLength?: boolean; }; -export const AssigneesList: React.FC = ({ users, userIds, length = 5 }) => { +export const AssigneesList: React.FC = ({ + users, + userIds, + length = 5, + showLength = true, +}) => { const router = useRouter(); const { workspaceSlug } = router.query; @@ -82,7 +88,13 @@ export const AssigneesList: React.FC = ({ users, userIds, len return ; })} - {userIds.length > length ? +{userIds.length - length} : null} + {showLength ? ( + userIds.length > length ? ( + +{userIds.length - length} + ) : null + ) : ( + "" + )} )} diff --git a/apps/app/components/ui/index.ts b/apps/app/components/ui/index.ts index 04cb34fa3..ca610a71c 100644 --- a/apps/app/components/ui/index.ts +++ b/apps/app/components/ui/index.ts @@ -15,3 +15,4 @@ export * from "./progress-bar"; export * from "./select"; export * from "./spinner"; export * from "./tooltip"; +export * from "./labels-list"; diff --git a/apps/app/components/ui/labels-list.tsx b/apps/app/components/ui/labels-list.tsx new file mode 100644 index 000000000..26257549b --- /dev/null +++ b/apps/app/components/ui/labels-list.tsx @@ -0,0 +1,32 @@ +import React from "react"; + +type IssueLabelsListProps = { + labels?: (string | undefined)[]; + length?: number; + showLength?: boolean; +}; + +export const IssueLabelsList: React.FC = ({ + labels, + length = 5, + showLength = true, +}) => ( + <> + {labels && ( + <> + {labels.map((color, index) => ( +
+ +
+ ))} + {labels.length > length ? +{labels.length - length} : null} + + )} + +); diff --git a/apps/app/components/workspace/help-section.tsx b/apps/app/components/workspace/help-section.tsx index 3b540e190..5a5e3a600 100644 --- a/apps/app/components/workspace/help-section.tsx +++ b/apps/app/components/workspace/help-section.tsx @@ -56,30 +56,10 @@ export const WorkspaceHelpSection: FC = (props) => { return (
- - + + + +
= (props) => { ))}
-
); diff --git a/apps/app/components/workspace/sidebar-dropdown.tsx b/apps/app/components/workspace/sidebar-dropdown.tsx index 5a892e069..5faf85618 100644 --- a/apps/app/components/workspace/sidebar-dropdown.tsx +++ b/apps/app/components/workspace/sidebar-dropdown.tsx @@ -69,45 +69,43 @@ export const WorkspaceSidebarDropdown = () => { return (
-
- -
-
- {activeWorkspace?.logo && activeWorkspace.logo !== "" ? ( - Workspace Logo - ) : ( - activeWorkspace?.name?.charAt(0) ?? "..." - )} -
- {!sidebarCollapse && ( -

- {activeWorkspace?.name - ? activeWorkspace.name.length > 17 - ? `${activeWorkspace.name.substring(0, 17)}...` - : activeWorkspace.name - : "Loading..."} -

+ +
+
+ {activeWorkspace?.logo && activeWorkspace.logo !== "" ? ( + Workspace Logo + ) : ( + activeWorkspace?.name?.charAt(0) ?? "..." )}
{!sidebarCollapse && ( -
-
+

+ {activeWorkspace?.name + ? activeWorkspace.name.length > 17 + ? `${activeWorkspace.name.substring(0, 17)}...` + : activeWorkspace.name + : "Loading..."} +

)} - -
+
+ {!sidebarCollapse && ( +
+
+ )} +
[ { - icon: HomeIcon, - name: "Home", + icon: GridViewIcon, + name: "Dashboard", href: `/${workspaceSlug}`, }, { - icon: ClipboardDocumentListIcon, + icon: AssignmentClipboardIcon, name: "Projects", href: `/${workspaceSlug}/projects`, }, { - icon: RectangleStackIcon, + icon: TickMarkIcon, name: "My Issues", href: `/${workspaceSlug}/me/my-issues`, }, { - icon: Cog6ToothIcon, + icon: SettingIcon, name: "Settings", href: `/${workspaceSlug}/settings`, }, ]; -export const WorkspaceSidebarMenu = () => { +export const WorkspaceSidebarMenu: React.FC = () => { // router const router = useRouter(); const { workspaceSlug } = router.query; @@ -49,15 +44,15 @@ export const WorkspaceSidebarMenu = () => {