mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
feat: page improvement (#797)
* feat: remove label icon added * feat: block menu dropdown state added * feat: page info icon added and style: overflow title and label fix
This commit is contained in:
parent
f2e8add29d
commit
d6dbfdc731
@ -1,9 +1,11 @@
|
||||
import { useState } from "react";
|
||||
|
||||
import useSWR, { mutate } from "swr";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
// services
|
||||
import pagesService from "services/pages.service";
|
||||
import projectService from "services/project.service";
|
||||
// hooks
|
||||
import useToast from "hooks/use-toast";
|
||||
// components
|
||||
@ -23,9 +25,9 @@ import {
|
||||
ALL_PAGES_LIST,
|
||||
FAVORITE_PAGES_LIST,
|
||||
MY_PAGES_LIST,
|
||||
PROJECT_MEMBERS,
|
||||
RECENT_PAGES_LIST,
|
||||
} from "constants/fetch-keys";
|
||||
import { mutate } from "swr";
|
||||
|
||||
type Props = {
|
||||
pages: IPage[] | undefined;
|
||||
@ -44,6 +46,13 @@ export const PagesView: React.FC<Props> = ({ pages, viewType }) => {
|
||||
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
const { data: people } = useSWR(
|
||||
workspaceSlug && projectId ? PROJECT_MEMBERS(projectId as string) : null,
|
||||
workspaceSlug && projectId
|
||||
? () => projectService.projectMembers(workspaceSlug as string, projectId as string)
|
||||
: null
|
||||
);
|
||||
|
||||
const handleEditPage = (page: IPage) => {
|
||||
setSelectedPageToUpdate(page);
|
||||
setCreateUpdatePageModal(true);
|
||||
@ -198,6 +207,7 @@ export const PagesView: React.FC<Props> = ({ pages, viewType }) => {
|
||||
<SinglePageListItem
|
||||
key={page.id}
|
||||
page={page}
|
||||
people={people}
|
||||
handleEditPage={() => handleEditPage(page)}
|
||||
handleDeletePage={() => handleDeletePage(page)}
|
||||
handleAddToFavorites={() => handleAddToFavorites(page)}
|
||||
@ -212,6 +222,7 @@ export const PagesView: React.FC<Props> = ({ pages, viewType }) => {
|
||||
<SinglePageDetailedItem
|
||||
key={page.id}
|
||||
page={page}
|
||||
people={people}
|
||||
handleEditPage={() => handleEditPage(page)}
|
||||
handleDeletePage={() => handleDeletePage(page)}
|
||||
handleAddToFavorites={() => handleAddToFavorites(page)}
|
||||
@ -226,6 +237,7 @@ export const PagesView: React.FC<Props> = ({ pages, viewType }) => {
|
||||
<SinglePageDetailedItem
|
||||
key={page.id}
|
||||
page={page}
|
||||
people={people}
|
||||
handleEditPage={() => handleEditPage(page)}
|
||||
handleDeletePage={() => handleDeletePage(page)}
|
||||
handleAddToFavorites={() => handleAddToFavorites(page)}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect, useState, useRef } from "react";
|
||||
|
||||
import { useRouter } from "next/router";
|
||||
import Link from "next/link";
|
||||
@ -16,6 +16,7 @@ import issuesService from "services/issues.service";
|
||||
import aiService from "services/ai.service";
|
||||
// hooks
|
||||
import useToast from "hooks/use-toast";
|
||||
import useOutsideClickDetector from "hooks/use-outside-click-detector";
|
||||
// components
|
||||
import { GptAssistantModal } from "components/core";
|
||||
import { CreateUpdateBlockInline } from "components/pages";
|
||||
@ -61,6 +62,9 @@ export const SinglePageBlock: React.FC<Props> = ({ block, projectDetails, index
|
||||
|
||||
const [gptAssistantModal, setGptAssistantModal] = useState(false);
|
||||
|
||||
const [isMenuActive, setIsMenuActive] = useState(false);
|
||||
const actionSectionRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId, pageId } = router.query;
|
||||
|
||||
@ -274,6 +278,7 @@ export const SinglePageBlock: React.FC<Props> = ({ block, projectDetails, index
|
||||
reset({ ...block });
|
||||
}, [reset, block]);
|
||||
|
||||
useOutsideClickDetector(actionSectionRef, () => setIsMenuActive(false));
|
||||
return (
|
||||
<Draggable draggableId={block.id} index={index} isDragDisabled={createBlockForm}>
|
||||
{(provided, snapshot) => (
|
||||
@ -308,7 +313,12 @@ export const SinglePageBlock: React.FC<Props> = ({ block, projectDetails, index
|
||||
<EllipsisVerticalIcon className="h-[18px]" />
|
||||
<EllipsisVerticalIcon className="h-[18px] -ml-3" />
|
||||
</button>
|
||||
<div className="absolute top-4 right-0 items-center gap-2 hidden group-hover:!flex bg-white pl-4">
|
||||
<div
|
||||
ref={actionSectionRef}
|
||||
className={`absolute top-4 right-0 items-center gap-2 hidden group-hover:!flex bg-white pl-4 ${
|
||||
isMenuActive ? "!flex" : ""
|
||||
}`}
|
||||
>
|
||||
{block.issue && block.sync && (
|
||||
<div className="flex flex-shrink-0 cursor-default items-center gap-1 rounded bg-gray-100 py-1 px-1.5 text-xs">
|
||||
{isSyncing ? (
|
||||
@ -350,7 +360,16 @@ export const SinglePageBlock: React.FC<Props> = ({ block, projectDetails, index
|
||||
>
|
||||
<PencilIcon className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
<CustomMenu label={<BoltIcon className="h-4.5 w-3.5" />} noBorder noChevron>
|
||||
<CustomMenu
|
||||
customButton={
|
||||
<button
|
||||
className="flex cursor-pointer items-center justify-between gap-1 px-2.5 py-1 text-xs duration-300 hover:bg-gray-100 text-left rounded w-full"
|
||||
onClick={() => setIsMenuActive(!isMenuActive)}
|
||||
>
|
||||
<BoltIcon className="h-4.5 w-3.5" />
|
||||
</button>
|
||||
}
|
||||
>
|
||||
{block.issue ? (
|
||||
<>
|
||||
<CustomMenu.MenuItem onClick={handleBlockSync}>
|
||||
|
@ -15,14 +15,16 @@ import {
|
||||
StarIcon,
|
||||
TrashIcon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
import { ExclamationIcon } from "components/icons";
|
||||
// helpers
|
||||
import { truncateText } from "helpers/string.helper";
|
||||
import { renderShortTime, renderShortDate } from "helpers/date-time.helper";
|
||||
import { renderShortTime, renderShortDate, renderLongDateFormat } from "helpers/date-time.helper";
|
||||
// types
|
||||
import { IPage } from "types";
|
||||
import { IPage, IProjectMember } from "types";
|
||||
|
||||
type TSingleStatProps = {
|
||||
page: IPage;
|
||||
people: IProjectMember[] | undefined;
|
||||
handleEditPage: () => void;
|
||||
handleDeletePage: () => void;
|
||||
handleAddToFavorites: () => void;
|
||||
@ -32,6 +34,7 @@ type TSingleStatProps = {
|
||||
|
||||
export const SinglePageDetailedItem: React.FC<TSingleStatProps> = ({
|
||||
page,
|
||||
people,
|
||||
handleEditPage,
|
||||
handleDeletePage,
|
||||
handleAddToFavorites,
|
||||
@ -132,6 +135,18 @@ export const SinglePageDetailedItem: React.FC<TSingleStatProps> = ({
|
||||
</button>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Tooltip
|
||||
theme="dark"
|
||||
position="top-right"
|
||||
tooltipContent={`Created by ${
|
||||
people?.find((person) => person.member.id === page.created_by)?.member
|
||||
.first_name ?? ""
|
||||
} on ${renderLongDateFormat(`${page.created_at}`)}`}
|
||||
>
|
||||
<span>
|
||||
<ExclamationIcon className="h-4 w-4 text-gray-400" />
|
||||
</span>
|
||||
</Tooltip>
|
||||
<CustomMenu verticalEllipsis>
|
||||
<CustomMenu.MenuItem
|
||||
onClick={(e: any) => {
|
||||
|
@ -1,5 +1,7 @@
|
||||
import React from "react";
|
||||
|
||||
import useSWR from "swr";
|
||||
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
@ -9,22 +11,22 @@ import useUser from "hooks/use-user";
|
||||
import { CustomMenu, Tooltip } from "components/ui";
|
||||
// icons
|
||||
import {
|
||||
DocumentTextIcon,
|
||||
LockClosedIcon,
|
||||
LockOpenIcon,
|
||||
PencilIcon,
|
||||
StarIcon,
|
||||
TrashIcon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
import { PencilScribbleIcon } from "components/icons";
|
||||
import { ExclamationIcon, PencilScribbleIcon } from "components/icons";
|
||||
// helpers
|
||||
import { truncateText } from "helpers/string.helper";
|
||||
import { renderShortDate, renderShortTime } from "helpers/date-time.helper";
|
||||
import { renderLongDateFormat, renderShortDate, renderShortTime } from "helpers/date-time.helper";
|
||||
// types
|
||||
import { IPage } from "types";
|
||||
import { IPage, IProjectMember } from "types";
|
||||
|
||||
type TSingleStatProps = {
|
||||
page: IPage;
|
||||
people: IProjectMember[] | undefined;
|
||||
handleEditPage: () => void;
|
||||
handleDeletePage: () => void;
|
||||
handleAddToFavorites: () => void;
|
||||
@ -34,6 +36,7 @@ type TSingleStatProps = {
|
||||
|
||||
export const SinglePageListItem: React.FC<TSingleStatProps> = ({
|
||||
page,
|
||||
people,
|
||||
handleEditPage,
|
||||
handleDeletePage,
|
||||
handleAddToFavorites,
|
||||
@ -49,11 +52,11 @@ export const SinglePageListItem: React.FC<TSingleStatProps> = ({
|
||||
<li>
|
||||
<Link href={`/${workspaceSlug}/projects/${projectId}/pages/${page.id}`}>
|
||||
<a>
|
||||
<div className="relative rounded p-4 hover:bg-[#E9ECEF]">
|
||||
<div className="relative rounded p-4 hover:bg-gray-200">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<PencilScribbleIcon className="h-4 w-4" />
|
||||
<p className="mr-2 truncate text-base text-[#212529] font-medium">
|
||||
<p className="mr-2 truncate text-base text-gray-800 font-medium">
|
||||
{truncateText(page.name, 75)}
|
||||
</p>
|
||||
{page.label_details.length > 0 &&
|
||||
@ -81,8 +84,9 @@ export const SinglePageListItem: React.FC<TSingleStatProps> = ({
|
||||
<div className="ml-2 flex flex-shrink-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<Tooltip
|
||||
tooltipContent={`Last updated at ${
|
||||
renderShortTime(page.updated_at)} on ${renderShortDate(page.updated_at)}`}
|
||||
tooltipContent={`Last updated at ${renderShortTime(
|
||||
page.updated_at
|
||||
)} on ${renderShortDate(page.updated_at)}`}
|
||||
>
|
||||
<p className="text-sm text-gray-400">{renderShortTime(page.updated_at)}</p>
|
||||
</Tooltip>
|
||||
@ -134,6 +138,18 @@ export const SinglePageListItem: React.FC<TSingleStatProps> = ({
|
||||
</button>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Tooltip
|
||||
theme="dark"
|
||||
position="top-right"
|
||||
tooltipContent={`Created by ${
|
||||
people?.find((person) => person.member.id === page.created_by)?.member
|
||||
.first_name ?? ""
|
||||
} on ${renderLongDateFormat(`${page.created_at}`)}`}
|
||||
>
|
||||
<span>
|
||||
<ExclamationIcon className="h-4 w-4 text-gray-400" />
|
||||
</span>
|
||||
</Tooltip>
|
||||
|
||||
<CustomMenu width="auto" verticalEllipsis>
|
||||
<CustomMenu.MenuItem
|
||||
|
@ -35,6 +35,7 @@ import {
|
||||
PlusIcon,
|
||||
StarIcon,
|
||||
LinkIcon,
|
||||
XMarkIcon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
import { ColorPalletteIcon, ClipboardIcon } from "components/icons";
|
||||
// helpers
|
||||
@ -330,7 +331,11 @@ const SinglePage: NextPage = () => {
|
||||
return (
|
||||
<div
|
||||
key={label.id}
|
||||
className="group flex items-center gap-1 rounded-2xl border px-2 py-0.5 text-xs"
|
||||
className="group flex items-center gap-1 cursor-pointer rounded-2xl border px-2 py-0.5 text-xs hover:border-red-500 hover:bg-red-50"
|
||||
onClick={() => {
|
||||
const updatedLabels = pageDetails.labels.filter((l) => l !== labelId);
|
||||
partialUpdatePage({ labels_list: updatedLabels });
|
||||
}}
|
||||
style={{
|
||||
backgroundColor: `${
|
||||
label?.color && label.color !== "" ? label.color : "#000000"
|
||||
@ -345,6 +350,7 @@ const SinglePage: NextPage = () => {
|
||||
}}
|
||||
/>
|
||||
{label.name}
|
||||
<XMarkIcon className="h-2.5 w-2.5 group-hover:text-red-500" />
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
Loading…
Reference in New Issue
Block a user