From 9e8afcf50af4f92b161ef95918e219d51758646f Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Fri, 25 Nov 2022 22:54:49 +0530 Subject: [PATCH] feat: preview modal, style: issues page --- components/command-palette/shortcuts.tsx | 6 +- .../project/issues/BoardView/SingleBoard.tsx | 198 +++++----- components/project/issues/ListView/index.tsx | 22 +- .../project/issues/PreviewModal/index.tsx | 138 +++++++ layouts/Navbar/Sidebar.tsx | 128 +++--- pages/me/my-issues.tsx | 7 +- pages/projects/[projectId]/issues/index.tsx | 372 +++++++++--------- pages/projects/[projectId]/settings.tsx | 9 + styles/globals.css | 4 +- ui/CustomListbox/index.tsx | 2 +- ui/Input/index.tsx | 2 +- ui/SearchListbox/index.tsx | 50 ++- ui/Select/index.tsx | 2 +- ui/TextArea/index.tsx | 2 +- 14 files changed, 555 insertions(+), 387 deletions(-) create mode 100644 components/project/issues/PreviewModal/index.tsx diff --git a/components/command-palette/shortcuts.tsx b/components/command-palette/shortcuts.tsx index f1942d134..2c77b95d4 100644 --- a/components/command-palette/shortcuts.tsx +++ b/components/command-palette/shortcuts.tsx @@ -71,9 +71,9 @@ const ShortcutsModal: React.FC = ({ isOpen, setIsOpen }) => { { title: "Common", shortcuts: [ - { key: "Ctrl + p", description: "To open create project modal" }, - { key: "Ctrl + i", description: "To open create issue modal" }, - { key: "Ctrl + q", description: "To open create cycle modal" }, + { key: "Ctrl + p", description: "To create project" }, + { key: "Ctrl + i", description: "To create issue" }, + { key: "Ctrl + q", description: "To create cycle" }, { key: "Ctrl + h", description: "To open shortcuts guide" }, { key: "Ctrl + alt + c", diff --git a/components/project/issues/BoardView/SingleBoard.tsx b/components/project/issues/BoardView/SingleBoard.tsx index 019b4ac5f..63065de1d 100644 --- a/components/project/issues/BoardView/SingleBoard.tsx +++ b/components/project/issues/BoardView/SingleBoard.tsx @@ -199,110 +199,107 @@ const SingleBoard: React.FC = ({ {groupedByIssues[groupTitle].map((childIssue: any, index: number) => ( {(provided, snapshot) => ( -
-
+ - {Object.keys(properties).map( - (key) => - properties[key as keyof Properties] && - !Array.isArray(childIssue[key as keyof IIssue]) && ( -
+ {Object.keys(properties).map( + (key) => + properties[key as keyof Properties] && + !Array.isArray(childIssue[key as keyof IIssue]) && ( +
- {key === "target_date" ? ( - <> - {" "} - {childIssue.target_date - ? renderShortNumericDateFormat(childIssue.target_date) - : "N/A"} - - ) : ( - "" - )} - {key === "name" && ( - - + > + {key === "target_date" ? ( + <> + {" "} + {childIssue.target_date + ? renderShortNumericDateFormat(childIssue.target_date) + : "N/A"} + + ) : ( + "" + )} + {key === "name" && ( + {childIssue.name} - - - )} - {key === "state" && ( - <>{addSpaceIfCamelCase(childIssue["state_detail"].name)} - )} - {key === "priority" && <>{childIssue.priority}} - {key === "description" && <>{childIssue.description}} - {key === "assignee" ? ( -
- {childIssue?.assignee_details?.length > 0 ? ( - childIssue?.assignee_details?.map( - (assignee: any, index: number) => ( -
- {assignee.avatar && assignee.avatar !== "" ? ( -
- {assignee.name} -
- ) : ( -
- {assignee.first_name.charAt(0)} -
- )} -
+ + )} + {key === "state" && ( + <>{addSpaceIfCamelCase(childIssue["state_detail"].name)} + )} + {key === "priority" && <>{childIssue.priority}} + {key === "description" && <>{childIssue.description}} + {key === "assignee" ? ( +
+ {childIssue?.assignee_details?.length > 0 ? ( + childIssue?.assignee_details?.map( + (assignee: any, index: number) => ( +
+ {assignee.avatar && assignee.avatar !== "" ? ( +
+ {assignee.name} +
+ ) : ( +
+ {assignee.first_name.charAt(0)} +
+ )} +
+ ) ) - ) - ) : ( - None - )} -
- ) : null} -
- ) - )} -
+ ) : ( + None + )} +
+ ) : null} +
+ ) + )} +
- {/*
= ({
*/} - + + )}
))} diff --git a/components/project/issues/ListView/index.tsx b/components/project/issues/ListView/index.tsx index 05a215c20..9c12ac1c3 100644 --- a/components/project/issues/ListView/index.tsx +++ b/components/project/issues/ListView/index.tsx @@ -1,5 +1,5 @@ // react -import React from "react"; +import React, { useEffect, useState } from "react"; // next import Link from "next/link"; import Image from "next/image"; @@ -25,6 +25,7 @@ import { renderShortNumericDateFormat, replaceUnderscoreIfSnakeCase, } from "constants/common"; +import IssuePreviewModal from "../PreviewModal"; // types type Props = { @@ -44,6 +45,9 @@ const ListView: React.FC = ({ setSelectedIssue, handleDeleteIssue, }) => { + const [issuePreviewModal, setIssuePreviewModal] = useState(false); + const [previewModalIssueId, setPreviewModalIssueId] = useState(null); + const { activeWorkspace, activeProject, states } = useUser(); const partialUpdateIssue = (formData: Partial, issueId: string) => { @@ -71,8 +75,23 @@ const ListView: React.FC = ({ activeWorkspace ? () => workspaceService.workspaceMembers(activeWorkspace.slug) : null ); + const handleHover = (issueId: string) => { + document.addEventListener("keydown", (e) => { + if (e.code === "Space") { + e.preventDefault(); + setPreviewModalIssueId(issueId); + setIssuePreviewModal(true); + } + }); + }; + return (
+
@@ -135,6 +154,7 @@ const ListView: React.FC = ({ index === 0 ? "border-gray-300" : "border-gray-200", "border-t" )} + onMouseEnter={() => handleHover(issue.id)} > {Object.keys(properties).map( (key) => diff --git a/components/project/issues/PreviewModal/index.tsx b/components/project/issues/PreviewModal/index.tsx new file mode 100644 index 000000000..b1c9b8e0a --- /dev/null +++ b/components/project/issues/PreviewModal/index.tsx @@ -0,0 +1,138 @@ +// next +import { useRouter } from "next/router"; +// react +import { Fragment } from "react"; +// headless ui +import { Dialog, Transition } from "@headlessui/react"; +// hooks +import useUser from "lib/hooks/useUser"; +// services +import issuesServices from "lib/services/issues.services"; +import projectService from "lib/services/project.service"; +// swr +import useSWR from "swr"; +// types +import { IIssue, ProjectMember } from "types"; +// constants +import { PROJECT_ISSUES_DETAILS, PROJECT_MEMBERS } from "constants/fetch-keys"; +import { Button } from "ui"; +import { ChartBarIcon, Squares2X2Icon, TagIcon, UserIcon } from "@heroicons/react/24/outline"; + +type Props = { + isOpen: boolean; + setIsOpen: React.Dispatch>; + issueId: string | null; +}; + +const IssuePreviewModal = ({ isOpen, setIsOpen, issueId }: Props) => { + const closeModal = () => { + setIsOpen(false); + }; + + const { activeWorkspace, activeProject } = useUser(); + + const router = useRouter(); + + const { data: issueDetails } = useSWR( + activeWorkspace && activeProject && issueId ? PROJECT_ISSUES_DETAILS(issueId) : null, + activeWorkspace && activeProject && issueId + ? () => issuesServices.getIssue(activeWorkspace.slug, activeProject.id, issueId) + : null + ); + + const { data: users } = useSWR( + activeWorkspace && activeProject ? PROJECT_MEMBERS(activeProject.id) : null, + activeWorkspace && activeProject + ? () => projectService.projectMembers(activeWorkspace.slug, activeProject.id) + : null + ); + + return ( + <> + + + +
+ + +
+
+ + + + {issueDetails?.project_detail.identifier}-{issueDetails?.sequence_id}{" "} + {issueDetails?.name} + + Created by{" "} + {users?.find((u) => u.id === issueDetails?.created_by)?.member.first_name} + + +
+

{issueDetails?.description}

+
+
+ + + {issueDetails?.state_detail.name} + + + + {issueDetails?.priority} + + + + {issueDetails?.label_details && issueDetails.label_details.length > 0 + ? issueDetails.label_details.map((label) => ( + {label.name} + )) + : "None"} + + + + {issueDetails?.assignee_details && issueDetails.assignee_details.length > 0 + ? issueDetails.assignee_details.map((assignee) => ( + {assignee.first_name} + )) + : "None"} + +
+
+ + +
+
+
+
+
+
+
+ + ); +}; + +export default IssuePreviewModal; diff --git a/layouts/Navbar/Sidebar.tsx b/layouts/Navbar/Sidebar.tsx index 961d3857a..03863f4cf 100644 --- a/layouts/Navbar/Sidebar.tsx +++ b/layouts/Navbar/Sidebar.tsx @@ -409,17 +409,6 @@ const Sidebar: React.FC = () => { ))} -
{ {projects.length > 0 ? ( projects.map((project) => ( - - - {project?.name.charAt(0)} - - {!sidebarCollapse && project?.name} - - - - {navigation(project?.id).map((item) => ( - - - - - ))} - - + + )} + + + + {navigation(project?.id).map((item) => ( + + + + + ))} + + + + )} )) ) : (
-

You don{"'"}t have any project yet

+ {!sidebarCollapse && ( +

+ You don{"'"}t have any project yet +

+ )}
)} diff --git a/pages/me/my-issues.tsx b/pages/me/my-issues.tsx index 548cbf96c..91f79ae81 100644 --- a/pages/me/my-issues.tsx +++ b/pages/me/my-issues.tsx @@ -25,6 +25,7 @@ import ChangeStateDropdown from "components/project/issues/my-issues/ChangeState import { PlusIcon, RectangleStackIcon } from "@heroicons/react/24/outline"; // types import { IIssue } from "types"; +import Link from "next/link"; const MyIssues: NextPage = () => { const { user } = useUser(); @@ -139,8 +140,10 @@ const MyIssues: NextPage = () => { "border-t text-sm text-gray-900" )} > - - {myIssue.name} + + + {myIssue.name} + {myIssue.description} diff --git a/pages/projects/[projectId]/issues/index.tsx b/pages/projects/[projectId]/issues/index.tsx index 03a23a85f..0d2f853a4 100644 --- a/pages/projects/[projectId]/issues/index.tsx +++ b/pages/projects/[projectId]/issues/index.tsx @@ -136,199 +136,197 @@ const ProjectIssues: NextPage = () => { isOpen={!!deleteIssue} data={projectIssues?.results.find((issue) => issue.id === deleteIssue)} /> -
- {!projectIssues ? ( -
- -
- ) : projectIssues.count > 0 ? ( -
- - - - -
-

Project Issues

-
-
- - -
- -
- - - {groupByOptions.find((option) => option.key === groupByProperty)?.name ?? - "No Grouping"} - -
-
-
-
- - - -
- {groupByOptions.map((option) => ( - - {({ active }) => ( - - )} - - ))} - {issueView === "list" ? ( - - {({ active }) => ( - - )} - - ) : null} -
-
-
-
- - {({ open }) => ( - <> - - Properties - - - - - -
-
- {Object.keys(properties).map((key) => ( - - ))} -
-
-
-
- - )} -
- + +
+ ) : projectIssues.count > 0 ? ( +
+ + + + +
+

Project Issues

+
+
+ +
-
- {issueView === "list" ? ( - - ) : ( - - )} -
- ) : ( -
- - - Use
Ctrl/Command + I
{" "} - shortcut to create a new issue - - } + +
+ + + {groupByOptions.find((option) => option.key === groupByProperty)?.name ?? + "No Grouping"} + +
+
+
+
+ + + +
+ {groupByOptions.map((option) => ( + + {({ active }) => ( + + )} + + ))} + {issueView === "list" ? ( + + {({ active }) => ( + + )} + + ) : null} +
+
+
+
+ + {({ open }) => ( + <> + + Properties + + + + + +
+
+ {Object.keys(properties).map((key) => ( + + ))} +
+
+
+
+ + )} +
+ setIsOpen(true)} + label="Add Issue" + onClick={() => { + const e = new KeyboardEvent("keydown", { + key: "i", + ctrlKey: true, + }); + document.dispatchEvent(e); + }} /> -
+
- )} -
+ {issueView === "list" ? ( + + ) : ( + + )} +
+ ) : ( +
+ + + Use
Ctrl/Command + I
{" "} + shortcut to create a new issue + + } + Icon={PlusIcon} + action={() => setIsOpen(true)} + /> +
+
+ )} ); }; diff --git a/pages/projects/[projectId]/settings.tsx b/pages/projects/[projectId]/settings.tsx index 6f72d095d..b06ad9586 100644 --- a/pages/projects/[projectId]/settings.tsx +++ b/pages/projects/[projectId]/settings.tsx @@ -444,6 +444,15 @@ const ProjectSettings: NextPage = () => {
+
+
+

Labels

+

+ Manage the labels of this project. +

+
+
+
diff --git a/styles/globals.css b/styles/globals.css index b8c8dcc33..d8e81c69c 100644 --- a/styles/globals.css +++ b/styles/globals.css @@ -1,4 +1,4 @@ -@import url("https://fonts.googleapis.com/css2?family=Lexend:wght@200;300;400;500;600;700;800&display=swap"); +@import url("https://fonts.googleapis.com/css2?family=Inter:wght@200;300;400;500;600;700;800&display=swap"); @tailwind base; @tailwind components; @@ -6,7 +6,7 @@ @layer base { html { - font-family: "Lexend", sans-serif; + font-family: "Inter", sans-serif; } } diff --git a/ui/CustomListbox/index.tsx b/ui/CustomListbox/index.tsx index 4a62e63da..1d3e3a812 100644 --- a/ui/CustomListbox/index.tsx +++ b/ui/CustomListbox/index.tsx @@ -64,7 +64,7 @@ const CustomListbox: React.FC = ({ leaveTo="opacity-0" > = ({ onChange && onChange(e); }} className={classNames( - "mt-1 block w-full px-3 py-2 text-base focus:outline-none sm:text-sm rounded-md", + "mt-1 block w-full px-3 py-2 text-base focus:outline-none sm:text-sm rounded-md bg-transparent", mode === "primary" ? "border border-gray-300 rounded-md" : "", mode === "transparent" ? "bg-transparent border-none transition-all ring-0 focus:ring-1 focus:ring-indigo-500 rounded" diff --git a/ui/SearchListbox/index.tsx b/ui/SearchListbox/index.tsx index ab82aab24..c37e874c7 100644 --- a/ui/SearchListbox/index.tsx +++ b/ui/SearchListbox/index.tsx @@ -82,7 +82,7 @@ const SearchListbox: React.FC = ({ leaveTo="opacity-0" > = ({ } ${optionsClassName || ""}`} > setQuery(event.target.value)} placeholder="Search" displayValue={(assigned: any) => assigned?.name} /> - {filteredOptions ? ( - filteredOptions.length > 0 ? ( - filteredOptions.map((option) => ( - - `${ - active ? "text-white bg-theme" : "text-gray-900" - } cursor-pointer select-none relative p-2 rounded-md` - } - value={option.value} - > -
- - {option.element ?? option.display} - -
-
- )) +
+ {filteredOptions ? ( + filteredOptions.length > 0 ? ( + filteredOptions.map((option) => ( + + `${ + active ? "text-white bg-theme" : "text-gray-900" + } cursor-pointer select-none truncate font-medium relative p-2 rounded-md` + } + value={option.value} + > + {option.element ?? option.display} + + )) + ) : ( +

No {title.toLowerCase()} found

+ ) ) : ( -

No {title.toLowerCase()} found

- ) - ) : ( -

Loading...

- )} +

Loading...

+ )} +
diff --git a/ui/Select/index.tsx b/ui/Select/index.tsx index 4477792b6..aefcbe7f6 100644 --- a/ui/Select/index.tsx +++ b/ui/Select/index.tsx @@ -27,7 +27,7 @@ const Select: React.FC = ({ value={value} {...(register && register(name, validations))} disabled={disabled} - className="mt-1 block w-full px-3 py-2 text-base border border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md" + className="mt-1 block w-full px-3 py-2 text-base border border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md bg-transparent" > {options.map((option, index) => (